全部博文(2005)
分类: 嵌入式
2009-09-11 14:11:57
linux 2.6.10内核下PCI Express Native热插拔框架的实现机制 |
级别: 初级 王兵 ()国防科学技术大学计算机学院软件所 2005 年 8 月 01 日 PCI 热插拔技术,可以有效避免由更换外设引起的服务器系统停机,对于提高服务器系统可用性和可扩展性意义重大。本文讨论了PCI Express热插拔所涉及的软件因素,并基于此,剖析了Linux 2.6.10内核下PCI Express 插槽热插拔子系统的关键实现机制。 1997年,PCI SIG制定了第一个PCI热插拔规范,其中定义了支持热插拔所必需的平台、板卡和软件元素。PCI SIG推出了标准热插拔控制器规范(SHPC SPEC),其中明确了热插拔的标准使用模式和严格的寄存器组要求,并且允许操作系统提供商在平台特定的软件之外提供热插拔支持,逐步完成了热插拔标准制 定工作,进入技术的全面推广阶段。 2002年以后,Intel把热插拔作为一种天然属性赋予新推出的PCI Express规范,PCI Express热插拔总结了五年来工业标准的经验,具有如下特点:
具有完整功能的PCI Express热插拔系统在平台硬件和固件支持之外,还必须有操作系统以及设备驱动程序的支持。 在各大芯片厂商纷纷推出支持PCI热插拔的产品的同时,Microsoft,Novell,SCO等公司在他们相关的操作系统中也都包括了支持热插 拔的技术, Novell NetWare 4.11&5和SCO UnixWare 7以及更新版本支持完整PCI热插拔(热替换和热添加)。Microsoft Windows NT 4.0利用Compaq 服务器支持盘(SSD) for NT 4.0,提供了PCI热替换支持,进入2000年后,Microsoft Windows 2000也内建了对PCI热插拔技术的全面软件支持。对于GNU/Linux,从2001年1月份的内核2.4版本开始,PCI 热插拔开始成为其标准特性之一,到2.6 版本,热插拔功能已经被整合进入核心设备模型。现在,几乎所有的Linux发行版本,包括RedHat, Debian和 United Linux,都对PCI 热插拔提供了良好支持,并分别有所扩展。BSD分支在这方面起步较晚,OpenBSD在2004年三月份的3.6版本中才出现了不含设备驱动的设备热插拔 参考框架,而FreeBSD 到5.3版本为止,尚未提供对PCI 热插拔的支持。 作为2002年出现的规范,各个通用操作系统对PCI Express的支持才刚刚起步。在微软公司的 Windows 2000, Windows XP, and Windows Server 2003中,PCI Express设备被当作PCI设备进行热插拔,其他PCI Express的高级功能可以由固件激活。在Windows Longhorn中,才提供对PCI Express高级特性的原生支持。GNU/Linux在内核版本2.6.9中开始专门提供对于PCI Express 热插拔的支持,中间历经软件架构的改动,逐渐发展为以内核版本2.6.10以及内核版本2.6.12下两种不同的PCI Express热插拔驱动架构。本文主要讨论2.6.10所采用的PCI Express热插拔驱动架构,并在最后给出2.6.12的新特性。
根据规范,一个完整的 Native PCI Express 热插拔系统需要几方面的相互配合,分别为硬件元素、固件元素和软件元素。 硬件元素是指主板总线系统的电气特性方面的支持,包括热插拔控制器(Hot-Plug Controller)、卡槽电源切换逻辑(Card Slot Power Switching Logic)、板卡重置逻辑(Card Reset Logic)、电源指示灯(Power Indicator)、提示按钮(Attention Button)和板卡存在检测引脚(Card Present Detect Pins)等等;固件元素是指主板BIOS必须对热插拔提供的支持,要实现Native PCI Express热插拔,固件必须提供OSHP方法或ACPI _OSC方法之一;软件元素是指操作系统操综合使用PCI Express 热插拔所必须提供的功能组件。 为了操纵平台的硬件元素,提供PCI Express热插拔服务,我们必须实现表1所示的主要热插拔软件元素。 PCIE热插拔(PCIEHP)子系统中的标准热插拔系统驱动程序通过检查PCI Express性能数据结构中的插槽性能寄存器(Slot Capabilities register),来获取硬件的热插拔部件信息。这些寄存器所能反映的信息如下:
一旦软件完成了PCI Express设备热插拔功能的开启和配置工作,热插拔行为(例如移出或插入请求,电源故障)就可以向系统热插拔处理机制提交系统中断和电源管理事件。这 种热插拔处理机制由操作系统独立提供,图1展示了PCI Express Native热插拔的服务模型,并展示了它与传统的基于ACPI的模型之间的区别。
为了提供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加载后,为了在热插拔执行过程中追踪其状态变化,我们把热插拔插槽的状态抽象如下:
各个插槽状态之间的转换关系如图2所示: 2 用户控制脚本/sbin/hotplug和核内外通信机制 在内核热插拔处理完毕后,它会在核内调用一个用户态脚本hotplug,这个脚本将根据内核提供的信息进行后续处理工作。它是设备热插拔通告的用户 空间部分,它接收内核传出的热插拔操作类型和环境变量,处理设备挂载和卸载操作。通常,hotplug会根据设备类型调用一个策略脚本,针对设备和系统当 前参数进行后续的配置工作。例如,对于网卡,可能会调用脚本指定IP地址,网关和域名服务器等等,对于存储设备,可能会调用mount命令来加载它到文件 系统内。 内核传送给hotplug的信息包含热插拔操作类型(例如PCI),并且在一个环境变量中提供本次操作相关信息:
核外的后续配置工作涉及如下文件: /etc/hotplug/pci.rc 在内核中,由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文件中,你可以看到:
完整的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文件中,你可以看到:
代码的主要任务就是在所有支持热插拔的PCIE桥上加载热插拔驱动程序,监控热插拔事件,并根据类型,如是热插入事件、热拔出事件还是电源故障等分别予以处理。 热插拔驱动程序的加载所进行的主要工作是开启并初始化PCIE热插拔的内核线程,这部分代码位于/driver/pci/hotplug/pciehp_core.c中。入口函数为: static int __init pcied_init(void)
pcied_init中所涉及的关键函数分析如下: retval = pcie_start_thread(); 初始化并开启通知机制: pciehp_event_start_thread()启动事件监控处理线程 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桥热插拔驱动程序模块。 把设备驱动加入已注册设备驱动列表,即使在期间没有相应设备出现驱动程序仍然保持有效。
用来泛化之,可以把drv->driver看作为drv的基类信息
注意:这里是如何从基类drv->driver一般设备的驱动向子类drv这个pci设备的驱动逆向关联的--使用CONTAINER宏 pcie_driver为PCIE驱动程序对象,定义在 pciehp_core.c中
设备探测pcie_probe:
已经确定了pdev就是pcie_driver 所匹配的PCI桥设备,而且它在pcie_driver中所对应的设备特征号是*ent,就可以对其进行进一步的初始化和探测。 具体行为如下:
热插拔驱动程序对于热插拔事件的轮询和通知采用异步机制,对热插拔功能部件的操纵是通过读写相关寄存器组进行的。主要功能函数关系请参见图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) B事件预处理函数 在插槽事件监控线程截获插槽事件后,它根据事件类型调用这组处理函数,执行完毕后,填写对应控制器上所挂接的事件队列,并激活睡眠等待的处理线程。可以激活睡眠中的处理线程的函数包括如下几个: pciehp_handle_attention_button :处理按钮事件 C热插拔事件处理核心线程event_thread event_thread的处理过程如下:
前一个处理按钮事件,后一个处理突然拔出事件。 参数pushbutton_pending是由如下函数提供的:
而这个函数又被作为定时task事件的行动部分,赋值给 static void interrupt_event_handler(struct controller *ctrl)中的 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. 更新插槽状态。
热插拔PCI板卡可以使用提示按钮或用户界面来进行,下面我们介绍使用用户界面来启动热插入和热拔出的操作过程,以及Fedora C4T2下所采用的方式。 设备的热插入 1. 操作员安装卡,闭合插槽保护锁,保护锁感应器通知热插拔控制器把连接信号接通到插槽。 2. 然后,操作员通知热插拔服务程序:卡已经被安装并可以激活。软件提示用户对此进行确认。 3. 在操作员请求连接后,热插拔服务程序向控制着热插拔控制器的热插拔系统驱动程序下达命令,闪烁插槽的电源指示灯,提示操作员此时不可以拔动适配卡。 4. 在热插拔软件对此请求进行确认期间内,电源指示灯继续闪烁。注意此时软件可能会拒绝这个安装请求(例如,安全策略此刻禁止插槽被激活)。另外,如果请求没 有生效,软件拒绝请求并对热插拔控制器发出命令关闭电源指示灯。规范建议软件通过一条消息通知操作员请求被拒绝的原因。 5. 如果请求被确认,热插拔服务程序对热插拔系统驱动发出请求,为插槽加电。 6. 加电后,软件发出命令完全打开电源指示灯。 7. 当link training完成后,操作系统指示平台配置程序赋予适配卡必需的资源,来配置适配卡的功能。 8. 操作系统为PCI Express设备中的功能寻找恰当的驱动程序,并加载之。 9. 接着系统调用驱动程序的初始化入口,并执行驱动的初始化代码。这些代码完成设备的设置,并填写设备的PCI 配置命令寄存器的相关标志位来激活设备。 1. 操作员通过指定适配卡所在物理插槽号码来初始化移出请求。 2. 软件弹出窗口要求操作员确认请求。注意,此时电源指示灯保持开启状态。 3. 操作员确认请求后,热插拔服务程序向热插拔系统驱动发出请求,要求热插拔控制器闪烁电源指示灯。注意此时软件可能会拒绝这个移出请求(例如,适配卡目前正 被关键系统功能所使用)。另外,如果请求没有被确认,软件将拒绝请求并对热插拔控制器发出命令,重新开启电源指示灯。规范建议软件通过一条消息通知操作员 请求被拒绝的原因。 4. 如果请求被确认,热插拔服务程序将命令适配卡的设备驱动保持静默,也就是说驱动一方面必须停止向适配卡发出请求,另一方面必须完成或者终止所有已经发出的请求,并禁止适配卡产生新的事务(包括中断)。 5. 软件发出命令,通过在插槽所连接的根端口或交换端口中的链接控制寄存器禁掉适配卡的链接。这使得链接两侧的端口均被禁止。 6. 软件指示热插拔控制器禁掉插槽。 7. 成功切断电源后,软件发出关闭电源指示灯命令。指示灯熄灭后,操作员可以开始安全地从插槽移出适配卡。打开插槽安全锁,热插拔控制器从插槽上撤除所有的信号(例如SMBus 和Vaux),此时卡可以被移出。 8. 操作系统释放内存空间,I/O空间,中断线等曾经属于该设备的系统资源。 如果在最近一次编译中选择了PCI Express 热插拔功能,而且驱动是以模块方式存在,那么,可以在命令行下键入以下内容: modprobe pciehp 如果驱动成功,则可以在/sys/bus/pci/slots/下面发现以可热插拔插槽编号命名的目录,进入相关目录,可以进行下一步操作。 echo 1 >power开启某个插槽上的电源 ,进行热插入 echo 0 >power关闭某个插槽上的电源,执行热拔出 若不能加载pciehp驱动,一般是由于硬件不支持或者固件缺少OSHP方法或ACPI _OSC方法之一。
在linux2.6.10中,Linux驱动程序模型要求物理设备被单独的驱动程序独占访问。 PCI Express端口是一个拥有许多独立功能的PCI-PCI桥设备,作为一个简洁的方案,每个功能要分别实现其自己的驱动程序,但是这样造成了多个驱动程 序在唯一的PCI-PCI桥设备中出现竞争的状况。也就是说,虽然PCI Express提供了如Power Management (PME)、 Advanced Error Reporting (AER)、 Hot-Plug (HP) 和Virtual Channel (VC) access等多种功能,但是如果某个PCI Express端口的native hotplug 驱动程序加载后,它就会独占这个PCI-PCI桥的端口,内核就不能再于其上加载其他功能的驱动程序了。为解决这个问题,在Linux内核版本 2.6.12中,PCI Express的热插拔又有所改变,提出了PCI Express 端口总线驱动程序(PCI Express Port Bus Driver)的概念。 在实现上,PCI Express 端口总线驱动程序管理主板上的所有PCI Express 端口,并且把所有提供的服务请求发送到对应的服务驱动程序上。其优点概括如下:
例如,在注册热插拔驱动程序时,不再使用pci_register_driver直接向系统注册,而是使用int pcie_port_service_register(struct pcie_port_service_driver *new) 向端口总线驱动程序注册;在注销热插拔驱动程序时,不再使用pci_unregister_driver直接向系统注销,而是使用void pcie_port_service_unregister(struct pcie_port_service_driver *new)来向端口总线驱动程序注销;而端口总线驱动程序是直接注册注销到系统的。 目前,PCI Express 端口总线驱动程序模型还在发展演变中。
根据可用性理论,系统的可用度可以使用下列公式来计算: 在高负荷运转的服务器上,商用可靠性的元件的故障更换并非小概率事件,对于更换故障元件和升级配件这样的事件,没有热插拔支持的系统必须停机断电进 行处理,而具备热插拔支持的系统则仅仅需要很少的软件切换和拔插时间开销。从上面的公式我们可以看出,MTTR值越小,系统的可用性就越高。我们定义了一 个比例因子V,V= MTTRnoHP/MTTRHP,根据业界经验,V的取值一般在10-100之间。描述可用性的一种常用的方法是使用"9"。如三个 9 表示 99.9% 的可用性,它表示一年大约有 8.5 小时的服务中断期。四个 9 (99.99%) 是更高一级的可用性,表示一年大约有 1 小时的服务中断期。根据可用度公式计算,在单个元件可靠度不变的情况下,Linux操作系统对PCI Express热插拔的支持,可以使服务器系统的外设相关可用性跃升一个等级(1个9),同时,PCI Express热插拔技术使得在线更换和升级PCI Express外设板卡成为可能,这使系统获得了良好的可扩展性。 综上所述,本文讨论了PCI Express热插拔所涉及的软件因素,分析了linux2.6.10的PCI Express插槽热插拔功能PCIEHP子系统,并对热插拔支持在提高服务器系统外设相关可用性的作用进行了定量的分析。 为了继续提高操作系统可用性和可扩展性支持能力,Linux PCI Express hotplug以下方面还有待发展:继续完善热插拔架构的开放性,以提供完整统一的接口供驱动开发人员编写其他设备的热插拔支持模块;在插槽热插拔之外, 提供对Server IO Module(SIOM)热插拔的支持;完善热替换和热升级技术。这些问题都是非常具有挑战性的。
|