4. Linux PCIe驱动框架:pci_driver结构、probe/remove回调、设备注册与注销
在Linux内核中,PCIe驱动采用标准的总线驱动模型。核心数据结构 pci_driver 是驱动与PCIe子系统之间的契约,它定义了驱动的身份、能力以及生命周期回调函数。理解这一框架是编写高效DMA传输驱动的基础。
4.1 pci_driver 结构体核心字段
struct pci_driver 定义在 include/linux/pci.h 中,驱动开发者必须填充以下关键成员:
| 字段 | 类型 | 说明 |
|---|---|---|
| name | const char * |
驱动名称,用于与设备ID表匹配及sysfs标识 |
| id_table | const struct pci_device_id * |
支持的设备Vendor/Device ID列表,匹配成功后触发probe |
| probe | int (*probe)(struct pci_dev *, const struct pci_device_id *) |
设备发现时调用,完成硬件初始化、DMA资源分配、中断注册 |
| remove | void (*remove)(struct pci_dev *) |
设备移除或驱动卸载时调用,释放DMA缓冲区、取消中断、注销设备 |
| suspend / resume | 电源管理回调 | 系统休眠/唤醒时保存或恢复DMA引擎状态(可选) |
| shutdown | void (*shutdown)(struct pci_dev *) |
系统关机时调用,确保DMA传输停止 |
| driver | struct device_driver |
内嵌通用驱动结构,由PCI核心自动管理 |
关键点: probe 回调是DMA控制器编程的入口点。在此函数中,驱动必须完成:
1. 启用PCIe设备(pci_enable_device)
2. 设置DMA掩码(dma_set_mask)
3. 分配DMA缓冲区(dma_alloc_coherent)
4. 映射BAR空间(pci_ioremap_bar)
5. 注册中断(request_irq / request_threaded_irq)
1. 启用PCIe设备(pci_enable_device)
2. 设置DMA掩码(dma_set_mask)
3. 分配DMA缓冲区(dma_alloc_coherent)
4. 映射BAR空间(pci_ioremap_bar)
5. 注册中断(request_irq / request_threaded_irq)
4.2 probe/remove 回调生命周期
PCIe驱动的生命周期完全由 probe 和 remove 两个回调控制。下图展示了从设备插入到驱动卸载的完整流程:
最佳实践: probe 函数应设计为可重入且幂等。如果 probe 中途失败(例如DMA掩码设置失败),必须回滚所有已分配的资源,并返回负错误码(如 -ENOMEM、-EIO)。PCI核心会自动将设备标记为“未绑定”。
4.3 设备注册与注销 API
驱动模块的入口和出口函数负责调用PCI核心提供的注册/注销接口:
/* 模块入口:注册PCIe驱动 */
static int __init my_pcie_driver_init(void)
{
int ret;
ret = pci_register_driver(&my_pci_driver);
if (ret < 0) {
pr_err("Failed to register PCIe driver: %d\n", ret);
return ret;
}
pr_info("PCIe driver registered successfully\n");
return 0;
}
/* 模块出口:注销PCIe驱动 */
static void __exit my_pcie_driver_exit(void)
{
pci_unregister_driver(&my_pci_driver);
pr_info("PCIe driver unregistered\n");
}
module_init(my_pcie_driver_init);
module_exit(my_pcie_driver_exit);
4.4 完整驱动框架示例
以下是一个包含DMA初始化骨架的完整 pci_driver 实现:
/* 设备ID表 */
static const struct pci_device_id my_pci_ids[] = {
{ PCI_DEVICE(0x10EE, 0x7011) }, /* Vendor=Xilinx, Device=DMA IP */
{ }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);
/* probe 回调:DMA引擎初始化 */
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct my_dev *dev;
int ret;
/* 1. 分配私有数据结构 */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* 2. 启用PCIe设备 */
ret = pci_enable_device(pdev);
if (ret)
return ret;
/* 3. 设置DMA掩码(64位优先,回退32位) */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "DMA mask setup failed\n");
goto err_disable;
}
}
/* 4. 映射BAR空间(假设使用BAR0) */
dev->bar_base = pci_ioremap_bar(pdev, 0);
if (!dev->bar_base) {
dev_err(&pdev->dev, "BAR0 remap failed\n");
ret = -ENOMEM;
goto err_disable;
}
/* 5. 分配DMA一致性缓冲区(用于描述符环) */
dev->desc_ring = dma_alloc_coherent(&pdev->dev,
DESC_RING_SIZE,
&dev->desc_dma,
GFP_KERNEL);
if (!dev->desc_ring) {
dev_err(&pdev->dev, "DMA coherent allocation failed\n");
ret = -ENOMEM;
goto err_iounmap;
}
/* 6. 注册中断(MSI-X优先) */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (ret < 0) {
dev_err(&pdev->dev, "MSI-X/MSI allocation failed\n");
goto err_dma_free;
}
dev->irq = pci_irq_vector(pdev, 0);
ret = request_irq(dev->irq, my_dma_isr, 0, "my_pcie_dma", dev);
if (ret)
goto err_free_vectors;
/* 7. 保存私有数据并启用DMA引擎 */
pci_set_drvdata(pdev, dev);
pci_set_master(pdev); /* 开启总线主控,允许DMA */
dev_info(&pdev->dev, "DMA engine initialized, IRQ=%d\n", dev->irq);
return 0;
err_free_vectors:
pci_free_irq_vectors(pdev);
err_dma_free:
dma_free_coherent(&pdev->dev, DESC_RING_SIZE, dev->desc_ring, dev->desc_dma);
err_iounmap:
iounmap(dev->bar_base);
err_disable:
pci_disable_device(pdev);
return ret;
}
/* remove 回调:释放所有DMA资源 */
static void my_pci_remove(struct pci_dev *pdev)
{
struct my_dev *dev = pci_get_drvdata(pdev);
/* 1. 停止DMA引擎(写硬件寄存器) */
writel(0x0, dev->bar_base + DMA_CONTROL_REG);
/* 2. 释放中断 */
free_irq(dev->irq, dev);
pci_free_irq_vectors(pdev);
/* 3. 释放DMA缓冲区 */
dma_free_coherent(&pdev->dev, DESC_RING_SIZE, dev->desc_ring, dev->desc_dma);
/* 4. 解除BAR映射 */
iounmap(dev->bar_base);
/* 5. 禁用PCIe设备 */
pci_disable_device(pdev);
dev_info(&pdev->dev, "DMA engine removed\n");
}
/* pci_driver 结构体实例 */
static struct pci_driver my_pci_driver = {
.name = "my_pcie_dma",
.id_table = my_pci_ids,
.probe = my_pci_probe,
.remove = my_pci_remove,
};
4.5 资源分配与释放对照表
probe 和 remove 中的资源操作必须严格对称,否则会导致内存泄漏或内核崩溃:
| 资源类型 | probe 中分配 | remove 中释放 | 失败回滚 |
|---|---|---|---|
| PCIe设备使能 | pci_enable_device() |
pci_disable_device() |
直接返回 |
| DMA掩码 | dma_set_mask_and_coherent() |
无需显式释放 | 跳转 err_disable |
| BAR空间映射 | pci_ioremap_bar() |
iounmap() |
跳转 err_disable |
| DMA一致性内存 | dma_alloc_coherent() |
dma_free_coherent() |
跳转 err_iounmap |
| 中断向量 | pci_alloc_irq_vectors() |
pci_free_irq_vectors() |
跳转 err_dma_free |
| 中断处理函数 | request_irq() |
free_irq() |
跳转 err_free_vectors |
| 总线主控 | pci_set_master() |
pci_clear_master() |
在 remove 中清理 |
常见错误:
1. probe 中调用
2. 使用
1. probe 中调用
pci_set_master() 但 remove 中忘记调用 pci_clear_master(),导致设备在驱动卸载后仍可发起DMA传输,造成数据损坏。2. 使用
devm_ 系列管理资源时,必须确认其释放顺序与硬件时序要求一致。例如DMA描述符环必须在中断释放之后才能释放。
4.6 多设备实例与并发安全
当系统中有多块相同PCIe设备时,每个设备都会触发一次 probe 调用。驱动必须通过 pci_set_drvdata() 将私有数据绑定到每个 pci_dev 实例:
/* 每个设备实例拥有独立的DMA状态 */
struct my_dev {
void __iomem *bar_base; /* BAR映射地址 */
dma_addr_t desc_dma; /* 描述符环DMA地址 */
void *desc_ring; /* 描述符环CPU地址 */
int irq; /* 中断号 */
spinlock_t lock; /* 保护DMA寄存器访问 */
struct pci_dev *pdev; /* 反向引用 */
};
并发注意事项:
- 多个设备实例的 probe 可能并行执行(如果PCIe总线枚举是多线程的)。
- 每个实例的DMA寄存器操作必须使用自旋锁保护,避免中断上下文与进程上下文冲突。
- 全局变量(如模块计数)必须使用原子操作或互斥锁保护。
- 多个设备实例的 probe 可能并行执行(如果PCIe总线枚举是多线程的)。
- 每个实例的DMA寄存器操作必须使用自旋锁保护,避免中断上下文与进程上下文冲突。
- 全局变量(如模块计数)必须使用原子操作或互斥锁保护。