Zynq/SoC开发难点:PS端Linux驱动与PL端自定义IP核的DMA数据交互实战
扫描二维码
随时随地手机看文章
在Zynq/SoC异构计算平台开发中,PS(Processing System)端运行Linux系统与PL(Programmable Logic)端自定义IP核之间的高速数据交互是核心挑战之一。DMA(直接内存访问)技术作为解决这一难题的关键,能够实现不占用CPU资源的大数据量传输。本文将深入探讨基于AXI DMA的完整实现方案,分享实战中的关键步骤与常见陷阱。
一、硬件架构设计:Vivado中的IP集成
在Vivado中构建硬件系统时,需要精心设计AXI DMA与自定义IP核的连接拓扑。典型的数据流路径为:自定义数据生成IP → AXI-Stream FIFO → AXI DMA → DDR内存(通过HP或ACP端口)。
关键配置要点:
1. AXI DMA IP配置:选择简单模式(Simple Mode)而非分散/收集模式,除非需要复杂的内存管理。设置合适的最大突发长度(Max Burst Size),通常设置为64或128以获得最佳性能。
2. FIFO配置:启用Packet Mode确保数据包完整性,设置合适的FIFO深度以平衡资源消耗和性能。
3. 时钟域处理:确保PL端逻辑时钟与AXI总线时钟的同步,必要时使用异步FIFO进行跨时钟域处理。
// 自定义数据生成IP的简化接口
module custom_data_generator (
input wire clk,
input wire rst_n,
input wire start,
output reg [31:0] data_out,
output reg data_valid,
output reg data_last
);
// 数据生成逻辑...
endmodule
二、设备树配置:Linux识别硬件的基础
设备树是Linux内核识别硬件资源的关键。AXI DMA节点需要正确定义,包括寄存器地址范围、中断号、DMA通道等。
// 设备树中的AXI DMA节点示例
axidma_chrdev: axidma_chrdev@0 {
compatible = "xlnx,axidma-chrdev";
dmas = <&axi_dma_0 0 // S2MM通道(PL到PS)
&axi_dma_0 1>; // MM2S通道(PS到PL)
dma-names = "rx_channel", "tx_channel";
};
axi_dma_0: dma@40400000 {
compatible = "xlnx,axi-dma-1.00.a";
reg = <0x40400000 0x10000>;
interrupts = <0 29 4>; // 中断号29,高电平触发
#dma-cells = <1>;
clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>;
clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk";
dma-channel@40400000 {
compatible = "xlnx,axi-dma-mm2s-channel";
interrupts = <0 29 4>;
xlnx,datawidth = <32>;
};
dma-channel@40400030 {
compatible = "xlnx,axi-dma-s2mm-channel";
interrupts = <0 30 4>;
xlnx,datawidth = <32>;
};
};
三、Linux驱动开发:字符设备与DMA API
Linux驱动需要完成设备注册、DMA缓冲区分配、中断处理和数据传输控制。推荐使用Xilinx官方DMA驱动框架或开源项目xilinx_axidma作为基础。
3.1 驱动初始化与资源申请
// 驱动初始化关键代码
static int axidma_probe(struct platform_device *pdev)
{
struct axidma_device *adev;
struct resource *res;
int ret;
// 1. 分配设备结构体
adev = devm_kzalloc(&pdev->dev, sizeof(*adev), GFP_KERNEL);
// 2. 获取寄存器资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
adev->regs = devm_ioremap_resource(&pdev->dev, res);
// 3. 获取中断资源
adev->irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, adev->irq,
axidma_interrupt_handler,
IRQF_SHARED, "axidma", adev);
// 4. 分配DMA一致性内存
adev->buf = dma_alloc_coherent(&pdev->dev,
DMA_BUF_SIZE,
&adev->dma_handle,
GFP_KERNEL);
// 5. 注册字符设备
ret = alloc_chrdev_region(&adev->devno, 0, 1, "axidma");
cdev_init(&adev->cdev, &axidma_fops);
cdev_add(&adev->cdev, adev->devno, 1);
// 6. 创建设备节点
adev->class = class_create(THIS_MODULE, "axidma");
device_create(adev->class, NULL, adev->devno, NULL, "axidma");
return 0;
}
3.2 DMA传输控制
// DMA传输启动函数
static int axidma_start_transfer(struct axidma_device *adev,
dma_addr_t dma_addr,
size_t length,
enum dma_transfer_direction dir)
{
struct dma_async_tx_descriptor *desc;
struct dma_chan *chan;
dma_cookie_t cookie;
// 选择DMA通道
chan = (dir == DMA_MEM_TO_DEV) ? adev->tx_chan : adev->rx_chan;
// 准备DMA传输描述符
desc = dmaengine_prep_slave_single(chan, dma_addr,
length, dir,
DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(adev->dev, "Failed to prepare DMA descriptor\n");
return -EINVAL;
}
// 设置完成回调
desc->callback = axidma_transfer_complete;
desc->callback_param = adev;
// 提交传输
cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie)) {
dev_err(adev->dev, "Failed to submit DMA transfer\n");
return -EINVAL;
}
// 启动DMA引擎
dma_async_issue_pending(chan);
return 0;
}
四、用户空间接口:零拷贝与高性能访问
为了获得最佳性能,用户空间应用应避免数据拷贝,直接访问DMA缓冲区。可以通过mmap系统调用将内核空间缓冲区映射到用户空间。
// 用户空间应用程序示例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main(void)
{
int fd;
void *dma_buf;
size_t buf_size = 1024 * 1024; // 1MB缓冲区
// 1. 打开DMA设备
fd = open("/dev/axidma", O_RDWR);
if (fd < 0) {
perror("Failed to open DMA device");
return -1;
}
// 2. 获取DMA缓冲区信息
struct axidma_buf_info info;
ioctl(fd, AXIDMA_GET_BUF_INFO, &info);
// 3. 内存映射(零拷贝)
dma_buf = mmap(NULL, buf_size,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (dma_buf == MAP_FAILED) {
perror("Failed to mmap DMA buffer");
close(fd);
return -1;
}
// 4. 启动DMA传输
struct axidma_transfer transfer = {
.buf_addr = info.phy_addr,
.length = buf_size,
.direction = DMA_DEV_TO_MEM // PL到PS传输
};
ioctl(fd, AXIDMA_START_TRANSFER, &transfer);
// 5. 等待传输完成
ioctl(fd, AXIDMA_WAIT_COMPLETE, NULL);
// 6. 处理数据
process_data(dma_buf, buf_size);
// 7. 清理资源
munmap(dma_buf, buf_size);
close(fd);
return 0;
}
五、实战难点与解决方案
5.1 内存对齐与缓存一致性
DMA传输要求物理内存连续且对齐。Linux内核的dma_alloc_coherent()函数可以保证这一点,但需要注意缓存一致性问题。
解决方案:
• 使用dma_alloc_coherent()分配一致性内存
• 对于非一致性内存,使用dma_map_single()/dma_unmap_single()进行映射
• 确保缓冲区大小为页面对齐(通常4KB)
5.2 中断处理与性能优化
频繁的中断会降低系统性能,特别是高速数据传输时。
优化策略:
// 使用轮询模式减少中断开销
static int axidma_poll_transfer(struct axidma_device *adev)
{
unsigned long timeout = jiffies + msecs_to_jiffies(100);
while (time_before(jiffies, timeout)) {
if (axidma_check_transfer_status(adev) == DMA_COMPLETE) {
return 0; // 传输完成
}
cpu_relax();
}
return -ETIMEDOUT; // 超时
}
5.3 多通道与并发访问
当系统中有多个DMA通道需要同时工作时,需要妥善处理资源竞争。
实现方案:
• 为每个DMA通道创建独立的设备节点
• 使用互斥锁保护共享资源
• 实现通道优先级调度
5.4 调试与性能分析
DMA传输问题的调试相对困难,需要系统化的调试方法。
调试工具:
1. 内核日志:通过printk()输出调试信息
2. 性能计数器:使用AXI性能监控IP核
3. 逻辑分析仪:抓取AXI总线信号
4. 系统监控:使用top、vmstat监控系统负载





