设计说明
DMA 源代码位于 drivers/dma/artinchip-dma.c。
Linux DMA Engine 子系统架构
Linux 提供了一个 DMA Engine 子系统,可封装不同类型的 DMA 控制器驱动,便于 DMA 用户了解硬件细节。

- DMA Device
- 对应物理上的一个 Controller。
- DMA Driver 需要提供 Controller 的属性和接口,并注册成为 DMA Device,供后续 DMA Engine 框架来调用。
- 支持注册多个 DMA Device,并使用一个链表 dma_device_list 来进行管理。
- DMA channel
- 对应物理上的 DMA 通道,如图中 DMA Controller 的 Chx。
- 物理 DMA 通道也是通过一个链表进行管理,归属于同一个 DMA Device。
- VC(Virtual channel)
- VC,即虚拟通道
- 基于物理的 DMA 通道,DMA Engine 提供了一种 VC。
- VC 数目往往多于物理通道数,比如 VC 有 48 个而物理通道只有 8 个,便于提供一种动态的物理通道分配机制。
- DMA Client
- DMA 模块的使用者。
- DMA 用户仅限内核中的其它模块,如 SPI、Audio Codec、UART 等。
- 暂未提供用户态的使用接口。
关键流程设计
本节介绍了 DMA 模块的关键流程。
初始化流程
DMA 驱动的初始化过程见 aic_dma_probe() 函数。
int dma_async_device_register(struct dma_device *device)
- DMA
控制器能力描述,包括但不限于地址宽度、传输方向以及剩余粒度。配置示例如下:
if (of_device_is_compatible(pdev->dev.of_node, "artinchip,aic-dma-v0.1")) sdev->slave.copy_align = DMAENGINE_ALIGN_128_BYTES; else sdev->slave.copy_align = DMAENGINE_ALIGN_8_BYTES; sdev->slave.src_addr_widths = AIC_DMA_BUS_WIDTH; sdev->slave.dst_addr_widths = AIC_DMA_BUS_WIDTH; sdev->slave.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); sdev->slave.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; INIT_LIST_HEAD(&sdev->slave.channels); dma_cap_set(DMA_PRIVATE, sdev->slave.cap_mask); dma_cap_set(DMA_MEMCPY, sdev->slave.cap_mask); dma_cap_set(DMA_SLAVE, sdev->slave.cap_mask); dma_cap_set(DMA_CYCLIC, sdev->slave.cap_mask);
表 1. DMA 控制器的能力特性含义 能力特性
含义
DMA_PRIVATE
不支持异步传输
DMA_MEMCPY
支持内存到内存的拷贝操作
DMA_SLAVE
支持设备到内存的传输操作
DMA_CYCLIC
支持循环 Buffer 的情况
-
DMA 操作 API,初始化示例如下:
sdev->slave.device_free_chan_resources = aic_dma_free_chan_resources; sdev->slave.device_prep_dma_memcpy = aic_dma_prep_dma_memcpy; sdev->slave.device_prep_slave_sg = aic_dma_prep_slave_sg; sdev->slave.device_prep_dma_cyclic = aic_dma_prep_dma_cyclic; sdev->slave.device_config = aic_dma_config; sdev->slave.device_pause = aic_dma_pause; sdev->slave.device_resume = aic_dma_resume; sdev->slave.device_terminate_all = aic_dma_terminate_all; sdev->slave.device_tx_status = aic_dma_tx_status; sdev->slave.device_issue_pending = aic_dma_issue_pending; sdev->slave.device_release = aic_dma_device_release;
DMA Client 的调用流程
作为 DMA 用户,调用流程如下:

在 DMA Client 调用流程中:
-
dmaengine_submit():将传输请求提交到 DMA Engine 的缓存中,但不开始传输数据。
-
dma_async_issue pending():将传输请求加入到 DMA Device 的请求队列中,接下来才会启动数据传输。
中断处理流程
DMA 中断处理流程可以确保数据高效传输并合理配置系统资源。当 DMA 传输完成或遇到错误时,中断机制会触发相应的处理程序。中断处理流程如下所示:
-
逐个 DMA 通道的查看完成状态。
-
如果当前传输是循环 Buffer 的情况,则直接调用预先注册好的回调接口。
-
如果不是循环模式,则更新相应的通道状态为 Complete。
数据结构设计
本节介绍了 DMA 数据类型及其结构描述。
struct aic_dma_dev
struct aic_dma_dev {
void __iomem *base;
int irq;
u32 num_pchans;
u32 num_vchans;
u32 max_request;
struct clk *clk;
struct reset_control *reset;
spinlock_t lock;
struct dma_pool *pool;
struct aic_pchan *pchans;
struct aic_vchan *vchans;
const struct aic_dma_inf *dma_inf;
struct dma_device slave;
};
struct aic_dma_inf
struct aic_dma_inf {
u8 nr_chans; /* count of dma physical channels */
u8 nr_ports; /* count of dma drq prots */
u8 nr_vchans; /* total valid transfer types */
u32 burst_length; /* burst length capacity */
u32 addr_widths; /* address width support capacity */
};
DMA 通道信息
DMA 物理通道和 DMA 虚拟通道是一对多的关系,在设计中需要互相记录对方的数据引用指针。
- DMA 物理通道信息:记录了一个 DMA
物理通道对应的通道号、寄存器基地址、对应的虚拟通道指针等。
struct aic_pchan { u32 id; /* DMA channel number */ void __iomem *base; /* DMA channel control registers */ struct aic_vchan *vchan; /* virtual channel info */ };
- DMA 虚拟通道信息:记录了一个 DMA 虚拟通道对应的 DRQ
端口号、传输类型、对应的物理通道指针等。
struct aic_vchan { u8 port; /* DRQ port number */ u8 irq_type; /* IRQ types */ bool cyclic; /* flag to mark if cyclic transfer one package */ struct aic_pchan *pchan; /* physical DMA channel */ struct aic_desc *desc; /* current transfer */ /* parameter for dmaengine */ struct virt_dma_chan vc; struct dma_slave_config cfg; enum dma_status status; };
struct aic_dma_task
DMA 控制器支持散列 (Scatter Gather) 的描述符参数形式,需要提前将参数分组打包到多个描述符中,一个 Buffer 对应一组散列参数。这些描述符会组成一个链表,然后将这个链表的第一个描述符的物理地址传给 DMA 控制器。描述符组成的链表结构如下图:

struct aic_dma_task {
u32 cfg; /* DMA transfer configuration */
u32 src; /* source address of one transfer package */
u32 dst; /* destination address of one transfer package */
u32 len; /* data length of one transfer package */
u32 delay; /* time delay for period transfer */
u32 p_next; /* next task node for DMA controller */
u32 mode; /* the negotiation mode */
/*
* virtual list for driver maintain package list,
* not used by DMA controller
*/
struct aic_dma_task *v_next;
};
接口设计
以下接口是遵循 Linux DMA Engine 子系统的标准接口:
函数原型 |
static int aic_dma_config(struct dma_chan *chan, struct dma_slave_config *config) |
---|---|
功能说明 |
配置指定的 DMA 物理通道 |
参数定义 |
chan - 指向一个 DMA 物理通道 config - 保存了需要的配置信息 |
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
static int aic_dma_pause(struct dma_chan *chan) |
---|---|
功能说明 |
暂停指定通道的传输操作 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
static int aic_dma_resume(struct dma_chan *chan) |
---|---|
功能说明 |
恢复指定通道的传输操作 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
0,成功 |
注意事项 |
- |
函数原型 |
static struct dma_async_tx_descriptor *aic_dma_prep_dma_memcpy(struct dma_chan *chan,dma_addr_t dest, dma_addr_t src,size_t len, unsigned long flags) |
---|---|
功能说明 |
memcpy 操作的预处理 |
参数定义 |
chan - 指向一个 DMA 物理通道 dest - 目标 Buffer 的物理地址 src - 源 Buffer 的物理地址 len - 数据长度 flags - 一些标记 |
返回值 |
成功,则返回一个 DMA 描述符。失败,返回 NULL |
注意事项 |
- |
函数原型 |
static struct dma_async_tx_descriptor *aic_dma_prep_slave_sg(struct dma_chan *chan,struct scatterlist *sgl, unsigned int sg_len,enum dma_transfer_direction dir, unsigned long flags,void *context) |
---|---|
功能说明 |
设备与内存之间传输操作的预处理 |
参数定义 |
chan - 指向一个 DMA 物理通道 sgl - 指向一个散列列表 sg_len - 散列中的数据长度 flags - 一些标记 context - 指向一些私有的上下文信息 dir - 传输方向,是 Dev to Mem,还是 Mem to Dev |
返回值 |
成功,则返回一个 DMA 描述符。失败,返回 NULL |
注意事项 |
- |
函数原型 |
static struct dma_async_tx_descriptor *aic_dma_prep_dma_cyclic(struct dma_chan *chan,dma_addr_t buf_addr, size_t buf_len, size_t period_len,enum dma_transfer_direction dir, unsigned long flags) |
---|---|
功能说明 |
(设备与内存之间)循环传输操作的预处理 |
参数定义 |
chan - 指向一个 DMA 物理通道 buf_addr - 循环 Buffer 的起始物理地址 buf_len - 循环 Buffer 的总长度 period_len - 循环的 Buffer 片段长度 dir - 传输方向,是 Dev to Mem,还是 Mem to Dev flags - 一些标记 |
返回值 |
成功,则返回一个 DMA 描述符。失败,返回 NULL |
注意事项 |
- |
函数原型 |
static void aic_dma_issue_pending(struct dma_chan *chan) |
---|---|
功能说明 |
启动指定通道的数据传输 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
无 |
注意事项 |
- |
函数原型 |
static int aic_dma_terminate_all(struct dma_chan *chan) |
---|---|
功能说明 |
终止所有通道的数据传输 |
参数定义 |
chan - 指向一个 DMA 物理通道 |
返回值 |
0,成功 |
注意事项 |
- |
Demo
SPI 驱动 (详见 drivers/spi/spi-artinchip.c)中调用了 DMA 进行数据传输,其使用过程可以当作 Demo 参考。
DMA 通道的申请
static int aic_spi_probe(struct platform_device *pdev)
{
...
aicspi->dma_rx = dma_request_slave_channel(aicspi->dev, "rx");
if (!aicspi->dma_rx)
dev_warn(aicspi->dev, "failed to request rx dma channel\n");
aicspi->dma_tx = dma_request_slave_channel(aicspi->dev, "tx");
if (!aicspi->dma_tx)
dev_warn(aicspi->dev, "failed to request tx dma channel\n");
...
}
DMA 数据提交
static int aic_spi_dma_rx_cfg(struct aic_spi *aicspi, struct spi_transfer *t)
{
struct dma_async_tx_descriptor *dma_desc = NULL;
struct dma_slave_config dma_conf = {0};
dma_conf.direction = DMA_DEV_TO_MEM;
dma_conf.src_addr = aicspi->dma_addr_rx;
dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
dma_conf.src_maxburst = 1;
dma_conf.dst_maxburst = 1;
dmaengine_slave_config(aicspi->dma_rx, &dma_conf);
dma_desc = dmaengine_prep_slave_sg(aicspi->dma_rx, t->rx_sg.sgl,
t->rx_sg.nents, dma_conf.direction,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!dma_desc) {
dev_err(aicspi->dev, "spi-%d prepare slave sg failed.\n",
aicspi->ctlr->bus_num);
return -EINVAL;
}
dma_desc->callback = aic_spi_dma_cb_rx;
dma_desc->callback_param = (void *)aicspi;
dmaengine_submit(dma_desc);
return 0;
}
启动 DMA 数据传输
static int aic_spi_dma_rx_start(struct spi_device *spi, struct spi_transfer *t)
{
struct aic_spi *aicspi = spi_controller_get_devdata(spi->master);
int ret = 0;
spi_ctlr_dma_rx_enable(aicspi->base_addr);
ret = aic_spi_dma_rx_cfg(aicspi, t);
if (ret < 0)
return ret;
dma_async_issue_pending(aicspi->dma_rx);
return ret;
}