精华内容
下载资源
问答
  • zynq dma linux 配置

    2017-04-02 18:56:52
    该资源是博客中附带的资源下载链接
  • 本文的部分内容可能来源于网络,该内容归原作者所有,如果侵犯到您的权益,请及时通知我,我将立即删除,...欢迎加入zynq-arm-linux提高交流群:788265722 文档错误可能很多,大家多包涵,主要理解文件的目的就好...

          本文的部分内容可能来源于网络,该内容归原作者所有,如果侵犯到您的权益,请及时通知我,我将立即删除,原创内容copyleft归tingkman@163.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。欢迎加入zynq-arm-linux提高交流群:788265722

    文档错误可能很多,大家多包涵,主要理解文件的目的就好。可留言

    Zynq-axidma是大家常用的功能,所以,很多同学都用到,但是有很大一部分同学比较熟悉裸跑下axidma使用,网络上也有很多这方面的教程,像米联客AXI_DMA_LOOP 环路测试,但是linux下使用axidma的使用方法却很少,所以才有了今天这个博文,

    阅读这篇博文之前,大家可以参考以下博客内容,为理解linux下axi dma使用做铺垫。

    Axidma 裸跑例子(更多的可以百度)

    AXI_DMA_LOOP 环路测

    https://www.cnblogs.com/milinker/p/6484011.html

    https://blog.csdn.net/long_fly/article/details/79702222

     

    AXI_DMA_LOOP,在viviado图里有两部分,一个是axidma模块,一个是数据回环模块,首先,用户通过写dma下发一个数据,比方1234,然后用户通过读dma读取数据读到1234,表示测试完成,这里面其实有两个dma通道一个写一个读做了两次dma操作,读写都是由用户发起的,搬运都是dma控制器执行的如下图:

     

     

    在linux下驱动主要牵涉的文件有3个,一个是设备文件描述dtb,一个是axidma控制器驱动xilinx_dma.c,一个是axidmatest.c,我们今天的主角就是axidmatest.c

    Xilinx官网给的axidma驱动说明在这里大家可以看看

    https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842337/Linux+Soft+DMA+Driver

    各个文件的位置如下:

    内核目录

    https://github.com/Xilinx/linux-xlnx/blob/master/arch/arm/boot/dts/zynq-zc706.c,这个是官网评估板的dtb描述,原版的dtb里面没有dma的描述,要把dma的描述加进来

    https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/xilinx_dma.c

    https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/axidmatest.c

     

    在设备树里表示dma读写通道,用以下表示

    dma-channel@40400000  axi-dma-mm2s-channel 写通道,是ddr网datafifo写

    dma-channel@40400030  axi-dma-s2mm-channel 读通道  是datafifo往ddr搬

    这个描述对应的是控制器驱动xilinx_dma.c,有这个

    描述xilinx_dma.c这个驱动才会加载。

    axi_dma_1: dma@40400000 {

                           #dma-cells = <1>;

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

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

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

                           interrupt-parent = <&intc>;

                           interrupts = <0 29 4 0 30 4>;

                           reg = <0x40400000 0x10000>;

                           xlnx,addrwidth = <0x20>;

                           xlnx,include-sg ;

                           dma-channel@40400000 {

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

                                   dma-channels = <0x1>;

                                   interrupts = <0 29 4>;

                                   xlnx,datawidth = <0x20>;

                                   xlnx,device-id = <0x0>;

                                   xlnx,include-dre ;

                           };

                           dma-channel@40400030 {

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

                                   dma-channels = <0x1>;

                                   interrupts = <0 30 4>;

                                   xlnx,datawidth = <0x20>;

                                   xlnx,device-id = <0x0>;

                                   xlnx,include-dre ;

                           };

                   };

    在linux里驱动是分层的,一般像spi i2c dma这类的驱动都是分控制器驱动,和下面的设备驱动,控制器是单独的驱动,下面挂的设备是设备的驱动,设备的驱动都是去想控制器去申请的。

    那么今天的主题设备驱动axidmatest.c也在设备树里面有自己的描述,我们看到它用了两个dma通道,就是dma控制器的两个读写通道。

    axidmatest_1: axidmatest@1 {

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

                         dmas = <&axi_dma_1 0

                                 &axi_dma_1 1>;

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

    } ;

    这里设备树和驱动是怎么对应的那,linux采用的是compatible里面的描述

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

    测试程序驱动compatible ="xlnx,axi-dma-test-1.00.a";

    驱动程序表示如下:

     

     

     

    这样写好,还要把内核配置也配置好,这些驱动才都能加载

     

    在写dma驱动我们不用关注xilinx_dma.c驱动的实现,只需要理解axidmatest.c的实现,下面就分析一下:axidmatest.c这个驱动。

    那么从哪里开始分析那,我们知道一般程序都有个入口,比方说main函数,那在linux下axidmatest.c入口就是以下函数。

    late_initcall这个意思大概意思就是后面初始化,实际上dma控制器xilinx_dma.c实在测试驱动axidmatest.c之前运行的,因为测试驱动要去向控制器申请通道,所以控制器要先初始化好,才能给测试驱动提供通道。

    static int __init axidma_init(void)

    {

    return platform_driver_register(&xilinx_axidmatest_driver);

    }

    late_initcall(axidma_init);

     

     

    static const struct of_device_id xilinx_axidmatest_of_ids[] = {

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

    {}

    };

    static struct platform_driver xilinx_axidmatest_driver = {

    .driver = {

    .name = "xilinx_axidmatest",

    .owner = THIS_MODULE,

    .of_match_table = xilinx_axidmatest_of_ids,

    },

    .probe = xilinx_axidmatest_probe,

    .remove = xilinx_axidmatest_remove,

    };

    axidma_init其实使用platform_driver_register,加载平台设备驱动xilinx_axidmatest_driver

    平台设备驱动有几个描述driver和probe,remove函数,remove函数是在驱动程序卸载才会执行,这里一般内核启动后不会卸载。当xilinx_axidmatest_of_ids里面的compatible和设备树描述文件dtb里面的对应后probe就会运行

    axidmatest_1: axidmatest@1 {

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

                         dmas = <&axi_dma_1 0

                                 &axi_dma_1 1>;

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

    } ;

    当probe函数xilinx_axidmatest_probe才开始测试驱动的初始化。

    dma_request_slave_channel是linux驱动编写的标准函数,dma测试程序申请dma通道要用这个函数,具体实现我们可以不管,只需要用这个申请就可以了,当然了,控制器驱动已经准备好了,才能申请成功。

     

    static int xilinx_axidmatest_probe(struct platform_device *pdev)

    {

    struct dma_chan *chan, *rx_chan;

    int err;

    chan = dma_request_slave_channel(&pdev->dev, "axidma0"); 

    //---------------------向控制器申请dma发送通道

    if (IS_ERR(chan)) {

    pr_err("xilinx_dmatest: No Tx channel\n");

    return PTR_ERR(chan);

    }

    rx_chan = dma_request_slave_channel(&pdev->dev, "axidma1");

    //---------------------向控制器申请dma接收通道

     

    if (IS_ERR(rx_chan)) {

    err = PTR_ERR(rx_chan);

    pr_err("xilinx_dmatest: No Rx channel\n");

    goto free_tx;

    }

    err = dmatest_add_slave_channels(chan, rx_chan);

    if (err) {

    pr_err("xilinx_dmatest: Unable to add channels\n");

    goto free_rx;

    }

    return 0;

    free_rx:

    dma_release_channel(rx_chan);

    free_tx:

    dma_release_channel(chan);

    return err;

    }

    dmatest_add_slave_channels(chan, rx_chan)函数进一步初始化。

    定义两个dmatest_chan类型的数据结构tx_dtcrx_dtc用来保存之前申请的两个通道,后面就用这两个数据接口来访问通道。

    static int dmatest_add_slave_channels(struct dma_chan *tx_chan,

    struct dma_chan *rx_chan)

    {

    struct dmatest_chan *tx_dtc;

    struct dmatest_chan *rx_dtc;

    unsigned int thread_count = 0;

     

    tx_dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);

    if (!tx_dtc) {

    pr_warn("dmatest: No memory for tx %s\n",

    dma_chan_name(tx_chan));

    return -ENOMEM;

    }

    rx_dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);

    if (!rx_dtc) {

    pr_warn("dmatest: No memory for rx %s\n",

    dma_chan_name(rx_chan));

    return -ENOMEM;

    }

    tx_dtc->chan = tx_chan;

    rx_dtc->chan = rx_chan;

    INIT_LIST_HEAD(&tx_dtc->threads);

    INIT_LIST_HEAD(&rx_dtc->threads);

     

    dmatest_add_slave_threads(tx_dtc, rx_dtc);

    thread_count += 1;

     

    pr_info("dmatest: Started %u threads using %s %s\n",

    thread_count, dma_chan_name(tx_chan), dma_chan_name(rx_chan));

     

    list_add_tail(&tx_dtc->node, &dmatest_channels);

    list_add_tail(&rx_dtc->node, &dmatest_channels);

    nr_channels += 2;

     

    if (iterations)

    wait_event(thread_wait, !is_threaded_test_run(tx_dtc, rx_dtc));

     

    return 0;

    }

     

    dmatest_add_slave_threads实际上是创建了一个内核任务,这个任务才是发起dma操作的,

    在linux-dma测试程序中发起dma请求,都是由标准的函数,我们写的时候也要用到这些函数

    这里要了解一些linuxdma的相关知识,这里找几篇博客参考

    http://www.wowotech.net/linux_kenrel/dma_engine_overview.html

    http://www.wowotech.net/linux_kenrel/dma_engine_api.html

    https://www.cnblogs.com/xiaojiang1025/archive/2017/02/11/6389194.html

    Dma驱动一般分两种,一种是一致性dma一种是流式dma,本文用的dma是流式的。

    相关函数如下:

     dma_map_single

    sg_init_table(tx_sg, bd_cnt);

    sg_dma_address

    sg_dma_len

    device_prep_slave_sg

    rxd->tx_submit

    dmatest_slave_func函数基本流程我先概况一下:

    先通过准备数据通过写通道dma发送给datafifo,然后在通过读通道dma从datafifo读出来,对比发送的数据是否和接收的数据一致,一致则认为测试通过,循环做几次测试

     

    static int dmatest_slave_func(void *data)函数分析

     

    static int dmatest_slave_func(void *data)

    {

    struct dmatest_slave_thread *thread = data;

    struct dma_chan *tx_chan;

    struct dma_chan *rx_chan;

    const char *thread_name;

    unsigned int src_off, dst_off, len;

    unsigned int error_count;

    unsigned int failed_tests = 0;

    unsigned int total_tests = 0;

    dma_cookie_t tx_cookie;

    dma_cookie_t rx_cookie;

    enum dma_status status;

    enum dma_ctrl_flags flags;

    int ret;

    int src_cnt;

    int dst_cnt;

    int bd_cnt = 11;

    int i;

     

    ktime_t ktime, start, diff;

    ktime_t filltime = 0;

    ktime_t comparetime = 0;

    s64 runtime = 0;

    unsigned long long total_len = 0;

    thread_name = current->comm;

    ret = -ENOMEM;

     

     

    /* Ensure that all previous reads are complete */

    smp_rmb();

    tx_chan = thread->tx_chan;

    rx_chan = thread->rx_chan;

    dst_cnt = bd_cnt; //这里为11,实际上是创建11个sg,sg支持多个分块dma传输,这里可以改成1,就一块

    src_cnt = bd_cnt;

    //申请发送缓冲区数据结构,

     

    thread->srcs = kcalloc(src_cnt + 1, sizeof(u8 *), GFP_KERNEL);

    if (!thread->srcs)

    goto err_srcs;

    //申请发送缓冲区,这里面放发送的数据,往datafifo发送的数据

     

    for (i = 0; i < src_cnt; i++) {

    thread->srcs[i] = kmalloc(test_buf_size, GFP_KERNEL);

    if (!thread->srcs[i])

    goto err_srcbuf;

    }

    thread->srcs[i] = NULL;

    //申请接收缓冲区数据结构,

    thread->dsts = kcalloc(dst_cnt + 1, sizeof(u8 *), GFP_KERNEL);

    if (!thread->dsts)

    goto err_dsts;

     

      //申请接收缓冲区,这里是dma从datafifo搬运的数据放到这里,

     

    for (i = 0; i < dst_cnt; i++) {

    thread->dsts[i] = kmalloc(test_buf_size, GFP_KERNEL);

    if (!thread->dsts[i])

    goto err_dstbuf;

    }

    thread->dsts[i] = NULL;

     

    set_user_nice(current, 10);

     

    flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;

     

    ktime = ktime_get();

    while (!kthread_should_stop() &&

           !(iterations && total_tests >= iterations)) {

    struct dma_device *tx_dev = tx_chan->device;

    struct dma_device *rx_dev = rx_chan->device;

    struct dma_async_tx_descriptor *txd = NULL;

    struct dma_async_tx_descriptor *rxd = NULL;

    dma_addr_t dma_srcs[src_cnt];

    dma_addr_t dma_dsts[dst_cnt];

    struct completion rx_cmp;

    struct completion tx_cmp;

    unsigned long rx_tmo =

    msecs_to_jiffies(300000); /* RX takes longer */

    unsigned long tx_tmo = msecs_to_jiffies(30000);

    u8 align = 0;

    struct scatterlist tx_sg[bd_cnt];   //定义发送dma数据结构,

    struct scatterlist rx_sg[bd_cnt];   //定义接收dma数据结构,

     

     

    total_tests++;

           //----------------字节对齐,dma传输要求字节对齐

    /* honor larger alignment restrictions */

    align = tx_dev->copy_align;

    if (rx_dev->copy_align > align)

    align = rx_dev->copy_align;

     

    if (1 << align > test_buf_size) {

    pr_err("%u-byte buffer too small for %d-byte alignment\n",

           test_buf_size, 1 << align);

    break;

    }

     

    len = dmatest_random() % test_buf_size + 1;

    len = (len >> align) << align;

    if (!len)

    len = 1 << align;

    total_len += len;

    src_off = dmatest_random() % (test_buf_size - len + 1);

    dst_off = dmatest_random() % (test_buf_size - len + 1);

     

    src_off = (src_off >> align) << align;

    dst_off = (dst_off >> align) << align;

     

    start = ktime_get();

    dmatest_init_srcs(thread->srcs, src_off, len);

    dmatest_init_dsts(thread->dsts, dst_off, len);

              //----------------字节对齐,dma传输要求字节对齐

     

    diff = ktime_sub(ktime_get(), start);

    filltime = ktime_add(filltime, diff);

     

    for (i = 0; i < src_cnt; i++) {

    u8 *buf = thread->srcs[i] + src_off;

               //dma映射后获取的物理地址,实际上dma初始化用的是物理地址,thread->srcs[i] 这个是虚拟地址,是程序可以直接使用的,但是dma用的是物理地址

    dma_srcs[i] = dma_map_single(tx_dev->dev, buf, len,

         DMA_MEM_TO_DEV);

    }

     

    for (i = 0; i < dst_cnt; i++) {

     //dma映射后获取的物理地址,实际上dma初始化用的是物理地址,thread->srcs[i] 这个是虚拟地址,是程序可以直接使用的,但是dma用的是物理地址

     

    dma_dsts[i] = dma_map_single(rx_dev->dev,

         thread->dsts[i],

         test_buf_size,

         DMA_BIDIRECTIONAL);

    }

         //初始化发送接收数据结构----------------------

    sg_init_table(tx_sg, bd_cnt);

    sg_init_table(rx_sg, bd_cnt);

     

    for (i = 0; i < bd_cnt; i++) {

    sg_dma_address(&tx_sg[i]) = dma_srcs[i]; //物理地址

    sg_dma_address(&rx_sg[i]) = dma_dsts[i] + dst_off;

     

    sg_dma_len(&tx_sg[i]) = len; //dma一次传输的长度

    sg_dma_len(&rx_sg[i]) = len;

    }

            //准备发送 接收sg

    rxd = rx_dev->device_prep_slave_sg(rx_chan, rx_sg, bd_cnt,

    DMA_DEV_TO_MEM, flags, NULL);

     

    txd = tx_dev->device_prep_slave_sg(tx_chan, tx_sg, bd_cnt,

    DMA_MEM_TO_DEV, flags, NULL);

     

    if (!rxd || !txd) {

    for (i = 0; i < src_cnt; i++)

    dma_unmap_single(tx_dev->dev, dma_srcs[i], len,

     DMA_MEM_TO_DEV);

    for (i = 0; i < dst_cnt; i++)

    dma_unmap_single(rx_dev->dev, dma_dsts[i],

     test_buf_size,

     DMA_BIDIRECTIONAL);

    pr_warn("%s: #%u: prep error with src_off=0x%x ",

    thread_name, total_tests - 1, src_off);

    pr_warn("dst_off=0x%x len=0x%x\n",

    dst_off, len);

    msleep(100);

    failed_tests++;

    continue;

    }

            //接收的dma,先准备接收的

    init_completion(&rx_cmp);

    rxd->callback = dmatest_slave_rx_callback;//dma搬运完这个函数会运行

    rxd->callback_param = &rx_cmp;

    rx_cookie = rxd->tx_submit(rxd);

     

           //发送的dma,

    init_completion(&tx_cmp);

    txd->callback = dmatest_slave_tx_callback;//dma搬运完这个函数会运行

    txd->callback_param = &tx_cmp;

    tx_cookie = txd->tx_submit(txd);

     

    if (dma_submit_error(rx_cookie) ||

        dma_submit_error(tx_cookie)) {

    pr_warn("%s: #%u: submit error %d/%d with src_off=0x%x ",

    thread_name, total_tests - 1,

    rx_cookie, tx_cookie, src_off);

    pr_warn("dst_off=0x%x len=0x%x\n",

    dst_off, len);

    msleep(100);

    failed_tests++;

    continue;

    }

    dma_async_issue_pending(rx_chan);

    dma_async_issue_pending(tx_chan);

    //--dma_async_issue_pending这个执行后就相当于给dma控制器发命令了,dma控制器就会根据我们提供的参数来自己搬运数据了

     

    tx_tmo = wait_for_completion_timeout(&tx_cmp, tx_tmo);

            //等待发送dma传输完 等completion

    status = dma_async_is_tx_complete(tx_chan, tx_cookie,

      NULL, NULL);

     

    if (tx_tmo == 0) {

    pr_warn("%s: #%u: tx test timed out\n",

    thread_name, total_tests - 1);

    failed_tests++;

    continue;

    } else if (status != DMA_COMPLETE) {

    pr_warn("%s: #%u: tx got completion callback, ",

    thread_name, total_tests - 1);

    pr_warn("but status is \'%s\'\n",

    status == DMA_ERROR ? "error" :

    "in progress");

    failed_tests++;

    continue;

    }

     

    rx_tmo = wait_for_completion_timeout(&rx_cmp, rx_tmo);

            //等待接收传输完

    status = dma_async_is_tx_complete(rx_chan, rx_cookie,

      NULL, NULL);

     

    if (rx_tmo == 0) {

    pr_warn("%s: #%u: rx test timed out\n",

    thread_name, total_tests - 1);

    failed_tests++;

    continue;

    } else if (status != DMA_COMPLETE) {

    pr_warn("%s: #%u: rx got completion callback, ",

    thread_name, total_tests - 1);

    pr_warn("but status is \'%s\'\n",

    status == DMA_ERROR ? "error" :

    "in progress");

    failed_tests++;

    continue;

    }

           //前面是先映射,这里是解除映射,这样缓冲区的数据才是正确的,不执行这个操作,缓冲区数据不对。

    /* Unmap by myself */

    for (i = 0; i < dst_cnt; i++)

    dma_unmap_single(rx_dev->dev, dma_dsts[i],

     test_buf_size, DMA_BIDIRECTIONAL);

     

    error_count = 0;

    start = ktime_get();

    pr_debug("%s: verifying source buffer...\n", thread_name);

    error_count += dmatest_verify(thread->srcs, 0, src_off,

    0, PATTERN_SRC, true);

    error_count += dmatest_verify(thread->srcs, src_off,

    src_off + len, src_off,

    PATTERN_SRC | PATTERN_COPY, true);

    error_count += dmatest_verify(thread->srcs, src_off + len,

    test_buf_size, src_off + len,

    PATTERN_SRC, true);

     

    pr_debug("%s: verifying dest buffer...\n",

     thread->task->comm);

            //--校验接收的数据和发送的数据,

    error_count += dmatest_verify(thread->dsts, 0, dst_off,

    0, PATTERN_DST, false);

    error_count += dmatest_verify(thread->dsts, dst_off,

    dst_off + len, src_off,

    PATTERN_SRC | PATTERN_COPY, false);

    error_count += dmatest_verify(thread->dsts, dst_off + len,

    test_buf_size, dst_off + len,

    PATTERN_DST, false);

    diff = ktime_sub(ktime_get(), start);

    comparetime = ktime_add(comparetime, diff);

     

    if (error_count) {

    pr_warn("%s: #%u: %u errors with ",

    thread_name, total_tests - 1, error_count);

    pr_warn("src_off=0x%x dst_off=0x%x len=0x%x\n",

    src_off, dst_off, len);

    failed_tests++;

    } else {

    pr_debug("%s: #%u: No errors with ",

     thread_name, total_tests - 1);

    pr_debug("src_off=0x%x dst_off=0x%x len=0x%x\n",

     src_off, dst_off, len);

    }

    }

     

    ktime = ktime_sub(ktime_get(), ktime);

    ktime = ktime_sub(ktime, comparetime);

    ktime = ktime_sub(ktime, filltime);

    runtime = ktime_to_us(ktime);

     

    ret = 0;

    for (i = 0; thread->dsts[i]; i++)

    kfree(thread->dsts[i]);

    err_dstbuf:

    kfree(thread->dsts);

    err_dsts:

    for (i = 0; thread->srcs[i]; i++)

    kfree(thread->srcs[i]);

    err_srcbuf:

    kfree(thread->srcs);

    err_srcs:

    pr_notice("%s: terminating after %u tests, %u failures %llu iops %llu KB/s (status %d)\n",

      thread_name, total_tests, failed_tests,

      dmatest_persec(runtime, total_tests),

      dmatest_KBs(runtime, total_len), ret);

     

    thread->done = true;

    wake_up(&thread_wait);

     

    return ret;

    }

     

     

    至此,axidmatest.c主要部分就是这个任务,所以,把这个搞明白就行,还有一点数据准备这块有个字节对齐,有点麻烦,容易搞不清楚,可以使用__get_free_pages申请缓冲区,这种函数本身就是对齐的,还有,这种流式的dma要先dma_map_single传输完后还要dma_unmap_single后才能去读传输的数据,这种函数我测试数据量小的话好用,如果很大的话,就比较花时间,一致性dma就不用这样。后面有时间吧这个改造一下,做一致性的dma,写的尽量简单能用,能理解大概就行。

     

     

    之前在闲鱼上买的一个fmc 测试nvme-ssd硬盘的测试板,测试过不怎么用了,现在出手,

    Fmc nvme sata pinggu评估 测试板,可以在具有fmc接口的开发板测试 比如zynq zc706上测试nvme协议的固态硬盘ssd,提供技术支持,比如怎么在vivado建立工程,linux下驱动指导等

     

    展开全文
  • Zynq-Linux-DMA-master

    2018-01-23 17:25:32
    DMA enabled Zynq PS-PL communication to implement high throughput data transfer between Linux applications and user IP core. (based on Xilinx UG873 chapter 6) This is a simple loop-back project in ...
  • ZYNQ AXI DMA 学习总结

    千次阅读 2019-02-21 09:32:06
    学习参考资料传送门: 【原创】ZYNQ AXIDMA详解(一) ...zynq linux AXI DMA传输步骤教程详解 【ZYNQ-7000——开发之五】:AXI DMA读写FIFO ZYNQ跑系统 系列(四) AXI-DMAlinux下运行  ...
    展开全文
  • Zynq MPSoC Linux官方DMA驱动调试 前言 Zynq平台下DMA驱动主要有官方在用户层控制的和某大神写的axi_dma驱动,今天主要用官方的进行测试。 环境 petalinux 19.1 vivado 19.1 开始 首先搭建逻辑,注意这里DMA用64地址...

    Zynq MPSoC Linux官方DMA驱动调试

    前言

    Zynq平台下DMA驱动主要有官方在用户层控制的和某大神写的axi_dma驱动,今天主要用官方的进行测试。
    环境
    petalinux 19.1
    vivado 19.1

    开始

    首先搭建逻辑,注意这里DMA用64地址线,不然4GB以上的DDR访问不到,然后输入输出就挂到FIFO上就行。
    在这里插入图片描述
    然后FIFO这样挂上去就行
    在这里插入图片描述
    然后编译,这是一个漫长的过程,主要是我有个自己的IP以及VCU,所以特别慢。

    Linux驱动

    1、首先下载驱动,地址如下:添加链接描述
    这个也是我参考的
    2、然后根据他的进行编译,注意这里编译完了之后,启动的时候会出现一个问题,驱动挂载失败,然后报错,这个是petalinux_19.1的问题,然后官方给的解决方案是打补丁,连接如下:添加链接描述
    3、然后呢,还有个问题就是DMA传输一遍后time_out,这里的解决方案也比较简单,如下:

    /* Close the file and there's nothing to do for it
     */
    static int release(struct inode *ino, struct file *file)
    {
    	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;
    	struct dma_device *dma_device = pchannel_p->channel_p->device;
    
    	/* Stop all the activity when the channel is closed assuming this
    	 * may help if the application is aborted without normal closure
    	 */
    	//注释掉这里就ok
    	//dma_device->device_terminate_all(pchannel_p->channel_p);
    	return 0;
    }
    

    4、测试,测试也用官方的就行

    自定义自己驱动

    1、这里就是等待DMA读取完成,然后我们用来读取数据进行编码,如果超过5s,自行跳出

    static ssize_t read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
    	int ret_val;
    	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;
    	//char* data = share_mem_dev.base_addr;
    	unsigned int cnt = size;
    	unsigned int p = *ppos;
    	u64 phy_addr = pchannel_p->dma_handle;
    	u64 off =  phy_addr - pchannel_p->reva_mem_start; //physcicl addr offset 
    	char* data = pchannel_p->reva_mem_vaddr + off;
    	//ensure read size right
    	if(p >= MEM_SIZE)
    		return cnt ? -ENXIO : 0;
    	if(cnt > MEM_SIZE - p)
    		cnt = MEM_SIZE - p;	
    	
    	#define WAIT_TIMEOUT_DURATION (HZ * 5)
    	int err = wait_event_interruptible_timeout(pchannel_p->queue,
    						   pchannel_p->dma_rx_finish_flag,
    						   WAIT_TIMEOUT_DURATION);
    	if(err = 0) {
    		printk("[%s] read time out\r\n", DRIVER_NAME);
    		return -EINVAL;
    	}					   
    
    	pchannel_p->dma_rx_finish_flag = 0;
    	//start next transmit , and the transmit and encode can be run at the same time
    	ret_val = copy_to_user(buf, data + p, cnt);
    	if(ret_val < 0) {
    		printk("[%s] err copy data to user\r\n", DRIVER_NAME);
    		return -1;
    	}else{
    		*ppos += cnt;
    	}
    
    	return cnt;
    }
    

    2、然后在DMA回调函数那里修改:

    static void sync_callback(void *pchannel_p)
    {
    	/* Indicate the DMA transaction completed to allow the other
    	 * thread of control to finish processing
    	 */
    	struct dma_proxy_channel *chan = pchannel_p;
    	chan->dma_rx_finish_flag = 1; //to tell read func i read finsh
    	complete(&chan->cmp);
    }
    

    3、测试
    就直接改的官方测试程序,然后我这个用fifo测试成功了,后面该到了自己的IP上,然后效果如下

    root@RS-IPU:/mnt/bin# ./dma_test 1 1
    DMA proxy test
    [   26.797779] start ip
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    DMA proxy test complete
    

    其实上面的结果就是一幅图像的ROI图,有1代表该区域为ROI,哈哈,成功了,开森,就是下面这个图了
    在这里插入图片描述

    END

    最后,我这里开源一下我封装好的驱动,用C++写的,因为在上层,所以比较方便调试:

    #ifndef _DMA_BSP_H_
    #define _DMA_BSP_H_
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <pthread.h>
    
    #define DMA_ALLOC_SIZE (3840 * 2160 * 5)
    
    #define IO_TRANSFRER_TEST 0
    #define IO_START_IP		  1
    #define IO_SET_IMG_PARAM  2
    
    enum {
    	TYPE_VEDIO = 0x01,
    	TYPE_IMG = 0x11
    };
    
    struct usr_param {
    	unsigned char type;
    	unsigned char fps;
    	unsigned char Qp;
    	unsigned char resv;
    	unsigned int width;
    	unsigned int height;
    };
    
    struct dma_proxy_channel_interface {
    	unsigned char buffer[DMA_ALLOC_SIZE];
    	enum proxy_status { PROXY_NO_ERROR = 0, PROXY_BUSY = 1, PROXY_TIMEOUT = 2, PROXY_ERROR = 3 } status;
    	unsigned int length;
    };
    
    
    class dma_ctrl
    {
    private:
        struct dma_proxy_channel_interface* dma_proxy_interface_p;
        struct usr_param dev_ctrl_param;
        int dev_fd;
    public:
        dma_ctrl(char* dev_name);
        ~dma_ctrl();
    public:    
        void dma_set_transmit_data(unsigned char* buff, unsigned int size, unsigned int off);
        void dma_get_transmit_data(unsigned char* buff, unsigned int size, unsigned int off);
        int dev_set_param(struct usr_param* param);
        void dma_set_size(unsigned int size);
        int dma_start_transmit(void);
        int dma_test_transmit(void);
    };
    #endif
    

    主程序:

    #include "dma_bsp.h"
    #include <cstring>
    
    dma_ctrl::dma_ctrl(char* dev_name)
    {
        dev_fd = open(dev_name, O_RDWR);
    	if (dev_fd < 1) {
    		printf("Unable to open device file, name: %s\r\n", dev_name);
            exit(EXIT_FAILURE);
    	}
    
        dma_proxy_interface_p = (struct dma_proxy_channel_interface *)mmap(NULL, sizeof(struct dma_proxy_channel_interface),
    									PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, 0);
        if (dma_proxy_interface_p == MAP_FAILED) {
            printf("Unable to mmap device file, name: %s\r\n", dev_name);
            exit(EXIT_FAILURE);
        }
    }
    
    dma_ctrl::~dma_ctrl()
    {
        if(dev_fd) {
            close(dev_fd);
        }
    }
    
    void dma_ctrl::dma_set_transmit_data(unsigned char* buff, unsigned int size, unsigned int off)
    {
        if(!dma_proxy_interface_p) {
            printf("err init mmap\r\n");
        }
        memcpy(dma_proxy_interface_p->buffer + off, buff, size);
    }
    
    void dma_ctrl::dma_set_size(unsigned int size)
    {
        dma_proxy_interface_p->length = size;
    }
    
    void dma_ctrl::dma_get_transmit_data(unsigned char* buff, unsigned int size, unsigned int off)
    {
        if(!dma_proxy_interface_p) {
            printf("err init mmap\r\n");
        }
        memcpy(buff, dma_proxy_interface_p->buffer + off, size);
        //dma_proxy_interface_p->length = size;
    }
    
    int dma_ctrl::dma_start_transmit()
    {
        return ioctl(dev_fd, IO_START_IP, NULL);
    }
    
    int dma_ctrl::dev_set_param(struct usr_param* param)
    {
        if(!param) return NULL;
        return ioctl(dev_fd, IO_SET_IMG_PARAM, param);
    }
    
    int dma_ctrl::dma_test_transmit()
    {
        return ioctl(dev_fd, IO_TRANSFRER_TEST, NULL);
    }
    
    

    测试用例:

    void safe_main_thread(int argc, char* argv[])
    {
        dma_ctrl rs_ipu_img_rx_ctrl(RS_IPU_IMG_RX_DEV);
        dma_ctrl rs_ipu_img_tx_ctrl(RS_IPU_IMG_TX_DEV);
        dma_ctrl rs_ipu_bitstream_rx_ctrl(RS_IPU_BIT_RX_DEV);
        dma_ctrl rs_ipu_bitstream_tx_ctrl(RS_IPU_BIT_TX_DEV);
    
        unsigned char rx_data[128];
    #define  TEST_SIZE (DMA_ALLOC_SIZE * 4 / 5)
        unsigned char* malloc_data = (unsigned char*)malloc(TEST_SIZE);  
        int index = 0;
        unsigned char gray = 0;
        for(int i = 0; i < 5; i++){
            for(int j = 0; j < 2160; j++) {
                for(int j = 0; j < 3840; j++) {
                    malloc_data[index++] = gray++;
                    if (gray >= 255)
                    {
                        gray = 0;
                    }
                }
            }
        }
        std::cout << "ipu_test" <<endl;
        rs_ipu_img_tx_ctrl.dma_set_transmit_data(malloc_data, 128, 0);
        rs_ipu_img_tx_ctrl.dma_set_size(128);
        rs_ipu_img_tx_ctrl.dma_test_transmit();
        std::cout << "ipu_test endl" <<endl;
        rs_ipu_bitstream_tx_ctrl.dma_set_transmit_data(malloc_data, 128, 0);
        rs_ipu_bitstream_tx_ctrl.dma_set_size(128);
        rs_ipu_bitstream_tx_ctrl.dma_test_transmit();
        std::cout << "bitstream_test endl" <<endl;
        rs_ipu_img_rx_ctrl.dma_set_size(128);
        rs_ipu_img_rx_ctrl.dma_test_transmit();
        rs_ipu_img_rx_ctrl.dma_get_transmit_data(rx_data, 128, 0);
        std::cout << "rx data : " << endl;
        for(int i = 0; i < 128; i++) {
            std::cout << (int)(rx_data[i]) << " ";
        }
        std::cout << endl;
        free(malloc_data);
    }
    

    tx过程就是先设置传输的data,然后设置大小,最后dma_test_transmit()
    rx过程先设置大小,然后transmit,然后get_data

    展开全文
  • Zynq-linux PL与PS通过DMA数据交互

    千次阅读 多人点赞 2019-10-04 10:39:55
    在米尔科技的z-turn板上,采用AXI DMA 实现zynq的PS与PL数据交互。 二、分析 ①PS数据传PL 驱动中的测试程序中给出一堆数据,通过DMA传输到AXI4-Stream Data FIFO ,PL端从DATA FIFO中把数据读出来。 ②PL数据传...

    一、目标
    在米尔科技的z-turn板上,采用AXI DMA 实现zynq的PS与PL数据交互。
    二、分析
    ①PS数据传PL
    驱动中的测试程序中给出一堆数据,通过DMA传输到AXI4-Stream Data FIFO ,PL端从DATA FIFO中把数据读出来。
    ②PL数据传PS
    将PS传入PL的数据回传,在PS端显示出数据,最后将数据乘2再送入DMA。
    ③PL端代码思路
    1)读数据
    在加上DATA FIFO的情况下,PL从DATA FIFO中读取数据。将DATA -FIFO的M_AXIS端引出,得到下面的信号。

    信号名称 作用 方向
    m_axis_tvalid 主机向从机发数据的信号声明,置1表示主机有数据要发向从机。 输出
    m_axis_tready 主机判断从机是否准备好接收数据,当为1时表示从机准备好接收数据。 输入
    m_axis_tdata 主机将要发送的数据。 输出
    m_axis_tkeep 主机发送数据时需拉高。 输出
    m_axis_tlast 表示主机发送是否是最后一个数据,置1时表示主机正在发送最后一个数据。 输出

    从上表可以看出,DATA FIFO接收完数据后,想要从FIFO中读取数据,靠的就是上面5根线,FIFO作主机,PL端作从机。所以FIFO会自动将m_axis_tvalid置1,表明可以从主机中读取数据。PL只需要给出回应m_axis_tready置1,便可以在时钟上升沿来临时读取数据。
    同时,AXI-DMA将PS数据传输完成后有完成标志mm2s_introut置1。通过这个标准来确定是否从FIFO中读取数据。所以这个信号用于PL中断的触发信号。
    2)写数据
    将PL读取出来的数据进行乘2,再传入PS,PS再将数据打印出来。PL端接的是AXI-DMA的S_AXIS_S2MM端口。其信号如下:

    信号名称 作用 方向
    s_axis_s2mm_tdata 从机接收数据线 输入
    s_axis_s2mm_tkeep 数据有效信号线 输入
    s_axis_s2mm_tlast 是否最后一个数据 输入
    s_axis_s2mm_tready 从机准备是否准备好接收数据 输出
    s_axis_s2mm_tvalid 主机是否有数据发向从机 输入

    从信号列表可以看出,此时DMA端口是作从机,PL端口作主机向DMA端口发送数据。PL端想发送数据,通过s_axis_s2mm_tvalid表明有数据发往从机。等待从机响应s_axis_s2mm_tready信号,响应过后便可以发送数据。发送数据时需要将s_axis_s2mm_tkeep拉高,同时当传到最后一个数据时,需要将s_axis_s2mm_tlast置1。
    3)整体架构
    在这里插入图片描述
    局部放大图:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    三、代码实现
    ①pl_read.v

    module pl_read(
    clk,
    rst,
    
    m_axis_tvalid,
    m_axis_tdata,
    m_axis_tkeep,
    m_axis_tlast,
    m_axis_tready,
    
    m_ready,
    m_data,
    m_datavalid,
    m_datalast
    );
    input clk;
    input rst;
    
    input m_axis_tvalid;
    input [31:0]m_axis_tdata;
    input [3:0]m_axis_tkeep;
    input m_axis_tlast;
    output m_axis_tready;
    
    input m_ready;
    output [31:0]m_data;
    output m_datavalid;
    output m_datalast;
    
    reg m_axis_tready;
    reg [31:0]m_data;
    reg m_datavalid;
    reg m_datalast; 
    always@(posedge clk or negedge rst)
    begin
        if(!rst)
        begin
            m_axis_tready <= 0;
            m_data <= 0;
            m_datavalid <= 0;
            m_datalast <= 0;
        end
        else
        begin
            if(m_ready==1)
            begin
                m_axis_tready <= 1;
                if(m_axis_tvalid==1&&m_axis_tkeep==4'b1111)
                begin
                    m_data <= m_axis_tdata;
                    m_datavalid <= 1;
                    if(m_axis_tlast==1) m_datalast <= 1;
                    else m_datalast <= 0;
                end
                else
                begin
                    m_data <= m_data;
                    m_datavalid <= 0;
                    m_datalast <= 0;
                end
            end
            else
            begin
                m_axis_tready <= 0;
                m_data <= m_data;
                m_datavalid <= 0;
                m_datalast <= 0;
            end
        end
    end
    
    endmodule
    

    ②pl_write.v

    module pl_write(
    clk,
    rst,
    
    s_data,
    s_datavalid,
    s_datalast,
    s_ready,
    
    s_axis_tdata,
    s_axis_tkeep,
    s_axis_tlast,
    s_axis_tready,
    s_axis_tvalid
    );
    input clk;
    input rst;
    
    input[31:0] s_data;//data stream
    input s_datalast;//data last flag
    input s_datavalid;//input data valid flag
    output s_ready;//数据可以写入标志
    
    input s_axis_tready;
    output[31:0] s_axis_tdata;
    output[3:0]s_axis_tkeep;
    output s_axis_tlast;
    output s_axis_tvalid;
    
    reg [31:0] s_axis_tdata;
    reg [3:0]s_axis_tkeep;
    reg s_axis_tlast;
    reg s_axis_tvalid;
    reg s_ready;
    
    always@(posedge clk or negedge rst)
    begin
        if(!rst)
        begin
            s_axis_tkeep <= 4'b0000;
            s_axis_tvalid <= 0;
            s_axis_tdata <= 0;
            s_axis_tlast<=0;
            s_ready <= 0;        
        end
        else
        begin
           if(s_axis_tready==1)
            begin
                if(s_datavalid==1)
                begin
                    s_axis_tvalid <= 1;
                    s_axis_tkeep <= 4'b1111;
                    s_axis_tdata <= s_data;
                    if(s_datalast)s_axis_tlast <= 1;
                    else s_axis_tlast <= 0;
                end
                else 
                begin
                    s_axis_tvalid <= 0;
                    s_axis_tkeep <= 4'b0000;
                    s_axis_tdata <= s_axis_tdata;
                    s_axis_tlast<=0;
                end
                s_ready <= 1;
            end
            else 
            begin
                s_axis_tkeep <= 4'b0000;
                s_axis_tvalid <= 0;
                s_axis_tdata <= s_axis_tdata;
                s_axis_tlast<=0;
                s_ready <= 0;
            end
        end
    end
    
    endmodule
    

    ③top.v

    module top(
    );
    wire clk;
    wire rst;
    
    wire m_axis_tvalid;
    wire [31:0]m_axis_tdata;
    wire [3:0]m_axis_tkeep;
    wire m_axis_tlast;
    wire m_axis_tready;
    
    wire m_ready;
    wire [31:0]m_data;
    wire m_datavalid;
    wire m_datalast;
    
    wire s_axis_tready;
    wire[31:0] s_axis_tdata;
    wire[3:0]s_axis_tkeep;
    wire s_axis_tlast;
    wire s_axis_tvalid;
    
    pl_write u1(
    .clk(clk),
    .rst(rst),
    
    .s_data(m_data),
    .s_datalast(m_datalast),
    .s_datavalid(m_datavalid),
    .s_ready(m_ready),
    
    .s_axis_tready(s_axis_tready),
    .s_axis_tdata(s_axis_tdata),
    .s_axis_tkeep(s_axis_tkeep),
    .s_axis_tlast(s_axis_tlast),
    .s_axis_tvalid(s_axis_tvalid)
    );
    pl_read u2(
    .clk(clk),
    .rst(rst),
    
    .m_data(m_data),
    .m_datalast(m_datalast),
    .m_datavalid(m_datavalid),
    .m_ready(m_ready),
    
    .m_axis_tready(m_axis_tready),
    .m_axis_tdata(m_axis_tdata),
    .m_axis_tkeep(m_axis_tkeep),
    .m_axis_tlast(m_axis_tlast),
    .m_axis_tvalid(m_axis_tvalid)
    );
    system_wrapper u3(
    .FCLK_CLK0(clk),
    .peripheral_aresetn(rst),
    .s_axis_aclk(clk),
    .s_axis_aclk_1(clk),
    .s_axis_aresetn(rst),
    .s_axis_aresetn_1(rst),
    
    .s_axis_tready(s_axis_tready),
    .s_axis_tdata(s_axis_tdata),
    .s_axis_tkeep(s_axis_tkeep),
    .s_axis_tlast(s_axis_tlast),
    .s_axis_tvalid(s_axis_tvalid),
    
    .m_axis_tready(m_axis_tready),
    .m_axis_tdata(m_axis_tdata),
    .m_axis_tkeep(m_axis_tkeep),
    .m_axis_tlast(m_axis_tlast),
    .m_axis_tvalid(m_axis_tvalid)
    );
    
    
    endmodule
    

    ④dma驱动代码

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <asm/io.h>
    #include <linux/init.h>
    #include <linux/platform_device.h>
    #include <linux/miscdevice.h>
    #include <linux/ioport.h>
    #include <linux/of.h>
    #include <linux/uaccess.h>
    #include <linux/interrupt.h>
    #include <asm/irq.h>
    #include <linux/irq.h>
    #include <asm/uaccess.h>
    #include <linux/dma-mapping.h>
    /**
     *DMA驱动程序
     *
     *
     *
     *
     *
     *
     *`
     *
     * **/
    //DMA 基地址
    #define DMA_S2MM_ADDR		0X40400000
    #define DMA_MM2S_ADDR       0X40410000
    
    //DMA MM2S控制寄存器
    volatile unsigned int  * mm2s_cr;
    #define MM2S_DMACR		0X00000000
    
    //DMA MM2S状态控制寄存器
    volatile unsigned int * mm2s_sr;
    #define MM2S_DMASR		0X00000004
    
    //DMA MM2S源地址低32位
    volatile unsigned int * mm2s_sa;
    #define MM2S_SA			0X00000018
    
    //DMA MM2S传输长度(字节)
    volatile unsigned int * mm2s_len;
    #define MM2S_LENGTH		0X00000028
    
    //DMA S2MM控制寄存器
    volatile unsigned int  * s2mm_cr;
    #define S2MM_DMACR		0X00000030
    
    //DMA S2MM状态控制寄存器
    volatile unsigned int  * s2mm_sr;
    #define S2MM_DMASR		0X00000034
    
    //DMA S2MM目标地址低32位
    volatile unsigned int  * s2mm_da;
    #define S2MM_DA			0X00000048
    
    //DMA S2MM传输长度(字节)
    volatile unsigned int  * s2mm_len;
    #define S2MM_LENGTH		0X00000058
    
    #define DMA_LENGTH		524288
    
    dma_addr_t axidma_handle;
    volatile unsigned int * axidma_addr;
    static irqreturn_t dma_mm2s_irq(int irq,void *dev_id)
    {
        printk("\nPs write data to fifo is over! irq=%d\n",irq);
        iowrite32(0x00001000,mm2s_sr);
        return IRQ_HANDLED;
    }
    static irqreturn_t dma_s2mm_irq(int irq,void *dev_id)
    {
        iowrite32(0x00001000,s2mm_sr);
        printk("\nps read data from fifo is over! irq=%d\n",irq);//读出了FIFO里的数据触发中断
        return IRQ_HANDLED;
    }
    int major;
    
    static struct class *dma_class   = NULL;
    static int dma_init(void);
    static int dma_exit(void);
    static int dma_open(struct inode *inode,struct file *file);
    static int dma_write(struct file *file,const char __user *buf, size_t count,loff_t *ppos);
    static int dma_read(struct file *file,char __user *buf,size_t size,loff_t *ppos);
    /*
     *file_operations 结构数据,沟通内核与操作系统桥梁
     *
     * */
    static struct file_operations dma_lops=
    {
    .owner = THIS_MODULE,
    .read  = dma_read,
    .open  = dma_open,
    .write = dma_write,
    };
    /*
     * 初始化,用于module init
     *
     * */
    static int dma_init(void)
    {
        major=register_chrdev(0,"dma_dev",&dma_lops);
        dma_class= class_create(THIS_MODULE,"dma_dev");
        device_create(dma_class,NULL,MKDEV(major,0),NULL,"dma_dev");
        printk("major dev number= %d",major);
    
        mm2s_cr  =  ioremap(DMA_MM2S_ADDR+MM2S_DMACR, 4);
        mm2s_sr  =  ioremap(DMA_MM2S_ADDR+MM2S_DMASR, 4);
        mm2s_sa  =  ioremap(DMA_MM2S_ADDR+MM2S_SA,    4);
        mm2s_len =  ioremap(DMA_MM2S_ADDR+MM2S_LENGTH,4);
    
        s2mm_cr  =  ioremap(DMA_S2MM_ADDR+S2MM_DMACR, 4);
        s2mm_sr  =  ioremap(DMA_S2MM_ADDR+S2MM_DMASR, 4);
        s2mm_da  =  ioremap(DMA_S2MM_ADDR+S2MM_DA,    4);
        s2mm_len =  ioremap(DMA_S2MM_ADDR+S2MM_LENGTH,4);
    
       return 0;
    }
    /*
     *退出 用于 module exit
     *
     * */
    static int dma_exit(void)
    {
        unregister_chrdev(major,"dma_dev");
        
        device_destroy(dma_class,MKDEV(major,0));
        class_destroy(dma_class);
    
        free_irq(dma_mm2s_irq,NULL);
        free_irq(dma_s2mm_irq,NULL);
        dma_free_coherent(NULL,DMA_LENGTH,axidma_addr,axidma_handle);
    
        iounmap(mm2s_cr);
        iounmap(mm2s_sr);
        iounmap(mm2s_sa);
        iounmap(mm2s_len);
    
        iounmap(s2mm_cr);
        iounmap(s2mm_sr);
        iounmap(s2mm_da);
        iounmap(s2mm_len);
    
        return 0;
    }
    /*
     *open 接口函数
     *
     * */
    static int dma_open(struct inode *inode,struct file *file)
    {
        int err;
        printk("DMA open\n");
        axidma_addr = dma_alloc_coherent(NULL,DMA_LENGTH,&axidma_handle,GFP_KERNEL);
        err = request_irq(61,dma_mm2s_irq,IRQF_TRIGGER_RISING,"dma_dev",NULL);
        printk("err=%d\n",err);
        err = request_irq(62,dma_s2mm_irq,IRQF_TRIGGER_RISING,"dma_dev",NULL);
        printk("err=%d\n",err);
    
    
        return 0;
    }
    /*
     * write 接口函数
     *
     * */
    static int dma_write(struct file *file,const char __user *buf, size_t count,loff_t *ppos)
    {
        unsigned int mm2s_status = 0;
        printk("dma write start !\n");
        if(count>DMA_LENGTH)
        {
    	printk("the number of data is too large!\n");
    	return 0;
        }
        memcpy(axidma_addr,buf,count);
    
        iowrite32(0x00001001,mm2s_cr);
        iowrite32(axidma_handle,mm2s_sa);
        iowrite32(count,mm2s_len);
    
        mm2s_status = ioread32(mm2s_sr);
        while((mm2s_status&(1<<1))==0)
        {
            mm2s_status = ioread32(mm2s_sr);
        }
        printk("mm2s_status =0x%x\n",mm2s_status);
        printk("dma write is over!\n");
    
        return 0;
    }
    /*
     * read 接口函数
     *DMA读取数据是按照32bit读取的
     * */
    static int dma_read(struct file *file,char __user *buf,size_t size,loff_t *ppos)
    {
        unsigned int s2mm_status=0;
        printk("dma read start!\n");
        if(size>DMA_LENGTH)
        {
    	printk("the number of data is not enough!\n");
    	return 1;
        }
    
        iowrite32(0x00001001,s2mm_cr);
        iowrite32(axidma_handle,s2mm_da);
        iowrite32(size,s2mm_len);
        
        s2mm_status=ioread32(s2mm_sr);
        while((s2mm_status&(1<<1))==0)
        {
            s2mm_status=ioread32(s2mm_sr);
        }
        printk("s2mm_sr=0x%x\n",s2mm_status);
        
        memcpy(buf,axidma_addr,size);
        printk("\ndma read is over!\n");
        return 0;
    }
    
    module_init(dma_init);
    module_exit(dma_exit);
    
    MODULE_AUTHOR("TEST@dma");
    MODULE_DESCRIPTION("dma driver");
    MODULE_ALIAS("dma linux driver");
    MODULE_LICENSE("GPL");
    

    ⑤测试代码

    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    
    void delay(void)
    {
        int i,j;
        for(i=0;i<20000;i++)
            for(j=0;j<10000;j++);
    }
    unsigned int readarray[10001];
    int main(int argc , char ** argv)
    {
        int fd;
        int i=0;
        fd = open("/dev/dma_dev",O_RDWR);
        if(fd<0) {printf("can not open file\n");while(1);}
        else printf("open file sucuss\n");
        delay();
        for(i=0;i<4000;i++)
        {
            readarray[i]=i+1;
        }
        while(1)
        {
            write(fd,readarray,4000*4);
    	if(read(fd,readarray,4000*4)==0)
    	{
    		for(i=0;i<4000;i++)
    		{
                printf(" %d",readarray[i]);
                readarray[i]=readarray[i]*2;
    		}
    		printf("\n=====================================\n");
            printf("======================================\n");
    	}
        delay();delay();
        }
    
        return 0;
    }
    

    ⑥Makefile文件

    KDIR = /home/python/Hard_disk_21G/04-Linux_Source/Kernel/linux-xlnx
    PWD := $(shell pwd)
    CC   = $(CROSS_COMPILE)gcc
    ARCH =arm
    MAKE =make
    obj-m:=dma_driver.o
    
    modules:
    	$(MAKE) -C $(KDIR) ARCH=$(ARCH) CROSS_COMPLE=$(CROSS_COMPLE) M=$(PWD) modules
    clean:
    	make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPLE=$(CROSS_COMPLE) M=$(PWD) clean
    

    ⑦运行结果
    在这里插入图片描述
    在这里插入图片描述
    可以看见程序运行结果,符合预期值。
    四、调试
    在调试数据交互的过程中,遇到了很多坑。

    	第一个大坑就是在DATA-FIFO中。数据位宽设置的8bit,PL将测试数据写入DMA,PS端读出数据,发现数据变得大而且只有前面部分数据不为0,后面数据全是0,后来发现是PS端读DMA是按照字节进行读取的,而PS端读取的数据放在整形数组里面,结果导致是PL端的4个数据合成了PS端一个数据,最后导致PL端发送200个数据,结果PS端只有前50个数据有值,后面全是0。后面将PL的位宽设置为32bit,PS端读出数据就正缺了。	
    	第二大坑就是忽略了tlast信号。这块问题找了很久,最后通过慢慢测试发现了。当时在PL端发送10万个数据,PS端一次性读取10万个数据没问题,然后想测试读取少一点,结果读取一千,两千,一万个数据都可以,同时数据是正确的。到了第二次循环是就出现问题了,第二次读取的数据还是同第一次读取的数据一样。通过读取DMA状态寄存器的值,打印出来是0x4011,判断出DMA内部错误。后来调试出问题是:在传输期间没有tlast信号,所以报出DMA内部错误。再次调试,设置一万个数据给个tlast,读取2万个数据,最后发现只能读取出前面1万个数据,后面全是0,第二轮还是一样,只有前面1万个有数据,后面全是0。最后明白了DMA传输中在一次传输过程中,只能且必须有一次tlast信号,也不能读出tlast信号后的数据。
    	第三个坑就是在模块设计时,报DMA的S_AXIS_S2MM与FIFO的M_AXIS时钟不匹配,还以为是哪儿没弄对,看别人的都没问题。后来把器件全删了,先把fifo与DMA联接起来,在进行自动连线,问题就解决了。
    	第四个坑就在PL端的例化,连线上。在仿真fifo的时候,先编写一个模块与fifo对应,同时在模块里面对fifo的S_AXIS端连线,在simulation文件中将fifo的M_AXIS连线,功能仿真想观察波形,结果咋调,波形都是xxxx,AXI时序修改了又修改,还是出问题。后来发现是出现了两个fifo,一个是模块里连线的fifo,另一个是simulation里的fifo,所以就出现了波形是xxxx的情况。后面改成了,增加一个top.v在top里面进行各个模块的连线,时钟也不要在单独例化在底层模块里,全部在top里面连线,例化。
    

    五、总结
    ①每次进行DMA传输是,读取DMA状态寄存器,根据状态寄存器的值,判断是否发生DMA错误。如果发生DMA内部错误,则是由于DMA传输过程中没有收到tlast信号。
    ②PS是按字节进行读取或写入DMA的,要注意存放数据的数组的变量类型,即整形数组和字符型数组和PL端的位宽匹配好。

    展开全文
  • ZYNQ PCIe-DMA源码 例程 PS-PL交互 linux/裸机 verilog C/C++ZYNQ PCIe-DMA的实现过程一、概述二、基础知识三、系统总框架四、工作原理与工作模式五、接口时序六、资源使用情况七、PS-PL交互以及测试程序 ZYNQ PCIe-...
  • ZYNQ Linux应用层 利用 AXI DMA 进行数据传输 软件版本: VIVADO2018.2 操作系统: Debian 9 硬件平台: ZYNQ-MZ7100FA 嵌入式操作系统:Debian(4.19.0-xilinx) AXI-DMA驱动:GitHub:bperez77/xilinx_axidma 一、...
  • 米尔科技ZYNQ -Linux下的DMA驱动

    千次阅读 2019-08-08 19:15:19
    在米尔科技的z-turn板上实现linux下的DMA驱动,同时对DMA中断进行测试。 二.分析 ZYNQ的AXIDMA有Direct Register Mode和Scatter/Gather Mode,本文使用的是Direct Register Mode。 Vivado上PL端的构造如下图所示...
  • ZYNQ跑系统 系列(四) AXI-DMAlinux下运行

    万次阅读 多人点赞 2018-05-30 10:47:42
    AXI-DMAlinux驱动 一、搭建硬件环境 vivado版本2017.4,芯片为7010,不过不管什么版本和芯片大致步骤是一样的 硬件平台PL的搭建同ZYNQ基础系列(六) DMA基本用法,在这个工程的基础上添加SD卡(根据自己的...
  • http://www.fpgadeveloper.com/2014/08/using-the-axi-dma-in-vivado.html https://blog.csdn.net/qq_20091945/article/details/70194026 github: *使用 vivado2018.2 ,linaro15.4,设备树卡一半内存,设置一半...
  • 从零开始zynq linux AXI DMA传输

    万次阅读 热门讨论 2017-04-02 19:24:33
    gsc@gsc-250:~/zynq7000/ZedBoard/projects/linux_kernel/scripts/dtc$ dtc -O dtb -I dts -o devicetree.dtb /home/gsc/zynq7000/ZedBoard/projects/hardware_design/axidma_user/axidma_user.sdk/device_tree_bsp_...
  • ZedBorad 嵌入式Linux下的DMA测试(PS+PL), 包含VIVADO工程代码,LinuxDMA测试APP源代码,Makefile文件,亲测可用。
  • zynq DMA分析

    千次阅读 2014-12-08 21:40:30
    Zynq Linux pl330 DMA Edit 0 10 … 3 Tags DMAlinuxZynq  Notify RSS Backlinks Source Print Export (PDF) IMPORTANT NOTE: The reference implementation contained ...
  • AXI DMA linux驱动

    2019-10-09 18:50:28
    PL端设计包括四个AXI DMA IP,它们分别和zynq处理IP的HP口相连接。 这个设计是基于Avnet-Digilent-ZedBoard-v2016.1-final.bsp,由于其它的ip都是xilinx开发环境开发环境就有,所以这里就不详细每一步设计过程了。 ...
  • CC drivers/dma/xilinx/zynqmp_dma.o drivers/dma/xilinx/zynqmp_dma.c:166:4: warning: attribute 'aligned' is ignored, place it after "struct" to apply attribute to type declaration [-...
  • 先不谈如何实现用户空间的零拷贝DMA传输,光是Linux环境下的DMA传输就已经感觉比较棘手,一方面是对Linux了解不够深入,另一方面则是Linux在相关的使用说明方面的确没有比较好的官方支持。   
  •   上一篇的一路双通道DMA的正常收发已经成功实现了,但是实际使用的时候大概率会挂载多路dma,那么我们调用的这个模块能不能支持多路的dma便是第一个要解决的问题   首先阅读初始化部分的代码,自然有了第一个...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 122
精华内容 48
关键字:

dmalinuxzynq

linux 订阅