UEFI驱动程序的研究与开发
摘 要: UEFI是Intel推出的新一代BIOS技术。在分析UEFI基本结构和驱动程序模型的基础上,详细论述了基于USB协议栈的CC2531 ZigBee模块的设备驱动程序设计与开发过程,实现了在操作系统启动之前主机与USB设备间的信息交互,扩展了UEFI的功能。关键词: UEFI BIOS;CC2531;设备驱动程序
统一可扩展固件接口UEFI(Unified Extensible Firmware Interface)是Intel推出的新一代BIOS技术,旨在定义一套操作系统与平台固件之间完整的接口规范,为操作系统的引导提供标准环境[1]。相比于使用汇编语言编写的传统BIOS,UEFI采用模块化的设计、C语言风格的参数堆栈传递方式,借由动态链接的形式所构建出来的系统,更易于实现[2]。同时,UEFI是以32 bit或64 bit CPU保护模式运行,突破了传统16 bit实模式代码的寻址能力,可达到CPU的最大寻址空间。得益于这些优点,UEFI经过近10年的推广,已经成为主流,将使用传统BIOS的微型计算机系统逐渐淘汰出市场[3]。 UEFI中引入了UEFI驱动程序模型的概念,采用驱动/协议的结构开发驱动程序,驱动程序和硬件完全独立,具有很强的扩展性,从而使得UEFI平台下添加新的特性变得简单[4]。1 UEFI基本结构分析 UEFI主要由引导管理器、固件内核、协议、驱动模型等组件构成[4]。其中固件内核为UEFI的基础,它将底层硬件功能抽象化,为上层的引导管理器提供两种服务:引导服务和运行时服务。引导管理器是一个策略引擎,它使用固件内核提供的服务加载UEFI驱动程序和应用程序,并最终加载操作系统。 图1所示为UEFI的系统框架图。固件内核运行在PEI和DXE阶段,UEFI在PEI阶段直接与硬件层打交道,启动必需的硬件资源,譬如完成CPU和芯片组的初始化,进而满足DXE的执行启动条件;其后,系统在DXE阶段完成其他所有硬件的初始化,并为上层接口实现引导服务和运行时服务。引导管理器运行在DXE和BDS阶段,它通过加载框架驱动和平台驱动向上层提供UEFI服务和接口,通过加载应用程序扩展系统功能,并在BDS阶段提供一个引导菜单,供用户选择引导设备。最终引导加载器在ROM上加载操作系统加载器,将控制权移交给操作系统,完成操作系统的引导。
2 CC2531 ZigBee模块的设备驱动程序开发 CC2531是TI公司推出的一款用于IEEE 802.15.4或ZigBee应用的片上系统解决方案,它能够以非常低的成本建立网络节点,支持低功耗的无线通信,主要用于远程控制、家庭控制等领域[5]。CC2531集成了USB2.0功能模块,可以更加方便地与主机进行通信。 本文将CC2531 ZigBee模块(以下简称CC2531模块)通过USB接口与无线POS机的主机相连,CC2531模块通过ZigBee无线网络与POS机的外设(如打印机、客显、键盘等)进行信息交互。此方案实现了在启动操作系统之前对POS机及其外设进行相应远程检测和诊断的功能。图2为无线POS机系统的结构框图。2.1 UEFI驱动程序模型分析2.1.1 句柄和协议 UEFI驱动程序模型使用句柄代表设备,每个设备对应有自己的句柄,句柄由一个或多个协议组成。协议是一个以128 bit的全局唯一标识符GUID(Globally Unique Identifier)命名的结构体,是一些指针和数据结构体或者规范定义的接口函数指针的集合,协议代表设备提供的一类服务,服务的具体功能在设备驱动程序(以下简称驱动)中实现。开发者首先找到指定设备句柄上挂载的指定协议,再通过协议提供的接口访问设备驱动中实现服务的功能函数,对设备进行操作。图3所示为设备句柄和协议的结构图。
2.1.2 驱动程序模型执行流程 UEFI驱动程序模型是一种用于简化设备驱动设计和执行的机制,遵循驱动程序模型规范的UEFI驱动的可执行镜像大小会得到有效的减小[6]。UEFI驱动程序模型的执行流程图。
驱动程序模型采用UEFI 驱动载入、连接的形式来进行硬件的辨识、控制及系统资源掌控。在DXE阶段,系统调用引导服务的LoadImage()函数将驱动镜像文件加载到内存中,调用StartImage()函数执行驱动的入口函数来启动驱动。遵循模型规范的设备驱动在入口函数的初始化中不涉及任何硬件操作,仅仅实现驱动绑定协议(Driver Binding Protocol),协议包含3个接口函数:Support()、Start()和Stop()。Support()函数用来验证驱动程序与给定的设备句柄是否匹配;Start()函数负责驱动与句柄的连接,即将抽象 I/O 功能的协议安装到设备句柄上;相对应的,Stop()函数则会强制停止驱动对一个设备句柄的管理和控制,并卸载设备句柄在Start()中安装的所有协议。 DXE阶段执行完成后,虽然加载和启动了驱动,但还在等待着连接设备句柄。因此,在BDS阶段引导管理器将会调用引导服务的ConnectController()函数执行驱动的连接过程。该函数执行所有驱动绑定协议的Support()进行设备句柄的验证,若验证正确,则会继续调用Start()连接驱动和设备句柄,并在设备句柄上安装抽象I/O功能的协议。2.2 CC2531模块USB驱动协议栈 图5所示是由一系列驱动组成的多层次的、完整的 USB 驱动协议栈。处于上层的驱动会使用下层驱动提供的服务,并为更高层的驱动提供服务。首先,PCI总线驱动枚举设备时发现USB主机控制器,并在控制器句柄上安装EFI_PCI_IO_PROTOCOL协议,USB主机控制器驱动则使用EFI_PCI_IO_PROTOCOL协议提供的接口实现EFI_USB_HC_PROTOCOL协议。然后,USB总线驱动使用EFI_USB_HC_PROTOCOL协议服务实现对USB设备的枚举,生成CC2531设备句柄,并在设备句柄上安装 EFI_USB_IO_PROTOCOL协议。最后,CC2531模块设备驱动使用EFI_USB_IO_PROTOCOL协议服务,在CC2531模块设备句柄上安装EFI_USB_CCCONTROL_PROTOCOL协议,完成CC2531模块驱动协议栈的加载。
2.3 CC2531模块驱动程序设计 CC2531模块驱动作为设备驱动,遵循UEFI驱动程序模型规范,使用驱动绑定协议的方式实现设备驱动的加载和连接。同时,驱动还提供通信协议EFI_USB_CCCONTROL_PROTOCOL用于系统与CC2531模块间的通信,并通过ZigBee网络与无线POS机外设进行通信。2.3.1 绑定协议接口函数的实现
(1)Support():图6所示为Support函数实现流程图,函数首先检查给定的目标设备句柄是否安装有 EFI_USB_IO_PROTOCOL协议,若有,则说明USB总线驱动已经辨认出该USB设备。其次,使用EFI_USB_IO_PROTOCOL协议提供的功能接口函数获取目标USB设备的设备描述符,描述符中的接口号、接口类型、子类型和协议等字段指定了的USB设备的类型,检查这些字段就可得知驱动是否为CC2531模块设备驱动,如果匹配则说明加载的驱动正确,就会执行Start()函数。以下是部分实现代码:
EFI_STATUS USBCC2531DriverBindingSupported(…) {… //检查目标设备句柄是否安装有USB_IO协议 Status = gBS->OpenProtocol (Controller,&gEfiUsbIoProtocolGuid, (VOID **) &UsbIo, This->DriverBindingHandle, Controller, EFI_OPEN_PROTOCOL_BY_DRIVER ); … //读出目标USB设备的设备描述符 Status = UsbIo->UsbGetInterfaceDescriptor ( UsbIo, &InterfaceDescriptor); //检查是否为USB-CC2531的驱动 if(InterfaceDescriptor.InterfaceNumber==1
&&InterfaceDescriptor.InterfaceClass ==0x0a && InterfaceDescriptor.InterfaceSubClass==0 &&
InterfaceDescriptor.InterfaceProtocol == 0 ) { … } //关闭USB_IO协议 gBS->CloseProtocol (…); return Status; } (2)Start():Start()
函数的目标是使用USB总线驱动提供的协议服务在CC2531模块设备驱动中安装通信协议EFI_USB_CCCONTROL_PROTOCOL。函数首先打开设备句柄上挂载的EFI_USB_IO_PROTOCOL协议,使用该协议提供的接口函数UsbGetInterfaceDescriptor()和UsbGetEndpointDescriptor()得到USB设备的接口描述符和端点描述符,分析设备描述符中的字段以确定USB设备信息和接口类型等参数。其次,为驱动私有数据结构体分配内存,并使用设备描述符中的字段信息进行初始化。最后,安装EFI_USB_CCCONTROL_PROTOCOL协议,完成驱动与设备的连接。图7所示为Start()函数实现流程图。 (3)Stop():Stop()是Start执行流程的逆过程,该函数用于卸载EFI_USB_CCCONTROL_PROTOCOL协议,释放驱动私有数据结构体占用的内存资源,关闭EFI_USB_IO_PROTOCOL协议,断开CC2531模块驱动与设备句柄的连接。
2.3.2 设备通信协议结构体 为了实现UEFI系统与CC2531模块之间的数据通信,本文设计了EFI_USB_CCCONTROL_PROTOCOL作为驱动的通信协议。该协议中包括了UEFI系统与CC2531模块间数据发送和接收的接口函数,还有部分与POS机系统外设交互的命令函数,如系统外设启动和复位命令的接口函数。协议的部分成员函数定义如下:struct _EFI_USB_CCCONTROL_PROTOCOL {… EFI_USB_CONTROLL_RESET Reset;//复位设备 EFI_USB_TEXTOUTPUT_STRING OutputString;//数据发送 EFI_USBINPUT_STRING ReadInput; //数据读取 EFI_USB_DETECTDEVICE DetectDevice;//设备启动命令 EFI_EVENT WaitForInputString;//等待读入事件 };2.3.3 设备通信协议接口函数的实现 CC2531 模块的USB接口固件代码是基于USB通信设备类CDC(Communication Device Class)协议实现的。CDC由通信接口类和数据接口类组成,通信接口类主要负责设备的管理和控制,数据接口类则负责数据的传输。CC2531模块的USB接口采用端点0作为通信接口类中的控制端点来管理设备的枚举和命令控制,数据接口类使用块传输输入(IN)端点和块传输输出(OUT)端点实现数据的双向传输。因此,UEFI系统对CC2531模块的枚举和识别采用控制传输方式,数据通信则采用批量传输方式。 协议的发送接口函数为UsbCCDataTransmit(),该函数调用EFI_USB_IO_PROTOCOL协议提供的服务函数UsbBulkTransfer(),通过将数据写入输出端口完成数据从主机到CC2531模块的批量传输。设备驱动发送数据接口函数的部分代码实现如下所示: EFI_STATUS UsbCCDataTransmit (…) { … //选择发送数据的输出端口 Endpoint=UsbCC2531Device->BulkOutEndpointDescriptor; //等待发送或接收的超
时时间 Timeout = Timeout / USB_MASS_1_MILLISECOND; //发送数据 Status=UsbCC2531Device->UsbIo->UsbBulkTransfer
( UsbCC2531Device->UsbIo, Endpoint.EndpointAddress,Data, TransLen, Timeout, &Result); if (EFI_ERROR (Status)) { …} return Status; } CC2531模块设备驱动的数据接收操作亦通过调用函数UsbBulkTransfer()检测输入端口的缓冲队列完成。由于数据接收操作的被动性,驱动需要建立一套读操作的响应触发机制。本方案使用UEFI的事件(EVENT)机制实现对数据接收操作的响应:首先在驱动的Start()创建一个定时器事件,并在定时器的响应函数USBCC2531Timer-Handler()中周期性调用UsbCCDataRecevice()执行对输入端口缓冲队列的读操作,如果成功读到数据,则将读回的数据存入私有数据结构体的循环队列UsbDataQueue中。其次,创建一个等待事件WaitForInputString,并在等待事件的触发函数中检查循环队列,如有数据则读取,否则循环等待。部分数据读取操作的代码如下: EFI_STATUS UsbCCDataRecevice ( …) { … Status=UsbCC2531Device->UsbIo->UsbBulkTransfer (…); if (EFI_ERROR (Status)) { … } else { //将读取的数据存入链表 Enqueue