DMA 使用指南

DMA 使用指南

DMA 使用指南

1. 模块介绍​

1.1. 术语定义​

术语定义注释说明DMADirect Memory Access直接存储器访问DRQDMA Request指DMA请求的端口号

1.2. 模块简介​

DMA 模块允许总线上的不同设备间的数据自动直接传输,最大优点是可减少CPU负载,并且具有高带宽、低延迟的特性。

DMA 模块的功能特性:

支持8个DMA通道,每通道有32个源端和32个终端可选

采用链表配置方式,寄存器描述通道状态

设备位宽支持8/16/32/64位,Burst长度支持1/4/8/16个

DMA 源端、终端地址 8Byte 对齐

图 4.5 DMA 硬件的原理框图

从上图中可以看出,根据数据的源、目的可以将DMA操作分为以下3种情况:

内核中类型定义含义1DMA_MEM_TO_MEM从内存到内存(包括DRAM、SRAM),可看作memcpy()的硬件加速2DMA_MEM_TO_DEV从内存到设备,支持DMA操作的设备一般需要提供握手信号、FIFO3DMA_DEV_TO_MEM从设备到内存,是情况2的逆操作4DMA_DEV_TO_DEV从设备到设备,这种比较少用

表中的类型定义详见Linux代码:include/linux/dmaengine.h

注解

USB、GMAC、eMMC等模块都有自己内置的DMA,为了区分开,所以有时候也将本 模块称作 “通用 DMA” 模块。

2. 参数配置​

2.1. 内核配置​

在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,按如下选择:

Linux Device Drivers [*] DMA Engine support <*> Artinchip SoCs DMA support

2.2. DTS 参数配置​

2.2.1. D211 配置​

common/d211.dtsi中的参数配置:

dma: dma-controller@10000000 { compatible = "artinchip,aic-dma-v1.0"; reg = <0x0 0x10000000 0x0 0x1000>; interrupts-extended = <&plic0 32 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cmu CLK_DMA>; resets = <&rst RESET_DMA>; #dma-cells = <1>; status = "okay";};

2.2.2. 引用DMA通道​

使用DMA进行数据传输的模块,可以通过DTS来配置。以SPI为例,要配置RX、TX对应的DMA端口号(DRQ Port):

spi0: spi@10400000 { compatible = "artinchip,aic-spi-v1.0"; reg = <0x10400000 0x1000>; interrupts = ; clocks = <&cmu CLK_SPI0>; resets = <&rst RESET_SPI0>; dmas = <&dma DMA_SPI0>, <&dma DMA_SPI0>; dma-names = "rx", "tx"; #address-cells = <1>; #size-cells = <0>; spi-max-frequency = <24000000>;};

其中端口号 DMA_SPI0 的定义见 U-Boot中代码 include/dt-bindings/dma/d211-dma.h

#define DMA_SRAM 0#define DMA_DRAM 1#define DMA_SPI0 10#define DMA_SPI1 11#define DMA_I2S0 12#define DMA_I2S1 13#define DMA_CODEC 14#define DMA_UART0 16#define DMA_UART1 17#define DMA_UART2 18#define DMA_UART3 19#define DMA_UART4 20#define DMA_UART5 21#define DMA_UART6 22#define DMA_UART7 23

注解

DMA端口号的宏定义仅在DTS编译过程中用到,我们的DTS编译过程是放在U-Boot编译中,所以将这些宏定义放在U-Boot。

3. 调试指南​

3.1. 调试开关​

在luban根目录下执行 make kernel-menuconfig,进入kernel的功能配置,可以打开DMA模块的DEBUG选项:

Linux Kernel hacking [*] DMA Engine support [*] DMA Engine debugging [*] DMA Engine verbose debugging

此DEBUG选项打开的影响:

DMA 子系统的pr_dbg()和dev_dbg()调试信息会被编译

DMA 子系统的Verbose debug信息也会被打开编译

在系统运行时,如果要打印pr_dbg()和dev_dbg()信息,还需要调整loglevel为8,两个方法:

在board.dts中修改bootargs,增加“loglevel=8”

在板子启动到Linux shell后,执行命令:

echo 8 > /proc/sys/kernel/printk

4. 测试指南​

4.1. 测试环境​

4.1.1. 硬件​

开发板,或FPGA板

4.1.2. 软件​

PC端的串口终端软件,用于PC和开发板进行串口通信

Linux内核原生的dmatest模块

4.1.3. 软件配置​

4.1.3.1. dmttest​

dmatest是 Linux 内核中原生的一个模块,在luban的根目录下通过make kernel-menuconfig,按如下选择:

Linux Device Drivers [*] DMA Engine support <*> DMA Test client

注解

dmatest模块只限于测试 Mem To Mem 的数据传输操作。

4.2. dmatest 测试​

dmatest模块初始化成功后,会在Sysfs目录创建一些节点,测试过程就是通过这些节点配置参数、启动测试。

[aic@] # cd /sys/module/dmatest/parameters/[aic@parameters] # lsalignment max_channels run transfer_sizechannel norandom test_buf_size verbosedevice noverify test_list waitdmatest polled threads_per_chan xor_sourcesiterations pq_sources timeout[aic@parameters] # echo 30 > iterations[aic@parameters] # echo 8 > max_channels[aic@parameters] # echo Y > polled[aic@parameters] # echo Y > run[ 104.696480] dmatest: No channels configured, continue with any[ 104.697377] dmatest: Added 1 threads using dma0chan2[ 104.698061] dmatest: Added 1 threads using dma0chan3[ 104.698695] dmatest: Added 1 threads using dma0chan4[ 104.699334] dmatest: Added 1 threads using dma0chan5[ 104.699964] dmatest: Added 1 threads using dma0chan6[ 104.700599] dmatest: Added 1 threads using dma0chan7[ 104.701248] dmatest: Added 1 threads using dma0chan8[ 104.701883] dmatest: Added 1 threads using dma0chan9[ 104.702328] dmatest: Started 1 threads using dma0chan2[ 104.702776] dmatest: Started 1 threads using dma0chan3[ 104.703223] dmatest: Started 1 threads using dma0chan4[ 104.703671] dmatest: Started 1 threads using dma0chan5[ 104.704118] dmatest: Started 1 threads using dma0chan6[ 104.704564] dmatest: Started 1 threads using dma0chan7[ 104.705011] dmatest: Started 1 threads using dma0chan8[ 104.705457] dmatest: Started 1 threads using dma0chan9[ 105.006038] dmatest: dma0chan4-copy0: summary 30 tests, 0 failures 106.28 iops 836 KB/s (0)[ 105.306046] dmatest: dma0chan2-copy0: summary 30 tests, 0 failures 106.58 iops 884 KB/s (0)[ 105.606055] dmatest: dma0chan3-copy0: summary 30 tests, 0 failures 106.60 iops 1044 KB/s (0)[ 105.906057] dmatest: dma0chan5-copy0: summary 30 tests, 0 failures 106.59 iops 835 KB/s (0)[ 106.206050] dmatest: dma0chan6-copy0: summary 30 tests, 0 failures 106.59 iops 792 KB/s (0)[ 106.506034] dmatest: dma0chan7-copy0: summary 30 tests, 0 failures 106.61 iops 856 KB/s (0)[ 106.806048] dmatest: dma0chan8-copy0: summary 30 tests, 0 failures 107.64 iops 843 KB/s (0)[ 107.106044] dmatest: dma0chan9-copy0: summary 30 tests, 0 failures 106.81 iops 993 KB/s (0)

5. 设计说明​

5.1. 源码说明​

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

5.2. 模块架构​

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

DMA Engine的软件框架如下图:

图 4.6 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等,暂未提供用户态的使用接口。

5.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, "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);/* 初始化 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的情况

5.3.2. DMA Client 的调用流程​

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

图 4.7 Linux DMA Client调用流程

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

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

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

5.3.3. 中断处理流程​

中断处理流程相对简单:

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

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

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

5.4. 数据结构设计​

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

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

5.4.3. DMA 通道信息​

由 模块架构 可知 DMA物理通道 和 DMA虚拟通道 是一对多的关系,所以在设计中它们互相都需要在记录好对方的数据引用指针。

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

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

5.4.4. DMA 描述符​

DMA 控制器支持散列(Scatter Gather)的描述符参数形式,需要提前将参数分组(一个Buffer对应一组散列参数)打包到多个描述符中,这些描述符会组成一个链表,然后将这个链表的第一个描述符的物理地址传给DMA控制器。描述符组成的链表结构如下图:

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

5.5. 接口设计​

以下接口是 Linux DMA Engine 子系统的标准接口。

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,成功注意事项

5.5.2. aic_dma_pause​

函数原型static int aic_dma_pause(struct dma_chan *chan)功能说明暂停指定通道的传输操作参数定义chan - 指向一个DMA物理通道返回值0,成功注意事项

5.5.3. aic_dma_resume​

函数原型static int aic_dma_resume(struct dma_chan *chan)功能说明恢复指定通道的传输操作参数定义chan - 指向一个DMA物理通道返回值0,成功注意事项

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注意事项

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 Devflags - 一些标记context - 指向一些私有的上下文信息返回值成功,则返回一个DMA描述符;失败,返回NULL注意事项

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 Devflags - 一些标记返回值成功,则返回一个DMA描述符;失败,返回NULL注意事项

5.5.7. aic_dma_issue_pending​

函数原型static void aic_dma_issue_pending(struct dma_chan *chan)功能说明启动指定通道的数据传输参数定义chan - 指向一个DMA物理通道返回值无注意事项

5.5.8. aic_dma_terminate_all​

函数原型static int aic_dma_terminate_all(struct dma_chan *chan)功能说明终止所有通道的数据传输参数定义chan - 指向一个DMA物理通道返回值0,成功注意事项

5.6. Demo​

SPI 驱动(详见drivers/spi/spi-artinchip.c)中调用了DMA进行数据传输,其使用过程可以当作Demo参考:

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"); ...}

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

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

6. 常见问题​

6.1. dmatest 时verify数据报错​

6.1.1. 现象​

当运行dmatest测试时,错误log类似如下:

[ 381.878419] dmatest: dma0chan5-copy0: dstbuf[0x3f70] mismatch! Expected cf, got d7[ 381.885999] dmatest: dma0chan5-copy0: dstbuf[0x3f71] mismatch! Expected ce, got d6[ 381.893611] dmatest: dma0chan5-copy0: dstbuf[0x3f72] mismatch! Expected cd, got d5[ 381.901199] dmatest: dma0chan5-copy0: dstbuf[0x3f73] mismatch! Expected cc, got d4[ 381.908783] dmatest: dma0chan5-copy0: dstbuf[0x3f74] mismatch! Expected cb, got d3[ 381.916350] dmatest: dma0chan5-copy0: dstbuf[0x3f75] mismatch! Expected ca, got d2

6.1.2. 原因分析​

dmatest的默认配置是需要verify测试数据的。

当进行多通道(max_channels>1)测试时,必须要使能polled属性,以保证通道的测试过程是串行的,否则会报verify错误。

设置polled属性的方法见 [dmatest 测试]

相关推荐

键盘各个键的功能图解 电脑键盘中哪个是搜索键?
谁有365体育投注网址

键盘各个键的功能图解 电脑键盘中哪个是搜索键?

📅 07-01 👁️ 8345
無律師代表訴訟人資源中心
谁有365体育投注网址

無律師代表訴訟人資源中心

📅 07-27 👁️ 7139
《塞尔达传说:旷野之息》神庙挑战 利特的兄弟岩 图文攻略
谁有365体育投注网址

《塞尔达传说:旷野之息》神庙挑战 利特的兄弟岩 图文攻略

📅 07-12 👁️ 2310