4.3.5. 设计说明¶
4.3.5.1. 源码说明¶
源代码位于 bsp/artinchip/
:
bsp/artinchip/drv/dma/drv_dma.c,DMA Driver 层实现
bsp/artinchip/include/drv/drv_dma.h,DMA Driver 层接口,提供了类似 Linux 的 DMA Engine 接口
bsp/artinchip/hal/dma/hal_dma.c,DMA HAL 层实现
bsp/artinchip/include/hal/hal_dma.h,DMA HAL 层接口头文件
bsp/artinchip/hal/dma/hal_dma_reg.h,DMA 控制器的寄存器定义
4.3.5.2. 模块架构¶
RTOS 系统中并没有提供了一个类似 Linux 的 DMA Engine 子系统,为了方便DMA使用者的代码兼容,DMA驱动提供了类似Linux的DMA Engine接口定义。
DMA 驱动的软件框架如下图:

图 4.21 DMA 驱动的软件架构图¶
图中可以看到,DMA 驱动不依赖任何 RTOS 的设备驱动模型,所以无论是在 RTOS 环境、还是 baremetal 环境,都是可以直接调用 DMA Engine API 的。
4.3.5.3. 关键流程设计¶
4.3.5.3.1. 初始化流程¶
DMA 驱动的初始化接口通过 INIT_BOARD_EXPORT(drv_dma_init)
完成注册,其中主要步骤有:
初始化模块的clk
初始化DMA通道、任务描述符管理信息
注册中断
4.3.5.4. 数据结构设计¶
4.3.5.4.1. struct aic_dma_dev¶
属于 HAL 层接口,记录DMA控制器的配置信息:
struct aic_dma_dev {
struct aic_dma_task task[TASK_MAX_NUM];
s32 inited;
unsigned long base;
u32 burst_length; /* burst length capacity */
u32 addr_widths; /* address width support capacity */
struct aic_dma_chan dma_chan[AIC_DMA_CH_NUM];
struct aic_dma_task *freetask;
};
4.3.5.4.2. struct aic_dma_chan¶
属于 HAL 层接口,记录了一个 DMA 物理通道对应的通道号、寄存器基地址等信息:
struct aic_dma_chan {
u8 ch_nr; /* drq port number */
u8 used;
u8 irq_type; /* irq types */
bool cyclic; /* flag to mark if cyclic transfer one package */
bool memset;
unsigned long base;
struct dma_slave_config cfg;
volatile int lock;
dma_async_callback callback;
void *callback_param;
struct aic_dma_task * desc;
};
4.3.5.4.3. struct aic_dma_task¶
DMA 控制器支持散列(Scatter Gather)的描述符参数形式,需要提前将参数分组(一个Buffer对应一组散列参数)打包到多个描述符中,这些描述符会组成一个链表,然后将这个链表的第一个描述符的物理地址传给DMA控制器。描述符组成的链表结构如下图:

图 4.23 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; /* distination address of one transfer package */
u32 len; /* data length of one transfer package */
u32 delay; /* time delay for period transfer */
u32 p_next; /* next package for dma controller */
u32 mode; /* the negotiation mode, not used by phsical task list */
#if (CACHE_LINE_SIZE == 64)
u32 pad[7];
#endif
/*
* virtual list for cpu maintain package list,
* not used by dma controller
*/
struct aic_dma_task *v_next;
};
4.3.5.4.4. struct dma_slave_config¶
DAM 通道的配置信息,和 Linux DMA Engine 接口的定义保持一致。
struct dma_slave_config {
enum dma_transfer_direction direction;
unsigned long src_addr;
unsigned long dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
uint32_t src_maxburst;
uint32_t dst_maxburst;
uint32_t slave_id;
};
4.3.5.5. Driver 层接口设计¶
以下接口是遵循 Linux DMA Engine 子系统的标准接口。
4.3.5.5.1. dmaengine_slave_config¶
函数原型 |
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config) |
---|---|
功能说明 |
配置指定的DMA物理通道 |
参数定义 |
chan - 指向一个DMA物理通道
config - 保存了需要的配置信息
|
返回值 |
0,成功 |
注意事项 |
4.3.5.5.2. dmaengine_pause¶
函数原型 |
int dmaengine_pause(struct dma_chan *chan) |
---|---|
功能说明 |
暂停指定通道的传输操作 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
0,成功 |
注意事项 |
4.3.5.5.3. dmaengine_resume¶
函数原型 |
int dmaengine_resume(struct dma_chan *chan) |
---|---|
功能说明 |
恢复指定通道的传输操作 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
0,成功 |
注意事项 |
4.3.5.5.4. dmaengine_prep_dma_memcpy¶
函数原型 |
int dmaengine_prep_dma_memcpy(struct dma_chan *chan,
uint32_t dest, uint32_t src, uint32_t len)
|
---|---|
功能说明 |
memcpy操作的预处理 |
参数定义 |
chan - 指向一个DMA物理通道
dest - 目标Buffer的物理地址
src - 源Buffer的物理地址
len - 数据长度
|
返回值 |
0,成功;<0,失败 |
注意事项 |
4.3.5.5.5. dmaengine_prep_dma_device¶
函数原型 |
int dmaengine_prep_dma_device(struct dma_chan *chan,
uint32_t dest, uint32_t src, uint32_t len,
enum dma_transfer_direction dir)
|
---|---|
功能说明 |
设备与内存之间传输操作的预处理 |
参数定义 |
chan - 指向一个DMA物理通道
dest - 目标Buffer的物理地址
src - 源Buffer的物理地址
len - 数据长度
dir - 传输方向,是 Dev to Mem,还是 Mem to Dev
|
返回值 |
0,成功;<0,失败 |
注意事项 |
4.3.5.5.6. dmaengine_prep_dma_cyclic¶
函数原型 |
int dmaengine_prep_dma_cyclic(struct dma_chan *chan,
uint32_t buf_addr, uint32_t buf_len, uint32_t period_len,
enum dma_transfer_direction dir)
|
---|---|
功能说明 |
(设备与内存之间)循环传输操作的预处理 |
参数定义 |
chan - 指向一个DMA物理通道
buf_addr - 循环Buffer的起始物理地址
buf_len - 循环Buffer的总长度
period_len - 循环的Buffer片段长度
dir - 传输方向,是 Dev to Mem,还是 Mem to Dev
|
返回值 |
0,成功;<0,失败 |
注意事项 |
4.3.5.5.7. dma_async_issue_pending¶
函数原型 |
void dma_async_issue_pending(struct dma_chan *chan) |
---|---|
功能说明 |
启动指定通道的数据传输 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
无 |
注意事项 |
4.3.5.5.8. dmaengine_terminate_async¶
函数原型 |
int dmaengine_terminate_async(struct dma_chan *chan) |
---|---|
功能说明 |
终止指定通道的数据传输 |
参数定义 |
chan - 指向一个DMA物理通道 |
返回值 |
0,成功 |
注意事项 |
4.3.5.5.9. dmaengine_tx_status¶
函数原型 |
enum dma_status dmaengine_tx_status(struct dma_chan *chan, uint32_t *residue) |
---|---|
功能说明 |
获取指定通道的传输状态 |
参数定义 |
chan - 指向一个DMA物理通道
residue - 还没有传输完成的剩余长度,单位:字节
|
返回值 |
DMA_COMPLETE,传输完成;DMA_IN_PROGRESS,传输中 |
注意事项 |
4.3.5.6. HAL 层接口设计¶
HAL 层的函数接口声明存放在 hal_dma.h 中,主要接口有:
int hal_dma_chan_prep_memset(struct aic_dma_chan *chan,
uint32_t p_dest,
uint32_t value,
uint32_t len);
int hal_dma_chan_prep_memcpy(struct aic_dma_chan *chan,
uint32_t p_dest,
uint32_t p_src,
uint32_t len);
int hal_dma_chan_prep_device(struct aic_dma_chan *chan,
uint32_t p_dest,
uint32_t p_src,
uint32_t len,
enum dma_transfer_direction dir);
int hal_dma_chan_prep_cyclic(struct aic_dma_chan *chan,
uint32_t p_buf_addr,
uint32_t buf_len,
uint32_t period_len,
enum dma_transfer_direction dir);
int hal_dma_chan_tx_status(struct aic_dma_chan *chan, uint32_t *left_size);
int hal_dma_chan_start(struct aic_dma_chan *chan);
int hal_dma_chan_stop(struct aic_dma_chan *chan);
int hal_dma_chan_pause(struct aic_dma_chan *chan);
int hal_dma_chan_resume(struct aic_dma_chan *chan);
int hal_dma_chan_terminate_all(struct aic_dma_chan *chan);
int hal_dma_chan_register_cb(struct aic_dma_chan *chan,
dma_async_callback callback,
void *callback_param);
int hal_dma_chan_config(struct aic_dma_chan *chan,
struct dma_slave_config *config);
int hal_release_dma_chan(struct aic_dma_chan *chan);
struct aic_dma_chan * hal_request_dma_chan(void);
int hal_dma_init(void);
int hal_dma_deinit(void);
int hal_dma_chan_dump(int ch_nr);
irqreturn_t hal_dma_irq(int irq, void *arg);
4.3.5.7. Demo¶
4.3.5.7.1. Mem to Device¶
SPI 驱动(详见 bsp/artinchip/hal/qspi/aic_hal_qspi.c)中调用了DMA进行数据传输,其使用过程可以当作Demo参考:
static s32 qspi_tx_rx_dma(u32 base, u8 *tx, u32 txlen, u8 *rx, u32 rxlen)
{
u32 poll_time, single_len;
s32 ret = 0;
single_len = 0;
struct aic_dma_chan *rx_dma, *tx_dma;
struct dma_slave_config dmacfg;
qspi_reset_fifo(base);
tx_dma = NULL;
rx_dma = NULL;
if (tx) {
spi_setbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base));
if (qspi_in_single_mode(base))
single_len = txlen;
qspi_set_xfer_cnt(base, txlen, 0, single_len, 0);
tx_dma = hal_request_dma_chan();
if (!tx_dma)
goto out;
dmacfg.direction = DMA_MEM_TO_DEV;
dmacfg.src_addr = (unsigned long)tx;
dmacfg.dst_addr = (unsigned long)SPI_REG_TXD(base);
dmacfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmacfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmacfg.src_maxburst = 1;
dmacfg.dst_maxburst = 1;
dmacfg.slave_id = 10; // FIXME: should set id according spi id
ret = hal_dma_chan_config(tx_dma, &dmacfg);
if (ret)
goto out;
ret = hal_dma_chan_prep_device(
tx_dma, (uint32_t)(unsigned long)SPI_REG_TXD(base),
(uint32_t)(unsigned long)tx, txlen, DMA_MEM_TO_DEV);
if (ret)
goto out;
ret = hal_dma_chan_start(tx_dma);
if (ret)
goto out;
/* Start transfer */
spi_setbits(TCR_BIT_XCH, SPI_REG_TCR(base));
poll_time = 0x7FFFFFFF;
while (!(readl(SPI_REG_ISR(base)) & ISR_BIT_TC)) {
poll_time--;
if (poll_time == 0) {
ret = -1;
spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base));
hal_log_err("TX Transfer complete timeout at the end.\n");
goto out;
}
}
spi_setbits(ISR_BIT_TX_EMP, SPI_REG_ISR(base));
spi_setbits(ISR_BIT_TX_FULL, SPI_REG_ISR(base));
spi_setbits(ISR_BIT_TX_RDY, SPI_REG_ISR(base));
spi_setbits(ISR_BIT_TC, SPI_REG_ISR(base));
spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base));
}
if (rx) {
spi_setbits(FCR_BIT_RX_DMA_EN, SPI_REG_FCR(base));
qspi_set_xfer_cnt(base, 0, rxlen, 0, 0);
rx_dma = hal_request_dma_chan();
if (!rx_dma)
goto out;
dmacfg.direction = DMA_DEV_TO_MEM;
dmacfg.src_addr = (unsigned long)SPI_REG_RXD(base);
dmacfg.dst_addr = (unsigned long)rx;
dmacfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmacfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmacfg.src_maxburst = 1;
dmacfg.dst_maxburst = 1;
dmacfg.slave_id = 10; // FIXME: should set id according spi id
ret = hal_dma_chan_config(rx_dma, &dmacfg);
if (ret)
goto out;
ret =
hal_dma_chan_prep_device(rx_dma, (uint32_t)(unsigned long)rx,
(uint32_t)(unsigned long)SPI_REG_RXD(base),
rxlen, DMA_DEV_TO_MEM);
if (ret)
goto out;
ret = hal_dma_chan_start(rx_dma);
if (ret)
goto out;
/* Start transfer */
spi_setbits(TCR_BIT_XCH, SPI_REG_TCR(base));
poll_time = 0x7FFFFFFF;
while (!(readl(SPI_REG_ISR(base)) & ISR_BIT_TC)) {
poll_time--;
if (poll_time == 0) {
ret = -1;
spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base));
hal_log_err("RX Transfer complete timeout at the end.\n");
goto out;
}
}
spi_setbits(ISR_BIT_TC, SPI_REG_ISR(base));
spi_setbits(ISR_BIT_RX_EMP, SPI_REG_ISR(base));
spi_setbits(ISR_BIT_RX_FULL, SPI_REG_ISR(base));
spi_setbits(ISR_BIT_RX_RDY, SPI_REG_ISR(base));
spi_clrbits(FCR_BIT_TX_DMA_EN, SPI_REG_FCR(base));
}
out:
if (tx_dma) {
hal_dma_chan_stop(tx_dma);
hal_release_dma_chan(tx_dma);
}
if (rx_dma) {
hal_dma_chan_stop(rx_dma);
hal_release_dma_chan(rx_dma);
}
return ret;
}
4.3.5.7.2. Mem to Mem¶
本Demo是 test_dma_memcpy 的源码(bsp/examples/test-dma/test_dma.c),完成一次Mem to Mem的拷贝操作:
static void dma_test_cb(void *param)
{
printf("DMA complete, callback....\n");
}
static void cmd_test_dma_memcpy(int argc, char **argv)
{
struct dma_chan *chan = NULL;
uint32_t test_len = 0, align_len = 0;
char *src = NULL, *dest = NULL;
int ret, i;
uint32_t size = 0;
#ifdef RT_USING_POSIX_CLOCK
struct timespec start, end;
#endif
if (argc != 2) {
pr_err("Invalid parameter\n");
return;
}
sscanf((char *)argv[1], "%u", &test_len);
test_len = roundup(test_len, 8);
align_len = roundup(test_len, CACHE_LINE_SIZE);
src = aicos_malloc_align(0, align_len, CACHE_LINE_SIZE);
dest = aicos_malloc_align(0, align_len, CACHE_LINE_SIZE);
if ((src == NULL) || (dest == NULL)){
pr_err("Alloc %d mem fail!\n ", align_len);
goto free_mem;
}
printf("DMA memcpy test: src = 0x%lx, dest = 0x%lx, len = 0x%x\n",
(unsigned long)src, (unsigned long)dest, test_len);
for (i = 0;i < test_len; i++)
src[i] = i & 0xff;
#ifdef RT_USING_POSIX_CLOCK
clock_gettime(CLOCK_REALTIME, &start);
#endif
chan = dma_request_channel();
if (chan == NULL){
pr_err("Alloc dma chan fail!\n ");
goto free_mem;
}
ret = dmaengine_prep_dma_memcpy(chan, (unsigned long)dest, (unsigned long)src, test_len);
if (ret){
pr_err("dmaengine_prep_dma_memcpy fail! ret = %d\n ", ret);
goto free_chan;
}
ret = dmaengine_submit(chan, dma_test_cb, chan);
if (ret){
pr_err("dmaengine_submit fail! ret = %d\n ", ret);
goto free_chan;
}
dma_async_issue_pending(chan);
while (dmaengine_tx_status(chan, &size) != DMA_COMPLETE);
aicos_dcache_invalid_range((unsigned long *)src, align_len);
aicos_dcache_invalid_range((unsigned long *)dest, align_len);
#ifdef RT_USING_POSIX_CLOCK
clock_gettime(CLOCK_REALTIME, &end);
#endif
for (i = 0;i < test_len; i++){
if (dest[i] != src[i]){
printf("addr 0x%x err: src - 0x%x, dest - 0x%x\n",
i, src[i], dest[i]);
ret = -1;
}
}
if (ret)
printf("DMA test fail!\n");
else
printf("DMA test succeed!\n");
#ifdef RT_USING_POSIX_CLOCK
printf("DMA memcpy %u bytes, speed %.2f MB/s\n", align_len,
(float)align_len / 1024 / 1024 / time_diff(&start, &end));
#endif
free_chan:
if (chan)
dma_release_channel(chan);
free_mem:
if (src)
aicos_free_align(0, src);
if (dest)
aicos_free_align(0, dest);
}