3. DMA控制器硬件抽象:核心数据结构详解
在Linux内核DMA引擎子系统中,dma_device、dma_chan_percpu和dma_cap_mask_t构成了DMA控制器硬件抽象的三根支柱。它们分别负责描述控制器全局属性、通道级统计计数以及能力位图。理解这三个结构体是编写DMA引擎驱动的第一步。
核心要点:dma_device代表一个DMA控制器硬件实例;dma_chan_percpu提供每个CPU核上的通道统计信息;dma_cap_mask_t用位图标记控制器支持的操作类型(如MEMCPY、XOR、PQ等)。
3.1 dma_device结构体:DMA控制器的“身份证”
dma_device定义在include/linux/dmaengine.h中,是DMA引擎驱动最核心的数据结构。每个DMA控制器硬件实例对应一个dma_device对象,它包含了控制器的能力描述、通道列表、操作函数集以及硬件参数。
| 字段 | 类型 | 说明 |
|---|---|---|
| dev | struct device * | 指向DMA控制器的通用设备结构体,用于设备模型关联 |
| channels | struct list_head | 该控制器下所有DMA通道的链表头 |
| cap_mask | dma_cap_mask_t | 能力位图,标记控制器支持哪些DMA操作类型 |
| copy_align | unsigned int | MEMCPY操作的对齐要求(2的幂次) |
| fill_align | unsigned int | MEMSET操作的对齐要求 |
| dev_id | int | 控制器在系统中的唯一ID,用于sysfs接口 |
| device_alloc_chan_resources | 函数指针 | 分配通道资源的回调函数 |
| device_free_chan_resources | 函数指针 | 释放通道资源的回调函数 |
| device_prep_dma_memcpy | 函数指针 | 准备MEMCPY传输描述符的回调 |
| device_issue_pending | 函数指针 | 触发通道开始执行待处理传输 |
驱动开发提示:在probe函数中,驱动需要先填充dma_device的cap_mask、对齐参数和所有回调函数指针,然后调用
dma_async_device_register()将设备注册到DMA引擎核心框架。
3.2 dma_chan_percpu结构体:每CPU通道统计
dma_chan_percpu是DMA引擎中用于统计每个CPU核上通道使用情况的结构体。它嵌入在dma_chan结构体中,以percpu变量的形式存在,避免多核竞争。
struct dma_chan_percpu {
/* 统计计数 */
unsigned long memcpy_count; /* MEMCPY操作完成次数 */
unsigned long bytes_transferred; /* 已传输字节总数 */
/* 时间戳 */
unsigned long timestamp; /* 最后一次操作的时间戳 */
};
| 字段 | 用途 | 更新时机 |
|---|---|---|
| memcpy_count | 记录该CPU上通过该通道完成的MEMCPY操作次数 | 每次传输完成回调中递增 |
| bytes_transferred | 累计传输的字节数,用于带宽统计 | 传输完成时累加实际长度 |
| timestamp | 记录最后一次传输完成的时间点 | 传输完成时写入jiffies |
注意事项:dma_chan_percpu是percpu变量,驱动代码中必须使用
this_cpu_ptr()或per_cpu_ptr()宏来访问,不能直接解引用。错误的使用方式会导致数据竞争或访问越界。
3.3 DMA通道能力描述:dma_cap_mask_t
dma_cap_mask_t是一个位图类型,用于标记DMA控制器支持哪些传输操作类型。它定义在include/linux/dmaengine.h中,本质上是DECLARE_BITMAP的封装。
/* 能力位图类型定义 */
typedef DECLARE_BITMAP(dma_cap_mask_t, DMA_MAX);
/* 常用能力标志枚举 */
enum dma_transaction_type {
DMA_MEMCPY, /* 内存到内存拷贝 */
DMA_XOR, /* XOR校验计算 */
DMA_PQ, /* P+Q RAID校验 */
DMA_XOR_VAL, /* XOR校验验证 */
DMA_PQ_VAL, /* P+Q校验验证 */
DMA_MEMSET, /* 内存填充 */
DMA_INTERRUPT, /* 中断触发(无数据传输) */
DMA_SLAVE, /* 外设到内存/内存到外设 */
DMA_CYCLIC, /* 循环传输(音频流等) */
DMA_INTERLEAVE, /* 交错传输 */
/* ... 其他类型 */
};
| 能力标志 | 典型应用场景 | 硬件要求 |
|---|---|---|
| DMA_MEMCPY | 内存拷贝加速、数据搬运 | 支持内存到内存的DMA传输 |
| DMA_SLAVE | SPI/UART/I2C外设数据收发 | 支持外设触发和流控制 |
| DMA_XOR | RAID5校验计算 | 硬件XOR引擎 |
| DMA_PQ | RAID6 P+Q校验 | 硬件Galois域计算引擎 |
| DMA_CYCLIC | 音频播放、波形生成 | 支持环形缓冲区自动循环 |
| DMA_INTERLEAVE | 多通道数据交织传输 | 支持多源多目标交错 |
位图操作API:内核提供了一组宏来操作dma_cap_mask_t:
dma_cap_set()设置能力位,dma_cap_clear()清除能力位,dma_has_cap()检查是否支持某能力。驱动在注册设备前必须正确设置cap_mask。
3.4 三者的协作关系
在DMA引擎驱动中,dma_device、dma_chan_percpu和dma_cap_mask_t协同工作,构成完整的硬件抽象层:
- dma_device作为顶层容器,包含cap_mask和通道链表
- dma_cap_mask_t在dma_device中标记控制器能力,供消费者查询
- 每个dma_chan包含percpu的dma_chan_percpu统计信息
- 消费者通过
dma_request_channel()传入cap_mask过滤条件,内核自动匹配
最佳实践:编写DMA引擎驱动时,建议按照以下顺序初始化:
1. 分配dma_device并清零
2. 使用dma_cap_set()设置所有支持的能力位
3. 填充对齐参数和回调函数指针
4. 初始化通道链表并添加所有dma_chan
5. 为每个dma_chan分配percpu的dma_chan_percpu
6. 调用dma_async_device_register()注册
1. 分配dma_device并清零
2. 使用dma_cap_set()设置所有支持的能力位
3. 填充对齐参数和回调函数指针
4. 初始化通道链表并添加所有dma_chan
5. 为每个dma_chan分配percpu的dma_chan_percpu
6. 调用dma_async_device_register()注册
/* 驱动初始化示例片段 */
static int my_dma_probe(struct platform_device *pdev)
{
struct dma_device *dma_dev;
struct my_dma_chan *my_chan;
int i, ret;
dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
if (!dma_dev)
return -ENOMEM;
/* 1. 设置能力位图 */
dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask);
dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
/* 2. 设置对齐参数 */
dma_dev->copy_align = DMAENGINE_ALIGN_8_BYTES;
/* 3. 填充回调函数 */
dma_dev->device_alloc_chan_resources = my_alloc_chan_resources;
dma_dev->device_free_chan_resources = my_free_chan_resources;
dma_dev->device_prep_dma_memcpy = my_prep_dma_memcpy;
dma_dev->device_issue_pending = my_issue_pending;
/* 4. 初始化通道 */
INIT_LIST_HEAD(&dma_dev->channels);
for (i = 0; i < MY_DMA_NUM_CHANNELS; i++) {
my_chan = &my_dma_data->channels[i];
my_chan->chan.device = dma_dev;
/* 为每个通道分配percpu统计 */
my_chan->chan.percpu = alloc_percpu(struct dma_chan_percpu);
list_add_tail(&my_chan->chan.device_node, &dma_dev->channels);
}
/* 5. 注册DMA设备 */
ret = dma_async_device_register(dma_dev);
if (ret)
return ret;
platform_set_drvdata(pdev, dma_dev);
return 0;
}
通过这三个核心数据结构的合理设计与使用,DMA引擎驱动能够向内核上层提供一个清晰、可扩展的硬件抽象层,使得DMA控制器消费者(如音频驱动、RAID加速模块、网络驱动等)无需关心底层硬件细节,仅通过标准API即可完成数据搬运任务。