UEFI之HOB详解

阶段转换

  1. 在PEI的结束阶段,调用PeiServicesLocatePpi来定位DXE IPL PPI,此PPI同事在FV上寻找DXE入口函数的地址。如果没有找到则报告EFI_ERROR_CODE和EFI_SW_PEI_CORE_EC_DXEIPL_NOT_FOUND,并调用CpuDeadLoop函数,启动过程停止。
  2. 找到DXE入口函数后,跳转并传递HOB列表。

HOB详解

HOB是Hand-offblock的缩写。是PEI阶段向DXE传递系统信息的手段。PEI阶段构建一些HOB结构,然后将其作为参数传给DXE阶段函数,数据被打包成数据块存放在一段连续的内存中,数据块的标识为GUID,DXE阶段可以通过该GUID在HOB中找到对应数据块,根据这些数据来使用平台相关资源。DXE阶段主要使用的几类HOB数据:
1.可用内存资源信息,类型为EFI_HOB_TYPE_RESOURCE_DESCRIPTOR,用于初始化内存申请与回收服务,提供申请和回收内存的方法
2.DXE模块数据,类型为EFI_HOB_MEMORY_ALLOCATION,子类型Name=gEfiHobMemoryAllocModuleGuid,用于初始化镜像服务,提供加载、解析和执行文件的方法
3.闪存卷信息,FlashVolume,类型为EFI_HOB_TYPE_FV,对每个闪存卷建立一个PROTOCOL用于读取数据。所有的驱动数据从这里面读取,然后调度执行。

HOB是系列的连续的内存结构体,可以认为其由三部分构成:第一部分,是PHIT头,它描述了HOB的起始地址以及总的内存使用;第二部分是各个Hob列表,DXE阶段会根据这一部分获取上关资源;第三部分是结束部分。

在HOB List中的第一个HOB必须是PHIT HOB(Phase Handoff Information Table),最后一个HOB必须是End of HOB List HOB。

只有PEI Phase才允许增加或改动这些HOBs,当HOB List被传送给DXE Phase之后,他们就是只读的(Read Only)。一个只读的HOB List的延伸就是Handoff 信息,比如Boot Mode,必须以别的方式来处理。比如,DXE Phase想要产生一个Recovery条件,它不能update Boot Mode,而是通过使用特殊方式的reset call来实现。

在HOB中包含的系统状态数据(System State Data)是指在PEI to DXE Handoff的时候的系统状态,而不是代表DXE当前的系统状态。

HOB在PEI到DXE传送信息的过程遵循one Producer to one Consumer的模式,即在PEI阶段,一个PEIM创建一个HOB,在DXE阶段,一个DXE Driver使用那个HOB并且把HOB相关的信息传送给其他的需要这些信息的DXE组件。

HOB list是在PEI Phase被建立的,它存在于已经present,initialized,tested的Memory中。一旦最初的HOB List被创建,物理内存就不能被remapped, interleaved, 或者被后来的程序moved。

PEI阶段最初HOB List中必须有以下三种HOBs,
然后才能暴露这个HOB List给其他的Module(一个指针指向PHIT HOB):

  1. PHIT HOB
  2. 一个描述了固定存储器所在的BSP堆栈位置的Memory allocation HOB
  3. 一个描述了物理内存范围的Resource descriptor HOB

在pei阶段,收集到的信息会按照如下规则进行拼装:

  1. 每个HOB必须以一个HOB generic header开头(EFI_HOB_GENERIC_HEADER)。
  2. HOBs可以包含boot services data,在DXE Phase结束之前,PEI和DXE都可以调用。
  3. HOBs可以被DXE重新安置在系统内存上,每个HOB都不能包含指向HOB List中其他数据的指针,也不能指向其他的HOB,这个Table必须可以被Copied而不需要任何内部指针的调整。
  4. 所有的HOB在长度上必须是8 bytes的倍数,是alignment的要求。
  5. PHIT HOB必须总是在8 byte处开始。
  6. 增加的HOB总是被加到HOB List的最后,而且只能在PEI Phase(HOB Producer Phase)增加,DXE Phase(HOB Consumer Phase)不能。
  7. HOBs不能被删除。每个HOB的generic header中都会描述这个HOB的长度,这样下一个HOB就很容易被找到。
    增加一个新的HOB到HOB List中

PEI Phase(HOB Producer Phase)肯定包含一个指向PHIT HOB(这是HOB List的开始)的指针,然后遵循以下的步骤:

  1. 确定NewHobSize,即确定要创建的HOB的大小(以Byte为单位)。
  2. 确定是否有足够的空闲内存分配给新的HOB(NewHobSize <= (PHIT->EfiFreeMemoryTop - PHIT->EfiFreeMemoryBottom))。
  3. 在(PHIT->EfiFreeMemoryBottom)处构建HOB。
  4. 设置PHIT->EfiFreeMemoryBottom = PHIT->EfiFreeMemoryBottom + NewHobSize 。

一、调度器

由于不同设备的驱动互相之间存在一定的依赖关系,而调度器不能保证总是先找到依赖链中较为靠前的驱动,因此在DXE调度器中,采用了两个队列。其中一个队列存放当前已经满足依赖项,可以加载的驱动的指针,另一个则存放当前已经找到但还未满足加载条件的所有驱动的指针,调度器每循环一次都会完成第一个队列中一个驱动的加载,同时将第二个队列中的项目检查一遍,若已满足依赖项则移到第一个队列中。流程如下图所示:

二、 两种驱动类型

根据是否符合UEFI Driver Model的规范来分,一种是不符合的普通Driver,一种是符合该模型规范的标准驱动。在DXE阶段,这两种类型的驱动,其加载流程是不同的。前者在DXE阶段被找到时就完成运行,而后者在DXE阶段先完成注册,然后在BDS阶段才真正运行初始化,通过调用系统服务来完成。驱动被调用的基本函数是LoadImage()和StartImage(),它们是Boot Service,所以可以在DXE和BDS阶段的大部分地方调用。
在DxeMain.c文件中的DxeMain()函数会调用CoreDispatcher(),也就是DXE阶段的核心调度器,就是用来执行各个驱动的,除非自己修改代码,否则DXE驱动都会在这个位置执行。

两种类型驱动的代码结构上类似,主要的区别是驱动的入口做了什么。

下面是一个驱动的inf文件的定义部分:

[Defines]

  INF_VERSION                    = 0x00010005
  BASE_NAME                      = DxeDriverInBds
  FILE_GUID                      = 04687443-0174-498F-A2F9-08F3A5363F84
  MODULE_TYPE                    = UEFI_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = DxeDriverEntry

最后一行是C代码的入口,对于普通的驱动,这个入口里面就是初始化设备的函数。
a. 在DXE Phase最早执行的Driver
b. 包含Dependency Expression Syntax(DEPEX) 来描述Dispatch的顺序。
c. 典型的包含:

          Basic Services
          Processor Initialization Code
          Chipset Initialization Code
          Platform Initialization Code

d. 产生Architectural Protocols
而对于符合UEFI Driver Model的驱动来说,它只是简单的安装了一个Protocol。

    a. 初始化的过程中不会涉及到硬件
    c. 典型的提供对Console Devices 和 Boot Devices的访问
    d. Abstract Bus Controller
    e. 只有Boot OS 所需要的Driver才被初始化
    f. DXE Dispather完成的时候才被呼叫
    g. 像个Driver一样被执行
    h. 需要建立控制台(Keyboard,Video)和处理EFI Boot Option(Boots OS)的时候要连接EFI Drivers。

接下来以SnpDxe模块为例看看驱动入口的形式:

EFI_STATUS
EFIAPI
InitializeSnpNiiDriver (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gSimpleNetworkDriverBinding,
           ImageHandle,
           &gSimpleNetworkComponentName,
           &gSimpleNetworkComponentName2
           );
}

这里的重点在于gSimpleNetworkDriverBinding这个Protocol,它的形式如下:

EFI_DRIVER_BINDING_PROTOCOL gSimpleNetworkDriverBinding = {
  SimpleNetworkDriverSupported,
  SimpleNetworkDriverStart,
  SimpleNetworkDriverStop,
  0xa,
  NULL,
  NULL
};

所有的符合UEFI Driver Model的驱动都会安装一个如上结构的Protocol,在《UEFI Spec》里面有对该类型Protocol的详细介绍。

简单来说,就是DXE阶段安装了一大堆这种Protocol,然后gBS->ConnectController的时候,首先会执行xxxSupported()函数,如果返回的是EFI_SUCCESS,则会继续执行xxxStart()函数,而这个函数中就包含设备初始化所需要的代码。大概流程如下:

  1. 当扫描的这个设备的时候(设备用Controller表示),先判断它是否安装了DevicePathProtocol,没有就表示这个设备还没有准备好(或者说不是设备),后面的xxxStart()不用执行;
  2. 然后判断NetworkInterfaceIdentifierProtocol是否安装,这个是网卡驱动一定会装的Protocol,Snp驱动底层的操作需要依赖于它,所以一定要安装,如果没有就不会执行后面的操作;
  3. 判断NetworkInterfaceIdentifierProtocol是否满足要求,如果不满足则不会执行xxxStart()函数。

如果以上条件都满足,就可以认为该设备是一个网卡,然后这个驱动就会被执行,而之前获取到的DevicePathProtocol和NetworkInterfaceIdentifierProtocol就会成为操作正确设备的基础。

你可能感兴趣的:(UEFI学习,linux,运维,服务器)