Linux驱动开发实战之PCIE驱动(一)

打印 上一主题 下一主题

主题 1008|帖子 1008|积分 3024

以下是针对Linux下PCI设备驱动开发的详细步骤指南及示例代码,得当刚入门的小白渐渐学习和实践:

一、开发情况准备


  • 安装开发工具
    1. sudo apt install build-essential linux-headers-$(uname -r)
    复制代码
  • 创建项目目录
    1. mkdir pci_driver && cd pci_driver
    复制代码

二、编写最简单的PCI驱动框架

1. 创建驱动源码文件 my_pci_driver.c

  1. #include <linux/module.h>
  2. #include <linux/pci.h>
  3. /* 定义驱动支持的PCI设备ID列表 */
  4. static const struct pci_device_id my_pci_ids[] = {
  5.     { PCI_DEVICE(0x1234, 0x5678) }, // 替换为你的设备厂商ID和设备ID
  6.     { 0, }  // 结束标记
  7. };
  8. MODULE_DEVICE_TABLE(pci, my_pci_ids);
  9. /* PCI设备探测函数(当系统发现匹配设备时调用) */
  10. static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
  11. {
  12.     printk(KERN_INFO "My PCI Driver: Device detected!\n");
  13.     return 0; // 返回0表示成功
  14. }
  15. /* PCI设备移除函数(驱动卸载或设备拔出时调用) */
  16. static void my_pci_remove(struct pci_dev *pdev)
  17. {
  18.     printk(KERN_INFO "My PCI Driver: Device removed.\n");
  19. }
  20. /* 定义PCI驱动结构体 */
  21. static struct pci_driver my_pci_driver = {
  22.     .name     = "my_pci_driver",   // 驱动名称
  23.     .id_table = my_pci_ids,        // 支持的设备ID表
  24.     .probe    = my_pci_probe,      // 探测函数
  25.     .remove   = my_pci_remove,     // 移除函数
  26. };
  27. /* 模块加载函数 */
  28. static int __init my_pci_init(void)
  29. {
  30.     return pci_register_driver(&my_pci_driver);
  31. }
  32. /* 模块卸载函数 */
  33. static void __exit my_pci_exit(void)
  34. {
  35.     pci_unregister_driver(&my_pci_driver);
  36. }
  37. module_init(my_pci_init);
  38. module_exit(my_pci_exit);
  39. MODULE_LICENSE("GPL");
  40. MODULE_AUTHOR("Your Name");
  41. MODULE_DESCRIPTION("Simple PCI Driver Example");
复制代码
2. 创建Makefile

  1. obj-m += my_pci_driver.o
  2. all:
  3.     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
  4. clean:
  5.     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
复制代码

三、获取设备厂商ID与设备ID


  • 查找PCI设备信息
    1. lspci -nn  # 显示所有PCI设备,例如:
    2.             # 01:00.0 Ethernet controller [0200]: Intel Corporation Device [8086:1533]
    复制代码

    • 8086是厂商ID(Vendor ID)
    • 1533是设备ID(Device ID)

  • 修改代码中的PCI_DEVICE宏
    1. { PCI_DEVICE(0x8086, 0x1533) }, // 替换为你的设备ID
    复制代码

四、编译与加载驱动


  • 编译驱动
    1. make  # 生成my_pci_driver.ko文件
    复制代码
  • 加载驱动
    1. sudo insmod my_pci_driver.ko
    复制代码
  • 查看日记
    1. dmesg | tail  # 应显示"Device detected!"
    复制代码
  • 卸载驱动
    1. sudo rmmod my_pci_driver
    2. dmesg | tail  # 显示"Device removed."
    复制代码

五、进阶功能实现

1. 启用PCI设备与映射内存

  1. static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
  2. {
  3.     int ret;
  4.     void __iomem *regs;
  5.     // 启用PCI设备
  6.     ret = pci_enable_device(pdev);
  7.     if (ret) {
  8.         printk(KERN_ERR "Failed to enable PCI device\n");
  9.         return ret;
  10.     }
  11.     // 请求内存区域(假设使用BAR0)
  12.     ret = pci_request_regions(pdev, "my_pci_driver");
  13.     if (ret) {
  14.         printk(KERN_ERR "Failed to request regions\n");
  15.         goto err_disable;
  16.     }
  17.     // 映射BAR0到内核虚拟地址
  18.     regs = pci_ioremap_bar(pdev, 0);
  19.     if (!regs) {
  20.         printk(KERN_ERR "Failed to map BAR0\n");
  21.         ret = -ENOMEM;
  22.         goto err_release;
  23.     }
  24.     // 示例:读取第一个寄存器(假设32位)
  25.     u32 value = ioread32(regs);
  26.     printk(KERN_INFO "Register value: 0x%x\n", value);
  27.     // 保存映射地址到设备私有数据
  28.     pci_set_drvdata(pdev, regs);
  29.     return 0;
  30. err_release:
  31.     pci_release_regions(pdev);
  32. err_disable:
  33.     pci_disable_device(pdev);
  34.     return ret;
  35. }
  36. static void my_pci_remove(struct pci_dev *pdev)
  37. {
  38.     void __iomem *regs = pci_get_drvdata(pdev);
  39.     iounmap(regs);
  40.     pci_release_regions(pdev);
  41.     pci_disable_device(pdev);
  42. }
复制代码
2. 处理停止

  1. #include <linux/interrupt.h>
  2. static irqreturn_t my_pci_interrupt(int irq, void *dev_id)
  3. {
  4.     struct pci_dev *pdev = dev_id;
  5.     void __iomem *regs = pci_get_drvdata(pdev);
  6.     // 读取中断状态寄存器
  7.     u32 status = ioread32(regs + 0x10);
  8.     if (status & 0x1) {
  9.         printk(KERN_INFO "Interrupt occurred!\n");
  10.         // 清除中断标志
  11.         iowrite32(status & 0x1, regs + 0x10);
  12.         return IRQ_HANDLED;
  13.     }
  14.     return IRQ_NONE;
  15. }
  16. // 在probe函数中添加:
  17. ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_LEGACY);
  18. if (ret < 0) {
  19.     printk(KERN_ERR "Failed to allocate IRQ\n");
  20.     goto err_unmap;
  21. }
  22. ret = request_irq(pci_irq_vector(pdev, 0), my_pci_interrupt,
  23.                   IRQF_SHARED, "my_pci_irq", pdev);
  24. if (ret) {
  25.     printk(KERN_ERR "Failed to request IRQ\n");
  26.     goto err_irq;
  27. }
  28. // 在remove函数中添加:
  29. free_irq(pci_irq_vector(pdev, 0), pdev);
  30. pci_free_irq_vectors(pdev);
复制代码

六、调试技巧


  • 查看驱动日记
    1. dmesg -w  # 实时监控内核日志
    复制代码
  • 检查设备是否被识别
    1. lspci -v -s 01:00.0  # 替换为你的设备地址
    复制代码
  • 查看驱动加载状态
    1. lsmod | grep my_pci_driver
    复制代码

七、注意事项


  • 内核版本兼容性:确保头文件路径正确(/lib/modules/$(uname -r)/build)。
  • 内存安全:使用ioread32/iowrite32访问寄存器,制止直接指针操作。
  • 错误处理:所有内核函数调用必须检查返回值!
  • 测试情况:建议在虚拟机或专用开发板测试,制止主机崩溃。

通过以上步骤,可以渐渐构建一个完整的PCI设备驱动。实际开发中需根据具体硬件手册调整寄存器操作和停止处理逻辑。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

反转基因福娃

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表