uefi基础学习----基本的efi架构

一.前言

最近在做linuxboot项目,需要具体知道dxe阶段每一个驱动的作用,心想着既然是做bios有关的项目,那么只是简单了解uefi的dxe各个驱动的作用怕是有点不尊重uefi现在的地位了,特地为此开了个专栏,学习的参考资料主要为《beyond bios》的汉化版以及csdn上一些写得好的博客以及目前最新的uefi spec2.9。因为是小白,所以内容有错误的,欢迎指正出来。

uefi中有许多规范,uefi spec、edk2 spec、acpi spec等等spec,这在之后的学习中经常见得到。

uefi历史作用就没啥好学习的了,有目共睹耳熟能详了。根据《beyond bios》的第二章,我了解到了一个基本的efi架构分析可以从EFI System Table、Handle & Protocol、EFI Images这些方面入手。

二.uefi system table

uefi system table基本介绍

要说uefi架构中最重要的一个部分,肯定是uefi system table。它是一个结构体指针,包含许多指针,在开源社区edk2参考目录\MdePkg\Include\Uefi\UefiSpec.h里面有system table的描述:

/// EFI System Table
///
typedef struct {
  ///
  /// The table header for the EFI System Table.
  ///
  EFI_TABLE_HEADER                   Hdr;
  ///
  /// A pointer to a null terminated string that identifies the vendor
  /// that produces the system firmware for the platform.
  ///
  CHAR16                             *FirmwareVendor;
  ///
  /// A firmware vendor specific value that identifies the revision
  /// of the system firmware for the platform.
  ///
  UINT32                             FirmwareRevision;
  ///
  /// The handle for the active console input device. This handle must support
  /// EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL.
  ///
  EFI_HANDLE                         ConsoleInHandle;
  ///
  /// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is
  /// associated with ConsoleInHandle.
  ///
  EFI_SIMPLE_TEXT_INPUT_PROTOCOL     *ConIn;
  ///
  /// The handle for the active console output device.
  ///
  EFI_HANDLE                         ConsoleOutHandle;
  ///
  /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface
  /// that is associated with ConsoleOutHandle.
  ///
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *ConOut;
  ///
  /// The handle for the active standard error console device.
  /// This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.
  ///
  EFI_HANDLE                         StandardErrorHandle;
  ///
  /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface
  /// that is associated with StandardErrorHandle.
  ///
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *StdErr;
  ///
  /// A pointer to the EFI Runtime Services Table.
  ///
  EFI_RUNTIME_SERVICES               *RuntimeServices;
  ///
  /// A pointer to the EFI Boot Services Table.
  ///
  EFI_BOOT_SERVICES                  *BootServices;
  ///
  /// The number of system configuration tables in the buffer ConfigurationTable.
  ///
  UINTN                              NumberOfTableEntries;
  ///
  /// A pointer to the system configuration tables.
  /// The number of entries in the table is NumberOfTableEntries.
  ///
  EFI_CONFIGURATION_TABLE            *ConfigurationTable;
} EFI_SYSTEM_TABLE;

可以看到,该uefi系统表中包含RuntimeServices和BootServices,这两个提供的函数是一定会用到的,需要进行特别介绍。

以及6个与控制台相关的入口:

ConsoleInHandle:活动控制台输入设备的handle,这一handle必须支持EFI简单文本输入协议。

ConIn:一个与ConsoleInHandle相关联的指向EFI简单文本输入协议接口的指针。

ConsoleOutHandle:活动控制台输出设备的handle,该handle必须支持EFI简单文本输出协议。

ConOut:一个与ConsoleOutHandle相关联的指向EFI简单文本输出协议接口的指针。

StandardErrorHandle:活动标准错误控制台设备的Handle。该Handle必须支持EFI简单文本输出协议。

StdErr:一个与StandardErrorHandle相关联的指向EFI简单文本输出协议接口的指针。

以及一个目前学习bios很重要的东西:ConfigrationTable,这个指针指向着BIOS里面非常重要的一项:ACPI、SMBIOS等表。

系统表单具体的代码在DxeMain.c中,下面列出简单的代码

EFI_SYSTEM_TABLE mEfiSystemTableTemplate = {
  {
    EFI_SYSTEM_TABLE_SIGNATURE,                                           // Signature
    EFI_SYSTEM_TABLE_REVISION,                                            // Revision
    sizeof (EFI_SYSTEM_TABLE),                                            // HeaderSize
    0,                                                                    // CRC32
    0                                                                     // Reserved
  },
  NULL,                                                                   // FirmwareVendor
  0,                                                                      // FirmwareRevision
  NULL,                                                                   // ConsoleInHandle
  NULL,                                                                   // ConIn
  NULL,                                                                   // ConsoleOutHandle
  NULL,                                                                   // ConOut
  NULL,                                                                   // StandardErrorHandle
  NULL,                                                                   // StdErr
  NULL,                                                                   // RuntimeServices
  &mBootServices,                                                         // BootServices
  0,                                                                      // NumberOfConfigurationTableEntries
  NULL                                                                    // ConfigurationTable
};
  //
  // Allocate the EFI System Table and EFI Runtime Service Table from EfiRuntimeServicesData
  // Use the templates to initialize the contents of the EFI System Table and EFI Runtime Services Table
  //
  gDxeCoreST = AllocateRuntimeCopyPool (sizeof (EFI_SYSTEM_TABLE), &mEfiSystemTableTemplate);
  ASSERT (gDxeCoreST != NULL);
 
  gDxeCoreRT = AllocateRuntimeCopyPool (sizeof (EFI_RUNTIME_SERVICES), &mEfiRuntimeServicesTableTemplate);
  ASSERT (gDxeCoreRT != NULL);
 
  gDxeCoreST->RuntimeServices = gDxeCoreRT;

笔者项目接触要用到acpi表,所以我基本学习了一下ConfigrationTable、BootServices和 RuntimeServices。

ConfigrationTable

同样是在uefispec.h里面,有关于EFI_CONFIGURATION_TABLE结构体的定义。

/// Contains a set of GUID/pointer pairs comprised of the ConfigurationTable field in the
/// EFI System Table.
///
typedef struct {
  ///
  /// The 128-bit GUID value that uniquely identifies the system configuration table.
  ///
  EFI_GUID    VendorGuid;
  ///
  /// A pointer to the table associated with VendorGuid.
  ///
  VOID        *VendorTable;
} EFI_CONFIGURATION_TABLE;

根据uefi spec,下面这些是某些产家使用的行业标准中定义的表的 GUID。

#define EFI_ACPI_20_TABLE_GUID \
{0x8868e871,0xe4f1,0x11d3,\
{0xbc,0x22,0x00,0x80,0xc7,0x3c,0x88,0x81}}
#define ACPI_TABLE_GUID \
{0xeb9d2d30,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define SAL_SYSTEM_TABLE_GUID \
{0xeb9d2d32,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define SMBIOS_TABLE_GUID \
{0xeb9d2d31,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define SMBIOS3_TABLE_GUID \
{0xf2fd1544, 0x9794, 0x4a2c,\
{0x99,0x2e,0xe5,0xbb,0xcf,0x20,0xe3,0x94})
#define MPS_TABLE_GUID \
{0xeb9d2d2f,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
//
// ACPI 2.0 or newer tables should use EFI_ACPI_TABLE_GUID
//
#define EFI_ACPI_TABLE_GUID \
{0x8868e871,0xe4f1,0x11d3,\
{0xbc,0x22,0x00,0x80,0xc7,0x3c,0x88,0x81}}
#define EFI_ACPI_20_TABLE_GUID EFI_ACPI_TABLE_GUID
#define ACPI_TABLE_GUID \
{0xeb9d2d30,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define ACPI_10_TABLE_GUID ACPI_TABLE_GUID

通过相应的GUID,就可以找到需要的ACPI Table指针了。

ACPI包含很多表单,是一个很复杂的东西,笔者这里对于acpi的认知还是比较小白,等到后面再进行学习。

BootServices

在dxe阶段进行进行调用,在uefi spec2.9中,bootservces中的函数清晰地被划分为了五大块:

• Event, Timer, and Task Priority Services (Section 7.1)
• Memory Allocation Services (Section 7.2)
• Protocol Handler Services (Section 7.3)
• Image Services (Section 7.4)
• Miscellaneous Services (Section 7.5


Event, Timer, and Task Priority Services

uefi基础学习----基本的efi架构_第1张图片

 Memory Allocation Services

uefi基础学习----基本的efi架构_第2张图片

  Protocol Handler Services

uefi基础学习----基本的efi架构_第3张图片

 Image Services

uefi基础学习----基本的efi架构_第4张图片

  Miscellaneous Services(其他引导服务)

uefi基础学习----基本的efi架构_第5张图片

 RuntimeServices

和BootServices有一个明显区别,就是runtimeservice在进入操作系统之后其中的服务还是能够被操作系统调用到的。

关于二者区别,在uefi spec中,有这样一句:

• Boot Services. Functions that are available before a successful call to
EFI_BOOT_SERVICES.ExitBootServices(). These functions are described in Section 7.
• Runtime Services. Functions that are available before and after any call to
ExitBootServices(). These functions are described in this section

意思就是说再操作系统调用完ExitBootServices()函数后,Runtime服务还能用而Boot服务已经被清掉内存。

它里面包含的函数可以分为这几类:

Runtime Rules and Restrictions (Section 8.1)
• Variable Services (Section 8.1.1)
• Time Services (Section 8.3)
• Virtual Memory Services (Section 8.4)
• Miscellaneous Services (Section 8.5)

Runtime Rules and Restrictions

uefi基础学习----基本的efi架构_第6张图片

 Variable Services

uefi基础学习----基本的efi架构_第7张图片

Time Services

uefi基础学习----基本的efi架构_第8张图片

Virtual Memory Services

uefi基础学习----基本的efi架构_第9张图片

Miscellaneous Services

uefi基础学习----基本的efi架构_第10张图片

下面参考了一下jiangwei0512的博客jiangwei

Runtime Service与Boot Service不同,它并不是在DxeMain.c中直接建立好的,它刚开始基本上是一个空的表:

EFI_RUNTIME_SERVICES mEfiRuntimeServicesTableTemplate = {
  {
    EFI_RUNTIME_SERVICES_SIGNATURE,                               // Signature
    EFI_RUNTIME_SERVICES_REVISION,                                // Revision
    sizeof (EFI_RUNTIME_SERVICES),                                // HeaderSize
    0,                                                            // CRC32
    0                                                             // Reserved
  },
  (EFI_GET_TIME)                    CoreEfiNotAvailableYetArg2,   // GetTime
  (EFI_SET_TIME)                    CoreEfiNotAvailableYetArg1,   // SetTime
  (EFI_GET_WAKEUP_TIME)             CoreEfiNotAvailableYetArg3,   // GetWakeupTime
  (EFI_SET_WAKEUP_TIME)             CoreEfiNotAvailableYetArg2,   // SetWakeupTime
  (EFI_SET_VIRTUAL_ADDRESS_MAP)     CoreEfiNotAvailableYetArg4,   // SetVirtualAddressMap
  (EFI_CONVERT_POINTER)             CoreEfiNotAvailableYetArg2,   // ConvertPointer
  (EFI_GET_VARIABLE)                CoreEfiNotAvailableYetArg5,   // GetVariable
  (EFI_GET_NEXT_VARIABLE_NAME)      CoreEfiNotAvailableYetArg3,   // GetNextVariableName
  (EFI_SET_VARIABLE)                CoreEfiNotAvailableYetArg5,   // SetVariable
  (EFI_GET_NEXT_HIGH_MONO_COUNT)    CoreEfiNotAvailableYetArg1,   // GetNextHighMonotonicCount
  (EFI_RESET_SYSTEM)                CoreEfiNotAvailableYetArg4,   // ResetSystem
  (EFI_UPDATE_CAPSULE)              CoreEfiNotAvailableYetArg3,   // UpdateCapsule
  (EFI_QUERY_CAPSULE_CAPABILITIES)  CoreEfiNotAvailableYetArg4,   // QueryCapsuleCapabilities
  (EFI_QUERY_VARIABLE_INFO)         CoreEfiNotAvailableYetArg4    // QueryVariableInfo
};

可以看到很多的CoreEfiNotAvailableYetxx,Runtime Service需要在DXE阶段运行过程中一个个的填补。

 三.Handle & Protocol基本知识

事实上handle和protocol的关系一直是uefi中比较难以理解的东西且很重要,由于笔者理解还不足,因此在本篇中,只对handle数据库进行基本的介绍。

在《beyond bios》中,有:

Handle 数据库由 handles 和 protocols 组成, handles 由一个或多个 protocols 组成, protocols 则是由 GUID 来命名的数据结构体,这个数据结构体可能是空,可能包含数据,可能包含服务程序,或者同时包括数据和服务程序。在 EFI 的初始化中,系统固件, EFl 驱动和 EFl 应用程序创建 handles ,并为每个 handle 挂上一个或多个 protocols 。在 handle 数据库中信息是全局的,而且能被任何一个可执行的 EFI Image 访问。
 Handle 数据库是 EFl 固件需要维护的最重要的对象库。这个 handle 数据库是所有 EFl handles 的列表,每个 EFI handle 由系统固件分配一个唯一的 handle 编号。这个唯一的编号就是 handle 数据库中入口的“钥匙”,每个 handle 数据库中入口处都是一个或多个 protocol 的集合,挂到 EFl handl 上面的 protocol 类型决定了这个 handle 的类型。一个 EFl handle 可以体现下面这样的类型组件:
■可执行的 Images ,例如 EFl Driver 和 EFI 应用软件。

■设备,例如网络控制器和硬盘分区
■ EFl 服务程序,例如 EFl 解压缩和 EBC 虚拟机

 每个handle的类型由其下面挂载的protocol所决定

这段话很好理解,就是说handle数据库是一个人类社会,它有Handle也就是人组成,而人有很多种不同的特征也就是protocol挂载到人(handle)上,同样的handle挂载着同样的protocol就好比是双胞胎三胞胎一样,这些各式各样的人和特征共同组成了一个人类社会,就如下图所示:

uefi基础学习----基本的efi架构_第11张图片

uefi基础学习----基本的efi架构_第12张图片

四.EFI Images

先来看《beyond bios》里面的原话:

所有的 EFI Images 都包含一个 PE / COFF 格式的头来定义这段可执行代码, PE / COFF 是微软在1997可移植的执行体和通用对象文件格式规范中定义的,这种代码可以用在 IA -32处理器,安腾处理器,或者未知的处理器,以及 EFl 二进制代码这个头文件定义了处理器和 Image 的类型,目前有3种处理器类型和以下3种 Image 类型被定义
■ EFI 应用程序一这种 Image 的内存资源和状态会在他们退出的时候释放。
■ EFI Boot Service 程序驱动这种 Inage 的内存资源和状态在进入操作系统之前都被保持。当0s loader 调用 ExitBootServices O 的时候被释放。
■ EFI Runtime 动一这种 Image 的内存资源和状态会一直存在,这些 Image 和 EFI 操作系统并存,并且能够被支持 EFI 的操作系统调用


 EFl lamge 格式的价值在于不同方产生的二进制文件能够互用,例如,微软 Windows 和 Linux 操作系统为支持 EFI 的操作系统的。另外,第三方开发的抽象特定硬件的 EFI 驱动,像网络接口主机总线适配器 HBA )域者其他设备. EFl image 通过 Boot Service gBS -> Loadimage ()被装载. EFI images 的几个可用的支持存储的地方有:
■ PCI 卡上面的 Expansion ROMs 
系统的只读存贮器或者系统的 flash 芯片媒介设备,像硬盘,软盘,光盘筝
■网络启动服务器
总的来说, EFl images 不是被编译和链接在一个固定位置。相反的, EFI images 能够被重新定位,所以 EFl images 能够被放在系统内存的任何地方. Boot Service gBS -> Loadlmage ()
 EFL IMAGE SUBSYSTEM EFL APPLICATION .执行时,当 image 退
出或从入日点返回时这种 image 会截自动卸较。
一种特殊类型的的应用程序,通常不会返回或退出,相反的,它会调
用 EFIBoot Service gBS->ExitBootServices0来把平台的控制权从固件
■为正在被转载的 image 分配内存

■自动对 iamge 进行重新定位
■在 handle 数据库中创建一个新的 image handle ,安装 EFL LOADED _ IMAGE _ PROTOCO 的一个实例。

uefi基础学习----基本的efi架构_第13张图片

这些话并不难理解,然后我在uefi spec找到了EFI image文件的入口函数。

The most significant parameter that is passed to an image is a pointer to the System Table. This pointer is
EFI_IMAGE_ENTRY_POINT (see definition immediately below), the main entry point for a UEFI Image.
The System Table contains pointers to the active console devices, a pointer to the Boot Services Table, a
pointer to the Runtime Services Table, and a pointer to the list of system configuration tables such as
ACPI, SMBIOS, and the SAL System Table. This section describes the System Table in detail

意思就是说传给EFI images最重要的是EFI system table的指针,指向一些运行时服务、启动服务和一些系统表单。

入口函数是这么定义的:

Prototype
typedef
EFI_STATUS
(EFIAPI *EFI_IMAGE_ENTRY_POINT) (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
Parameters
ImageHandle The firmware allocated handle for the UEFI image.
SystemTable A pointer to the EFI System Table

而关于EFI_HANDLE,本人也是在uefi spec中找了比较久,没有发现关于EFI_HANDLE的定义,搜索后通过西瓜在树上的博客知道了EFI_HANDLE的定义:

首先在code中EFI_HANDLE的定义是:

//MdePkg\Include\Uefi\UefiBaseType.h
typedef VOID                      *EFI_HANDLE;
//摘选自 MdeModulePkg\Core\Dxe\Hand\Handle.c
// CoreInstallProtocolInterfaceNotify 函数

EFI_HANDLE     *UserHandle;
IHANDLE             *Handle;
Handle = NULL;
// ... 
Handle = (IHANDLE *)*UserHandle;

可以发现,最终转换成了IHANDLE *Handle = (IHANDLE* )EFI_HANDLE的形式,即指向的对象为IHANDLE,我们来查看IHANDLE结构体。

//MdeModulePkg\Core\Dxe\Hand\Handle.h
#define EFI_HANDLE_SIGNATURE            SIGNATURE_32('h','n','d','l')

/// IHANDLE - contains a list of protocol handles

typedef struct {
  UINTN               Signature;
  /// All handles list of IHANDLE
  LIST_ENTRY          AllHandles;
  /// List of PROTOCOL_INTERFACE's for this handle
  LIST_ENTRY          Protocols;
  UINTN               LocateRequest;
  /// The Handle Database Key value when this handle was last created or modified
  UINT64              Key;
} IHANDLE;

比较不解的是我没有在uefi spec中找到关于IHANDLE的定义,先暂时分析到最表层的结构体,之后的博客再进行相对应地深入研究。

五.一些常见的数据类型和指针

uefi基础学习----基本的efi架构_第14张图片

 uefi基础学习----基本的efi架构_第15张图片

 uefi基础学习----基本的efi架构_第16张图片

 uefi基础学习----基本的efi架构_第17张图片

三个常见的全局变量

EFI_SYSTEM_TABLE *gST;

EFI_BOOT_SERVICES *gBS;

EFI_RUNTIME_SERVICES *gRT;

gST = SystemTable;
gBS = gST->BootServices;
gRT = gST->RuntimeServices;

参考了一些博客的内容,学习了一下uefi的基础架构,有错误的地方还请多多指教,欢迎改正。

你可能感兴趣的:(uefi基础知识学习,学习,嵌入式硬件,linux)