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 驱动的软件框架如下图:

../../../_images/sw_system12.png

图 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) 完成注册,其中主要步骤有:

  1. 初始化模块的clk

  2. 初始化DMA通道、任务描述符管理信息

  3. 注册中断

4.3.5.3.2. DMA Client 的调用流程

作为DMA 用户,调用流程和 Linux 的 DMA Engine 完全相同,如下:

../../../_images/client_flow.png

图 4.22 DMA Client调用流程

其中有两个操作的概念需要注意:

  • submit,是指传输请求提交给了 DMA 驱动的缓存中,还没有开始传输数据

  • issue pending,才会真正启动数据传输动作

4.3.5.3.3. 中断处理流程

中断处理流程主要是:

  1. 逐个DMA通道的查看完成状态;

  2. 如果通道有任务传输完成,就调用相应DMA client注册的回调函数。

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控制器。描述符组成的链表结构如下图:

../../../_images/dma_task.png

图 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);
}