前言
MCU、SOC 内部通常带有 DMA 控制器,要想使用 DMA 通常需要如下操作
- 选择通道
- 配置传输方向(内存到外设、内存到内存、外设到内存)
- 设置源地址、目的地址(内存地址、外设地址)
- 设置源地址、目的地址是否自增
- 设置位宽(字节对齐、半字、字、双字)
- 设置传输数据长度
- 传输模式(单次、循环)
- 优先级
- 开始传输
网卡作为一个对性能十分看重的设备,其内部本身通常是带有 DMA 控制器的。那么网卡内部的 DMA 控制器是如何使用的呢?
也和上述操作流程类似吗?今天我们就来研究一下。
网卡内部的 DMA
- SOC 内部的 DMA 是一个通用设施,通过配置不同通道给不同外设使用,而网卡内部的 DMA 是专门给网卡使用的,所以没有选择通道一说,可以理解为,有固定两个通道,一个给 TX 使用,一个给 RX 使用。
- 对于 TX DMA 或者 RX DMA 来说,它们本身设计上就固化了传输方向,TX DMA 方向就是内存到外设,RX DMA 就是外设到内存,所以也无需设置传输方向。
- 对于 TX DMA 来说,需要设置源地址,即 skb->data 的物理地址,我们的目的就是将这个包通过网卡发送出去。目的地址不用设,是网卡芯片内部的内存,网卡自己会处理,我们 host 端无需操心,也无法干预。同样的,对于 RX DMA 来说,需要设置目的地址,也即 skb->data 的物理地址,我们的目的就是将网卡芯片收到的数据包存入 skb,供后续走网络协议栈。
- 对于网卡的 DMA 来说,其源地址、目的地址肯定是自增的,因为我们需要传输一整包数据,而不是其中的某个字节,所以地址自增无需设置,属性固定为自增。
- 位宽也无需设置,应该是字对齐(32bit)。
- 设置传输数据长度
- 传输模式固定为单次,无需设置。
- 优先级有些网卡可以设置 RX 优先级高于 TX,通常优先级是一样的,无需设置。
- 开始传输
总结,对于 TX DMA 来讲,只要设置源地址,传输数据长度,然后开始传输就可以了。
DMA 描述符列表
有一个问题需要考虑,对于 TX DMA 我们需要设置源地址,也就是 skb->data 的物理地址,不过数据包往往不止一个,有时同时需要发送若干个数据包,那我们如何设置源地址呢?
最容易想到的方案就是,一次设置一个,发送一个,再设置下一个。很显然,这种方式开销很大,因为网卡端每发送完一个数据包就需要告知 host 端完成事件,host 端才能进行下一包数据的传输设置。这样网卡芯片、host 端 CPU 都很累。
另一种方案是,一次性设置很多个源地址(比方说 512 个)到网卡芯片,网卡芯片全部传输完成后告知 host 端,这样效率会比较高。不过,除了源地址外,还需要设置传输数据长度。另外,可能还需要设置一些额外的信息给网卡芯片。所以在此基础上,舍弃设置源地址,而是设置描述符地址(描述符可以承载更多信息);并且舍弃一次性设置多个地址,而是设置一个列表(DMA 描述符列表)的首地址。
案例分析
硬件:OrangePi PC
软件:Linux 5.10.92
网卡:stmmac(TX 方向)
设置 TX 描述符列表首地址
设置传输数据、启动传输
整体框图
链表
描述符中的 des3 存储下一个元素的地址,这样构成了一个链表,网卡芯片通过描述符首地址,可以依次遍历链表中的每一个元素,DMA 数据到网卡芯片,然后发送。
dma_tx 是虚拟地址,也就是 CPU 角度看到的链表首地址;dma_tx_phy 是物理地址,也就是网卡芯片角度看到的链表首地址。它俩就像真人和影子一样,指代同一事物。CPU 对 dma_tx 链表内容的更改,网卡芯片可以通过 dma_tx_phy 完全读取到,反之亦然。
总结
网卡芯片 TX 描述符列表寄存器中存储了 dma_tx_phy 地址, 每当 Trigger 一次 TX DMA 传输时,网卡芯片就开始读取这个 list 的数据,依次将链表中的每个元素读走发送。
附录