Linux 2.6.10内核下PCI Express Native热插拔框架的实现机制(2)热插拔框架2
- UID
- 1066743
|
Linux 2.6.10内核下PCI Express Native热插拔框架的实现机制(2)热插拔框架2
retval = pci_register_driver(&pcie_driver);
注册并初始化PCI桥热插拔驱动程序模块。
把设备驱动加入已注册设备驱动列表,即使在期间没有相应设备出现驱动程序仍然保持有效。
1
2
| int count = 0;
/* initialize common driver fields */
|
用来泛化之,可以把drv->driver看作为drv的基类信息
1
2
3
4
5
6
7
| 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中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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:
1
2
3
| 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的处理过程如下:
在一个无限循环中,阻塞等待插槽事件发生
当线程被某一事件唤醒后,
如果 热插拔请求已通过延时确认
进入热插拔请求处理函数
否则
轮询热插拔控制器队列:
把控制器作为参数传给插槽事件处理函数
其中
1
2
3
4
| if (pushbutton_pending)
pciehp_pushbutton_thread(pushbutton_pending);
else if (surprise_rm_pending)
pciehp_surprise_rm_thread(surprise_rm_pending);
|
前一个处理按钮事件,后一个处理突然拔出事件。
参数pushbutton_pending是由如下函数提供的:
1
2
3
4
| 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. 电源故障
弹出提示信息
4. 其他
读取插槽的功能寄存器,更新状态信息。
E热插拔请求处理函数pciehp_pushbutton_thread
一个定期运行的程序,处理当前插槽上阻塞的请求,根据请求类型:对插槽中的设备进行热移出(F)或者热添加操作(G)。
F插槽热拔出设备pciehp_disable_slot
保证所除去的不是视频控制器
卸载所除去的适配卡占用的系统资源,更新总线结构,关闭电源
通知用户态守护进程
更新插槽状态
G插槽热添加设备pciehp_enable_slot
1. 执行适配卡添加的一系列相关操作: 失败恢复预处理,存在性检验、打开电源,检查link training状态,获取设备基本信息,配置设备,为设备建立相关数据结构,挂接到上级总线等。
2. 为新添加的设备查找并挂接驱动程序。
3. 特别地,对于桥接设备,把它挂接到上级总线后,还要继续对其下级总线进行扫描和挂接。
4. 通知用户态守护进程。
5. 更新插槽状态。 |
|
|
|
|
|