zynq PS侧DMA驱动

Posted shichaog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了zynq PS侧DMA驱动相关的知识,希望对你有一定的参考价值。

linux中,驱动必然会有驱动对应的设备类型。在linux4.4版本中,其设备是以设备树的形式展现的。

PS端设备树的devicetree表示如下

324         dmac_s: dmac@f8003000 {  
325             compatible = "arm,pl330", "arm,primecell";  
326             reg = <0xf8003000 0x1000>;  
327             interrupt-parent = <&intc>;  
328             interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3",  
329                 "dma4", "dma5", "dma6", "dma7";  
330             interrupts = <0 13 4>,  
331                          <0 14 4>, <0 15 4>,  
332                          <0 16 4>, <0 17 4>,  
333                          <0 40 4>, <0 41 4>,  
334                          <0 42 4>, <0 43 4>;  
335             #dma-cells = <1>;  
336             #dma-channels = <8>;  
337             #dma-requests = <4>;  
338             clocks = <&clkc 27>;  
339             clock-names = "apb_pclk";  
340         };  

<drivers/of/platform.c>这个文件根据设备树信息创建设备信息,在驱动程序注册时就可以找到该设备信息,执行probe函数。

zynq下dma的设备channel如下:

 ​root@linaro-ubuntu-desktop:/sys/class/dma# ls
dma0chan0  dma0chan2  dma0chan4  dma0chan6  dma1chan0
dma0chan1  dma0chan3  dma0chan5  dma0chan7
root@linaro-ubuntu-desktop:/sys/class/dma# ll
total 0
drwxr-xr-x  2 root root 0 1970-01-01 00:00 ./
drwxr-xr-x 50 root root 0 1970-01-01 00:00 ../
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan0 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan0/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan1 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan1/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan2 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan2/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan3 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan3/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan4 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan4/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan5 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan5/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan6 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan6/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma0chan7 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan7/
lrwxrwxrwx  1 root root 0 1970-01-01 00:00 dma1chan0 -> ../../devices/soc0/fpga-axi@0/43000000.axivdma/dma/dma1chan0/

dma1chan0是xilinx AXI-VDMA IP生成的DMA控制器,其处于PL端,而dma0相关的控制器是ps端的pl330.本篇看pl330这个驱动程序的注册。

查看物理地址分布

 ​root@linaro-ubuntu-desktop:~# cat /proc/iomem 
00000000-1fffffff : System RAM
  00008000-006651a3 : Kernel code
  006a2000-00700867 : Kernel data
41600000-4160ffff : /fpga-axi@0/i2c@41600000
43000000-43000fff : /fpga-axi@0/axivdma@43000000
70e00000-70e0ffff : /fpga-axi@0/axi_hdmi@70e00000
75c00000-75c00fff : /fpga-axi@0/axi-spdif-tx@0x75c00000
77600000-77600fff : /fpga-axi@0/axi-i2s@0x77600000
79000000-7900ffff : /fpga-axi@0/axi-clkgen@79000000
e0001000-e0001fff : xuartps
e0002000-e0002fff : /amba/usb@e0002000
  e0002000-e0002fff : /amba/usb@e0002000
e000a000-e000afff : /amba/gpio@e000a000
e000b000-e000bfff : /amba/eth@e000b000
e000d000-e000dfff : /amba/spi@e000d000
e0100000-e0100fff : mmc0
f8003000-f8003fff : /amba/dmac@f8003000
  f8003000-f8003fff : /amba/dmac@f8003000
f8005000-f8005fff : /amba/watchdog@f8005000
f8007000-f80070ff : /amba/devcfg@f8007000
f8007100-f800711f : /amba/adc@f8007100
f800c000-f800cfff : /amba/ocmc@f800c000
fffc0000-ffffffff : f800c000.ocmc
root@linaro-ubuntu-desktop:~# 

ARM采用统一编址,其访问内存和外设的指令是一样没有差异的,linux内核并不通过物理地址直接访问外设,而是通过虚拟地址,虚拟地址经过MMU转换成物理访问外设。所以外设需要使用ioremap()对将物理地址空间转换成虚拟地址。

I/O设备使用第三种地址,总线地址。如果一个设备在MMIO(memory mapped IO内存映射地址空间)有寄存器,或者其对系统存储系统执行DMA读写操作,设备使用的就是总线地址。

在系统枚举阶段,内核知道I/O设备以及他们的MMIO空间。如果一个设备支持DMA方式,驱动通过kmalloc()申请一段内存空间,返回申请空间的首地址,设为X,虚拟地址系统将虚拟地址X映射到物理地址Y。驱动程序可以使用虚拟地址X访问设备地址空间,但是设备本身却不行,这是因为DMA本身并不是通过虚拟地址方式来工作的。

在zynq7000设备里,DMA可以直接操作物理地址,但另一些处理器使用IOMMU将DMA地址转换物理地址,

 

linux建议使用DMA API而不是特定总线的DMA API,比如使用dma_map_*()接口而不是pci_map_*()接口,在zynq7000中,pl330 DMA已经实现了直接调用通用的DMA框架API即可以。

DMA开发相关API

首先得包含

 ​#include <linux/dma-mapping.h>

其提供了dma_addr_t定义,其可以作为设备使用DMA的源地址或者目的地址,并且可以使用DMA_XXX相关的API。

1.使用大DMA 一致性buffer

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

分配<size>大小的一致性区域。其返回值<dma_handle>能被强制类型转换成unsigned integer,这样就是总线的宽度

 ​void * dma_zalloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)

是对dma_alloc_coherent的封装,并且将返回的地址空间内存清零。

 ​void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle)

释放一致性内存。

2.使用小DMA一致性buffer

需要包括如下代码

 ​#include <linux/dmapool.h>

其工作有点类似kmem_cache(),不过其使用的是DMA一致性分配器,而非__get_free_pages(),并且需要N-byte对齐。

 ​	struct dma_pool *
	dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t alloc);

“name”字段用于诊断,dev和size和传递给dma_alloc_coherent()类似,align以字节计,且需要是2的指数。如果设备没有跨界限制,传递0;传递4096则意味着分配的DMA空间不能跨越4KByte空间。

 ​void *dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle)
 ​	void *dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags, dma_addr_t *dma_handle);

从pool分配内存,返回内存将会满足size和alignment要求。传递GFP_ATOMIC防止阻塞,或者如果允许阻塞,则可以传递GFP_KERNEL,和dma_alloc_coherent()类似,返回两个值,一个是CPU使用的地址,以及一个设备使用的DMA地址。

 ​void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr); 

释放由dma_pool_alloc()分配的内存空间到pool。

 ​void dma_pool_destroy(struct dma_pool *pool);

dma_pool_destroy()将内存池的资源释放。

part1c DMA寻址限制

 ​int dma_set_mask_and_coherent(struct device *dev, u64 mask)

检查mask是否合法,如果是跟新设备streaming以及DMA mask参数。返回0是正确。

 ​int dma_set_mask(struct device *dev, u64 mask)
int dma_set_coherent_mask(struct device *dev, u64 mask

检查mask是否合法,如果是就跟新。

 ​u64 dma_get_required_mask(struct device *dev)

检查系统合法的DMA掩码。

part 1D Streaming(流式) DMA 映射

 ​dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction direction)

将处理器的虚拟地址空间进行映射,这样让处理器可以访问外设,返回值是DMA地址。

其direction参数可选字段即意义如下:

 ​DMA_NONE		no direction (used for debugging) 
DMA_TO_DEVICE		data is going from the memory to the device 
DMA_FROM_DEVICE		data is coming from the device to the memory 
DMA_BIDIRECTIONAL	direction isn't known

并不是所有的存储空间都能够使用上面的函数进行映射。内核虚拟地址也许是连续的但是其映射的物理地址却可以不一样。由于这个API不提供scatter/gather功能,如果尝试映射非连续物理存储空间。所以由该API进行映射的地址需要确保物理地址连续,如kmalloc。

设备DMA的地址内存范围必须在dma_mask,需要确保由kmalloc分配的内存空间在dma_mask所能达到的范围之内。驱动程序也许会传递各种flags以限制DMA地址范围(比如X86能够表示的地址范围在前16MB内)。

对具有IOMMU的平台,物理地址连续性和dma_mask要求也许不再适用。然而,考虑到可移植性,通常会忽略IOMMU的存在。

 

内存操作粒度被成为cache line 宽度,为了让这里的API映射的DMA操作成功执行,映射的区域必须在cache line边界开始和结束(目的是不发生对一个cache line出现两个独立的映射区域)。

 

DMA_TO_DEVICE,在软件修改内存块后程序退出控制前,需要进行同步。一个这一原语被使用,由这一原语包括的这一区域被成为只读,如果同时设备想写,需要使用DMA_BIDIRECTIONAL标志。

DMA_FROM_DEVICE 在驱动程序修改数据前先同步。内存块需要被当成只读对待。如果驱动要写功能,则需要设置DMA_BIDIRECTIONAL。

DMA_BIDIRECTIONAL需要特殊对待。这意味这驱动程序不能确定内存的改变在是否发生在将存储空间交给设备前,同时不能确定设备是否需要改变这块内存。所以,需要双向同步内存块。一次是在内存控制权移交给设置前,一次是在获取数据前。

 ​void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction) 

Unmap先前映射的区域,所有传递进来的参数必须和建立映射接口时的参数一致。

 ​dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size,
		    enum dma_data_direction direction)
void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction)

对页进行映射和逆映射。轻易不要动<size>和<offset>这两个参数。

 ​int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)

在一些场景下,dma_map_single() and dma_map_page()在创建一个映射时也许会失败。调用上面的接口可以检测出错的原因。

 ​	int
	dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)

返回segment 映射的DMA地址。

当使用scatterlist方式时,建立映射的过程如下:

 ​	int i, count = dma_map_sg(dev, sglist, nents, direction);
	struct scatterlist *sg;
	for_each_sg(sglist, sg, count, i) { hw_address[i] = sg_dma_address(sg);
		hw_len[i] = sg_dma_len(sg); }

zynq PS端pl330驱动注册

3002 static struct amba_driver pl330_driver = {
3003     .drv = {
3004         .owner = THIS_MODULE,
3005         .name = "dma-pl330",
3006         .pm = &pl330_pm,
3007     },
3008     .id_table = pl330_ids,
3009     .probe = pl330_probe,
3010     .remove = pl330_remove,
3011 };
3012 
3013 module_amba_driver(pl330_driver);
见到此则知道必然是调用probe函数。注册函数调用如下:

2775 static int
2776 pl330_probe(struct amba_device *adev, const struct amba_id *id)
2777 {
2778     struct dma_pl330_platdata *pdat;
2779     struct pl330_config *pcfg;
2780     struct pl330_dmac *pl330;
2781     struct dma_pl330_chan *pch, *_p;
2782     struct dma_device *pd;
2783     struct resource *res;
2784     int i, ret, irq;
2785     int num_chan;
2786 
2787     pdat = dev_get_platdata(&adev->dev);
上面定义了三个重要的数据结构体,它们的关系如下图:


2806     pl330->base = devm_ioremap_resource(&adev->dev, res);
2807     if (IS_ERR(pl330->base))
2808         return PTR_ERR(pl330->base);
对地址空间进行映射,并且存在了struct pl330_dmac结构体里。

2812     for (i = 0; i < AMBA_NR_IRQS; i++) {
2813         irq = adev->irq[i];
2814         if (irq) {
2815             ret = devm_request_irq(&adev->dev, irq,
2816                            pl330_irq_handler, 0,
2817                            dev_name(&adev->dev), pl330);
2818             if (ret)
2819                 return ret;
2820         } else {
2821             break;
2822         }
2823     }
每一个channel对应于一个中断,但是它们的中断服务函数是同一个。
2825     pcfg = &pl330->pcfg;
2826
2827     pcfg->periph_id = adev->periphid;
2828     ret = pl330_add(pl330);
设置pl330的configure字段。

2832     INIT_LIST_HEAD(&pl330->desc_pool);
2833     spin_lock_init(&pl330->pool_lock);
2834 
2835     /* Create a descriptor pool of default size */
2836     if (!add_desc(pl330, GFP_KERNEL, NR_DEFAULT_DESC))
2837         dev_warn(&adev->dev, "unable to allocate desc\\n");
设置pl330的描述符池,并且初始化16个描述符。
2842     if (pdat)
2843         num_chan = max_t(int, pdat->nr_valid_peri, pcfg->num_chan);
2844     else
2845         num_chan = max_t(int, pcfg->num_peri, pcfg->num_chan);
2846 
2847     pl330->num_peripherals = num_chan;
2848 
2849     pl330->peripherals = kzalloc(num_chan * sizeof(*pch), GFP_KERNEL);
2850     if (!pl330->peripherals) {
初始化channel数,并且为dma channel分配内存空间。
2856     for (i = 0; i < num_chan; i++) {
2857         pch = &pl330->peripherals[i];
2858         if (!adev->dev.of_node)
2859             pch->chan.private = pdat ? &pdat->peri_id[i] : NULL;
2860         else
2861             pch->chan.private = adev->dev.of_node;
2862 
2863         INIT_LIST_HEAD(&pch->submitted_list);
2864         INIT_LIST_HEAD(&pch->work_list);
2865         INIT_LIST_HEAD(&pch->completed_list);
2866         spin_lock_init(&pch->lock);
2867         pch->thread = NULL;
2868         pch->chan.device = pd;
2869         pch->dmac = pl330;
2870 
2871         /* Add the channel to the DMAC list */
2872         list_add_tail(&pch->chan.device_node, &pd->channels);
2873     }
初始化dma_pl330_chan相关字段。

2886     pd->device_alloc_chan_resources = pl330_alloc_chan_resources;
2887     pd->device_free_chan_resources = pl330_free_chan_resources;
2888     pd->device_prep_dma_memcpy = pl330_prep_dma_memcpy;
2889     pd->device_prep_dma_cyclic = pl330_prep_dma_cyclic;
2890     pd->device_tx_status = pl330_tx_status;
2891     pd->device_prep_slave_sg = pl330_prep_slave_sg;
2892     pd->device_config = pl330_config;
2893     pd->device_pause = pl330_pause;
2894     pd->device_terminate_all = pl330_terminate_all;
2895     pd->device_issue_pending = pl330_issue_pending;
2896     pd->src_addr_widths = PL330_DMA_BUSWIDTHS;
2897     pd->dst_addr_widths = PL330_DMA_BUSWIDTHS;
2898     pd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
2899     pd->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
初始化上图中struct dma_device的若干成员和函数。

2901     ret = dma_async_device_register(pd);
2902     if (ret) {
2903         dev_err(&adev->dev, "unable to register DMAC\\n");
2904         goto probe_err3;
2905     }
注册dma设备并创建sys/class接口,会有如下显示:

 ​root@linaro-ubuntu-desktop:/sys/class/dma# ls
dma0chan0  dma0chan2  dma0chan4  dma0chan6  dma1chan0
dma0chan1  dma0chan3  dma0chan5  dma0chan7
将DMA控制器注册到DT DMA helpers。通过of_dma_list就可以找到该DMA控制器。

2907     if (adev->dev.of_node) {
2908         ret = of_dma_controller_register(adev->dev.of_node,
2909                      of_dma_pl330_xlate, pl330);
至此,DMA注册函数基本流程完毕。但是还留下了一个irq函数。

DMA中断服务函数

2720 static irqreturn_t pl330_irq_handler(int irq, void *data)
2721 {
2722     if (pl330_update(data))
2723         return IRQ_HANDLED;
2724     else
2725         return IRQ_NONE;
2726 }
该函数实际上调用了pl330_update去完成中断服务函数的请求。由于其是在中断函数中调用的,则中断函数的那些注意事项还是要遵从的。

1533 static int pl330_update(struct pl330_dmac *pl330)
1534 {
1535     struct dma_pl330_desc *descdone, *tmp;
1536     unsigned long flags;
1537     void __iomem *regs;
1538     u32 val;
1539     int id, ev, ret = 0;
1540 
1541     regs = pl330->base;
该函数的base地址是经过ioremap得到的。
1545     val = readl(regs + FSM) & 0x1;
1546     if (val)
1547         pl330->dmac_tbd.reset_mngr = true;
1548     else
1549         pl330->dmac_tbd.reset_mngr = false;
首先都FSM寄存器,然后看bit0是否需要复位mngr。

1551     val = readl(regs + FSC) & ((1 << pl330->pcfg.num_chan) - 1);
1552     pl330->dmac_tbd.reset_chan |= val;
1553     if (val) {
1554         int i = 0;
1555         while (i < pl330->pcfg.num_chan) {
1556             if (val & (1 << i)) {
1557                 dev_info(pl330->ddma.dev,
1558                     "Reset Channel-%d\\t CS-%x FTC-%x\\n",
1559                         i, readl(regs + CS(i)),
1560                         readl(regs + FTC(i)));
1561                 _stop(&pl330->channels[i]);
1562             }
1563             i++;
1564         }
1565     }
各个通复位。

1568     val = readl(regs + ES);
1569     if (pl330->pcfg.num_events < 32
1570             && val & ~((1 << pl330->pcfg.num_events) - 1)) {
1571         pl330->dmac_tbd.reset_dmac = true;
1572         dev_err(pl330->ddma.dev, "%s:%d Unexpected!\\n", __func__,
1573             __LINE__);
1574         ret = 1;
1575         goto updt_exit;
1576     }
读取事件寄存器,并判断是否出错了。出错则置reset标志。

1578     for (ev = 0; ev < pl330->pcfg.num_events; ev++) {
1579         if (val & (1 << ev)) { /* Event occurred */
1580             struct pl330_thread *thrd;
1581             u32 inten = readl(regs + INTEN);
1582             int active;
1583 
1584             /* Clear the event */
1585             if (inten & (1 << ev))
1586                 writel(1 << ev, regs + INTCLR);
1587 
1588             ret = 1;
1589 
1590             id = pl330->events[ev];
1591 
1592             thrd = &pl330->channels[id];
1593 
1594             active = thrd->req_running;
1595             if (active == -1) /* Aborted */
1596                 continue;
1597 
1598             /* Detach the req */
1599             descdone = thrd->req[active].desc;
1600             thrd->req[active].desc = NULL;
1601 
1602             thrd->req_running = -1;
1603 
1604             /* Get going again ASAP */
1605             _start(thrd);
1606 
1607             /* For now, just make a list of callbacks to be done */
1608             list_add_tail(&descdone->rqd, &pl330->req_done);
1609         }
1610     }
处理相应事件。

以上是关于zynq PS侧DMA驱动的主要内容,如果未能解决你的问题,请参考以下文章

利用ZYNQ SOC快速打开算法验证通路——PS端DMA缓存数据到PS端DDR

嵌入式开发之zynq---Zynq PS侧I2C驱动架构

zynq7020开发记录(持续更新)--PS和PL间的数据交互

zynq7020开发记录(持续更新)--PS和PL间的数据交互

zynq7020开发记录(持续更新)--PS和PL间的数据交互

ZYNQ.DMA基本用法