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_mask | u64 * | 设备DMA可寻址的最大物理地址(位宽) |
coherent_dma_mask | u64 | 一致性映射(coherent)的DMA地址上限 |
bus_dma_limit | u64 | 总线施加的DMA地址上限 |
dma_pfn_offset | unsigned long | 设备DMA地址与物理地址之间的页帧偏移 |
iommu_group | struct 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_link | unsigned long | 指向 struct page * 的低位,含偏移标志 |
offset | unsigned int | 该SG条目在页面内的起始偏移(字节) |
length | unsigned int | 该SG条目的数据长度(字节) |
dma_address | dma_addr_t | 映射后得到的设备DMA地址(由 dma_map_sg 填充) |
dma_length | unsigned int | 映射后的DMA传输长度(通常与length相同) |
📐 SG表典型使用流程
- 分配SG表:
sg_alloc_table(&sgt, nents, GFP_KERNEL) - 填充SG条目:
sg_set_page(sg, page, size, offset)或sg_set_buf(sg, buf, size) - 映射SG表:
nents = dma_map_sg(dev, sgt.sgl, sgt.nents, dir) - 遍历已映射的SG:
for_each_sg(sgt.sgl, sg, nents, i),使用sg_dma_address(sg)和sg_dma_len(sg) - 解映射:
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映射过程中四个核心数据结构的协作关系:
3.6 数据结构对比总结
| 数据结构 | 所属子系统 | 核心职责 | 流式DMA中的角色 |
|---|---|---|---|
struct device | 驱动模型 | 设备抽象与DMA掩码 | 映射操作的“主体”,提供DMA能力信息 |
struct dma_chan | dmaengine | DMA通道控制 | 传输执行的“引擎”,接收已映射地址 |
struct scatterlist | 内存管理 | 非连续内存描述 | 批量映射的“容器”,存储映射结果 |
dma_addr_t | DMA映射层 | 设备可见地址 | 映射的“产出”,直接供硬件使用 |
⚠️ 常见误区:初学者容易混淆
dma_addr_t 和物理地址。在无IOMMU的简单系统上两者相等,但在虚拟化或IOMMU场景下完全不同。永远不要假设 dma_addr_t 等于 __pa() 的结果,必须通过DMA映射API获取。