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

PCIe Linux驱动:如何在字符设备读写方法中访问BAR资源?

解决方案:绑定PCIe设备资源与字符设备上下文

我来帮你解决这个问题——核心就是把你的PCIe设备信息和字符设备上下文绑定起来,通过自定义结构体串联起所有需要的资源,然后在字符设备的open方法里把这个结构体挂到filp->private_data上。下面是具体的实现步骤和修改后的代码:

1. 定义自定义设备结构体

首先,我们需要一个结构体来封装所有需要在字符设备方法中访问的资源:

#include <linux/cdev.h>
#include <linux/semaphore.h>
#include <linux/pci.h>

#define MY_DRIVER_NAME "my_pcie_driver"
#define MY_DEV_COUNT 1  // 注册一个字符设备实例

struct my_device {
    struct pci_dev *pdev;       // PCIe设备指针
    void __iomem *BAR0;         // BAR0的映射虚拟地址
    struct semaphore sem;       // 同步信号量
    struct cdev cdev;           // 字符设备对象
    dev_t dev_num;              // 字符设备号
};

2. 完善probe函数:初始化自定义结构体并关联资源

修改你的my_pcie_probe函数,完成自定义结构体的分配、初始化,以及字符设备的注册,同时把结构体和PCIe设备绑定:

static int my_pcie_probe(struct pci_dev *dev, const struct pci_device_id *id) {
    int err = 0;
    struct my_device *my_dev = NULL;

    // 1. 启用PCIe设备
    err = pci_enable_device(dev);
    if (err < 0) {
        printk(KERN_ALERT "Unable to enable PCIe device!\n");
        return err;
    }

    // 2. 请求BAR资源
    err = pci_request_regions(dev, MY_DRIVER_NAME);
    if (err) {
        printk(KERN_ALERT "Unable to request memory regions (BARs) from PCIe device!\n");
        goto disable_device;
    }

    // 3. 分配并初始化自定义设备结构体(devm前缀让内核自动管理内存)
    my_dev = devm_kzalloc(&dev->dev, sizeof(struct my_device), GFP_KERNEL);
    if (!my_dev) {
        err = -ENOMEM;
        printk(KERN_ALERT "Failed to allocate my_device structure!\n");
        goto release_regions;
    }

    // 4. 映射BAR0
    my_dev->BAR0 = pci_iomap(dev, 0, 0);
    if (!my_dev->BAR0) {
        err = -ENOMEM;
        printk(KERN_ALERT "Failed to map BAR0!\n");
        goto free_my_dev;
    }

    // 5. 初始化成员变量
    my_dev->pdev = dev;
    sema_init(&my_dev->sem, 1);  // 初始化信号量,初始值为1

    // 6. 动态分配字符设备号(避免固定号冲突)
    err = alloc_chrdev_region(&my_dev->dev_num, 0, MY_DEV_COUNT, MY_DRIVER_NAME);
    if (err) {
        printk(KERN_ALERT "Failed to allocate chrdev region!\n");
        goto unmap_bar0;
    }

    // 7. 初始化并添加字符设备
    cdev_init(&my_dev->cdev, &my_fops);  // my_fops是你的file_operations结构体
    my_dev->cdev.owner = THIS_MODULE;
    err = cdev_add(&my_dev->cdev, my_dev->dev_num, MY_DEV_COUNT);
    if (err) {
        printk(KERN_ALERT "Failed to add cdev!\n");
        goto unregister_chrdev;
    }

    // 8. 把自定义结构体和PCIe设备绑定,方便后续remove函数获取
    dev_set_drvdata(&dev->dev, my_dev);

    // 保留你的BAR读写测试代码
    int x, i;
    for(i = 0; i < 16; i++) {
        x = ioread32(my_dev->BAR0 + i);
        printk(KERN_ALERT "cycle: %d bar0: 0x%p, x = 0x%016x\n", i, my_dev->BAR0, x);
    }
    for(i = 0; i < 16; i++) {
        iowrite32(i, my_dev->BAR0 + i);
    }

    printk(KERN_INFO "PCIe driver probe successful!\n");
    return 0;

    // 错误回滚:按申请顺序逆序释放资源
unregister_chrdev:
    unregister_chrdev_region(my_dev->dev_num, MY_DEV_COUNT);
unmap_bar0:
    pci_iounmap(dev, my_dev->BAR0);
free_my_dev:
    devm_kfree(&dev->dev, my_dev);
release_regions:
    pci_release_regions(dev);
disable_device:
    pci_disable_device(dev);
    return err;
}

3. 实现file_operations的open方法

在你的file_operations结构体中添加open方法,这里的关键是通过inode的cdev反向获取自定义结构体,并赋值给filp->private_data:

static int char_device_open(struct inode *inode, struct file *filp) {
    struct my_device *my_dev;

    // 通过inode的cdev指针,反向获取自定义结构体
    my_dev = container_of(inode->i_cdev, struct my_device, cdev);
    // 把结构体赋值给filp的private_data,这样read/write就能直接访问了
    filp->private_data = my_dev;

    printk(KERN_INFO "Char device opened!\n");
    return 0;
}

// 你的file_operations结构体定义
static const struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = char_device_open,
    .read = char_device_read,
    .write = char_device_write,  // 假设你有对应的write方法
    .release = char_device_release,  // 可选:资源释放方法
};

4. 完善字符设备的read方法

现在你的char_device_read方法就能正常通过filp->private_data访问到BAR0和信号量了,注意增加空指针检查:

ssize_t char_device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
    ssize_t ret = 0;
    struct my_device *my_dev;
    int device_buf[16];
    int i = 0;

    my_dev = filp->private_data;
    if (!my_dev) {
        return -EINVAL;
    }

    if (down_interruptible(&my_dev->sem)) {
        return -ERESTARTSYS;
    }

    // 读取BAR0的16个32位寄存器
    for(i = 0; i < 16; i++) {
        device_buf[i] = ioread32(my_dev->BAR0 + i);
    }

    // 限制拷贝长度,避免越界
    if (count > sizeof(device_buf)) {
        count = sizeof(device_buf);
    }
    if (copy_to_user(buf, device_buf, count)) {
        ret = -EFAULT;
        goto up_sem;
    }

    *f_pos += count;
    ret = count;

up_sem:
    up(&my_dev->sem);
    return ret;
}

5. 实现remove函数:清理所有资源

最后别忘了实现remove函数,释放所有申请的资源:

static void my_pcie_remove(struct pci_dev *dev) {
    struct my_device *my_dev = dev_get_drvdata(&dev->dev);

    if (!my_dev) {
        return;
    }

    // 删除字符设备
    cdev_del(&my_dev->cdev);
    // 注销设备号
    unregister_chrdev_region(my_dev->dev_num, MY_DEV_COUNT);
    // 解除BAR映射
    pci_iounmap(dev, my_dev->BAR0);
    // 释放自定义结构体(devm分配的内存内核会自动回收,这里写了更严谨)
    devm_kfree(&dev->dev, my_dev);
    // 释放PCIe资源
    pci_release_regions(dev);
    pci_disable_device(dev);
    // 清除设备关联数据
    dev_set_drvdata(&dev->dev, NULL);

    printk(KERN_INFO "PCIe driver removed!\n");
}

关键说明

  • 之前dev_set_drvdata没生效的原因:你只是把数据绑定到了PCIe设备对象上,但字符设备的filp->private_data并没有和这个数据关联,必须通过open方法把自定义结构体传递给filp。
  • 用devm_kzalloc分配结构体的好处是内核会自动管理内存释放,即使probe失败也不用手动kfree(当然我们还是写了回滚逻辑更严谨)。
  • 错误回滚一定要按资源申请的逆序进行,避免内存泄漏或资源残留。

内容的提问来源于stack exchange,提问作者Spyro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:02:04