四Linux 2.6.10 PCIE 热插拔子系统代码分析热插拔框架为了提供PCI Express热插拔服务,Linux 2.6.10 PCIE热插拔(PCIEHP)子系统必须实现用户操作界面、热插拔服务程序和标准热插拔系统驱动等热插拔软件元素,并使软件的行为符合热插拔标准使用模式[4]。另外,一些特定的热插拔功能必须与支持热插拔的设备驱动结合在一起发挥作用。
PCIEHP核内部分的主体是一个核心线程,其控制逻辑以模块的方式加载入内核,负责监控PCI Express总线上的热插拔事件,并做相关处理;核外部分是一个用户空间的脚本,从内核中调用,并根据内核传回的信息执行后续处理过程。
1 热插拔驱动程序生命周期
从PCIEHP子系统启动,到子系统被卸载期间,PCIEHP完全接管系统中PCI Express插槽热插拔事件。
PCIEHP子系统启动时,首先要开启用户态守护进程。然后初始化通知机制,开启PCI Express热插拔事件处理核心线程,为操作系统中各总线数据结构分别预初始化一个热插拔插槽列表。接着,根据内核编译时是否指定ACPI式资源管理,初始化设备资源管理方式。最后,根据设备标识,在系统中注册PCI Express热插拔驱动程序,并将其绑定到总线上所有可能挂载热插拔插槽的PCIE桥接设备,然后初始化对应的热插拔控制器,分配资源,开启定时的中断轮询机制。
卸载PCIEHP子系统,首先释放热插拔控制器全局列表中每个控制器所占用的资源,释放热插拔插槽全局列表中每个插槽所占用的资源,终止通知机制的运行,结束PCI Express热插拔事件处理核心线程;接着,根据内核编译时是否指定ACPI式资源管理,释放相关设备占用的系统资源;然后,注销PCI Express热插拔驱动程序;在处理完当前热插拔事件后,终止核外守护进程。
在PCIEHP加载后,为了在热插拔执行过程中追踪其状态变化,我们把热插拔插槽的状态抽象如下:
- 静态 (STATIC STATE ),
正常工作或者空闲的时段, - 开启前闪烁提示态(BLINKINGON STATE)
发出开启请求到确认之前的时段 - 关闭前闪烁提示态 (BLINKINGOFF STATE)
发出关闭请求到确认之前的时段 - 开启态 (POWERON STATE)
执行开启操作的时段 - 关闭态(POWEROFF STATE)
执行关闭操作的时段 各个插槽状态之间的转换关系如图2所示:
图2 插槽状态转换图2 用户控制脚本/sbin/hotplug和核内外通信机制
在内核热插拔处理完毕后,它会在核内调用一个用户态脚本hotplug,这个脚本将根据内核提供的信息进行后续处理工作。它是设备热插拔通告的用户空间部分,它接收内核传出的热插拔操作类型和环境变量,处理设备挂载和卸载操作。通常,hotplug会根据设备类型调用一个策略脚本,针对设备和系统当前参数进行后续的配置工作。例如,对于网卡,可能会调用脚本指定IP地址,网关和域名服务器等等,对于存储设备,可能会调用mount命令来加载它到文件系统内。
内核传送给hotplug的信息包含热插拔操作类型(例如PCI),并且在一个环境变量中提供本次操作相关信息:
- 行为种类:添加或删除
- 对象设备所属PCI 类、子类和编程接口
- 对象制造商标志和设备标志
- 对象总线地址、插槽地址和功能函数编号
核外的后续配置工作涉及如下文件:
/etc/hotplug/pci.rc/ect/hotplug/pci.Agent/etc/rc.d/init.d/hotplug在内核中,由kernel/kmod.c中的函数
int call_usermodehelper (char *path, char **argv, char **envp, int wait)来开启用户态脚本/sbin/hotplug。在参数表中,path表示了所启动的核外应用程序的路径,argv是应用程序的参数表,envp是环境变量列表,wait则指出了是否同步等待应用程序执行完毕再返回执行结果的状态。
3 PCIE 热插拔模块构成
为了使用PCI Express Native Hotplug,我们必须在编译的时候开启对应的功能模块。
在内核配置中,PCIE Hotplug对应的开关为HOTPLUG_PCI_PCIE,它依赖于HOTPLUG_PCI。如果你的主板支持PCI Express Native Hotplug,可以选择Y;如果你只是想把这个驱动作为模块来编译,那么选择M,此模块叫做pciehp,在源代码\dirver\pci\hotplug\Kconfig文件中,你可以看到:
config HOTPLUG_PCI_PCIE tristate "PCI Express Hotplug driver" depends on HOTPLUG_PCI
完整的pciehp模块功能涉及到如下几个文件pci_hotplug_core.c, pciehp_core.c,pciehp_ctrl.c,pciehp_pci.c pciehp_hpc.c,另外根据是否开启了ACPI,包含pciehprm_acpi.c 或者pciehprm_nonacpi.c,在源代码\dirver\pci\hotplug\Makefile文件中,你可以看到:
pci_hotplug-objs := pci_hotplug_core.opciehp-objs := pciehp_core.o \ pciehp_ctrl.o \ pciehp_pci.o \ pciehp_hpc.oifdef CONFIG_ACPI_BUS pciehp-objs += pciehprm_acpi.oelse pciehp-objs += pciehprm_nonacpi.oendif
代码的主要任务就是在所有支持热插拔的PCIE桥上加载热插拔驱动程序,监控热插拔事件,并根据类型,如是热插入事件、热拔出事件还是电源故障等分别予以处理。
热插拔驱动的加载热插拔驱动程序的加载所进行的主要工作是开启并初始化PCIE热插拔的内核线程,这部分代码位于/driver/pci/hotplug/pciehp_core.c中。入口函数为:
static int __init pcied_init(void)
#ifdef CONFIG_HOTPLUG_PCI_PCIE_POLL_EVENT_MODE pciehp_poll_mode = 1;#endif retval = pcie_start_thread(); if (retval) goto error_hpc_init; retval = pciehprm_init(PCI); if (!retval) { retval = pci_register_driver(&pcie_driver); dbg("pci_register_driver = %d\n", retval); info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); }
pcied_init中所涉及的关键函数分析如下:
retval = pcie_start_thread();
初始化并开启通知机制:
pciehp_event_start_thread()启动事件监控处理线程
然后初始化slot列表,系统中每个bus给一个slot列表。
struct pci_func *pciehp_slot_list[256]; 都设为NULL
retval = pciehprm_init(PCI);
原型:int pciehprm_init(enum php_ctlr_type ctlr_type)
初始化资源(区别两种情况:acpi和非acpi的) 在非acpi的初始化方式下,调用空函数legacy_pciehprm_init_pci();在pcihprm_nonacpi.h中,定义了irq_info,irq_routing_table两个结构。在acpi初始化方式下,通过pciehprm_acpi_scan_pci()在acpi树下遍历搜寻PCI设备。不论acpi和非acpi的,都要求php_ctlr_type为PCI。
retval = pci_register_driver(&pcie_driver);
注册并初始化PCI桥热插拔驱动程序模块。
把设备驱动加入已注册设备驱动列表,即使在期间没有相应设备出现驱动程序仍然保持有效。
int count = 0; /* initialize common driver fields */
用来泛化之,可以把drv->driver看作为drv的基类信息
drv->driver.name = drv->name; drv->driver.bus = &pci_bus_type; drv->driver.probe = pci_device_probe; drv->driver.remove = pci_device_remove; drv->driver.kobj.ktype = &pci_driver_kobj_type; pci_init_dynids(&drv->dynids); count = driver_register(&drv->driver);
注意:这里是如何从基类drv->driver一般设备的驱动向子类drv这个pci设备的驱动逆向关联的--使用CONTAINER宏
pcie_driver为PCIE驱动程序对象,定义在 pciehp_core.c中
static struct pci_driver pcie_driver = { //驱动名称定义为"pciehp".name= PCIE_MODULE_NAME, // id_table指定探测函数probe所应用的范围,这里在表中指定为所有的PCI桥设备 .id_table = pcied_pci_tbl, //probe为指定的设备探测函数 .probe = pcie_probe, };static struct pci_device_id pcied_pci_tbl[] = { { //此处选择所有PCI桥.class = ((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), .class_mask = ~0, .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, },
设备探测pcie_probe:
static int pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent)in pcie-core.c
已经确定了pdev就是pcie_driver 所匹配的PCI桥设备,而且它在pcie_driver中所对应的设备特征号是*ent,就可以对其进行进一步的初始化和探测。
具体行为如下:
- 绑定热插拔插槽
- 设置了控制器及其状态
- 注册中断处理函数
- 读写配置头,然后分配资源,最后对插槽检测,挂接
|