编者按
芒的“跟我学”系列文章之一。 UEFI驱动模型和USB协议栈。
什么是UEFI驱动模型
驱动程序包含有关允许计算机与设备通信的硬件设备的信息。 一个完整的驱动框架至少需要完成以下任务:
1.查找支持的硬件设备
2.给这个硬件设备安装驱动
3.从硬件设备上卸载驱动程序
UEFI 中引入了驱动程序模型的概念,以简化设备/总线驱动程序的设计和实现。 下面简单介绍一下UEFI驱动模型:
UEFI驱动模型的核心是通过UEFI驱动绑定协议来管理驱动。 符合 UEFI 驱动程序模型的驱动程序必须在其句柄上安装 UEFI 驱动程序绑定协议。 该协议提供了三个接口函数:Supported()、Start()、Stop()。
1. Supported函数用于检测一个设备控制器是否支持该驱动,如果支持则返回EFI_SUCCESS,否则返回错误信息
2. Start函数用于将驱动安装到设备控制器上,调用InstallProtocolInterface()或InstallMultipleInterface()将驱动协议安装到ControllerHandle上。
3. Stop函数用于卸载驱动,调用UninstallProtocolInterface或UninstallMultipleInterface()从ControllerHandle中卸载驱动协议。
UEFI驱动模型定义了两种基本类型的驱动,设备驱动和总线驱动。 设备驱动程序并不创建新的设备句柄,只是在已有的设备句柄上安装其他协议接口,如简单的文本输入输出协议、Block I/O协议、网络协议等。Start函数决定了哪个I/ O 协议安装在设备句柄上,Stop 函数使驱动程序卸载 Start 函数安装的所有协议接口。 总线驱动程序会为在自己的总线上找到的子控制器创建新的设备句柄,然后在这些新创建的子句柄上安装协议接口(总线服务协议和提供I/O抽象的设备路径协议)。 下图是总线控制器调用Start函数前后的情况。
连接总线控制器的虚线表示总线控制器的父控制器与该总线控制器的连接。 节点 ABCDE 表示连接到总线控制器的子控制器。
下面以USB驱动为例io1.1鼠标驱动,了解UEFI驱动模型和USB设备的工作原理。
USB 设备的工作原理
对于USB设备,必须提供主机控制器驱动程序、USB总线驱动程序和USB设备驱动程序,USB设备才能正常工作。
下图是USB驱动栈的模型示例,以及USB驱动生成和使用的一些协议。 图中包含三个USB设备,分别是USB存储设备、USB键盘和USB鼠标。
在上面的示例中,有一个 USB 主机控制器连接到 PCI 总线。 PCI 总线驱动程序为 USB 主机控制器句柄安装设备路径协议 EFI_DEVICE_PATH_PROTOCOL 和 PCI I/O 协议 EFI_PCI_IO_PROTOCOL。 USB 主机控制器驱动程序将使用 EFI_PCI_IO_PROTOCOL 提供的服务在其句柄上安装主机控制器协议 EFI_USB2_HC_PROTOCOL。 主机控制器协议接口如下:
typedef 结构 _EFI_USB2_HC_PROTOCOL {
EFI_USB2_HC_PROTOCOL_GET_CAPABILITY 获取能力;
EFI_USB2_HC_PROTOCOL_RESET 重置;
EFI_USB2_HC_PROTOCOL_GET_STATE 获取状态;
EFI_USB2_HC_PROTOCOL_SET_STATE 设置状态;
EFI_USB2_HC_PROTOCOL_CONTROL_TRANSFER 控制传输;
EFI_USB2_HC_PROTOCOL_BULK_TRANSFER 批量传输;
EFI_USB2_HC_PROTOCOL_ASYNC_INTERRUPT_TRANSFER 异步中断传输;
EFI_USB2_HC_PROTOCOL_SYNC_INTERRUPT_TRANSFER SyncInterruptTransfer;
EFI_USB2_HC_PROTOCOL_ISOCHRONOUS_TRANSFER 等时传输;
EFI_USB2_HC_PROTOCOL_ASYNC_ISOCHRONOUS_TRANSFER 异步等时传输;
EFI_USB2_HC_PROTOCOL_GET_ROOTHUB_PORT_STATUS GetRootHubPortStatus;
EFI_USB2_HC_PROTOCOL_SET_ROOTHUB_PORT_FEATURE SetRootHubPortFeature;
EFI_USB2_HC_PROTOCOL_CLEAR_ROOTHUB_PORT_FEATURE ClearRootHubPortFeature;
UINT16 重大修订;
UINT16 次要版本;
} EFI_USB2_HC_PROTOCOL;
USB 总线驱动程序使用 EFI_USB2_HC_PROTOCOL 提供的服务。 USB 总线驱动程序将发现连接到 USB 总线的 USB 设备。 在此示例中,USB 总线发现了 USB 键盘、USB 鼠标和 USB 存储设备。 因此,USB总线驱动会创建3个子句柄,并分别为这些句柄安装设备路径协议EFI_DEVICE_PATH_PROTOCOL和USB I/O协议EFI_USB_IO_PROTOCOL。 USB I/O协议接口结构如下:
typedef 结构 _EFI_USB_IO_PROTOCOL {
EFI_USB_IO_CONTROL_TRANSFER UsbControlTransfer;
EFI_USB_IO_BULK_TRANSFER UsbBulkTransfer;
EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER UsbAsyncInterruptTransfer;
EFI_USB_IO_SYNC_INTERRPUT_TRANSFER UsbSyncInterruptTransfer;
EFI_USB_IO_ISOCHRONOUS_TRANSFER UsbIsochronousTransfer;
EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSER UsbAsyncIsochronousTransfer;
EFI_USB_IO_GET_DEVICE_DESCRIPTOR UsbGetDeviceDescriptor;
EFI_USB_IO_GET_CONFIG_DESCRIPTOR UsbGetConfigDescriptor;
EFI_USB_IO_GET_INTERFACE_DESCRIPTOR UsbGetInterfaceDescriptor;
EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR UsbGetEndpointDescriptor;
EFI_USB_IO_GET_STRING_DESCRIPTOR UsbGetStringDescriptor;
EFI_USB_IO_GET_SUPPORTED_LANGUAGES UsbGetSupportedLanguages;
EFI_USB_IO_PORT_RESET UsbPortReset;
} EFI_USB_IO_PROTOCOL;
USB 鼠标驱动程序将在其句柄上使用 EFI_USB_IO_PROTOCOL 并生成 EFI_SIMPLE_POINTER_PROTOCOL。 USB 键盘驱动程序将使用 EFI_USB_IO_PROTOCOL 并生成 EFI_SIMPLE_INPUT_PROTOCOL。 USB 存储设备将使用 EFI_USB_IO_PROTOCOL 并生成 Block I/O 协议。
1个
USB主机控制器驱动
目前主要的USB主机控制器分为以下几种:
1. 通用主机控制器接口 (UHCI)(USB1.0 和 USB 1.1)
2. 打开主机控制器接口 (OHCI)(USB 1.0 和 USB 1.1)
3.增强型主机控制器接口(EHCI)(USB2.0)
4. 可扩展主机控制器接口(XHCI)(USB3.0)
USB主机控制器驱动是一个符合UEFI驱动模型的设备驱动。 它将使用 EFI_PCI_IO_PROTOCOL 提供的服务,并在主机控制器句柄上安装 EFI_USB2_HC_PROTOCOL。 下面是USB主机控制器驱动EFI_DRIVER_BINDING_PROTOCOL的实现。
1.支持()
Supporoted()函数用于判断传入的ControllerHandle是否是主机控制器驱动知道如何管理的USB主机控制器。 实现这个判断的方法是先读取host controller的PCI配置空间头,检查controller的设备类型,device ID和vendor ID,如果信息满足host controller driver的要求,Supported()该函数返回 EFI_SUCCESS。 否则返回 EFI_UNSUPPORTED。
我们可以在Github上下载EDKII主干,XHCI驱动在MdeModulePkgBusPciXhciDxe,具体在:
edk2-平台核心MdeModulePkg总线PciXhciDxe
我们可以在 Xhci.c 中找到 Supported() 函数实现的示例。 首先,它尝试在主机控制器句柄上打开 PCI I/O 协议。 如果打开失败,主机控制器驱动不支持ControllerHandle代表的控制器,返回错误信息。 如果开启成功,则使用PCI I/O协议从PCI配置空间中读取PCI设备ID等信息。 如果信息匹配,则返回 EFI_SUCCESS。 调用 CloseProtocol() 关闭 PCI I/O 协议。
2.开始()和停止()
USB主机控制器驱动Driver Binding Protocol的Start()会先开启PCI I/O协议,然后使用协议提供的服务初始化主机控制器,生成主机控制器协议EFI_USB2_HC_PROTOCOL。
Stop() 函数与 Start() 相反,它释放分配给主机控制器的所有内存,确保没有内存泄漏,然后卸载安装在 USB 控制器上的协议。
2个
USB总线驱动
EDKII 中有通用的总线驱动程序代码。 总线驱动程序使用主机控制器驱动程序提供的 EFI_USB2_HC_PROTOCOL 协议来检测和发现 USB 总线上的 USB 设备控制器,为每个 USB 设备控制器创建句柄并在句柄上安装 EFI_DEVICE_PATH_PROTOCOL 和 EFI_USB_IO_PROTOCOL。 代码实现请参考:
edk2-platformsCoreMdeModulePkgBusUsbUsbBusDxe
3个
USB设备驱动程序
USB 设备驱动程序使用 USB I/O 协议提供的服务为 USB 设备提供一个或多个 I/O 抽象。 下面是USB设备驱动的EFI_DRIVER_BINDING_PROTOCOL的实现。
1.支持()
USB驱动的EFI_DRIVER_BINDING_PROTOCOL Supported()函数的实现步骤:
(1) 检查设备控制器句柄是否安装了EFI_USB_IO_PROTOCOL。 如果未安装,则当前句柄不是 USB 设备的句柄。
(2)使用EFI_USB_IO_PROTOCOL协议提供的服务读取USB设备描述符,获取并查看USB设备的InterfaceClass、InterfaceSubClass、InterfaceProtocol等信息,并通过这些信息判断当前设备是否为USB支持的设备USB 设备驱动程序。
如果以上两步检测通过,则证明USB设备驱动可以控制controller handle所代表的USB设备,Supported()函数返回EFI_SUCCESS。 否则返回 EFI_UNSUPPORTED。 以上检查不能影响USB设备的当前状态,因为其他USB设备驱动程序可能正在控制USB设备。
2.Start()函数和Stop()函数
Start()函数会打开USB_IO_PROTOCOL并为USB设备安装USB I/O抽象,比如Block I/O协议和Simple Input协议。 下面以USB存储设备为例,简单介绍USB设备驱动Start()的实现框架。
(1) 打开USB设备控制器上安装的EFI_USB_IO_PROTOCOL协议。
(2)使用EFI_USB_IO_PROTOCOL协议的UsbGetInterfaceDescriptor()获取接口描述符。
(3) 准备私有数据结构。 私有数据结构为USB_MASS_STORAGE_DEVICE类型,包含接口描述符、端点描述符等信息。 这一步会为私有数据结构分配空间,并做一些必要的初始化,比如设置签名、USB IO和接口描述符等。
(4) 解析接口描述符。 在该步骤中io1.1鼠标驱动,它解析步骤2中获得的接口描述符结果并对其进行验证。 InterfaceDescriptor 的 NumEndpoints 描述了 USB 接口的端点数。 调用 UsbGetEndpointDescriptor() 函数以获取所有端点信息。 然后使用 EndpointDescriptor 中的 Attributes 和 EndpointAddress 来确定端点的类型。
(5) 在USB设备句柄上安装Block I/O协议。
Stop()函数的作用与Start()相反。 它将卸载Block I/O协议并关闭USB I/O协议的支持,然后释放分配的资源,如私有数据结构。
后记
USB设备驱动的实现代码可以参考:
USB键盘驱动:
edk2-platformsCoreMdeModulePkgBusUsbUsbKbDxe
USB鼠标启动:
edk2-platformsCoreMdeModulePkgBusUsbUsbMouseDxe
USB 存储驱动器:
edk2-platformsCoreMdeModulePkgBusUsbUsbMassStorageDxe