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)

4.2 probe/remove 回调生命周期

PCIe驱动的生命周期完全由 probe 和 remove 两个回调控制。下图展示了从设备插入到驱动卸载的完整流程:

PCIe驱动 probe/remove 生命周期 设备插入/匹配 pci_register_driver probe() 调用 设备正常工作 remove() 调用 pci_unregister_driver 设备移除/驱动卸载 匹配依据: pci_device_id 表中的 Vendor/Device ID probe 失败处理: 返回负错误码 PCI核心自动清理
最佳实践: 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 中调用 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寄存器操作必须使用自旋锁保护,避免中断上下文与进程上下文冲突。
- 全局变量(如模块计数)必须使用原子操作或互斥锁保护。