基于zynqMPsoc15eg的linux端axi dma教训总结

1.AXI Direct Memory Access IP核

1. Width of Buffer Length = 14(总任务量上限)

  • 这是什么 :这是包工头(CPU驱动)每次能给 DMA 下达的最大订单总量。因为寄存器只有 14 位,所以最大数值是 2^{14}-1 = 16383 字节(约 16 KB)。

  • 对应关系 :你在驱动代码里写的 CHUNK_SIZE 绝对不能超过这个数。超过了,DMA 就会拒收订单(报错 Failed to prepare rx descriptor)。我们把它改成 4KB (4096 字节),DMA 愉快地接单了。

2. Data Width = 32 bit(搬运工具的口径)

  • 这是什么 :这是 DMA 内部铲车的铲斗大小。32 bit = 4 个字节。

  • 对应关系:DMA 每消耗一个时钟周期(Clock Cycle),就能精准地铲起 4 个字节的数据。

3. Max Burst Size = 16(一口气连续干活的体力)

  • 这是什么 :这是 DMA 的"爆发力"。在共享的 AXI 总线高速公路上,每次申请到路权后,DMA 最多可以一口气不停歇地挥动多少次铲车(连续传输拍数)。

  • 对应关系:16 意味着 DMA 申请到一次总线后,可以连续铲 16 下,中间不需要停下来重新查地址。

  • 单次突发量 :所以,DMA 的一次 Burst(突发)操作,能搬运的字节数 = Data Width (4 字节) × Max Burst Size (16) = 64 字节

💡 它们是如何协同工作的?(以你的 4KB 测试代码为例)

当你在代码里下达了 "给我搬运 4KB (4096 字节)" 的指令时,硬件底层的真实运作画面是这样的:

  1. 接单检查(14位限制):DMA 看到你要搬 4096 字节。它查了一下自己的 Buffer Length 寄存器(14位,最大 16383),发现 4096 < 16383,判定订单合法,开始干活!

  2. 拆分微动作(Burst 与 Width) :DMA 知道自己一次 Burst 爆发最多只能搬 64 字节(32bit × 16)。

  3. 疯狂搬运:于是 DMA 在 AXI 总线上连续发起了 4096 \\div 64 = 64 次的 Burst 突发传输。每次突发占用 16 个时钟周期,每个周期搬 4 个字节。

  4. 交差:当这 64 次 Burst 全跑完,刚好搬齐了 4096 字节,DMA 就会给 CPU 拍一个中断(Interrupt),说:"老板,这一个 Chunk 的任务完成了!"

  5. 循环:你的驱动程序收到中断后,立刻又下发了下一个 4KB 的订单,如此循环 1024 次,直到凑齐 4MB,最终通过网络发给你的 NetAssist。

4.总结

  • 14 是宏观的 总配额

  • WidthBurst 是微观的 马路宽度和单次发车数量

    如果你以后觉得 35MB/s 不够快,除了把总配额(14)改大之外,你也可以把 Data Width 改成 64 或 128,把 Burst 改成 64 或 128,让 DMA 每次"铲"得更多、一口气干得更久,吞吐量就会成倍飙升!

2.axi dma搬运4MB任务

要将现有的 1024 字节(1KB)微型测试推进到 MB 级大数据吞吐量与高性能连续传输,核心在于解决两个性能瓶颈:

  1. 内核大内存分配瓶颈 :Linux 内核通过 dma_alloc_coherent 申请连续大内存时,受伙伴系统(Buddy System)限制,通常单次申请超过几 MB 就会容易因内存碎片而失败(-ENOMEM)。

  2. CPU 内存拷贝瓶颈 :大吞吐量下,如果在应用层用 read() + 内核层 copy_to_user,每搬运 100MB 数据 CPU 就要做一次全内存拷贝,CPU 占用率会拉满。必须引入 mmap 零拷贝(Zero-Copy) 技术。

1核心架构设计

  • 内核驱动层

    • 使用 CMA(连续内存分配器) 分配例如 4MB(4 * 1024 * 1024 = 4194304 字节)的超大连续物理内存。

    • 实现驱动的 .mmap 接口,把这 4MB 的 DMA 物理缓冲区直接映射到应用层的虚拟地址空间(实现真正的 Zero-Copy 零拷贝)。

    • 引入 ioctl 接口,用来由应用层触发 DMA 启动并等待完成。

  • 应用层(多线程连续收发)

    • 主线程 / DMA 采集线程 :负责通过 ioctl 循环触发 DMA 搬运硬件数据到共享缓冲区。

    • TCP 网络发送线程:通过双缓冲(Ping-Pong Buffer)或者环形队列机制,将采集好的数据异步通过 TCP 发送,互不阻塞。

2.错误驱动代码

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mm.h>

#define DMA_BUFFER_SIZE (4 * 1024 * 1024) /* 4MB 大缓冲区 */
#define DMA_IOCTL_START_LOOPBACK _IO('D', 1) /* 触发 4MB 自发自收环回的命令字 */

struct dmatest_dev {
    struct device *dev;
    struct dma_chan *tx_chan;
    struct dma_chan *rx_chan;

    char *tx_buffer;
    dma_addr_t tx_dma_addr;

    char *rx_buffer;
    dma_addr_t rx_dma_addr;

    struct completion tx_cmp;
    struct completion rx_cmp;
};

static struct dmatest_dev *dma_dev;

static void dma_tx_callback(void *completion)
{
    complete(completion);
}

static void dma_rx_callback(void *completion)
{
    complete(completion);
}

/* 核心搬运:驱动自造 4MB 数据,并通过 AXI DMA 环回搬运到 rx_buffer */
static int trigger_dma_loopback(void)
{
    struct dma_async_tx_descriptor *tx_desc, *rx_desc;
    dma_cookie_t tx_cookie, rx_cookie;
    int i;

    /* 1. 驱动自身生成 4MB 测试递增数据 (0x00 ~ 0xFF 循环) */
    for (i = 0; i < DMA_BUFFER_SIZE; i++) {
        dma_dev->tx_buffer[i] = (char)(i & 0xFF);
    }
    memset(dma_dev->rx_buffer, 0, DMA_BUFFER_SIZE); /* 清空接收区 */

    /* 2. 准备接收通道描述符 (S2MM) */
    rx_desc = dmaengine_prep_slave_single(dma_dev->rx_chan, dma_dev->rx_dma_addr,
                                          DMA_BUFFER_SIZE, DMA_DEV_TO_MEM,
                                          DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!rx_desc) {
        pr_err("Failed to prepare rx descriptor\n");
        return -EIO;
    }
    rx_desc->callback = dma_rx_callback;
    rx_desc->callback_param = &dma_dev->rx_cmp;
    reinit_completion(&dma_dev->rx_cmp);

    /* 3. 准备发送通道描述符 (MM2S) */
    tx_desc = dmaengine_prep_slave_single(dma_dev->tx_chan, dma_dev->tx_dma_addr,
                                          DMA_BUFFER_SIZE, DMA_MEM_TO_DEV,
                                          DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!tx_desc) {
        pr_err("Failed to prepare tx descriptor\n");
        return -EIO;
    }
    tx_desc->callback = dma_tx_callback;
    tx_desc->callback_param = &dma_dev->tx_cmp;
    reinit_completion(&dma_dev->tx_cmp);

    /* 4. 提交到队列 */
    rx_cookie = dmaengine_submit(rx_desc);
    tx_cookie = dmaengine_submit(tx_desc);

    /* 5. 启动 DMA 硬件搬运 */
    dma_async_issue_pending(dma_dev->rx_chan);
    dma_async_issue_pending(dma_dev->tx_chan);

    /* 6. 等待 4MB 传输完成 (超时时间设为 5 秒) */
    if (wait_for_completion_timeout(&dma_dev->rx_cmp, msecs_to_jiffies(5000)) == 0) {
        pr_err("DMA RX timeout!\n");
        return -ETIMEDOUT;
    }
    if (wait_for_completion_timeout(&dma_dev->tx_cmp, msecs_to_jiffies(5000)) == 0) {
        pr_err("DMA TX timeout!\n");
        return -ETIMEDOUT;
    }

    pr_info("DMA 4MB loopback completed successfully!\n");
    return 0;
}

/* mmap 接口:将接收区物理内存直接映射到用户空间虚拟地址 */
static int dmatest_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    unsigned long size = vma->vm_end - vma->vm_start;

    if (size > DMA_BUFFER_SIZE)
        return -EINVAL;

    /* 设为非 Cache 模式,保证用户态读取到的是硬件刚刚刷进来的真实物理内存数据 */
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    ret = remap_pfn_range(vma, vma->vm_start,
                          dma_dev->rx_dma_addr >> PAGE_SHIFT,
                          size, vma->vm_page_prot);
    if (ret) {
        pr_err("remap_pfn_range failed: %d\n", ret);
        return -EAGAIN;
    }

    return 0;
}

/* ioctl 控制接口:应用层发送指令触发搬运 */
static long dmatest_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case DMA_IOCTL_START_LOOPBACK:
        return trigger_dma_loopback();
    default:
        return -EINVAL;
    }
}

static struct file_operations dmatest_fops = {
    .owner          = THIS_MODULE,
    .mmap           = dmatest_mmap,
    .unlocked_ioctl = dmatest_ioctl,
};

static struct miscdevice dmatest_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "axi_dma_test",
    .fops  = &dmatest_fops,
};

static int dma_probe(struct platform_device *pdev)
{
    int ret;

    dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
    if (!dma_dev)
        return -ENOMEM;

    dma_dev->dev = &pdev->dev;
    init_completion(&dma_dev->tx_cmp);
    init_completion(&dma_dev->rx_cmp);

    /* 申请 TX 和 RX DMA 通道 */
    dma_dev->tx_chan = dma_request_chan(&pdev->dev, "axidma0");
    if (IS_ERR(dma_dev->tx_chan)) {
        pr_err("Failed to request TX channel\n");
        return PTR_ERR(dma_dev->tx_chan);
    }

    dma_dev->rx_chan = dma_request_chan(&pdev->dev, "axidma1");
    if (IS_ERR(dma_dev->rx_chan)) {
        pr_err("Failed to request RX channel\n");
        ret = PTR_ERR(dma_dev->rx_chan);
        goto err_tx_chan;
    }

    /* 申请 4MB 连续物理一致性内存 */
    dma_dev->tx_buffer = dma_alloc_coherent(&pdev->dev, DMA_BUFFER_SIZE,
                                            &dma_dev->tx_dma_addr, GFP_KERNEL);
    if (!dma_dev->tx_buffer) {
        pr_err("Failed to allocate TX buffer (4MB)\n");
        ret = -ENOMEM;
        goto err_rx_chan;
    }

    dma_dev->rx_buffer = dma_alloc_coherent(&pdev->dev, DMA_BUFFER_SIZE,
                                            &dma_dev->rx_dma_addr, GFP_KERNEL);
    if (!dma_dev->rx_buffer) {
        pr_err("Failed to allocate RX buffer (4MB)\n");
        ret = -ENOMEM;
        goto err_tx_buf;
    }

    ret = misc_register(&dmatest_misc);
    if (ret) {
        pr_err("Failed to register misc device\n");
        goto err_rx_buf;
    }

    pr_info("AXI DMA 4MB Loopback driver (mmap + ioctl) loaded!\n");
    return 0;

err_rx_buf:
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->rx_buffer, dma_dev->rx_dma_addr);
err_tx_buf:
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->tx_buffer, dma_dev->tx_dma_addr);
err_rx_chan:
    dma_release_channel(dma_dev->rx_chan);
err_tx_chan:
    dma_release_channel(dma_dev->tx_chan);
    return ret;
}

static int dma_remove(struct platform_device *pdev)
{
    misc_deregister(&dmatest_misc);
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->rx_buffer, dma_dev->rx_dma_addr);
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->tx_buffer, dma_dev->tx_dma_addr);
    dma_release_channel(dma_dev->rx_chan);
    dma_release_channel(dma_dev->tx_chan);
    pr_info("AXI DMA 4MB Loopback driver removed\n");
    return 0;
}

static const struct of_device_id dma_of_match[] = {
    { .compatible = "xlnx,axi-dma-test-1.00.a", },
    { /* end of table */ }
};
MODULE_DEVICE_TABLE(of, dma_of_match);

static struct platform_driver dma_driver = {
    .driver = {
        .name = "axi_dma_test",
        .of_match_table = dma_of_match,
    },
    .probe = dma_probe,
    .remove = dma_remove,
};

module_platform_driver(dma_driver);
MODULE_LICENSE("GPL");

/* 2. 准备接收通道描述符 (S2MM) */
    rx_desc = dmaengine_prep_slave_single(dma_dev->rx_chan, dma_dev->rx_dma_addr,
                                          DMA_BUFFER_SIZE, DMA_DEV_TO_MEM,
                                          DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!rx_desc) {
        pr_err("Failed to prepare rx descriptor\n");
        return -EIO;
    }
    rx_desc->callback = dma_rx_callback;
    rx_desc->callback_param = &dma_dev->rx_cmp;
    reinit_completion(&dma_dev->rx_cmp);

    /* 3. 准备发送通道描述符 (MM2S) */
    tx_desc = dmaengine_prep_slave_single(dma_dev->tx_chan, dma_dev->tx_dma_addr,
                                          DMA_BUFFER_SIZE, DMA_MEM_TO_DEV,
                                          DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!tx_desc) {
        pr_err("Failed to prepare tx descriptor\n");
        return -EIO;
    }
    tx_desc->callback = dma_tx_callback;
    tx_desc->callback_param = &dma_dev->tx_cmp;
    reinit_completion(&dma_dev->tx_cmp);

    /* 4. 提交到队列 */
    rx_cookie = dmaengine_submit(rx_desc);
    tx_cookie = dmaengine_submit(tx_desc);

    /* 5. 启动 DMA 硬件搬运 */
    dma_async_issue_pending(dma_dev->rx_chan);
    dma_async_issue_pending(dma_dev->tx_chan);

    /* 6. 等待 4MB 传输完成 (超时时间设为 5 秒) */
    if (wait_for_completion_timeout(&dma_dev->rx_cmp, msecs_to_jiffies(5000)) == 0) {
        pr_err("DMA RX timeout!\n");
        return -ETIMEDOUT;
    }
    if (wait_for_completion_timeout(&dma_dev->tx_cmp, msecs_to_jiffies(5000)) == 0) {
        pr_err("DMA TX timeout!\n");
        return -ETIMEDOUT;
    }

    pr_info("DMA 4MB loopback completed successfully!\n");

一口气发4MB。

2.1错误

94.347570 Failed to prepare rx descriptor

这个错误直指问题核心: 94.347570 Failed to prepare rx descriptor

为什么会报这个错?(硬件底层限制)在 Xilinx Vivado 中例化 AXI DMA IP 核 时,有一个非常关键的参数叫做

Width of Buffer Length Register(缓冲长度寄存器位宽)。

1.硬件寄存器位宽限制:

Vivado AXI DMA 默认的 Buffer Length 位宽通常是 14 位 ~ 23 位(默认常用 14 位或 16 位)。

如果设为 14 位,单次 Direct Register (Simple) DMA 搬运的最大字节数只有 2^{14} - 1 = 16383字节 ~ 16KB。

即使在 Vivado 里拉到最大设为 23 位,单次最大长度也只能到 2^{23} - 1 ~ 8MB。

2.当你在代码中调用 dmaengine_prep_slave_single(..., DMA_BUFFER_SIZE, ...) 试图一次性提交 4MB(4,194,304 字节)时,底层 Xilinx DMA 驱动

(xilinx_dma.c)会检查长度是否超出了硬件寄存器的位宽上限。一旦超出,它直接拒绝生成描述符并返回 NULL,从而导致了 Failed to prepare rx descriptor。

解决方案我们不用重新去 Vivado 改硬件工程,直接将驱动和应用调整为 "多块分段(Chunk-based)搬运"。例如:单次硬件搬运设为安全兼容的 4 KB(或 16 KB 64KB),

在驱动内部循环搬运凑齐 4MB,或者直接将单次基准包调为 64 KB 测试大吞吐量。

3.正确驱动代码(axi dma分多次发,一次发4kb)

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mm.h>
#include <linux/mutex.h>

#define DMA_BUFFER_SIZE (4 * 1024 * 1024)    /* 4MB 大缓冲区 */
#define DMA_IOCTL_START_LOOPBACK _IO('D', 1) /* 触发 4MB 环回的命令字 */

/* 采用最保守的 4KB 分片,完美兼容 Vivado AXI DMA 所有长度寄存器位宽配置 */
#define CHUNK_SIZE (4 * 1024) 
#define TOTAL_CHUNKS (DMA_BUFFER_SIZE / CHUNK_SIZE) 

struct dmatest_dev {
    struct device *dev;
    struct dma_chan *tx_chan;
    struct dma_chan *rx_chan;

    char *tx_buffer;
    dma_addr_t tx_dma_addr;

    char *rx_buffer;
    dma_addr_t rx_dma_addr;

    struct completion tx_cmp;
    struct completion rx_cmp;
    
    struct mutex lock; /* 互斥锁,防止并发调用 ioctl 导致时序混乱 */
};

static struct dmatest_dev *dma_dev;

static void dma_tx_callback(void *completion)
{
    complete(completion);
}

static void dma_rx_callback(void *completion)
{
    complete(completion);
}

/* 核心搬运:分块循环搬运 4MB 数据 */
static int trigger_dma_loopback(void)
{
    struct dma_async_tx_descriptor *tx_desc, *rx_desc;
    dma_cookie_t tx_cookie, rx_cookie;
    int i, chunk;
    int ret = 0;

    /* 获取并发互斥锁 */
    if (mutex_lock_interruptible(&dma_dev->lock))
        return -ERESTARTSYS;

    /* 1. 填充 4MB 数据 */
    for (i = 0; i < DMA_BUFFER_SIZE; i++) {
        dma_dev->tx_buffer[i] = (char)(i & 0xFF);
    }
    memset(dma_dev->rx_buffer, 0, DMA_BUFFER_SIZE);

    /* 2. 分块循环搬运 4MB (每次 4KB) */
    for (chunk = 0; chunk < TOTAL_CHUNKS; chunk++) {
        dma_addr_t tx_offset = dma_dev->tx_dma_addr + (dma_addr_t)chunk * CHUNK_SIZE;
        dma_addr_t rx_offset = dma_dev->rx_dma_addr + (dma_addr_t)chunk * CHUNK_SIZE;

        /* 在准备和提交描述符之前,先重置完成量 (时序安全的做法) */
        reinit_completion(&dma_dev->rx_cmp);
        reinit_completion(&dma_dev->tx_cmp);

        /* 准备 RX 描述符 */
        rx_desc = dmaengine_prep_slave_single(dma_dev->rx_chan, rx_offset,
                                              CHUNK_SIZE, DMA_DEV_TO_MEM,
                                              DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!rx_desc) {
            pr_err("Failed to prepare rx descriptor at chunk %d\n", chunk);
            ret = -EIO;
            goto out_unlock;
        }
        rx_desc->callback = dma_rx_callback;
        rx_desc->callback_param = &dma_dev->rx_cmp;

        /* 准备 TX 描述符 */
        tx_desc = dmaengine_prep_slave_single(dma_dev->tx_chan, tx_offset,
                                              CHUNK_SIZE, DMA_MEM_TO_DEV,
                                              DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!tx_desc) {
            pr_err("Failed to prepare tx descriptor at chunk %d\n", chunk);
            ret = -EIO;
            goto out_unlock;
        }
        tx_desc->callback = dma_tx_callback;
        tx_desc->callback_param = &dma_dev->tx_cmp;

        /* 提交描述符 */
        rx_cookie = dmaengine_submit(rx_desc);
        tx_cookie = dmaengine_submit(tx_desc);

        /* 触发硬件传输 */
        dma_async_issue_pending(dma_dev->rx_chan);
        dma_async_issue_pending(dma_dev->tx_chan);

        /* 等待本轮传输完成 */
        if (wait_for_completion_timeout(&dma_dev->rx_cmp, msecs_to_jiffies(1000)) == 0) {
            pr_err("DMA RX timeout at chunk %d!\n", chunk);
            ret = -ETIMEDOUT;
            goto out_unlock;
        }
        if (wait_for_completion_timeout(&dma_dev->tx_cmp, msecs_to_jiffies(1000)) == 0) {
            pr_err("DMA TX timeout at chunk %d!\n", chunk);
            ret = -ETIMEDOUT;
            goto out_unlock;
        }
    }

out_unlock:
    mutex_unlock(&dma_dev->lock);
    return ret;
}

/* mmap 接口:使用标准 dma_mmap_coherent 实现零拷贝 */
static int dmatest_mmap(struct file *filp, struct vm_area_struct *vma)
{
    size_t size = vma->vm_end - vma->vm_start;

    if (size > DMA_BUFFER_SIZE)
        return -EINVAL;

    return dma_mmap_coherent(dma_dev->dev, vma, 
                             dma_dev->rx_buffer, 
                             dma_dev->rx_dma_addr, 
                             size);
}

/* ioctl 控制接口 */
static long dmatest_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case DMA_IOCTL_START_LOOPBACK:
        return trigger_dma_loopback();
    default:
        return -EINVAL;
    }
}

static struct file_operations dmatest_fops = {
    .owner          = THIS_MODULE,
    .mmap           = dmatest_mmap,
    .unlocked_ioctl = dmatest_ioctl,
};

static struct miscdevice dmatest_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "axi_dma_test",
    .fops  = &dmatest_fops,
};

static int dma_probe(struct platform_device *pdev)
{
    int ret;

    dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
    if (!dma_dev)
        return -ENOMEM;

    dma_dev->dev = &pdev->dev;
    init_completion(&dma_dev->tx_cmp);
    init_completion(&dma_dev->rx_cmp);
    mutex_init(&dma_dev->lock);

    /* 设置 32 位 DMA 寻址掩码,保障 4MB 物理内存成功分配 */
    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
    if (ret) {
        pr_err("Failed to set DMA mask\n");
        return ret;
    }

    /* 申请 TX 和 RX DMA 通道 */
    dma_dev->tx_chan = dma_request_chan(&pdev->dev, "axidma0");
    if (IS_ERR(dma_dev->tx_chan)) {
        pr_err("Failed to request TX channel\n");
        return PTR_ERR(dma_dev->tx_chan);
    }

    dma_dev->rx_chan = dma_request_chan(&pdev->dev, "axidma1");
    if (IS_ERR(dma_dev->rx_chan)) {
        pr_err("Failed to request RX channel\n");
        ret = PTR_ERR(dma_dev->rx_chan);
        goto err_tx_chan;
    }

    /* 申请 4MB 连续物理一致性内存 */
    dma_dev->tx_buffer = dma_alloc_coherent(&pdev->dev, DMA_BUFFER_SIZE,
                                            &dma_dev->tx_dma_addr, GFP_KERNEL);
    if (!dma_dev->tx_buffer) {
        pr_err("Failed to allocate TX buffer (4MB)\n");
        ret = -ENOMEM;
        goto err_rx_chan;
    }

    dma_dev->rx_buffer = dma_alloc_coherent(&pdev->dev, DMA_BUFFER_SIZE,
                                            &dma_dev->rx_dma_addr, GFP_KERNEL);
    if (!dma_dev->rx_buffer) {
        pr_err("Failed to allocate RX buffer (4MB)\n");
        ret = -ENOMEM;
        goto err_tx_buf;
    }

    ret = misc_register(&dmatest_misc);
    if (ret) {
        pr_err("Failed to register misc device\n");
        goto err_rx_buf;
    }

    pr_info("AXI DMA 4MB Loopback driver (Safe Chunk & dma_mmap) loaded!\n");
    return 0;

err_rx_buf:
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->rx_buffer, dma_dev->rx_dma_addr);
err_tx_buf:
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->tx_buffer, dma_dev->tx_dma_addr);
err_rx_chan:
    dma_release_channel(dma_dev->rx_chan);
err_tx_chan:
    dma_release_channel(dma_dev->tx_chan);
    return ret;
}

static int dma_remove(struct platform_device *pdev)
{
    misc_deregister(&dmatest_misc);
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->rx_buffer, dma_dev->rx_dma_addr);
    dma_free_coherent(&pdev->dev, DMA_BUFFER_SIZE, dma_dev->tx_buffer, dma_dev->tx_dma_addr);
    dma_release_channel(dma_dev->rx_chan);
    dma_release_channel(dma_dev->tx_chan);
    mutex_destroy(&dma_dev->lock);
    pr_info("AXI DMA 4MB Loopback driver removed\n");
    return 0;
}

static const struct of_device_id dma_of_match[] = {
    { .compatible = "xlnx,axi-dma-test-1.00.a", },
    { /* end of table */ }
};
MODULE_DEVICE_TABLE(of, dma_of_match);

static struct platform_driver dma_driver = {
    .driver = {
        .name = "axi_dma_test",
        .of_match_table = dma_of_match,
    },
    .probe = dma_probe,
    .remove = dma_remove,
};

module_platform_driver(dma_driver);
MODULE_LICENSE("GPL");

/* 2. 分块循环搬运 4MB (每次 4KB) */
    for (chunk = 0; chunk < TOTAL_CHUNKS; chunk++) {
        dma_addr_t tx_offset = dma_dev->tx_dma_addr + (dma_addr_t)chunk * CHUNK_SIZE;
        dma_addr_t rx_offset = dma_dev->rx_dma_addr + (dma_addr_t)chunk * CHUNK_SIZE;

        /* 在准备和提交描述符之前,先重置完成量 (时序安全的做法) */
        reinit_completion(&dma_dev->rx_cmp);
        reinit_completion(&dma_dev->tx_cmp);

        /* 准备 RX 描述符 */
        rx_desc = dmaengine_prep_slave_single(dma_dev->rx_chan, rx_offset,
                                              CHUNK_SIZE, DMA_DEV_TO_MEM,
                                              DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!rx_desc) {
            pr_err("Failed to prepare rx descriptor at chunk %d\n", chunk);
            ret = -EIO;
            goto out_unlock;
        }
        rx_desc->callback = dma_rx_callback;
        rx_desc->callback_param = &dma_dev->rx_cmp;

        /* 准备 TX 描述符 */
        tx_desc = dmaengine_prep_slave_single(dma_dev->tx_chan, tx_offset,
                                              CHUNK_SIZE, DMA_MEM_TO_DEV,
                                              DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!tx_desc) {
            pr_err("Failed to prepare tx descriptor at chunk %d\n", chunk);
            ret = -EIO;
            goto out_unlock;
        }
        tx_desc->callback = dma_tx_callback;
        tx_desc->callback_param = &dma_dev->tx_cmp;

        /* 提交描述符 */
        rx_cookie = dmaengine_submit(rx_desc);
        tx_cookie = dmaengine_submit(tx_desc);

        /* 触发硬件传输 */
        dma_async_issue_pending(dma_dev->rx_chan);
        dma_async_issue_pending(dma_dev->tx_chan);

        /* 等待本轮传输完成 */
        if (wait_for_completion_timeout(&dma_dev->rx_cmp, msecs_to_jiffies(1000)) == 0) {
            pr_err("DMA RX timeout at chunk %d!\n", chunk);
            ret = -ETIMEDOUT;
            goto out_unlock;
        }
        if (wait_for_completion_timeout(&dma_dev->tx_cmp, msecs_to_jiffies(1000)) == 0) {
            pr_err("DMA TX timeout at chunk %d!\n", chunk);
            ret = -ETIMEDOUT;
            goto out_unlock;
        }
    }

3.axi dma在linux端的代码分析

1.申请内存

dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);

2.设置 32 位 DMA 寻址掩码,保障 4MB 物理内存成功分配

dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));

3.申请 TX 和 RX DMA 通道

dma_dev->tx_chan = dma_request_chan(&pdev->dev, "axidma0");

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

4.申请 4MB 连续物理一致性内存

dma_dev->tx_buffer = dma_alloc_coherent(&pdev->dev, DMA_BUFFER_SIZE,

&dma_dev->tx_dma_addr, GFP_KERNEL);

dma_dev->rx_buffer = dma_alloc_coherent(&pdev->dev, DMA_BUFFER_SIZE,

&dma_dev->rx_dma_addr, GFP_KERNEL);

5.在准备和提交描述符之前,先重置完成量 (时序安全的做法)

reinit_completion(&dma_dev->rx_cmp);

reinit_completion(&dma_dev->tx_cmp);

6.准备 RX,TX 描述符

rx_desc = dmaengine_prep_slave_single(dma_dev->rx_chan, rx_offset,

CHUNK_SIZE, DMA_DEV_TO_MEM,

DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

rx_desc->callback = dma_rx_callback;

rx_desc->callback_param = &dma_dev->rx_cmp;

tx_desc = dmaengine_prep_slave_single(dma_dev->tx_chan, tx_offset,

CHUNK_SIZE, DMA_MEM_TO_DEV,

DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

tx_desc->callback = dma_tx_callback;

tx_desc->callback_param = &dma_dev->tx_cmp;

7.提交描述符

rx_cookie = dmaengine_submit(rx_desc);

tx_cookie = dmaengine_submit(tx_desc);

8.触发硬件传输

dma_async_issue_pending(dma_dev->rx_chan);

dma_async_issue_pending(dma_dev->tx_chan);

9. 等待本轮传输完成

if (wait_for_completion_timeout(&dma_dev->rx_cmp, msecs_to_jiffies(1000)) == 0) {

pr_err("DMA RX timeout at chunk %d!\n", chunk);

ret = -ETIMEDOUT;

goto out_unlock;

}

if (wait_for_completion_timeout(&dma_dev->tx_cmp, msecs_to_jiffies(1000)) == 0) {

pr_err("DMA TX timeout at chunk %d!\n", chunk);

ret = -ETIMEDOUT;

goto out_unlock;

}

相关推荐
ZJU_统一阿萨姆1 小时前
【算子开发】全局内存访问与合并访存
java·服务器·网络·人工智能·语言模型
动力 continue1 小时前
Linux 基础篇 · Vim 三种模式与常用指令
linux·运维·vim
7177771 小时前
不止工具集成:基于 Gitee 软件工厂构建 DevSecOps 研发治理底座
java·服务器·gitee
钱栈up1 小时前
Mac 开发机一键发版不用切环境:我这样改造了团队的后端部署脚本
运维·python·mac
深念Y2 小时前
系统内存与Swap优化记录
linux·内存·优化·虚拟内存·压缩·ram·zram
funnycoffee1232 小时前
Huawei USG firewall no-nat config for ipsec traffic
linux·服务器
可涵不会debug2 小时前
LangChain 示例选择器(Example selectors)完整基础概念解读
服务器·前端·数据库
byte轻骑兵2 小时前
【BlueZ 】蓝牙 L2CAP 协议核心:BlueZ 中基础数据传输的源码落地
linux·网络·网络协议·bluez·嵌入式蓝牙
袁小皮皮不皮2 小时前
数通完整讲义-上册-HCIA篇
服务器·网络·网络协议·tcp/ip·智能路由器