您当前的位置:五五电子网电子知识单片机-工控设备嵌入式系统-技术Linux 2.6.10内核下PCI Express Native热插拔框架的实现机制 正文
Linux 2.6.10内核下PCI Express Native热插拔框架的实现机制

Linux 2.6.10内核下PCI Express Native热插拔框架的实现机制

点击数:7468 次   录入时间:03-04 11:47:25   整理:http://www.55dianzi.com   嵌入式系统-技术

在内核中,由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 插槽热插拔事件处理函数关系

图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

根据控制器信息,获取控制器对应插槽事件
尚有事件等待处理,则执行以下内容:

上一页  [1] [2] [3]  下一页


本文关键字:热插拔  Linux  嵌入式系统-技术单片机-工控设备 - 嵌入式系统-技术