4. 描述符核心概念

在 DMA 驱动设计中,描述符(Descriptor)是硬件与软件之间的“任务契约”。CPU 不直接搬运数据,而是把“从哪里搬、搬多少、搬到哪里、搬完后做什么”写进描述符,再由 DMA 控制器按描述符执行。理解描述符的五类核心概念——结构、链式、环形、分散聚集、控制位与状态位——是设计高带宽传输驱动的第一道门槛。

一、描述符结构:一次传输的最小信息单元

一个描述符通常包含源地址、目的地址、传输长度、控制字段和状态字段。不同 SoC 的字段宽度和排列不同,但语义高度一致。以常见的 32 字节描述符为例:

struct dma_desc {
    u32 src_addr;      // 源物理地址
    u32 dst_addr;      // 目的物理地址
    u32 length;        // 本次传输字节数
    u32 ctrl;          // 控制位:方向、中断、链尾等
    u32 status;        // 状态位:完成、错误、半满
    u32 next;          // 下一个描述符地址(链式/环形)
    u32 reserved[2];   // 对齐填充,保证 32 字节对齐
};

描述符必须按硬件要求对齐,常见为 16 或 32 字节对齐。对齐不仅影响访问效率,也决定描述符能否被 DMA 控制器一次性读取。若描述符跨越缓存行边界,硬件可能产生多次总线读,降低高带宽场景下的吞吐。

描述符结构设计的第一原则:字段顺序尽量与硬件寄存器映射一致,避免驱动层做额外拼装;同时保证描述符本身按缓存行对齐,减少伪共享。

二、链式描述符与环形描述符

单个描述符只能描述一次传输。当数据量很大时,需要把多个描述符组织起来,形成链式或环形结构。

链式描述符通过 next 指针把描述符串成单向链表。每个描述符执行完后,硬件自动读取 next 指向的下一个描述符。链式结构灵活,适合长度不固定的分散缓冲区,但每次跳转都要访问内存,存在指针追逐开销。

环形描述符把描述符数组首尾相连,硬件维护一个当前指针,执行完最后一个后回到第一个。环形结构没有指针追逐,地址可预测,适合高带宽、固定缓冲池的场景,如网卡收发队列。缺点是环大小固定,需要软件及时回收已完成的描述符,否则会“追尾”。

链式描述符 vs 环形描述符 链式描述符(指针跳转) Desc 0 next → Desc 1 next → Desc 2 next → Desc N next = NULL 环形描述符(首尾相连) Desc 0 head Desc 1 Desc 2 Desc 3 tail 执行完 Desc 3 后回到 Desc 0,形成闭环

三、分散聚集列表(Scatter-Gather List)

分散聚集列表是描述符的高级组织形式:它允许一次 DMA 传输把多个不连续的源缓冲区“聚集”到多个不连续的目的缓冲区,或反向“分散”。每个缓冲区对应一个描述符,描述符之间通过链式或环形连接。SG 列表的价值在于:

  • 避免为 DMA 额外拷贝数据到连续内存,减少 CPU 和内存带宽消耗;
  • 直接使用用户态或文件系统已有的页碎片,提升零拷贝能力;
  • 支持大块数据跨页传输,适合网络、存储、视频采集等高带宽场景。

在 Linux 中,SG 列表常通过 struct scatterlist 描述,驱动再将其转换为硬件描述符。转换时要注意:每个 scatterlist 的物理地址必须映射为 DMA 地址,且长度不能超过硬件单次传输上限。

四、控制位与状态位

控制位由软件写入,告诉硬件“怎么做”;状态位由硬件写入,告诉软件“做得怎么样”。两者是驱动与硬件同步的关键。

类别 典型位 方向 作用
控制位 DIR 软件 → 硬件 传输方向:内存到设备 / 设备到内存
控制位 IE 软件 → 硬件 完成中断使能,决定是否触发 IRQ
控制位 EOL 软件 → 硬件 链尾标志,表示当前描述符是最后一个
状态位 DONE 硬件 → 软件 传输完成,驱动可回收缓冲区
状态位 ERR 硬件 → 软件 总线错误、地址错误或长度错误
状态位 HALF 硬件 → 软件 半满标志,用于双缓冲或流式处理
控制位与状态位常复用同一寄存器地址。写入控制位后,硬件可能在同一位置回写状态位。驱动必须使用“读-改-写”或独立的状态寄存器,避免把状态位误当控制位写回,导致传输异常。

五、案例:高带宽采集驱动的描述符组织

假设一个视频采集驱动需要以 1.2 GB/s 持续搬运帧数据,每帧 2 MB,硬件单次传输上限 64 KB。设计如下:

  1. 为每帧分配 32 个 64 KB 的分散缓冲区,组成 SG 列表;
  2. 每个缓冲区对应一个描述符,描述符按环形组织,共 256 个描述符;
  3. 控制位设置 DIR=设备到内存、IE=1(每帧最后一个描述符触发中断)、EOL 标记链尾;
  4. 状态位轮询 DONE,回收已完成描述符并重新提交;
  5. 使用半满中断 HALF 提前唤醒上层,降低延迟。

该设计把“分散聚集”与“环形描述符”结合,既避免大块连续内存分配,又保持硬件指针可预测,是高带宽 DMA 驱动的典型做法。

六、常见误区与复盘

  • 误区一:描述符数量越多越好。描述符过多会增加内存占用和缓存压力,环形队列深度应结合带宽、延迟和中断频率权衡。
  • 误区二:忽略缓存一致性。描述符和缓冲区若被 CPU 缓存,DMA 看到的数据可能不是最新的。必须使用 dma_alloc_coherent 或正确的 cache flush/invalidate。
  • 误区三:状态位未清除就复用描述符。硬件可能仍认为该描述符有效,导致重复传输或数据错乱。回收前必须确认 DONE 并清除状态。
  • 误区四:SG 列表长度超过硬件限制。部分 DMA 控制器对单个描述符链长度有限制,超长链需要拆分为多个链或使用环形队列。
描述符管理的本质是“生产者-消费者”模型:驱动是生产者,硬件是消费者。控制位是生产指令,状态位是消费回执。高带宽传输的稳定性,取决于这对生产者和消费者能否在环形或链式结构上持续、无锁、无错地同步。