前言

MCU、SOC 内部通常带有 DMA 控制器,要想使用 DMA 通常需要如下操作

  1. 选择通道
  2. 配置传输方向(内存到外设、内存到内存、外设到内存)
  3. 设置源地址、目的地址(内存地址、外设地址)
  4. 设置源地址、目的地址是否自增
  5. 设置位宽(字节对齐、半字、字、双字)
  6. 设置传输数据长度
  7. 传输模式(单次、循环)
  8. 优先级
  9. 开始传输

网卡作为一个对性能十分看重的设备,其内部本身通常是带有 DMA 控制器的。那么网卡内部的 DMA 控制器是如何使用的呢?
也和上述操作流程类似吗?今天我们就来研究一下。

网卡内部的 DMA

  1. SOC 内部的 DMA 是一个通用设施,通过配置不同通道给不同外设使用,而网卡内部的 DMA 是专门给网卡使用的,所以没有选择通道一说,可以理解为,有固定两个通道,一个给 TX 使用,一个给 RX 使用。
  2. 对于 TX DMA 或者 RX DMA 来说,它们本身设计上就固化了传输方向,TX DMA 方向就是内存到外设,RX DMA 就是外设到内存,所以也无需设置传输方向
  3. 对于 TX DMA 来说,需要设置源地址,即 skb->data 的物理地址,我们的目的就是将这个包通过网卡发送出去。目的地址不用设,是网卡芯片内部的内存,网卡自己会处理,我们 host 端无需操心,也无法干预。同样的,对于 RX DMA 来说,需要设置目的地址,也即 skb->data 的物理地址,我们的目的就是将网卡芯片收到的数据包存入 skb,供后续走网络协议栈。
  4. 对于网卡的 DMA 来说,其源地址、目的地址肯定是自增的,因为我们需要传输一整包数据,而不是其中的某个字节,所以地址自增无需设置,属性固定为自增。
  5. 位宽也无需设置,应该是字对齐(32bit)。
  6. 设置传输数据长度
  7. 传输模式固定为单次,无需设置。
  8. 优先级有些网卡可以设置 RX 优先级高于 TX,通常优先级是一样的,无需设置
  9. 开始传输

总结,对于 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 的数据,依次将链表中的每个元素读走发送。

附录

在这里插入图片描述
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐