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
相关产品推荐
相关产品推荐

