以下是针对Linux下PCI设备驱动开发的详细步骤指南及示例代码,得当刚入门的小白渐渐学习和实践:
一、开发情况准备
- 安装开发工具
- sudo apt install build-essential linux-headers-$(uname -r)
复制代码 - 创建项目目录
- mkdir pci_driver && cd pci_driver
复制代码 二、编写最简单的PCI驱动框架
1. 创建驱动源码文件 my_pci_driver.c
- #include <linux/module.h>
- #include <linux/pci.h>
- /* 定义驱动支持的PCI设备ID列表 */
- static const struct pci_device_id my_pci_ids[] = {
- { PCI_DEVICE(0x1234, 0x5678) }, // 替换为你的设备厂商ID和设备ID
- { 0, } // 结束标记
- };
- MODULE_DEVICE_TABLE(pci, my_pci_ids);
- /* PCI设备探测函数(当系统发现匹配设备时调用) */
- static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
- {
- printk(KERN_INFO "My PCI Driver: Device detected!\n");
- return 0; // 返回0表示成功
- }
- /* PCI设备移除函数(驱动卸载或设备拔出时调用) */
- static void my_pci_remove(struct pci_dev *pdev)
- {
- printk(KERN_INFO "My PCI Driver: Device removed.\n");
- }
- /* 定义PCI驱动结构体 */
- static struct pci_driver my_pci_driver = {
- .name = "my_pci_driver", // 驱动名称
- .id_table = my_pci_ids, // 支持的设备ID表
- .probe = my_pci_probe, // 探测函数
- .remove = my_pci_remove, // 移除函数
- };
- /* 模块加载函数 */
- static int __init my_pci_init(void)
- {
- return pci_register_driver(&my_pci_driver);
- }
- /* 模块卸载函数 */
- static void __exit my_pci_exit(void)
- {
- pci_unregister_driver(&my_pci_driver);
- }
- module_init(my_pci_init);
- module_exit(my_pci_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Your Name");
- MODULE_DESCRIPTION("Simple PCI Driver Example");
复制代码 2. 创建Makefile
- obj-m += my_pci_driver.o
- all:
- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
- clean:
- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
复制代码 三、获取设备厂商ID与设备ID
- 查找PCI设备信息
- lspci -nn # 显示所有PCI设备,例如:
- # 01:00.0 Ethernet controller [0200]: Intel Corporation Device [8086:1533]
复制代码
- 8086是厂商ID(Vendor ID)
- 1533是设备ID(Device ID)
- 修改代码中的PCI_DEVICE宏
- { PCI_DEVICE(0x8086, 0x1533) }, // 替换为你的设备ID
复制代码 四、编译与加载驱动
- 编译驱动
- make # 生成my_pci_driver.ko文件
复制代码 - 加载驱动
- sudo insmod my_pci_driver.ko
复制代码 - 查看日记
- dmesg | tail # 应显示"Device detected!"
复制代码 - 卸载驱动
- sudo rmmod my_pci_driver
- dmesg | tail # 显示"Device removed."
复制代码 五、进阶功能实现
1. 启用PCI设备与映射内存
- static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
- {
- int ret;
- void __iomem *regs;
- // 启用PCI设备
- ret = pci_enable_device(pdev);
- if (ret) {
- printk(KERN_ERR "Failed to enable PCI device\n");
- return ret;
- }
- // 请求内存区域(假设使用BAR0)
- ret = pci_request_regions(pdev, "my_pci_driver");
- if (ret) {
- printk(KERN_ERR "Failed to request regions\n");
- goto err_disable;
- }
- // 映射BAR0到内核虚拟地址
- regs = pci_ioremap_bar(pdev, 0);
- if (!regs) {
- printk(KERN_ERR "Failed to map BAR0\n");
- ret = -ENOMEM;
- goto err_release;
- }
- // 示例:读取第一个寄存器(假设32位)
- u32 value = ioread32(regs);
- printk(KERN_INFO "Register value: 0x%x\n", value);
- // 保存映射地址到设备私有数据
- pci_set_drvdata(pdev, regs);
- return 0;
- err_release:
- pci_release_regions(pdev);
- err_disable:
- pci_disable_device(pdev);
- return ret;
- }
- static void my_pci_remove(struct pci_dev *pdev)
- {
- void __iomem *regs = pci_get_drvdata(pdev);
- iounmap(regs);
- pci_release_regions(pdev);
- pci_disable_device(pdev);
- }
复制代码 2. 处理停止
- #include <linux/interrupt.h>
- static irqreturn_t my_pci_interrupt(int irq, void *dev_id)
- {
- struct pci_dev *pdev = dev_id;
- void __iomem *regs = pci_get_drvdata(pdev);
- // 读取中断状态寄存器
- u32 status = ioread32(regs + 0x10);
- if (status & 0x1) {
- printk(KERN_INFO "Interrupt occurred!\n");
- // 清除中断标志
- iowrite32(status & 0x1, regs + 0x10);
- return IRQ_HANDLED;
- }
- return IRQ_NONE;
- }
- // 在probe函数中添加:
- ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY);
- if (ret < 0) {
- printk(KERN_ERR "Failed to allocate IRQ\n");
- goto err_unmap;
- }
- ret = request_irq(pci_irq_vector(pdev, 0), my_pci_interrupt,
- IRQF_SHARED, "my_pci_irq", pdev);
- if (ret) {
- printk(KERN_ERR "Failed to request IRQ\n");
- goto err_irq;
- }
- // 在remove函数中添加:
- free_irq(pci_irq_vector(pdev, 0), pdev);
- pci_free_irq_vectors(pdev);
复制代码 六、调试技巧
- 查看驱动日记
- 检查设备是否被识别
- lspci -v -s 01:00.0 # 替换为你的设备地址
复制代码 - 查看驱动加载状态
- lsmod | grep my_pci_driver
复制代码 七、注意事项
- 内核版本兼容性:确保头文件路径正确(/lib/modules/$(uname -r)/build)。
- 内存安全:使用ioread32/iowrite32访问寄存器,制止直接指针操作。
- 错误处理:所有内核函数调用必须检查返回值!
- 测试情况:建议在虚拟机或专用开发板测试,制止主机崩溃。
通过以上步骤,可以渐渐构建一个完整的PCI设备驱动。实际开发中需根据具体硬件手册调整寄存器操作和停止处理逻辑。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |