对应路线图:Phase 4 (Day 120-140) | 内核 v6.6 | 实验代码:code/dpu_dev/01_pci/,引用均标文件:行号
上一篇:P4-02 MSI-X | 下一篇:P5-01 DMA/IOMMU
一句话定位
PCIe 驱动的最小骨架不是操作寄存器,而是参与内核的"设备-驱动匹配协议":填一张 pci_device_id 表 + 一个 probe 回调,driver core 就会在设备出现时替你调它。code/dpu_dev/01_pci/pci_scan.c 全文件只有 24 行,却能 probe 整条总线上的所有设备——这是每个网卡驱动(ixgbe、mlx5)的同款起点。
真实源码精读
① 全文件带读:code/dpu_dev/01_pci/pci_scan.c:1-24
#include <linux/module.h>
#include <linux/pci.h>
static int probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
printk("pci_scan: %04x:%04x (rev %02x) BAR0=%llx IRQ=%d\n",
pdev->vendor, pdev->device, pdev->revision,
pci_resource_start(pdev, 0), pdev->irq);
return 0;
}
static struct pci_device_id ids[] = {
{ PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);
static struct pci_driver drv = {
.name = "pci_scan",
.id_table = ids,
.probe = probe,
};
module_pci_driver(drv);
MODULE_LICENSE("GPL");逐行拆:
probe回调(:4-10):签名(struct pci_dev *, const struct pci_device_id *)是 PCI 子系统的硬约定。这里读的vendor/device/revision正是 P4-01 里pci_scan_device从 Config Space 填进pci_dev的(probe.c:2507-2508);pci_resource_start(pdev, 0)是 BAR0 物理地址(宏定义 pci.h:2107);pdev->irq是内核分配的 IRQ 号(pci.h:420)。返回 0 = "这个设备我接管了"。ids表(:12-16):全PCI_ANY_ID表示匹配所有设备——所以本模块会 probe 系统里每一个 PCI 设备,只能在学习机上玩。真正的驱动用PCI_DEVICE(0x8086, 0x10fb)这类精确匹配(宏在 pci.h:962)。表项结构在 mod_devicetable.h:44-50:vendor/device/subvendor/subdevice/class/class_mask 六维过滤 + driver_data 私有数据。module_pci_driver(drv)(:23):展开成module_driver(drv, pci_register_driver, pci_unregister_driver)(pci.h:1598-1599),自动生成 init/exit,替换手写 module_init/module_exit 的样板代码。
② 匹配与 probe 触发:struct pci_driver 与 driver core(pci.h:928-943)
struct pci_driver {
struct list_head node;
const char *name;
const struct pci_device_id *id_table; /* Must be non-NULL for probe to be called */
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
void (*remove)(struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
// ...省略 suspend/resume/shutdown/sriov/err_handler ...
struct device_driver driver;
// ...
};id_table 注释说得很直白:没有它 probe 就不会被调用。注册入口 __pci_register_driver(pci-driver.c:1451-1467)只做一件事:填 drv->driver.bus = &pci_bus_type 然后 driver_register()——后面就是通用设备模型的 match → probe 机器了。设备侧由 pci_device_probe(pci-driver.c:444-467)接棒:
static int pci_device_probe(struct device *dev)
{
int error;
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *drv = to_pci_driver(dev->driver);
// ...
pci_assign_irq(pci_dev);
error = pcibios_alloc_irq(pci_dev);
// ...
pci_dev_get(pci_dev);
error = __pci_device_probe(drv, pci_dev);
// ...
}注意顺序:先 pci_assign_irq 分配 IRQ,再进驱动的 probe——所以 probe 里 pdev->irq 一定已经有值。
③ remove 与卸载:内核兜底(pci-driver.c:469-510 节选)
static void pci_device_remove(struct device *dev)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *drv = pci_dev->driver;
if (drv->remove) {
// ... runtime PM barrier ...
drv->remove(pci_dev);
// ...
}
pcibios_free_irq(pci_dev);
pci_dev->driver = NULL;
// ...
}drv->remove 是驱动自己的清理窗口(放走 IRQ、unmap、还资源);窗口之外的事内核兜底:pcibios_free_irq 收尾 IRQ(:486)、解绑 driver = NULL(:487)。rmmod 会经 pci_unregister_driver(pci-driver.c:1480)对每个被该驱动接管的设备各调一次 remove。
④ 升级路线:把"只读"变成"拥有"
pci_scan.c 只读不占,等于借设备看了一眼。要真正"拥有"一个 BAR(比如网卡的 MMIO 寄存器区),probe 里按序做四件事(API 均已核实存在):
| 步骤 | API | 位置 | 作用 |
|---|---|---|---|
| 1 | pci_enable_device(pdev) | pci.c:2191 | 打开 IO/MEM decode,唤醒设备 |
| 2 | pci_request_regions(pdev, name) | pci.c:4273 | 声明 resource 所有权(/proc/iomem 可见) |
| 3 | pci_ioremap_bar(pdev, bar) | pci.c:252 | 映射 BAR 到内核虚拟地址 |
| 4 | request_irq(pci_irq_vector(pdev, 0), handler, ...) | interrupt.h:165 | 挂中断(配合 P4-02 的 MSI-X) |
remove 里严格逆序:free_irq(interrupt.h:196)→ iounmap → pci_release_regions(pci.c:4254)→ pci_disable_device(pci.c:2379)。
🐧 内核机制对照:驱动协议 vs 用户态"野路子"
| 环节 | 内核驱动(本模块) | DPDK 用户态(bus/pci) |
|---|---|---|
| 设备发现 | driver core 匹配 id_table → probe | 自己扫 /sys/bus/pci/devices/ |
| "接管"动作 | probe 返回 0,设备归属本驱动 | bind 到 vfio-pci/uio 驱动后拿 fd |
| BAR 访问 | ioremap → 内核虚拟地址 | mmap,pci_common_uio.c:178-179 |
| 中断 | request_irq + 上半部/NAPI | 轮询或 epoll 中断线程 |
| 资源冲突防护 | pci_request_regions 互斥 | 依赖内核驱动让位(bound to VFIO) |
DPDK 不是"没有驱动协议",而是把协议放进了用户态:内核里那个 vfio-pci 驱动替它完成了 probe/接管,用户态只负责 mmap 和轮询。
面试 3 问
- probe 什么时候被调用? → 设备注册或驱动注册后 driver core 完成 match 即调;前提是 id_table 非 NULL(pci.h:931 注释);入口 pci_device_probe(pci-driver.c:444)先
pci_assign_irq再调驱动的 probe(pci-driver.c:453-460)。 - PCI_ANY_ID 表项的风险? → 匹配所有设备(pci_scan.c:12-13),probe 返回 0 会接管每一个设备(pci_scan.c:9),真实驱动必须用 PCI_DEVICE 精确匹配(pci.h:962-964)。
- remove 里不释放资源会怎样? → IRQ 方面内核会兜底
pcibios_free_irq(pci-driver.c:486),但 ioremap 的地址和 request_regions 占用的 resource 泄漏,再加载驱动会 EBUSY——所以 remove 必须逆序归还(见上表)。
🔧 动手实验
cd code/dpu_dev/01_pci
make # Makefile:1-2,obj-m += pci_scan.o
sudo insmod pci_scan.ko
dmesg | tail -30 # 每个 PCI 设备一行:vendor:device rev BAR0 IRQ
sudo rmmod pci_scan升级练习(在实验机上,不要在主力机上):给 probe 加 pci_enable_device + pci_request_regions,验证 /proc/iomem 里出现 pci_scan 名字段;再试 pci_ioremap_bar 读 4 字节网卡寄存器与 lspci -xxxx 对拍。数据落点 perf-data/P4/03-pci-scan-dmesg.txt(dmesg 原文 + iomem 段 + 升级版对比)。
评论 (0)