4.3.5. 设计说明

4.3.5.1. 源码说明

源代码位于:drivers/dma/zx-dma.c

4.3.5.2. 模块架构

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

DMA Engine的软件框架如下图:

../../../_images/sw_system3.png

图 4.14 Linux 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 用户,调用流程如下:

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

图 4.15 Linux DMA Client调用流程

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

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

  • issue pending,将传输请求加入到DMA Device的请求队列中,接下来才会启动数据传输动作

4.3.5.3.3. 中断处理流程

中断处理流程相对简单:

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

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

  3. 如果不是循环模式,则更新相应的通道状态为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控制器。描述符组成的链表结构如下图:

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

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