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
zynq7020开发记录(持续更新)--PS和PL间的数据交互
zynq7020开发记录(持续更新)--PS和PL间的数据交互