You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:32:11