UEFI原理与编程(十):UEFI的基础服务-系统表

xiaoxiao2021-02-27  383

UEFI的基础服务-系统表

一、前言

  对UEFI应用程序和驱动程序开发人员来讲,系统表是最重要的数据结构之一,它是用户空间通往内核空间的通道。有了它,UEFI应用程序和驱动才可以访问UEFI内核、硬件资源和I/O设备。   (1)在应用程序和驱动中访问系统表   计算机系统进入DXE阶段后系统表被初始化,因而系统表只能用于DXE阶段以及以后的应用程序和驱动中。系统表是UEFI内核的一个全局结构体,其指针作为程序映像(Image)入口函数的参数传递到用户空间。程序映像(包括UEFI应用程序、DXE驱动程序以及UEFI驱动程序)的入口函数有统一的格式,其函数原型如下:

typedef EFI_STATUS (EFIAPI *EFI_IMAGE_ENTRY_POINT) ( IN EFI_HANDLE ImageHandle, //程序映像(Image)的句柄 IN EFI_SYSTEM_TABLE *SystemTable //系统表指针 ); 返回值描述EFI_SUCCESSThe driver was initializedEFI_OUT_OF_RESOURCESThe request could not be completed due to a lack of resources

  (2)系统表指针从内核传递到用户空间的过程   通常,程序映像的入口函数是_ModuleEntryPoint(当一个Image被启动服务的StartImage服务启动时,执行的就是这个入口函数)。当应用程序或驱动加载到内存形成Image后(ImageHandle是这个Image的句柄),_ModuleEntryPoint函数地址被赋值给Image对象的EntryPoint,然后Image->EntryPoint(ImageHandle,SystemTable)会被执行,最终会从Image的入口函数_ModuleEntryPoint执行到模块的入口函数(模块的入口函数是通过.inf文件中ENTRY_POINT指定的那个函数)。

二、系统表的构成

  系统表可分为如下6部分:

表头:包括表的版本号、表的CRC校验码等。固件信息:包括固件开发商名字的字符串和固件的版本号。标准输入控制台、标准输出控制台、标准错误控制台。启动服务表运行时服务表

系统配置表

  系统表数据结构:

typedef struct { EFI_TABLE_HEADER Hdr; ///标准UEFI表头 CHAR16 *FirmwareVendor; //固件提供商 UINT32 FirmwareRevision; //固件版本号 EFI_HANDLE ConsoleInHandle; //输入控制台设备句柄 EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; EFI_HANDLE ConsoleOutHandle; //输出控制台设备句柄 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; //标准错误控制台设备 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; EFI_RUNTIME_SERVICES *RuntimeServices; //运行时服务表 EFI_BOOT_SERVICES *BootServices; //启动时服务表 UINTN NumberOfTableEntries; // CongratulationTable数组的大小 EFI_CONFIGURATION_TABLE *ConfigurationTable; //系统配置表数组 } EFI_SYSTEM_TABLE;

  简要介绍系统表重要组成部分:   (1) 表头 EFI_TABLE_HEADER   UEFI中的表通常都是以EFI_TABLE_HEADER开头,EFI_TABLE_HEADER的数据结构如下:

typedef struct { UINT64 Signature; UINT32 Revision; UINT32 HeaderSize; UINT32 CRC32; UINT32 Reserved; } EFI_TABLE_HEADER;

  UEFI中的Signature为64位的无符号整数,为了帮助开发者的使用,EDK2提供了宏SIGNATURE_64(A,B,C,D,E,F,G,H),它用于将ASCII码串转化为64位的无符号整数。例如,EFI_SYSTEM_TABLE的Signature为SIGNATURE_64(‘I’,’B’,’I’,’ ‘,’S’,’Y’,’S’,’T’)。   HeaderSize是整个表的长度,对系统表来讲,就是sizeof(EFI_SYSTEM_TABLE)。   CRC32是标的校验码。计算CRC32校验码时,首先将数据结构中CRC32域清零,然后计算整张表(表大小为HeaderSize)的CRC32码,计算后将校验码填充到CRC32域。   (2)标准的输入控制台、标准的输出控制台和标准错误控制台。   系统表还提供了三个控制台设备以及操作三个控制台的Protocol。ConIn用于从输入控制台ConsoleInHandle读取字符,通常输入控制台为键盘。ConOut用于向输出控制台ConsoleOutHandle输出字符串,通常输出控制台为屏幕。stdErr用于向标准错误控制台StandardErrorHandle输出字符串,通常这个标准错误控制台为屏幕。这三个控制台设备以及ConIn、ConOut、stdErr三个Protocol在驱动ConSplitterDxe中被初始化。   (3)系统配置表   ConfigurationTable是系统配置表,它指向EFI_CONFIGURATION_TABLE数组,数组中的每一项是一个表。   EFI_CONFIGURATION_TABLE的数据结构:

typedef struct{ EFI_GUID VendorGuid; //配置表标识符 VOID *VendorTable; //指向配置表的数据 } EFI_CONFIGURATION_TABLE;

  例如,ConfigurationTable通常会包含APCI(Advance Configuration and Power Interface)表,ACPI在系统配置表中可以表示为:{gEfiAcpiTableGuid,EFI_ACPI_3_0_ROOT_SYSTEM_DESCRIPTION_POINTER*}

三、使用系统表

  系统表是UEFI内核的全局数据结构,应用程序运行在用户空间。那么用户空间是如何获得内核空间的系统表指针的?其实在UEFI中只有一个地址空间,所有的程序都运行在RING0优先级,应用程序地址空间占用UEFI地址空间的一部分。既然用户空间和内核空间是一个整体,在应用程序内也就可以直接使用内核空间的任何地址了,那么在应用程序内,只要得到了系统表的地址,就可以使用系统表了。   (1)在用户空间使用系统表   系统表的地址可以通过模块的入口函数的参数得到。

#include<Uefi.h> EFI_Status UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable) { EFI_STATUS Status; UINTN Index; EFI_INPUT_KEY Key; CHAR16 StrBuffer[3] = {0}; SystemTable->BootServices->WaitForEvent(1,&SystemTable->ConIn->WaitForKey,&Index); Status = SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn,&Key); StrBuffer[0] = Key.UnicodeChar; StrBuffer[1] = '\n'; SystemTable->ConOut->OutputString(SystemTable->ConOut,StrBuffer); return EFI_SUCCESS; }

代码解析:

SystemTable->BootServices指向系统的启动服务表。SystemTable->ConIn 指向安装在标准输入设备上的EFI_SIMPLE_TEXT_INPUT_PROTOCOL.SystemTable->ConOut 指向安装在标准输出设备上的EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.   该示例首先用WaitForEvent等待键盘事件,然后调用ConIn的ReadKeyStroke读取键盘,最后调用ConOut的OutputString服务将按键显示到屏幕。   启动服务BootServices提供的EFI_STATUS WaitForEvent(IN UINTN NumberOfEvents, IN EFI_EVENT * Event,OUT UINTN *Index)服务用于等待Event数组中任一事件的发生。Event数组(NumberOfEvent是数组大小)中任一事件被触发时该函数返回,Index返回被触发事件在Event数组的下标。该函数是阻塞函数。   EFI_STATUS ReadKeyStroke(EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,OUTEFI_INPUT_KEY *Key)用于读取按键,它是ConInProtocol的成员函数,第一个参数是This指针,第二个参数用于返回按键。   EFI_STATUS OutputString(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,CHAR16 String)用于向屏幕打印字符串,它是ConOut Protocol的成员函数,第一个参数同样是This指针,第二个参数是打印到屏幕的字符串。   (2)在用户空间使用gST访问系统表   上面(1)中的示例中的模块入口函数UefiMain中使用传入参数SystemTable访问系统表。EDK2为了方便开发者,提供了UefiBootServicesTableLib,在UefiLib定义了全局变量gST(指向SystemTable),gBS(指向SystemTable->BootServices)和gImageHandle(ImageHandle)。这三个全局变量在函数UefiBootServicesTableLibConstructor中被初始化,这个函数是库UefiBootServicesTableLib的构造函数,在AutoGen.c中的ProcessLibraryConstructorList被调用。而ProcessLibraryConstructorList是在UefiMain之前被调用的。   构造函数UefiBootServicesTableLibConstructor源码: ///\EDK2\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c EFI_STATUS EFIAPI UefiBootServicesTableLibConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { // // Cache the Image Handle // gImageHandle = ImageHandle; ASSERT (gImageHandle != NULL); // // Cache pointer to the EFI System Table // gST = SystemTable; ASSERT (gST != NULL); // // Cache pointer to the EFI Boot Services Table // gBS = SystemTable->BootServices; ASSERT (gBS != NULL); return EFI_SUCCESS; }

  gST变量定义在用户空间,它指向的系统表定义在UEFI内核中。在应用程序或驱动工程文件的[LibraryClasses]里引用UefiBootServicesTableLib后,就可以使用gST访问系统表了。

/// 示例:使用了gST #include<Uefi.h> #include<Library/UefiBootServicesTableLib.h> EFI_Status UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable) { EFI_STATUS Status; UINTN Index; EFI_INPUT_KEY Key; CHAR16 StrBuffer[4] = {0}; gST -> ConOut -> OutputString(gST -> ConOut,L"Please enter any key\n"); gBS->WaitForEvent(1,&gST->ConIn->WaitForKey,&Index); Status = gST->ConIn->ReadKeyStroke(gST->ConIn,&Key); StrBuffer[0] = Key.UnicodeChar; StrBuffer[1] = '\n'; gST->ConOut->OutputString(gST->ConOut,StrBuffer); return EFI_SUCCESS; }

  从代码上看,其实就是使用gST和gBS替换掉SystemTable和SystemTable -> BootServices。

四、总结

  本文主要系统表的构成、系统表从内核空间传递到用户空间的过程,以及在用户空间访问系统表的两种方法。得到系统表就可以在用户空间访问UEFI内核了,应用程序和驱动对内核的控制是通过两个核心成员BootServices和RuntimeServices。EDK2在用户空间分配了全局变量gBS和gRT指代这两个服务。

参考资料

1.《UEFI原理与编程》戴正华 著 2. UEFI Spec 2_6 3. EDK2 GitHub 

转载请注明原文地址: https://www.6miu.com/read-2086.html

最新回复(0)