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 字节)" 的指令时,硬件底层的真实运作画面是这样的:
-
接单检查(14位限制):DMA 看到你要搬 4096 字节。它查了一下自己的 Buffer Length 寄存器(14位,最大 16383),发现 4096 < 16383,判定订单合法,开始干活!
-
拆分微动作(Burst 与 Width) :DMA 知道自己一次 Burst 爆发最多只能搬 64 字节(32bit × 16)。
-
疯狂搬运:于是 DMA 在 AXI 总线上连续发起了 4096 \\div 64 = 64 次的 Burst 突发传输。每次突发占用 16 个时钟周期,每个周期搬 4 个字节。
-
交差:当这 64 次 Burst 全跑完,刚好搬齐了 4096 字节,DMA 就会给 CPU 拍一个中断(Interrupt),说:"老板,这一个 Chunk 的任务完成了!"
-
循环:你的驱动程序收到中断后,立刻又下发了下一个 4KB 的订单,如此循环 1024 次,直到凑齐 4MB,最终通过网络发给你的 NetAssist。
4.总结
-
14 是宏观的 总配额。
-
Width 和 Burst 是微观的 马路宽度和单次发车数量。
如果你以后觉得 35MB/s 不够快,除了把总配额(14)改大之外,你也可以把 Data Width 改成 64 或 128,把 Burst 改成 64 或 128,让 DMA 每次"铲"得更多、一口气干得更久,吞吐量就会成倍飙升!
2.axi dma搬运4MB任务
要将现有的 1024 字节(1KB)微型测试推进到 MB 级大数据吞吐量与高性能连续传输,核心在于解决两个性能瓶颈:
-
内核大内存分配瓶颈 :Linux 内核通过
dma_alloc_coherent申请连续大内存时,受伙伴系统(Buddy System)限制,通常单次申请超过几 MB 就会容易因内存碎片而失败(-ENOMEM)。 -
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;
}