基于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 字节)”的指令时硬件底层的真实运作画面是这样的接单检查14位限制DMA 看到你要搬 4096 字节。它查了一下自己的 Buffer Length 寄存器14位最大 16383发现 4096 16383判定订单合法开始干活拆分微动作Burst 与 WidthDMA 知道自己一次 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连续内存分配器分配例如4MB4 * 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, ...) 试图一次性提交 4MB4,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.准备 RXTX 描述符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;}

相关新闻

Zigbee子设备数据丢失排查指南:从Topic绑定原理到可验证的配置闭环

Zigbee子设备数据丢失排查指南:从Topic绑定原理到可验证的配置闭环

本文以JVS-IOT平台为例,面向开发者与IoT运维工程师,详解Zigbee子设备数据‘丢失’的真实原因——非网络或协议问题,而是平台侧缺失子设备注册与自定义Topic绑定两个显式操作。通过状态检查清单、配置路径说明、字段匹配规则及代码级验证逻辑&…

2026/8/21 5:22:47 阅读更多 →
Highcharts 导出模块介绍与使用说明

Highcharts 导出模块介绍与使用说明

Highcharts 导出模块 Highcharts 导出模块(Exporting Module)用于为图表提供导出与打印能力。启用该模块后,用户可将图表导出为 PNG、JPG、PDF 或 SVG 格式,并可直接打印图表内容,以便用于报表、演示、归档或离线分发等…

2026/8/21 9:40:40 阅读更多 →
LLM成本治理三步法:从可视化监控到高效优化实战

LLM成本治理三步法:从可视化监控到高效优化实战

1. 从“惊喜”到“惊吓”:LLM成本失控的典型场景最近和几个技术团队的朋友聊天,发现一个普遍现象:大家刚开始接入大语言模型(LLM)时,都挺兴奋的。API调用简单,效果惊艳,项目上线速度…

2026/8/21 8:56:27 阅读更多 →

最新新闻

Sequo开源AI上下文管理引擎:从原理到实战构建智能问答系统

Sequo开源AI上下文管理引擎:从原理到实战构建智能问答系统

最近在开发AI应用时,你是否遇到过这样的困扰:与大模型对话时,需要反复粘贴冗长的背景文档;构建AI Agent时,上下文管理混乱,导致模型“失忆”或回答偏离主题;或者,多个AI工具间的上下…

2026/8/22 20:44:14 阅读更多 →
有限元分析(FEA)面试核心53题与实战技巧

有限元分析(FEA)面试核心53题与实战技巧

1. 有限元分析(FEA)面试准备指南作为一名在工程仿真领域工作多年的CAE工程师,我深知有限元分析(FEA)在工程设计和产品开发中的重要性。无论是汽车碰撞分析、飞机结构强度计算,还是电子设备热仿真&#xff0…

2026/8/22 20:44:14 阅读更多 →
逻辑回归实战:用sklearn实现二分类与特征工程全流程

逻辑回归实战:用sklearn实现二分类与特征工程全流程

1. 项目概述:从“分类”的直觉到逻辑回归的落地刚接触机器学习的朋友,一听到“分类”这个词,脑子里可能立刻会浮现出各种复杂的神经网络、支持向量机。但在我十多年的数据科学项目经验里,有一个算法出场率极高,它结构简…

2026/8/22 20:44:14 阅读更多 →
数学建模插值算法:从原理到实战,掌握数据重构的核心工具

数学建模插值算法:从原理到实战,掌握数据重构的核心工具

1. 项目概述:为什么插值算法是数学建模的“瑞士军刀”刚接触数学建模那会儿,我总觉得那些复杂的微分方程、优化算法才是“硬核”的体现。直到在一次比赛中,我们拿到了一组关于城市空气质量监测的数据,采样点稀疏且时间间隔不规律&…

2026/8/22 20:44:14 阅读更多 →
从循环工程到Harness工程:构建生产级AI智能体的架构与实践

从循环工程到Harness工程:构建生产级AI智能体的架构与实践

如果你正在构建一个AI智能体,是否曾陷入这样的困境:精心设计的Agent在演示时表现惊艳,一旦投入实际业务,却频繁“掉链子”——要么无法稳定执行多步任务,要么在复杂环境中“失忆”或“跑偏”?问题往往不在于…

2026/8/22 20:44:14 阅读更多 →
基于Python的企业招聘信息采集系统设计与实现(源码+讲解视频+LW)

基于Python的企业招聘信息采集系统设计与实现(源码+讲解视频+LW)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/22 20:43:14 阅读更多 →

日新闻

沉金PCB工艺实战指南:从设计到SMT焊接的可靠性保障

沉金PCB工艺实战指南:从设计到SMT焊接的可靠性保障

在电子硬件开发领域,PCB(印制电路板)的沉金工艺是提升产品可靠性和焊接质量的关键环节。对于需要高密度互连、长期稳定运行或高频信号传输的板卡,如“黍姐仿通行证”这类可能涉及身份识别、数据交互的硬件项目,选择正确…

2026/8/22 0:00:11 阅读更多 →
电气考研电路八月强化四步法:从知识体系到真题实战的闭环攻略

电气考研电路八月强化四步法:从知识体系到真题实战的闭环攻略

这次我们来看一个针对电气考研电路科目的学习规划项目。它不是软件工具,而是一套聚焦于8月份关键节点的备考策略。对于电气工程考研的同学来说,电路分析是专业课的重中之重,也是拉开分差的关键。进入8月,复习进入强化阶段&#xf…

2026/8/22 0:00:11 阅读更多 →
消除AI代码的“AI味”:Claude Code设计优化技能配置与实战指南

消除AI代码的“AI味”:Claude Code设计优化技能配置与实战指南

大家好,我是专注于前端开发与AI工具实践的技术博主。在日常使用 Claude Code 等AI编程助手时,你是否也遇到过这样的困扰:生成的代码功能上没问题,但代码风格、组件设计、交互逻辑总透着一股“AI味”——布局单调、样式简陋、交互生…

2026/8/22 0:00:11 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/21 3:21:33 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/22 8:09:09 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/21 6:07:56 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/22 18:08:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/22 7:31:03 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/22 3:22:48 阅读更多 →