AXI DMA总结、内核axidmatest.c测试程序分析、SG mode

AXI DMA

概述:

  1. XILINX提供的AXI DMA支持Scatter/Gather mode和Direct Register mode

  1. 数据位宽支持32,64,128,256,512,1024bits,stream数据位宽支持8,16,32,64,128,256,512,1024bits,这里数据位宽表示DDR到上图中DataMover的数据位宽,stream数据位宽表示的DataMover到设备的数据位宽,表示一次可以传输多少位的数据。最高支持64bit地址。
  2. AXI DMA在Scatter/Gather mode下可以支持16个通道进行数据传输。
  3. 最大频率及速率

Direct Register mode寄存器:

该模式下有MM2S_DMACR、MM2S_DMASR、MM2S_SA、MM2S_SA_MSB、MM2S_LENGTH、S2MM_DMACR、S2MM _DMASR、S2MM _DA、S2MM _SA_MSB、S2MM _LENGTH。DMACR(DMA控制寄存器),DMASR(DMA状态寄存器),DMA_SA、DMA_DA(源地址和目的地址寄存器),LENGTH(数据长度寄存器),MM2S表示内存到设备的数据传输,S2MM表示设备到内存的数据传输。

  • DMACR控制寄存器:该寄存器可以设置DMA软复位和启动DMA传输以及相关标志位的使能(比如中断标志和错误标志等)。

   

  • DMASR状态寄存器:该寄存器可以获取DMA的各种状态,例如中断标志、错误标志以及DMA是否传输完成等。

   

 

  • DMASA源地址寄存器:该寄存器的值表示DMA从内存的何处地址开始搬移数据到设备。

  • DMADA目的地址寄存器:该寄存器的值表示DMA从设备搬移数据到内存的什么地址。

  • LENGTH长度寄存器:该寄存器表示DMA传输的数据长度。

MM2S传输步骤(S2MM传输步骤一样,设置对应寄存器即可):

  1. 设置MM2S_DMACR.RS = 1;设置后DMASR.Halted为1,表示MM2S channel在running。
  2. 如果希望使能中断,设置MM2S_DMACR.IOC_IrqEn和MM2S_DMACR.Err_IrqEn位为1。
  3. 写地址到MM2S_SA寄存器,地址必须4字节对齐(也就是地址只能以0、4、8、c结尾)。
  4. 写传输的数据长度到LENGTH寄存器。

内核驱动分析,(驱动分控制器驱动和设备驱动):

设备树:

DMA控制器设备树:

这里只是发送通道的,接收通道的只是名字不一样,然后寄存器的偏移地址不一样

MAC_axi_dma_0: dma@40400000 {

     #dma-cells = <1>;

  clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk";

     clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>;

     compatible = "xlnx,axi-dma-1.00.a";

     //interrupt-parent = <&intc>;

     //interrupts = <0 31 4>;

     reg = <0x40400000 0x10000>;

     xlnx,addrwidth = <0x20>;

     dma-channel@40400000 {

        compatible = "xlnx,axi-dma-mm2s-channel";

          dma-channels = <0x1>;

          //interrupts = <0 31 4>;

          xlnx,datawidth = <0x20>;

          xlnx,device-id = <0x0>;

     };

};

DMA 测试驱动设备树:

dmatest_0: dmatest@0 {

  compatible ="xlnx,axi-dma-test-1.00.a";

  dmas = <&axi_dma_0 0 &axi_dma_0 1>;

  dma-names = "axidma0", "axidma1";

} ;

DMA设备驱动设备树:
ad9361_vnet: ad9361_vnet@40400000 {

     compatible = "ad9361_vnet";

     reg = < 0x40400000 0x1000

           0x40410000 0x1000

           0x40420000 0x1000

           0x40430000 0x1000

           0x40440000 0x1000

           0x40450000 0x1000
            
           0x40460000 0x1000
           //mac reg

           0x40000000 0x10000

           //axi intc reg

           0x41800000 0x10000

           //bram

           0x42000000 0x10000

           //fpga version

           0x43c10000 0x10000

     >;

     interrupt-parent = <&intc>;

     interrupts = <0 31 4>;

     //interrupts = <0 29 4>;

     dmas = <&MAC_axi_dma_0 0

           &MAC_axi_dma_1 0

           &MAC_axi_dma_2 0

           &MAC_axi_dma_3 0

           &MAC_axi_dma_4 0

           &MAC_axi_dma_5 0
            
           &MAC_axi_dma_6 0
     >;

     dma-names = "tx0","tx1","tx2","tx3","tx4","tx5","rx0";

};

控制器驱动(内核版本:4.9):

控制器设备树里compatible = "xlnx,axi-dma-1.00.a",使用的是drivers/dma/xilinx/xilinx_dma.c。

  • COMPATIBLE:

  • Probe函数:

 

以上代码获取了设备树里的地址以及其他属性的设置,比如地址位宽等。

这部分代码比较关键,根据DMA类型的不同,设置了设备驱动会调用的相关接口。

最后注册DMA设备。这段代码里还有一个probe函数,

xilinx_dma_child_probe:

函数里获取设备树里dma-channel的个数,调用

xilinx_dma_chan_probe:

处理设备树里的信息。

这部分代码根据设备树里dma-channel的compatible设置通道的传输方向,比如DMA_MEM_TO_DEV或者DMA_DEV_TO_MEM,根据不同的方向设置了desc寄存器的偏移地址等。

dma-channel@40400000 {

     compatible = "xlnx,axi-dma-mm2s-channel";

最后申请注册中断、复位通道。

XILINX DMA测试驱动(内核版本:4.9):

  • COMPATIBLE:

 

申请dma channel:

/**

 * dma_request_slave_channel - try to allocate an exclusive slave channel

 * @dev: pointer to client device structure

 * @name:  slave channel name

 *

 * Returns pointer to appropriate DMA channel on success or NULL.

 */

struct dma_chan *dma_request_slave_channel(struct device *dev, const char *name)

该部分代码申请了DMA通道,调用dma_test_add_slave_channels函数,内容如下:

该函数中调用了dmatest_add_slave_threads,该函数创建了一个内核线程,如下:

线程调用的函数dmatest_slave_func主要是分配内存、构造数据、发送数据、校验数据;

分配内存:

Consistent DMA mappings(一致性):

函数原型:

static inline void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)

使用方法:

dma_addr_t dma_handle;

   cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);

………

dma_free_coherent(dev, size, cpu_addr, dma_handle);

Streaming DMA mapping(流式)

函数原型:

dma_map_single(dev, addr, size, direction);

使用方法:

  struct device *dev = &my_dev->dev;

dma_addr_t dma_handle;

  void *addr = buffer->ptr;

  size_t size = buffer->len;

  dma_handle = dma_map_single(dev, addr, size, direction);

  if (dma_mapping_error(dev, dma_handle)) { /*

      * reduce current DMA mapping usage,

      * delay and try again later or

      * reset driver.

          */ goto map_error_handling;

}

Unmap:

dma_unmap_single(dev, dma_handle, size, direction);

两种方式分配的内存的区别:

  1. 一致性和流式最大的区别就是一致性分配的内存可以一直使用到最后不使用时释放即可,而流式分配的内存使用完一次以后必须释放重新申请。
  2. 一致性分配内存的方式,分配可以在中断上下文中,但释放不可以在中断上下文中释放,一般分配的内存较大。
  3. 流式分配无法使用高端内存,但提供了新的接口取代。
struct device *dev = &my_dev->dev; dma_addr_t dma_handle;

struct page *page = buffer->page;

unsigned long offset = buffer->offset;

size_t size = buffer->len;

dma_handle = dma_map_page(dev, page, offset, size, direction);

if (dma_mapping_error(dev, dma_handle)) { /*

   * reduce current DMA mapping usage,

   * delay and try again later or

   * reset driver.

   */ goto map_error_handling;

}

...

dma_unmap_page(dev, dma_handle, size, direction);

   4.流式分配需要先分配好缓冲区,然后返回dma_addr_t地址,一致性是一次性分配好缓冲区和dma_addr_t。

更多信息参见

Kernel_code\Documentation\DMA-API-HOWTO.txt

Dma pool:

如果程序里需要使用很多很小的buffer,可以使用dma pool,dma pool类似于kmen_cache,它使用dma_alloc_coherent,非__get_free_pages,dma_alloc_coherent分配的内存比较大,dma pool使用dma_alloc_coherent分配出一块大的内存,在从中取一小部分使用。如下:

struct dma_pool *pool;

pool = dma_pool_create(name, dev, size, align, boundary);

Allocate memory from a DMA pool:

cpu_addr=dma_pool_alloc(pool,flags,&dma_handle);

Free memory:

dma_pool_free(pool, cpu_addr, dma_handle);

Destroy a dma_pool:

dma_pool_destroy(pool);

这里看一下dmatest_slave_func,了解dma使用的内存如何分配和使用。

程序里使用常规的函数分配出了CPU使用的地址,紧接着定义了completion,设置了超时时间,定义了两个散列表结构,代码里的rx_cmp以及rx_tmo用于判断传输是否超时或者完成,完成后则会调用设置的回调函数,回调函数的设置在后边的代码中,下边的代码获取了设备的地址位宽进行对齐,初始化了分配的空间,然后使用流式map的方式将分配的CPU地址和映射到dma使用地址,该函数设置了dma传输的方向和传输的长度。

紧接着在下边的代码中初始化了scatterlist,获取描述符

初始化了completion,设置了回调函数,提交并发起传输,等待传输完成。

最后释放分配的空间

获取传输描述符:

slave_sg - DMA a list of scatter gather buffers from/to a peripheral

dma_cyclic  - Perform a cyclic DMA operation from/to a peripheral till the operation is explicitly stopped.

interleaved_dma   - This is common to Slave as well as M2M clients. For slave address of devices' fifo could be already known to the driver.

Various types of operations could be expressed by setting appropriate values to the 'dma_interleaved_template' members.

  函数:       

 struct dma_async_tx_descriptor

*dmaengine_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_data_direction direction,unsigned long flags);

(slave_sg以散列表的形式可以一次性发起多次dma传输)

 struct dma_async_tx_descriptor

*dmaengine_prep_dma_cyclic( struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, size_t period_len, enum dma_data_direction direction);

(dmaengine_prep_dma_cyclic可循环的发起传输,类似于环形链表一般,进行循环传输)

         struct dma_async_tx_descriptor

*dmaengine_prep_interleaved_dma( struct dma_chan *chan, struct dma_interleaved_template *xt, unsigned long flags);

        (多用于M2M,进行内存到内存数据的拷贝)

提交传输、开始传输:

提交传输:

cookie = chan->desc->tx_submit(chan->desc);

  if (dma_submit_error(cookie)) {

     axidma_err("dma_submit_error.\n");

      }

开始传输:

  dma_async_issue_pending(tx_chan);

释放dma channel:

void dma_release_channel(struct dma_chan *chan)

测试驱动(内核版本:4.9):

直接操作寄存器:

DMA框架:

     

 

  1. 参考:
  1. Kernel_code\Documentation\DMA-API-HOWTO.txt
  2. Kernel_code\Documentation\client.txt
  3. Kernel_code\Documentation\provider.txt
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页