Linux程序与FPGA上IP核经AXI总线交互的内存映射及地址转换方案咨询
解决方案:用户空间mmap文件配合内核DMA API实现FPGA IP核数据传输
核心逻辑
FPGA无法识别Linux虚拟地址,必须拿到物理内存地址才能访问。用户空间直接调用mmap(2)后,不能用virt_to_phys()转换(该函数仅适用于内核逻辑地址),需要内核驱动配合DMA API完成虚拟地址到物理地址的映射,并处理缓存一致性问题,最终把物理地址通过AXI总线传给FPGA IP核。
驱动层实现步骤
1. 实现字符设备的mmap方法
在驱动的file_operations结构体中实现mmap函数,将用户要操作的文件页映射到用户空间,同时内核跟踪这些页的信息:
static int fpga_char_mmap(struct file *filp, struct vm_area_struct *vma) { struct inode *inode = filp->f_path.dentry->d_inode; struct address_space *mapping = inode->i_mapping; struct vm_area_struct *vma_tmp = vma; // 逐页映射文件到用户空间 while (vma_tmp->vm_end > vma_tmp->vm_start) { unsigned long pgoff = vma_tmp->vm_pgoff + (vma_tmp->vm_start - vma->vm_start) / PAGE_SIZE; struct page *page = find_get_page(mapping, pgoff); if (!page) { return -EFAULT; } // 将物理页映射到用户虚拟地址区间 if (remap_pfn_range(vma_tmp, vma_tmp->vm_start, page_to_pfn(page), PAGE_SIZE, vma_tmp->vm_page_prot)) { put_page(page); return -EAGAIN; } put_page(page); vma_tmp->vm_start += PAGE_SIZE; } return 0; }
2. 通过DMA API获取物理地址并处理缓存一致性
当用户空间发起传输请求(比如通过ioctl),驱动完成以下操作:
- 解析用户传递的虚拟地址与传输长度
- 获取虚拟地址对应的物理页
- 用DMA API映射出FPGA可访问的物理地址(自动同步缓存)
- 通过AXI总线将地址发送给FPGA IP核
示例代码:
static long fpga_char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct fpga_dev *dev = filp->private_data; struct user_transfer_req req; dma_addr_t dma_addr; struct page *page; unsigned long virt_addr; if (copy_from_user(&req, (void __user *)arg, sizeof(req))) { return -EFAULT; } virt_addr = req.virt_addr; // 获取用户虚拟地址对应的物理页 page = vmalloc_to_page((void *)virt_addr); if (!page) { return -EINVAL; } // 映射DMA地址(FPGA可识别的物理地址),同步缓存到物理内存 dma_addr = dma_map_page(dev->dev, page, 0, PAGE_SIZE, DMA_TO_DEVICE); if (dma_mapping_error(dev->dev, dma_addr)) { put_page(page); return -EIO; } // 通过AXI总线向FPGA IP核发送地址与长度命令 axi_send_command(dev, dma_addr, req.length); // 等待FPGA传输完成,解除DMA映射 wait_for_fpga_transfer(dev); dma_unmap_page(dev->dev, dma_addr, PAGE_SIZE, DMA_TO_DEVICE); put_page(page); return 0; }
3. 初始化DMA设备句柄
驱动加载时,绑定FPGA的AXI总线设备,获取DMA操作的设备句柄:
static int fpga_char_probe(struct platform_device *pdev) { struct fpga_dev *dev; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); dev->dev = &pdev->dev; // 初始化AXI总线寄存器、中断等资源 axi_init(dev); // 注册字符设备节点 // ... return 0; }
用户空间代码示例
#include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define FPGA_IOCTL_TRANSFER _IOW('F', 1, struct user_transfer_req) struct user_transfer_req { unsigned long virt_addr; size_t length; }; int main() { int fpga_fd = open("/dev/fpga_char", O_RDWR); int file_fd = open("data_file.bin", O_RDWR); // 映射文件到用户空间虚拟地址 void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0); struct user_transfer_req req = { .virt_addr = (unsigned long)addr, .length = 4096 }; // 通知驱动发起FPGA传输 ioctl(fpga_fd, FPGA_IOCTL_TRANSFER, &req); munmap(addr, 4096); close(file_fd); close(fpga_fd); return 0; }
关键注意事项
- 缓存一致性:必须使用DMA API的
dma_map_*和dma_unmap_*函数,自动处理CPU缓存与物理内存的同步,避免FPGA读取过期数据。 - 页对齐要求:FPGA IP核的内存访问通常要求地址、长度按页(如4KB)对齐,用户空间
mmap和传输参数需符合该要求。 - 合法性校验:驱动要校验用户传递的虚拟地址是否属于当前进程的合法地址空间,防止非法内存访问。
内容的提问来源于stack exchange,提问作者rickastley
相关产品推荐
相关产品推荐

