Linux PCIe 驱动 | 注册 / 绑定 / 调用

注:本文为 “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_initmodule_exit

并且驱动 initexit 的名字是 xxx_init 开始的,比如 rtl8169_pci_driver 就是:rtl8169_pci_driver_init

然后宏定义内部使用的是:pci_register_driver,所以最后本质都是 pci_register_driverexit 函数就是 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 没有,也就是在 insmodrmmod 的时候如果没有 device 存在(比如指定的 vendor iddevice 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 如何指定。

何时如何被绑定到一起

调用时机

预览图:

热插拔中部分流程:
预览图:

在这里插入图片描述

关于 insmodrmmod

insmodrmmod 的时候会调用模块的 initexit 函数。如果是 module_pci_driver 这种方式注册的模块,insmodrmmod 的时候只会 registerunregister。但是 register 的时候如何发现对应的 PCIe 驱动指定的 vendoriddevice 存在,就会直接调用 probe

注意设备如果存在,对于 Linux 的 PCI 子系统已经能够通过 lspci 查看到 PCIe 设备。insmod 的时候主要是告诉 PCI 子系统,这个驱动管理了哪些 vendor iddevice 的驱动。然后会触发一次遍历匹配这个 vendoriddeviceid

关于 driverdevice 匹配上

PCIe 驱动在 insmod 驱动,或者 plug device 时候都会触发调用。(对于 Linux 系统,热插拔 PCIe 驱动需要开启 cmdline 指定 pci=realloc 的参数。

设备和驱动指定 vendor id 和 device id 方式

设备的 vendor id 和 device id

在设备上 vendor iddevice 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_idpci_device_id 的 table 表。
  • struct pci_dev 是 PCIe 设备的结构,其中有 device 和 vendor 的配置信息。

在这里插入图片描述

综述

本文零散的记录了一些 PCIe 设备和驱动是如何匹配的以及热插拔的流程。关于匹配的关键点就是:

  • 驱动中通过 pci_device_id 结构来指定支持的 PCIe vendordevice 的表。
  • 设备中通过在 PCIe 配置空间的前四个字段来配置对应的 vendoriddeviceid(这个信息是出厂的时候写入的,而且一般是先写入 EEPROM,然后网卡固件加载的时候写入 PCIe 的配置空间)。

via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值