在内核中,由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,在源代码dirverpcihotplugKconfig文件中,你可以看到:
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,在源代码dirverpcihotplugMakefile文件中,你可以看到:
pci_hotplug-objs := pci_hotplug_core.o pciehp-objs := pciehp_core.o pciehp_ctrl.o pciehp_pci.o pciehp_hpc.o ifdef CONFIG_ACPI_BUS pciehp-objs += pciehprm_acpi.o else pciehp-objs += pciehprm_nonacpi.o endif
代码的主要任务就是在所有支持热插拔的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 ", retval); info(DRIVER_DESC " version: " DRIVER_VERSION " "); }
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,就可以对其进行进一步的初始化和探测。
具体行为如下:
热插拔事件的监控处理线程
热插拔驱动程序对于热插拔事件的轮询和通知采用异步机制,对热插拔功能部件的操纵是通过读写相关寄存器组进行的。主要功能函数关系请参见图3:
图3 插槽热插拔事件处理函数关系
图3中主要函数的功能介绍如下:
A插槽事件监控线程
作为热插拔活动最直接的信息,插槽事件由硬件操作并置位相关寄存器组,系统软件可以通过定时轮询或者中断方式获取事件信息,执行对应的事件预处理函数。插槽事件如下:
php_ctlr->int_poll_timer.function = &int_poll_timeout 其中php_ctlr是热插拔控制器状态php_ctlr_state_s类型,它定义于pciehp_hpc.h中,记录当前热插拔控制器重要状态,被用作HPC(controller)的控制器句柄;热插拔控制器controller位于文件pciehp.h中,描述了PCIE热插拔控制器的特征;
定时轮询函数原型如下:
static void int_poll_timeout(unsigned long lphp_ctlr)
其中调用pcie_isr( 0, (void *)php_ctlr, NULL );
当最后一个参数是NULL时,采用polling机制。
static irqreturn_t pcie_isr(int IRQ, void *dev_id, struct pt_regs *regs);
轮询机制通过定期(2second,使用一个计时器)读取ctrller对应的状态寄存器,来获取事件,然后调用ctrl状态参量对应的事件处理函数。也就是按钮、电源、MRL等等事件处理函数,并分别调用event_semaphore来激活event_thread.
B事件预处理函数
在插槽事件监控线程截获插槽事件后,它根据事件类型调用这组处理函数,执行完毕后,填写对应控制器上所挂接的事件队列,并激活睡眠等待的处理线程。可以激活睡眠中的处理线程的函数包括如下几个:
pciehp_handle_attention_button :处理按钮事件
pciehp_handle_switch_change:处理开关状态改变事件
pciehp_handle_presence_change:处理存在性状态变化事件
pciehp_handle_power_fault:处理电源故障事件
pushbutton_helper_thread:按钮动作处理线程
C热插拔事件处理核心线程event_thread
event_thread的处理过程如下:
在一个无限循环中,阻塞等待插槽事件发生
当线程被某一事件唤醒后,
如果 热插拔请求已通过延时确认
进入热插拔请求处理函数
否则
轮询热插拔控制器队列:
把控制器作为参数传给插槽事件处理函数
其中
if (pushbutton_pending) pciehp_pushbutton_thread(pushbutton_pending); else if (surprise_rm_pending) pciehp_surprise_rm_thread(surprise_rm_pending);
前一个处理按钮事件,后一个处理突然拔出事件。
参数pushbutton_pending是由如下函数提供的:
static void pushbutton_helper_thread(unsigned long data) { pushbutton_pending = data; up(&event_semaphore);
而这个函数又被作为定时task事件的行动部分,赋值给
static void interrupt_event_handler(struct controller *ctrl)中的
p_slot->task_event.function:
函数:
p_slot->task_event.function
= (void (*)(unsigned long)) pushbutton_helper_thread;
参数:
p_slot->task_event.data
= (unsigned long) p_slot;
D插槽事件处理函数interrupt_event_handler
根据控制器信息,获取控制器对应插槽事件
尚有事件等待处理,则执行以下内容: