注:本文为 “Linux PCIe 驱动” 相关文章合辑。
略作重排,如有内容异常,请看原文。
微代码 - Linux PCIe 驱动模块注册的两种方式?(pci_register_driver、module_pci_driver、pci_unregister_driver)
北冥的备忘录 已于 2024-12-08 11:46:13 修改
背景
PCIe 驱动可以通过内核模块标准的 init/exit 里面调用注册接口注册,也可以通过更高效的 PCIe 模块专用初始化定义接口。本文以 rtl8168 网卡为例简要说明。
两种接口:
# 1
int __init rtl8168_init_module(void){
pci_register_driver(&rtl8168_pci_driver);
}
module_init(rtl8168_init_module);
# 2
module_pci_driver(rtl8169_pci_driver);
两种注册方式
通过 module_init
函数
static struct pci_driver rtl8168_pci_driver = {
.name = MODULENAME,
.id_table = rtl8168_pci_tbl,
.probe = rtl8168_init_one,
.remove = __devexit_p(rtl8168_remove_one),
};
static int __init
rtl8168_init_module(void)
{
return pci_register_driver(&rtl8168_pci_driver);
}
static void __exit
rtl8168_cleanup_module(void)
{
pci_unregister_driver(&rtl8168_pci_driver);
}
module_init(rtl8168_init_module);
module_exit(rtl8168_cleanup_module);
通过 PCIe 专用 module
函数
这里直接使用 module_pci_driver (rtl8169_pci_driver);
,注册内核模块。
static struct pci_driver rtl8169_pci_driver = {
.name = MODULENAME,
.id_table = rtl8169_pci_tbl,
.probe = rtl_init_one,
.remove = rtl_remove_one,
.shutdown = rtl_shutdown,
};
module_pci_driver(rtl8169_pci_driver);
//不用指定下面的函数,内核模块 insmod 和 rmmod 的时候会根据 ib_table 中是否有 dev,调用probe 和 remove 函数
//module_init(rtl8168_init_module);
//module_exit(rtl8168_cleanup_module);
其他
注意:在 Linux 2.6 以前 PCIe 驱动注册接口不是 pci_register_driver
而是 pci_module_init
。
参考:
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
return pci_register_driver(&rtl8168_pci_driver);
#else
return pci_module_init(&rtl8168_pci_driver);
#endif
再看 module_pci_driver
内部实现
可以看到 module_pci_driver
的本质就是调用了 module_driver
这个通用宏定义。
module_driver
用宏定义定义了 __init
和 __exit
函数,然后自动调用 module_init
和 module_exit
。
并且驱动 init
和 exit
的名字是 xxx_init
开始的,比如 rtl8169_pci_driver
就是:rtl8169_pci_driver_init
。
然后宏定义内部使用的是:pci_register_driver
,所以最后本质都是 pci_register_driver
。exit
函数就是 pci_unregister_driver
。
#define module_pci_driver(__pci_driver) \
module_driver(__pci_driver, pci_register_driver, pci_unregister_driver)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
再细化下,module_pci_driver
三个参数分别是:struct pci_driver
的 driver,注册函数,注销函数。
#define module_pci_driver(__pci_driver) module_driver(__pci_driver, pci_register_driver, pci_unregister_driver)
#define module_driver(__driver, __register, __unregister, …) \
\
static int __init __driver##_init(void) \ { \ return
__register(&(__driver) , ##VA_ARGS); \ }
module_init(__driver##_init); \
\
static void __exit
__driver##_exit(void) \ { \ __unregister(&(__driver) , ##VA_ARGS); \ } \ module_exit(__driver##_exit);
再还原 module_pci_driver (rtl8169_pci_driver);
实际的操作就是:
把上面的 __driver
替换为 rtl8169_pci_driver
,__register
替换为 pci_register_driver
,__unregister
替换为 pci_unregister_driver
。
//#define module_pci_driver(__pci_driver) \
// module_driver(__pci_driver, pci_register_driver, pci_unregister_driver)
//#define module_driver(__driver, pci_register_driver, __unregister, ...)
static int __init rtl8169_pci_driver_init(void)
{
return pci_register_driver(&(rtl8169_pci_driver));
}
module_init(rtl8169_pci_driver_init);
static void __exit rtl8169_pci_driver_exit(void)
{
pci_unregister_driver(&(rtl8169_pci_driver));
}
module_exit(rtl8169_pci_driver_exit);
其他:还可以看到 module_driver
这个宏定义,支持不定长参数调用。调用的地方指定有限个,定义的地方指定不限个,使用 …
定义,使用 ##__VA_ARGS__
解析,这个和 printf
类似。
通用 PCIe 模块注册模板
static struct pci_driver rtl8169_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = rtl8169_pci_tbl,
.probe = rtl_init_one,
.remove = rtl_remove_one,
.shutdown = rtl_shutdown,
.driver.pm = pm_ptr(&rtl8169_pm_ops),
};
module_pci_driver(rtl8169_pci_driver);
综述
pci_register_driver
强调的是 PCIe 模块,是 PCIe 提供的直接接口。module_pci_driver
强调的是 module 管理模块,封装了pci_driver
的接口,不过也是 PCIe 提供的,在include/linux/pci.h
中。
不过这两个接口 module_pci_driver
会造成 init
函数和 exit
没有,也就是在 insmod
和 rmmod
的时候如果没有 device
存在(比如指定的 vendor id
和 device id
没有),那么不会调用。所以最好是直接使用 pci_register_driver
配合 module_init
函数,可以添加调试信息。
参考:
https://resource.tp-link.com.cn/pc/docCenter/showDoc?id=1634201944817599
https://elixir.bootlin.com/linux/v6.13-rc1/source/drivers/net/ethernet/realtek/r8169_main.c#L5612
微代码 - Linux PCIe 驱动是如何将 driver 和 device 绑定到一起,以及何时调用的?(pci_device_id table、配置空间)
北冥的备忘录 于 2024-12-08 13:27:23 发布
背景
Linux PCIe 设备驱动框架主体框架分为 device 和 driver 两部分。
driver 通过指定 vendorid 和 deviceid 告诉内核 PCIe 子模块,该驱动支持的绑定的设备有哪些,以便有对应设备的时候可以直接调用。
device 是 BIOS 期间通过 ACPI 扫描预留资源或者开启 pci=realloc
之后动态创建的 PCIe 设备,是一棵树,包括了 bus 和 id。
本文主要介绍这里提到的他们两个何时如何被绑定到一起,以及这里提到的关键 vendorid 和 deviceid 如何指定。
何时如何被绑定到一起
调用时机
预览图:
热插拔中部分流程:
预览图:
关于 insmod
和 rmmod
insmod
和 rmmod
的时候会调用模块的 init
和 exit
函数。如果是 module_pci_driver
这种方式注册的模块,insmod
和 rmmod
的时候只会 register
和 unregister
。但是 register
的时候如何发现对应的 PCIe 驱动指定的 vendorid
和 device
存在,就会直接调用 probe
。
注意设备如果存在,对于 Linux 的 PCI 子系统已经能够通过 lspci
查看到 PCIe 设备。insmod
的时候主要是告诉 PCI 子系统,这个驱动管理了哪些 vendor id
和 device
的驱动。然后会触发一次遍历匹配这个 vendorid
和 deviceid
。
关于 driver
和 device
匹配上
PCIe 驱动在 insmod
驱动,或者 plug device
时候都会触发调用。(对于 Linux 系统,热插拔 PCIe 驱动需要开启 cmdline 指定 pci=realloc
的参数。
设备和驱动指定 vendor id 和 device id 方式
设备的 vendor id 和 device id
在设备上 vendor id 和 device id 是写在 PCIe 的配置空间,更多参考:
微知 - PCIe 配置空间中哪个字段表示设备类型?有哪三种类型?哪个字段表示厂商 ID
北冥的备忘录于 2024-08-24 02:24:00 发布
PCIe 配置空间早期为 246 字节,由头部和设备相关部分两个区域组成。其中,头部为 64 字节,设备相关部分为 192 字节。头部的 64 字节称为预定义头部,用于存储设备的基本信息和通用控制部分。例如,PCIe 的厂商 ID 存储在第 0 字节和第 1 字节。
厂商 ID 由 PCI SIG 组织统一管理,每个厂商的 ID 是固定的。例如,Mellanox 网卡的厂商 ID 是15b3
,TP-Link 的厂商 ID 是10ec
。可以通过以下命令扫描特定厂商的设备:lspci -d 15b3: # 注意需要加上冒号
设备相关区域的剩余 192 字节包含各种功能模块。
PCIe 配置空间中,地址为0Eh
的字段用于存储设备类型,共有三种类型:
0x00
表示该设备是一个 PCIe 设备0x01
表示该设备是一个 PCI 桥0x02
表示该设备是一个 CardBus 桥
设备两个 id 在 PCIe 扫描后会存入内存中。
驱动
驱动注册的时候指定的 PCIe 驱动描述结构中会指定一个 id 表,这个表可以表示当前驱动可支持多个设备的 id 信息。比如 8168 的驱动程序:
static struct pci_device_id rtl8168_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8168), },
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8161), },
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x2502), },
{ PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x2600), },
{ PCI_VENDOR_ID_DLINK, 0x4300, 0x1186, 0x4b10,},
{0,},
};
static struct pci_driver rtl8168_pci_driver = {
.name = MODULENAME,
.id_table = rtl8168_pci_tbl,
.probe = rtl8168_init_one,
.remove = __devexit_p(rtl8168_remove_one)
};
可以看到这个驱动指定了 5 个设备(deviceid 和 vendorid)。
其中四个都是 Realtek 的 vendor,device 型号指定的有 8168、8161、2502、2600 四款设备。
所以如果加载驱动,有这 5 个设备在的话,就会直接调用 probe
对网卡进行初始化。
其他
热插拔几个打印
【pcieport】pciehp: Slot (1): Card present
Linux PCIe 子模块 pcieport
触发 pciehp
事件流程:
热插拔触发,系统打印对应 PCIe BDF 的信息:
pcieport 0000:ab:00.0: pciehp: Slot (1): Card present
//drivers/pci/hotplug/pciehp_ctrl.c
pciehp_handle_presence_or_link_change(...) {
switch (ctrl->state) {
case OFF_STATE: //当前(上一个)状态是OFF
ctrl->state = POWERON_STATE; //切换状态
ctrl_info(ctrl, "Slot(%s): Card present\n", slot_name(ctrl));
ctrl->request_result = pciehp_enable_slot(ctrl);
}
# pciehp_enable_slot会添加设备和busid,调用的一些关键点
pciehp_enable_slot
pciehp_configure_device
num = pci_scan_slot(); //扫描pcie设备
pci_hp_add_bridge(dev);
pci_assign_unassigned_bridge_resources() //获取分配的bridge resources信息
pcie_bus_configure_settings() //配置bus空间
pci_bus_add_devices() //将设备加入bus
}
状态切换后 PCIe 热插拔会触发分配新的 busid,以及新分配 BDF 放入 bus 上,bus 是一个 bridge。
【pci】 Max Payload Size set to
Max Payload Size set to 512 (was 128, max 512)
// 这里是制定了 TLP 的 payload 的大小
【probe】
这里打印了 vendor 和 device。在 scan device 的时候会调用到这里打印新找到的 vendor 和 device 的设备。这个打印是在 pci_scan_slot
之后找到新设备就会打印。
pci_scan_slot
pci_scan_single_device
pci_scan_device
pci_setup_device
pci_info(dev, "[%04x:%04x] type %02x class %#08x\n",
dev->vendor, dev->device, dev->hdr_type, dev->class);
关于 pci_device_id
的数据结构
struct pci_driver
是驱动的描述符,其中有pci_device_id
的 table 表。struct pci_device_id
是pci_device_id
的 table 表。struct pci_dev
是 PCIe 设备的结构,其中有 device 和 vendor 的配置信息。
综述
本文零散的记录了一些 PCIe 设备和驱动是如何匹配的以及热插拔的流程。关于匹配的关键点就是:
- 驱动中通过
pci_device_id
结构来指定支持的 PCIevendor
和device
的表。 - 设备中通过在 PCIe 配置空间的前四个字段来配置对应的
vendorid
和deviceid
(这个信息是出厂的时候写入的,而且一般是先写入 EEPROM,然后网卡固件加载的时候写入 PCIe 的配置空间)。
via:
-
微代码 - Linux PCIe 驱动模块注册的两种方式?(pci_register_driver、module_pci_driver、pci_unregister_driver)_pcie 字符设备 注册 - CSDN 博客
https://blog.csdn.net/essencelite/article/details/144322795 -
微代码 - Linux pcie 驱动是如何将 driver 和 device 绑定到一起,以及何时调用的?(pci_device_id table、配置空间)_pcie linux driver-CSDN 博客
https://blog.csdn.net/essencelite/article/details/144323790 -
微知-PCIe配置空间中哪个字段表示设备类型?有哪三种类型?哪个字段表示厂商ID_15b3是是什么设备id-CSDN博客
https://blog.csdn.net/essencelite/article/details/141477618