
1. Linux PCI设备驱动开发概述PCIPeripheral Component Interconnect设备驱动开发是Linux内核开发中的重要组成部分。作为系统与硬件交互的桥梁PCI驱动在现代计算机系统中扮演着关键角色。从1993年PCI标准首次发布至今它已经发展成为计算机系统中最为普遍的总线标准之一。在Linux系统中PCI设备驱动开发有着独特的架构和规范。与用户空间程序开发不同内核驱动开发需要考虑更多底层细节如中断处理、DMA操作、内存映射等。同时由于PCI设备的多样性从简单的IO卡到复杂的高速网卡、显卡等驱动开发也呈现出不同的技术特点。PCI驱动开发的核心在于理解Linux内核提供的PCI子系统接口。这些接口抽象了底层硬件差异为驱动开发者提供了统一的编程模型。通过pci_register_driver()等核心API开发者可以专注于设备特定的功能实现而不必过多关注硬件平台的差异。2. PCI驱动的基本结构2.1 驱动注册与初始化PCI驱动的入口点是pci_register_driver()函数。这个函数将驱动程序与内核PCI子系统连接起来使得当匹配的设备出现时驱动能够被自动调用。典型的驱动注册代码如下static struct pci_driver my_pci_driver { .name my_pci_drv, .id_table my_pci_ids, .probe my_pci_probe, .remove my_pci_remove, }; static int __init my_pci_init(void) { return pci_register_driver(my_pci_driver); } module_init(my_pci_init);这里有几个关键点需要注意id_table定义了驱动支持的设备列表probe函数是设备被发现时的回调remove函数是设备被移除时的回调__init标记表示这是初始化代码初始化完成后可以被丢弃2.2 设备识别与匹配PCI设备通过vendor ID和device ID进行识别。驱动需要提供一个pci_device_id数组列出所有支持的设备。例如static const struct pci_device_id my_pci_ids[] { { PCI_DEVICE(VENDOR_ID, DEVICE_ID) }, { PCI_DEVICE(VENDOR_ID2, DEVICE_ID2) }, { 0, } }; MODULE_DEVICE_TABLE(pci, my_pci_ids);MODULE_DEVICE_TABLE宏使得模块工具能够识别驱动支持的设备这在自动加载驱动时非常有用。在实际开发中我们还可以使用更灵活的匹配方式PCI_DEVICE_CLASS()按设备类匹配PCI_DEVICE_SUB()按子系统和子设备匹配PCI_ANY_ID作为通配符3. 设备初始化的详细步骤3.1 启用PCI设备在访问任何设备寄存器之前必须首先启用设备。这是通过pci_enable_device()函数完成的int err pci_enable_device(pdev); if (err) { dev_err(pdev-dev, Failed to enable PCI device\n); return err; }这个函数执行以下操作唤醒可能处于低功耗状态的设备分配设备的I/O和内存区域如果BIOS没有完成分配中断资源启用设备后如果需要设备作为DMA主控还需要调用pci_set_master()pci_set_master(pdev);3.2 请求资源PCI设备通常使用两种资源内存区域(MMIO)和I/O端口。在访问这些资源前必须首先请求它们if (pci_request_regions(pdev, my_pci_drv)) { dev_err(pdev-dev, Failed to request PCI regions\n); goto err_disable; }对于需要单独管理的资源可以使用pci_request_region()if (!request_mem_region(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar), my_pci_drv)) { dev_err(pdev-dev, Failed to request mem region\n); goto err_release; }3.3 设置DMA现代PCI设备通常支持64位DMA但需要驱动程序明确声明if (dma_set_mask_and_coherent(pdev-dev, DMA_BIT_MASK(64))) { if (dma_set_mask_and_coherent(pdev-dev, DMA_BIT_MASK(32))) { dev_err(pdev-dev, No suitable DMA available\n); goto err_release; } }这个操作告诉内核设备支持的DMA地址范围。如果64位DMA不可用可以回退到32位。3.4 中断处理PCI设备中断处理有几个关键点需要注意注册中断处理程序if (request_irq(pdev-irq, my_interrupt_handler, IRQF_SHARED, my_pci_drv, pdev)) { dev_err(pdev-dev, Failed to request IRQ\n); goto err_release; }对于支持MSI/MSI-X的设备应该优先使用int nvec pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_ALL_TYPES); if (nvec 0) { dev_err(pdev-dev, Failed to allocate IRQ vectors\n); goto err_release; }MSI/MSI-X相比传统INTx中断有几个优势每个设备有独立的中断向量避免DMA/IRQ竞争条件支持多消息中断4. 设备操作与配置空间访问4.1 配置空间访问PCI配置空间包含了设备的关键信息和控制接口。Linux提供了多种访问方式直接访问pci_read_config_word(pdev, PCI_VENDOR_ID, vendor); pci_write_config_byte(pdev, PCI_COMMAND, cmd);查找扩展功能int pos pci_find_capability(pdev, PCI_CAP_ID_MSI); if (pos) { /* 处理MSI能力 */ }4.2 MMIO操作设备寄存器通常通过MMIO方式访问。首先需要映射内存区域void __iomem *regs pci_iomap(pdev, bar, 0); if (!regs) { dev_err(pdev-dev, Failed to map BAR%d\n, bar); goto err_free_irq; }然后可以使用标准的IO访问函数u32 val ioread32(regs REG_OFFSET); iowrite32(new_val, regs REG_OFFSET);需要注意的是对于顺序敏感的寄存器操作可能需要添加读屏障iowrite32(CMD_START, regs REG_CMD); /* 确保命令已经发出 */ val ioread32(regs REG_STATUS);5. 设备关闭与资源释放5.1 停止设备操作在卸载驱动或设备移除时需要按顺序执行以下操作停止所有DMA活动禁用中断生成释放中断处理程序free_irq(pdev-irq, pdev);对于MSI/MSI-X设备pci_free_irq_vectors(pdev);5.2 释放资源所有分配的资源都需要正确释放取消MMIO映射pci_iounmap(pdev, regs);释放区域pci_release_regions(pdev);禁用设备pci_disable_device(pdev);6. 高级主题与最佳实践6.1 电源管理现代PCI设备需要支持电源管理。基本操作包括/* 挂起设备 */ pci_save_state(pdev); pci_set_power_state(pdev, PCI_D3hot); /* 恢复设备 */ pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev);6.2 DMA缓冲区管理DMA操作需要特别注意分配一致性DMA缓冲区dma_addr_t dma_handle; void *cpu_addr dma_alloc_coherent(pdev-dev, size, dma_handle, GFP_KERNEL);流式DMA映射dma_addr_t dma_handle dma_map_single(pdev-dev, buffer, size, direction); /* 使用DMA... */ dma_unmap_single(pdev-dev, dma_handle, size, direction);6.3 调试技巧PCI驱动调试可以使用以下工具和技术lspci命令查看设备信息/proc/interrupts查看中断统计printk和动态调试(pr_debug)PCI错误检查pci_read_config_word(pdev, PCI_STATUS, status); if (status PCI_STATUS_DETECTED_PARITY) { dev_warn(pdev-dev, Parity error detected\n); }7. 实际开发中的注意事项在开发PCI驱动时有几个常见的陷阱需要注意资源泄漏确保所有分配的资源都有对应的释放操作竞态条件特别是在中断处理程序和主程序之间字节序问题PCI设备可能是大端或小端需要正确处理原子性对设备寄存器的操作可能需要原子性保证错误处理每个可能失败的操作都需要检查返回值一个健壮的PCI驱动应该能够处理各种异常情况包括热插拔、电源管理事件和硬件错误。