请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
插个新告知:YIE001的开发板,思路已经比较清晰了-使用PCIE芯片,编写Option ROM,在此基础上控制硬件,做一些有趣的实验。由于主要是用业余时间,估计在这两个月内就能把要做的实验完成。
因此,有了新的想法-做一个USB的开发板YIE002,实现从UEFI访问、Windows/Linux访问的全通道USB HID设备。当然,可以用它来收集温度、产生随机数等。准备做成U盘大小,方便直接插在电脑上进行实验。我正在收集网友的想法,有啥想实现的,留言就行,资源允许,我就在板子上实现了。
回到UEFI驱动的介绍。
UEFI开发探索66和67,介绍了服务型驱动的构建和实现。下面几篇,用来介绍符合UEFI驱动模型的驱动程序。本篇主要介绍其框架结构和程序接口,内容有点枯燥,最好对照着源码进行阅读。
一个完整的符合UEFI驱动模型的驱动程序,大致可以分为两个部分:EFI Driver Binding Protocol和驱动本身提供的服务。前者用来管理驱动,后者才是用户需要使用的部分,一般包括一个或多个Protocol。
比如UEFI的USB主控制器驱动,它提供了两个Protocol供用户使用,包括EFI_USB_HC_PROOCOL和EFI_USB2_HC_PROTOCOL,用来访问USB设备。另外,为了方便用户使用,驱动程序一般还会包含EFI Component Name Protocol,用来显示驱动信息。
在网页https://sourceforge.net/projects/edk2/files/EDK%20II%20Releases/Demo%20apps/上,可以下载Intel早期提供的驱动框架示例BlankDrv,用来学习UEFI驱动模型。
1 EFI Driver Binding Protocol
示例工程BlankDrv包含四个文件:
- BlankDrv.c。实现EFI_DRIVER_BINDING_PROTOCOL及其接口函数,并安装EFI_DRIVER_BINDING_PROTOCOL和EFI_COMPONENT_NAME_PROTOCOL。
- BlankDrv.h。定义了驱动所需数据结构以及Protocol接口函数原型;
- ComponentName.c。实现了EFI_COMPONENT_NAME_PROTOCOL的接口函数;
- BlankDrv.inf,编译UEFI驱动的INF文件。
从BlankDrv.c的文件中,实现的EFI_DRIVER_BINDING_PROTOCOL接口函数,是UEFI驱动能够被系统管理的核心所在。EFI_DRIVER_BINDING_PROTOCOL的结构体如代码清单1所示。
代码清单1 EFI_DRIVER_BINDING_PROTOCOL的结构体
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED Supported; //检查设备控制器是否支持驱动
EFI_DRIVER_BINDING_PROTOCOL_START Start; //安装驱动并启动设备
EFI_DRIVER_BINDING_PROTOCOL_STOP Stop; //停止设备并卸载驱动
UINT32 Version; //版本
EFI_HANDLE ImageHandle; //镜像句柄
EFI_HANDLE DriverBindingHandle; //Protocol实例安装其上
} EFI_DRIVER_BINDING_PROTOCOL;
EFI_DRIVER_BINDING_PROTOCOL有3个接口函数和3个接口变量。其中,接口变量ImageHandle是产生此Protocol实例的镜像句柄,而DriverBindingHandle是安装了Protocol实例的句柄,大多数情况下两者相同。当然,如果驱动产生了多个Protocol实例时,两者并不相同。
接口变量Version表示驱动的版本号,启动服务的ConnectController()函数用它来确定使用哪个驱动的服务,版本高的优先被安装到设备上。0x0~0x0f和0xfffffff0~0xffffffff保留给平台和OEM驱动,0x10~0xffffffef则留给第三方独立硬件商(IHV)开发的驱动使用。
EFI_DRIVER_BINDING_PROTOCOL的三个接口函数Supported()、Start()和Stop()是此Protocol的核心,分别用来检测驱动、安装驱动和卸载驱动。
1.1 Supproted()函数
Supported()函数用来检测给定的设备控制器是否支持该驱动,其函数原型如代码清单2所示。
代码清单2 Supported()函数原型
typedef EFI_STATUS (EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED) (
IN EFI_DRIVER_BINDING_PROTOCOL *This, //Protocol实例
IN EFI_HANDLE ControllerHandle, //设备控制器句柄
//如果非NULL,总线驱动使用此参数;此参数对设备驱动无效
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
此函数在平台初始化的时候,可能被调用多次。因此为了缩短启动时间,检测流程和使用的时间应该尽可能的短。函数的执行过程中,不能修改硬件设备的状态。另外,实现此函数代码时,应时刻意识到设备控制器句柄有可能已经被其他驱动或者本驱动使用了。
在调用启动服务提供的接口函数时,需要注意必须匹配,使用AllocatePages()则必须使用FreePages()配合,AllocatePool()和FreePool()配合,OpenProtocol()则与CloseProtocol()配合。示例工程BlankDrv中,有完整的代码,请自行查看。
1.2 Start()函数
Start()接口函数用来将驱动安装到设备上,并启动硬件设备。UEFI驱动所提供的Protocol,一般在此函数中,使用InstallProtocolInterface()或InstallMultipleProtocolInterfaces()函数进行安装。其函数原型如代码清单3所示。
代码清单3 Start()函数原型
typedef EFI_STATUS (EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_START) (
IN EFI_DRIVER_BINDING_PROTOCOL *This, //Protocol实例
IN EFI_HANDLE ControllerHandle, //驱动所安装的控制器句柄
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
其中,第三个参数RemainingDevicePath由总线驱动使用,用来指明如何创建子设备句柄;对于设备驱动来说,此参数可以忽略掉。
需要注意的是,在Start()函数中申请的资源,必须在Stop()函数中释放掉。也即在Start()函数中使用AllocatePool()、AllocatePages()、OpenProtocol()和InstallProtocolInterface()时,相应的在Stop()函数中,应该使用FreePool()、FreePages()、CloseProtocol()和UninstallProtocolInterface()与之对应。
在示例工程BlankDrv中,Start()函数并没有实现具体的功能,只是打印了表明函数运行的字符串,并返回EFI_UNSUPPORTED。
1.3 Stop()函数
Stop()函数用于停止硬件设备,并卸载驱动,它与Start()函数是互为镜像的关系。其函数原型如代码清单4所示。
代码清单4 Stop()函数原型
typedef EFI_STATUS (EFIAPI *EFI_DRIVER_BINDING_PROTOCOL_STOP) (
IN EFI_DRIVER_BINDING_PROTOCOL *This, //Protocol实例
IN EFI_HANDLE ControllerHandle, //停止此控制器句柄上对应的驱动
IN UINTN NumberOfChildren, //子控制器数量
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL //子控制器数组
);
此函数根据子控制器的数量,有不同的操作。对设备驱动来说,子控制器数量为0,则子控制器数组为NULL;对总线驱动来说,如果子控制器数量不为0,则子控制器数组中所保存的子节点句柄都要被释放。
由于示例工程BlankDrv的Start()函数中并没有实现具体功能,相应的Stop()函数也不需要释放任何资源。
2 EFI Component Name Protocol
方便用户使用,UEFI驱动通常会提供名字,便于向用户显示驱动信息。此功能可以由EFI_COMPONENT_NAME_PROTOCOL或EFI_COMPONENT_NAME2_PROTOCOL提供。
这两种Protocol的功能相同,其结构体也完全相同,区别仅仅在于语言代码的格式不同。前者使用的是ISO 639-2语言代码,后者使用的是RFC 4646语言代码。
代码清单5给出了EFI_COMPONENT_NAME2_PROTOCOL的结构体。
代码清单5 EFI_COMPONENT_NAME2_PROTOCOL结构体
typedef struct _EFI_COMPONENT_NAME2_PROTOCOL {
EFI_COMPONENT_NAME_GET_DRIVER_NAME GetDriverName; //取得驱动名
EFI_COMPONENT_NAME_GET_CONTROLLER_NAME GetControllerName; //取得控制器名
CHAR8 *SupportedLanguages; //所支持的语言列表,此Protocol的为RFC 4646
} EFI_COMPONENT_NAME2_PROTOCOL;
//根据指定的语言代码返回驱动的名字
typedef EFI_STATUS (EFIAPI *EFI_COMPONENT_NAME_GET_DRIVER_NAME) (
IN EFI_COMPONENT_NAME2_PROTOCOL *This, //Protocol实例
IN CHAR8 *Language, //语言代码
OUT CHAR16 **DriverName //返回驱动的名字
);
//根据指定的语言代码返回控制器或子控制器的名字
typedef EFI_STATUS (EFIAPI *EFI_COMPONENT_NAME_GET_CONTROLLER_NAME) (
IN EFI_COMPONENT_NAME2_PROTOCOL *This, //Protocol实例
IN EFI_HANDLE ControllerHandle, //控制器句柄
IN EFI_HANDLE ChildHandle OPTIONAL, //子控制器句柄
IN CHAR8 *Language, //语言代码
OUT CHAR16 **ControllerName //控制器或子控制器名字
);
EFI_COMPONENT_NAME_PROTOCOL的结构体和接口函数,除了支持的语言列表为ISO 639-2外,与代码清单5中给出的结构体是完全一样的。
示例工程BlankDrv中,针对这两种Protocol,所定义的变量如示例1所示。
【示例1】定义两种Protocol变量
EFI_COMPONENT_NAME_PROTOCOL gBlankDrvComponentName = {
BlankDrvComponentNameGetDriverName,
BlankDrvComponentNameGetControllerName,
“eng” //所支持的语言列表
};
EFI_COMPONENT_NAME2_PROTOCOL gBlankDrvComponentName2 = { (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)BlankDrvComponentNameGetDriverName,
(EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) \
BlankDrvComponentNameGetControllerName,
“en” //所支持的语言列表
};
从示例1可以看出,除了所支持的语言列表不同外(实际上相同,只是所使用的语言代码不同),两个接口函数是一样的。
对于两个接口函数的实现,可以直接查看BlankDrv中源码,此处就不列出了。
至此,UEFI驱动模型的基本构建就完成了。驱动框架中,还有安装Protocol以及卸载驱动的函数没有实现。在下一篇中,将完成整个驱动框架的构建,并介绍如何在UEFI Shell下测试此类驱动。
1,480 total views, 2 views today