Edit online

设计说明

DMA 源代码位于 drivers/dma/artinchip-dma.c

Edit online

Linux DMA Engine 子系统架构

Linux 提供了一个 DMA Engine 子系统,可封装不同类型的 DMA 控制器驱动,便于 DMA 用户了解硬件细节。


sw_system20

1. Linux DMA Engine 子系统架构
Linux DMA Engine 子系统架构 展示了 DMA Engine 中的下列组成部分:
  • 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 等。
    • 暂未提供用户态的使用接口。
Edit online

关键流程设计

本节介绍了 DMA 模块的关键流程。

Edit online

初始化流程

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 控制器能力描述,包括但不限于地址宽度、传输方向以及剩余粒度。配置示例如下:
    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;
Edit online

DMA Client 的调用流程

作为 DMA 用户,调用流程如下:


client_flow1

2. DMA Client 调用流程

DMA Client 调用流程中:

  • dmaengine_submit():将传输请求提交到 DMA Engine 的缓存中,但不开始传输数据。

  • dma_async_issue pending():将传输请求加入到 DMA Device 的请求队列中,接下来才会启动数据传输。

Edit online

中断处理流程

DMA 中断处理流程可以确保数据高效传输并合理配置系统资源。当 DMA 传输完成或遇到错误时,中断机制会触发相应的处理程序。中断处理流程如下所示:

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

  2. 如果当前传输是循环 Buffer 的情况,则直接调用预先注册好的回调接口。

  3. 如果不是循环模式,则更新相应的通道状态为 Complete。

Edit online

数据结构设计

本节介绍了 DMA 数据类型及其结构描述。

struct 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;
};

struct 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 */
};

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


dma_task

3. 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;

};
Edit online

接口设计

以下接口是遵循 Linux DMA Engine 子系统的标准接口:

2. aic_dma_config

函数原型

static int aic_dma_config(struct dma_chan *chan, struct dma_slave_config *config)

功能说明

配置指定的 DMA 物理通道

参数定义

chan - 指向一个 DMA 物理通道

config - 保存了需要的配置信息

返回值

0,成功

注意事项

-

3. aic_dma_pause

函数原型

static int aic_dma_pause(struct dma_chan *chan)

功能说明

暂停指定通道的传输操作

参数定义

chan - 指向一个 DMA 物理通道

返回值

0,成功

注意事项

-

4. aic_dma_resume

函数原型

static int aic_dma_resume(struct dma_chan *chan)

功能说明

恢复指定通道的传输操作

参数定义

chan - 指向一个 DMA 物理通道

返回值

0,成功

注意事项

-

5. 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

注意事项

-

6. 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 - 散列中的数据长度

flags - 一些标记

context - 指向一些私有的上下文信息

dir - 传输方向,是 Dev to Mem,还是 Mem to Dev

返回值

成功,则返回一个 DMA 描述符。失败,返回 NULL

注意事项

-

7. 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

注意事项

-

8. aic_dma_issue_pending

函数原型

static void aic_dma_issue_pending(struct dma_chan *chan)

功能说明

启动指定通道的数据传输

参数定义

chan - 指向一个 DMA 物理通道

返回值

注意事项

-

9. aic_dma_terminate_all

函数原型

static int aic_dma_terminate_all(struct dma_chan *chan)

功能说明

终止所有通道的数据传输

参数定义

chan - 指向一个 DMA 物理通道

返回值

0,成功

注意事项

-

Edit online

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