4.3.5. 设计说明
4.3.5.1. 源码说明
源代码位于:drivers/dma/zx-dma.c
4.3.5.2. 模块架构
Linux提供了一个 DMA Engine 子系统,可封装不同类型的 DMA控制器驱动,便于实现 DMA 用户对硬件细节的透明。
DMA Engine的软件框架如下图:
图中可以看到DMA Engine中有几个概念:
- DMA Device
对应物理上的一个DMA Controller。DMA Driver需要提供DMA Controller的一些属性、接口,然后注册为一个DMA Device,供后续DMA Engine框架来调用。支持注册多个DMA Device,会使用一个链表 dma_device_list 来进行管理。
- DMA channel
和物理上的一个DMA通道(如图中DMA Controller的Chx)一一对应。这些通道也是通过一个链表进行管理,归属于某一个DMA Device。
- VC(Virtual channel)
基于物理的DMA通道,DMA Engine提供了一种虚拟的通道概念VC,VC数目往往多于物理通道数,比如VC有48个而物理通道只有8个,这样可以提供一个动态的物理通道分配机制。
- DMA Client
指DMA模块的使用者,DMA用户仅限内核中的其他模块,如SPI、Audio Codec、UART等,暂未提供用户态的使用接口。
4.3.5.3. 关键流程设计
4.3.5.3.1. 初始化流程
DMA驱动的初始化过程见aic_dma_probe()函数,除了普通platform设备的处理过程(申请regs资源、clk、reset)外,需要调用DMA子系统的接口dma_async_device_register()来注册DMA备。
int dma_async_device_register(struct dma_device *device)
其中参数struct dma_device 需要提供的关键信息有:DMA控制器能力描述、DMA操作API等,其初始化内容如下:
/* 配置 DMA 控制器的能力描述信息 */
if (of_device_is_compatible(pdev->dev.of_node,
"zx,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);
/* 初始化 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控制器的能力特性含义如下:
能力特性 |
含义 |
---|---|
DMA_PRIVATE |
不支持异步传输 |
DMA_MEMCPY |
支持内存到内存的拷贝操作 |
DMA_SLAVE |
支持设备到内存的传输操作 |
DMA_CYCLIC |
支持循环Buffer的情况 |
4.3.5.3.2. DMA Client 的调用流程
作为DMA 用户,调用流程如下:
其中有两个操作的概念需要注意:
submit,是指传输请求提交给了DMA Engine的缓存中,还没有开始传输数据
issue pending,将传输请求加入到DMA Device的请求队列中,接下来才会启动数据传输动作
4.3.5.3.3. 中断处理流程
中断处理流程相对简单:
逐个DMA通道的查看完成状态;
如果当前传输是循环Buffer的情况,则直接调用预先注册好的回调接口;
如果不是循环模式,则更新相应的通道状态为Complete。
4.3.5.4. 数据结构设计
4.3.5.4.1. aic_dma_dev
记录DMA控制器的配置信息:
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;
};
4.3.5.4.2. aic_dma_inf
记录DMA控制器的一些特性,如通道数、端口数、Burst长度、地址宽度,这些特性会因不同SoC而不同,所以此数据结构会用在 of_device_id 中的私有数据,配合 compatible 来区分不同的SoC。
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 */
};
4.3.5.4.3. DMA 通道信息
由 模块架构 可知 DMA物理通道 和 DMA虚拟通道 是一对多的关系,所以在设计中它们互相都需要在记录好对方的数据引用指针。
4.3.5.4.3.1. DMA 物理通道信息
记录了一个 DMA 物理通道对应的通道号、寄存器基地址、对应的虚拟通道指针等:
struct aic_pchan {
u32 id; /* DMA channel number */
void __iomem *base; /* DMA channel control registers */
struct aic_vchan *vchan; /* virtual channel info */
};
4.3.5.4.3.2. 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;
};
4.3.5.4.4. DMA 描述符
DMA 控制器支持散列(Scatter Gather)的描述符参数形式,需要提前将参数分组(一个Buffer对应一组散列参数)打包到多个描述符中,这些描述符会组成一个链表,然后将这个链表的第一个描述符的物理地址传给DMA控制器。描述符组成的链表结构如下图:
小技巧
End Flag
是DMA控制器硬件预先定义好的一个数值:0xfffff800。
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;
};
4.3.5.5. 接口设计
以下接口是 Linux DMA Engine 子系统的标准接口。
4.3.5.5.1. aic_dma_config
函数原型 |
static int aic_dma_config(struct dma_chan *chan, struct dma_slave_config *config) |
---|---|
功能说明 |
配置指定的DMA物理通道 |
参数定义 |
chan - 指向一个DMA物理通道
config - 保存了需要的配置信息
|
返回值 |
0,成功 |
注意事项 |
4.3.5.5.2. aic_dma_pause
函数原型 |
static int aic_dma_pause(struct dma_chan *chan) |
---|---|
功能说明 |
暂停指定通道的传输操作 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
0,成功 |
注意事项 |
4.3.5.5.3. aic_dma_resume
函数原型 |
static int aic_dma_resume(struct dma_chan *chan) |
---|---|
功能说明 |
恢复指定通道的传输操作 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
0,成功 |
注意事项 |
4.3.5.5.4. aic_dma_prep_dma_memcpy
函数原型 |
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 |
注意事项 |
4.3.5.5.5. aic_dma_prep_slave_sg
函数原型 |
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 - 散列中的数据长度
dir - 传输方向,是 Dev to Mem,还是 Mem to Dev
flags - 一些标记
context - 指向一些私有的上下文信息
|
返回值 |
成功,则返回一个DMA描述符;失败,返回NULL |
注意事项 |
4.3.5.5.6. aic_dma_prep_dma_cyclic
函数原型 |
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 |
注意事项 |
4.3.5.5.7. aic_dma_issue_pending
函数原型 |
static void aic_dma_issue_pending(struct dma_chan *chan) |
---|---|
功能说明 |
启动指定通道的数据传输 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
无 |
注意事项 |
4.3.5.5.8. aic_dma_terminate_all
函数原型 |
static int aic_dma_terminate_all(struct dma_chan *chan) |
---|---|
功能说明 |
终止所有通道的数据传输 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
0,成功 |
注意事项 |
4.3.5.6. Demo
SPI 驱动(详见drivers/spi/spi-zx.c)中调用了DMA进行数据传输,其使用过程可以当作Demo参考:
4.3.5.6.1. 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");
...
}
4.3.5.6.2. 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;
}
4.3.5.6.3. 启动 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;
}