3. 流式DMA映射核心数据结构:struct device、struct dma_chan、struct scatterlist、dma_addr_t

流式DMA映射的整个生命周期——从申请通道、建立映射、启动传输到同步与解映射——都围绕四个核心数据结构展开。理解这些数据结构的职责、内存布局和典型用法,是正确调用DMA API的前提。本章逐一剖析 struct device、struct dma_chan、struct scatterlist 和 dma_addr_t,并给出它们之间的协作关系图。

3.1 struct device:DMA操作的“身份证”

struct device 是Linux设备模型中最基础的实体,几乎每个硬件设备在内核中都对应一个 struct device 实例。在DMA上下文中,它承担以下关键角色:

  • DMA掩码持有者:通过 dma_mask 和 coherent_dma_mask 告知内核该设备能访问的物理地址范围。
  • DMA API的第一个参数:几乎所有流式DMA映射函数(如 dma_map_single、dma_map_sg)的第一个参数都是 struct device *dev,内核据此查找IOMMU或直接内存映射信息。
  • DMA域与IOMMU绑定:当系统使能IOMMU/SMMU时,struct device 关联到对应的IOMMU域,映射操作会经过地址翻译。
🔑 关键字段速查
字段类型DMA相关含义
dma_masku64 *设备DMA可寻址的最大物理地址(位宽)
coherent_dma_masku64一致性映射(coherent)的DMA地址上限
bus_dma_limitu64总线施加的DMA地址上限
dma_pfn_offsetunsigned long设备DMA地址与物理地址之间的页帧偏移
iommu_groupstruct iommu_group *设备所属的IOMMU组(若使能IOMMU)
💡 实践要点:在调用任何DMA映射API之前,务必确认设备已正确设置 dma_mask。常见错误是驱动未调用 dma_set_mask_and_coherent(),导致映射返回无效地址或DMA传输失败。

3.2 struct dma_chan:DMA通道的抽象

struct dma_chan 是Linux DMA引擎(dmaengine)框架下对硬件DMA通道的软件抽象。它代表一个可以发起和控制DMA传输的独立逻辑通道。对于流式DMA映射,struct dma_chan 主要在以下场景出现:

  • 使用dmaengine API时:通过 dma_request_channel() 获得通道,然后使用 dmaengine_prep_slave_sg() 等函数准备传输描述符。
  • 与流式映射协同:dmaengine传输的数据缓冲区通常需要先通过 dma_map_sg() 或 dma_map_single() 获得设备可见地址,再填充到描述符中。
  • 通道属性:包含 device(所属DMA控制器)、chan_id(通道编号)、client(使用该通道的设备驱动)等字段。
⚠️ 注意区分:struct dma_chan 是dmaengine框架的概念,而流式DMA映射API(如 dma_map_single)并不直接操作 struct dma_chan。前者用于“谁来做传输”,后者用于“如何让设备看到内存”。两者在驱动中经常配合使用,但职责不同。

3.3 struct scatterlist:分散/聚合内存的描述符

struct scatterlist(简称SG)是流式DMA映射中处理非连续内存的核心数据结构。它描述了一段物理上可能不连续、但逻辑上连续的内存区域。SG表由多个SG条目组成,每个条目记录一个物理内存块的地址和长度。

字段类型说明
page_linkunsigned long指向 struct page * 的低位,含偏移标志
offsetunsigned int该SG条目在页面内的起始偏移(字节)
lengthunsigned int该SG条目的数据长度(字节)
dma_addressdma_addr_t映射后得到的设备DMA地址(由 dma_map_sg 填充)
dma_lengthunsigned int映射后的DMA传输长度(通常与length相同)
📐 SG表典型使用流程
  1. 分配SG表:sg_alloc_table(&sgt, nents, GFP_KERNEL)
  2. 填充SG条目:sg_set_page(sg, page, size, offset) 或 sg_set_buf(sg, buf, size)
  3. 映射SG表:nents = dma_map_sg(dev, sgt.sgl, sgt.nents, dir)
  4. 遍历已映射的SG:for_each_sg(sgt.sgl, sg, nents, i),使用 sg_dma_address(sg) 和 sg_dma_len(sg)
  5. 解映射:dma_unmap_sg(dev, sgt.sgl, sgt.nents, dir)

3.4 dma_addr_t:设备视角的地址

dma_addr_t 是流式DMA映射中最重要的“货币”——它是设备用来访问内存的地址。在无IOMMU的系统中,dma_addr_t 通常等于物理地址;在使能IOMMU的系统上,它是IOMMU转换后的I/O虚拟地址(IOVA)。

  • 定义:typedef u64 dma_addr_t;(在64位系统上为64位,32位系统上为32位)
  • 来源:由 dma_map_single() 返回,或通过 sg_dma_address() 从SG条目中获取。
  • 使用:写入DMA控制器的源地址/目的地址寄存器,或填入dmaengine描述符的 src_addr/dst_addr 字段。
  • 注意事项:dma_addr_t 只在映射有效期内可用;解映射后,该地址对设备不再有效,CPU也不能直接解引用它。
🔍 调试技巧:打印 dma_addr_t 时使用 %pad 格式说明符(如 dev_info(dev, "dma addr: %pad\n", &dma_handle);),避免因类型宽度不匹配导致的截断。

3.5 核心数据结构协作关系

下图展示了流式DMA映射过程中四个核心数据结构的协作关系:

流式DMA映射核心数据结构协作图 流式DMA映射核心数据结构协作关系 struct device dma_mask / coherent_dma_mask dma_addr_t 设备可见地址(物理/IOVA) struct scatterlist page_link | offset | length dma_address | dma_length struct dma_chan DMA通道抽象(dmaengine) dma_map_single(dev, ...) dma_map_sg(dev, ...) sg_dma_address(sg) dmaengine_prep_slave_sg() 📌 协作流程说明 1. 驱动通过 struct device 向内核声明DMA能力(掩码)。 2. 对于单缓冲区:dma_map_single(dev, cpu_addr, size, dir) → 返回 dma_addr_t。 3. 对于SG表:dma_map_sg(dev, sglist, nents, dir) → 填充每个sg的 dma_address。 4. 使用dmaengine时:将 dma_addr_t 或 sg_dma_address() 填入描述符, 通过 struct dma_chan 提交传输。 5. 传输完成后:dma_unmap_single / dma_unmap_sg 释放映射。

3.6 数据结构对比总结

数据结构所属子系统核心职责流式DMA中的角色
struct device驱动模型设备抽象与DMA掩码映射操作的“主体”,提供DMA能力信息
struct dma_chandmaengineDMA通道控制传输执行的“引擎”,接收已映射地址
struct scatterlist内存管理非连续内存描述批量映射的“容器”,存储映射结果
dma_addr_tDMA映射层设备可见地址映射的“产出”,直接供硬件使用
⚠️ 常见误区:初学者容易混淆 dma_addr_t 和物理地址。在无IOMMU的简单系统上两者相等,但在虚拟化或IOMMU场景下完全不同。永远不要假设 dma_addr_t 等于 __pa() 的结果,必须通过DMA映射API获取。