You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Linux设备驱动中FPGA寄存器读写实现方法咨询

Linux设备驱动中FPGA寄存器读写实现方法咨询

兄弟,第一次写Linux内核驱动就能搭出这个完整的骨架真的很棒!我当年第一次写的时候还卡在模块初始化那里半天呢😂 你现在卡的点其实是如何把ioremap得到的虚拟地址和read/write函数结合起来操作寄存器,这是驱动开发里很常见的步骤,咱们一步步来捋清楚:

首先,你已经通过devm_ioremap_resource()拿到了寄存器基地址的虚拟映射base_addr,这一步完全没问题——这就是Linux驱动操作内存映射型设备的标准流程。接下来要解决两个核心问题:把这个虚拟地址保存下来让read/write函数能访问到,以及用正确的内核API读写寄存器

一、保存映射后的虚拟地址

因为module_probe函数里的base_addr是局部变量,read/write函数拿不到它,所以得把它存到一个全局可访问的地方。这里有两种常见做法:

  1. 简单场景用全局静态变量(如果你的FPGA设备是单板唯一的):
    // 在驱动文件开头定义全局变量
    static void __iomem *fpga_base;
    static resource_size_t fpga_reg_size; // 保存寄存器区域的总大小,用来做越界检查
    
    // 然后在module_probe里赋值
    static int module_probe(struct platform_device *pdev) {
        // ... 其他代码
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        base_addr = devm_ioremap_resource(dev, res);
        if(IS_ERR(base_addr)){
            pr_err("module_probe: Error when calling devm_ioremap_resource\n");
            return PTR_ERR(base_addr); // 这里注意不要返回-1,应该用PTR_ERR拿错误码
        }
        fpga_base = base_addr;
        fpga_reg_size = resource_size(res); // 获取寄存器区域的大小(你的dt里是0x10000)
        // ... 其他代码
    }
    
  2. 规范场景用设备私有数据(如果以后可能支持多设备):
    先定义一个私有数据结构体,把需要的变量存进去,再绑定到platform设备上:
    struct fpga_dev_data {
        void __iomem *base_addr;
        resource_size_t reg_size;
    };
    
    static int module_probe(struct platform_device *pdev) {
        // ... 其他代码
        struct fpga_dev_data *data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
        if (!data) {
            return -ENOMEM;
        }
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        data->base_addr = devm_ioremap_resource(&pdev->dev, res);
        if(IS_ERR(data->base_addr)){
            pr_err("module_probe: Error when calling devm_ioremap_resource\n");
            return PTR_ERR(data->base_addr);
        }
        data->reg_size = resource_size(res);
        platform_set_drvdata(pdev, data); // 把数据绑定到设备上
        // 如果要在misc设备的read/write里拿到这个数据,还可以把miscdevice的private_data也设置一下
        module_dev.private_data = data;
        // ... 其他代码
    }
    

二、用内核标准API读写寄存器

绝对不要直接用指针解引用(比如*(u32*)base_addr)来操作寄存器!内核提供了专门的IO内存操作函数,会自动适配不同架构的内存屏障、缓存等要求:

  • 读寄存器:ioread8()/ioread16()/ioread32()(对应8/16/32位寄存器)
  • 写寄存器:iowrite8()/iowrite16()/iowrite32()

比如你要往基地址偏移0x0的32位寄存器写值0x12345678,就这么写:

iowrite32(0x12345678, fpga_base + 0x0);

读这个寄存器的值:

u32 val = ioread32(fpga_base + 0x0);

三、修改read/write函数实现

现在就可以把寄存器读写逻辑加到你的read/write函数里了,还要注意用户空间和内核空间的数据拷贝(用copy_from_usercopy_to_user),以及文件位置(f_pos)的处理,比如:

示例write函数(处理32位寄存器写入)

static ssize_t module_write(struct file *file_p, const char __user *user_buffer, size_t count, loff_t *f_pos) {
    u32 val;
    // 只处理4字节的写入(对应32位寄存器),如果你的寄存器是其他宽度可以改
    if (count != sizeof(u32)) {
        pr_err("Invalid write size! Expect 4 bytes\n");
        return -EINVAL;
    }
    // 检查偏移是否越界
    if (*f_pos + sizeof(u32) > fpga_reg_size) {
        pr_err("Write offset out of range!\n");
        return -EINVAL;
    }
    // 从用户空间拷贝数据到内核
    if (copy_from_user(&val, user_buffer, sizeof(val))) {
        return -EFAULT;
    }
    // 写入指定偏移的寄存器
    iowrite32(val, fpga_base + *f_pos);
    // 更新文件位置,下次写入会从下一个寄存器开始
    *f_pos += sizeof(val);
    pr_notice("Wrote 0x%x to offset 0x%llx\n", val, *f_pos - sizeof(val));
    return count; // 返回实际写入的字节数
}

示例read函数(读取32位寄存器)

static ssize_t module_read(struct file *file_p, char __user *user_buffer, size_t count, loff_t *f_pos) {
    u32 val;
    if (count != sizeof(u32)) {
        pr_err("Invalid read size! Expect 4 bytes\n");
        return -EINVAL;
    }
    if (*f_pos + sizeof(u32) > fpga_reg_size) {
        pr_err("Read offset out of range!\n");
        return -EINVAL;
    }
    // 读取指定偏移的寄存器
    val = ioread32(fpga_base + *f_pos);
    // 把数据拷贝到用户空间
    if (copy_to_user(user_buffer, &val, sizeof(val))) {
        return -EFAULT;
    }
    *f_pos += sizeof(val);
    pr_notice("Read 0x%x from offset 0x%llx\n", val, *f_pos - sizeof(val));
    return count;
}

最后回答你的疑问:这是不是正确的方法?

完全正确!你选择的Platform驱动+IO内存映射+Misc字符设备的方案,就是Linux下操作FPGA这类内存映射型外设的标准范式:

  • Platform驱动用来匹配设备树节点、获取硬件资源;
  • devm_ioremap_resource是安全的内存映射方式,内核自动管理资源释放;
  • Misc设备用来快速创建字符设备节点,给用户空间提供访问接口,省去了手动申请主设备号、创建设备文件等繁琐步骤。

只要把上面的步骤补上,你就能实现对FPGA寄存器的读写操作了,加油!

备注:内容来源于stack exchange,提问作者EmbeddedNoob

火山引擎 最新活动