总述

使用 HOB 的原因是因为,在 PEI 阶段内存尚未完全初始化,到了 DXE 阶段才完整初始化了内存,所以无法通过简单的内存地址传递数据,并且我们仍然有一些对于内存空间存储的需求,因此发明了 HOB 机制。

HOBs may be relocated in system memory by the HOB consumer phase. HOBs must not contain pointers to other data in the HOB list, including that in other HOBs. The table must be able to be copied without requiring internal pointer adjustment.[1]

也就是需要注意 PEI 的 HOB 内存空间在 DXE 阶段会被DxeCore relocation,所以不要用 HOB 来存放内存地址。[2]

UEFI 提供了 HOB(Hand-Off-Block)机制,即在 PEI 阶段将数据打包成数据块存放在一段连续的内存中,数据块的标识为 GUID,DXE 阶段可以通过该 GUID 在 HOB 中找到对应数据块。内存中一系列的 HOB 组成了 HOB List

如下图所示:

HOB的结构列表 HOB List

HOB 是非常重要数据结构,在 CAR 时期将会初始化好,UEFI 的早期的堆栈都基于HOB,

PEI 阶段会创建大量的 HOB,包含板级数据(UBA), silicon Hob(Memmap, RC resource), Setup,当进入PEI PostMem phase后,CAR 里的 HOB 会 shadow 到 Mem, 当进入 DXE (也就是PeiMain 调用了TempPtr.DxeIpl->Entry) 后,这些 HOB 会被 DXE driver 大量采用, 用来创建 Protocol 或者其他初始化。

由 PEI 阶段进入 DXE 阶段,PeiCore 中传递 HOB 的代码

HOB 在 PEI 阶段会被修改(写),在 DXE 和 SMM 中会被使用,原则上不推荐SMM修改 HOB。创建的单个HOB Size最大为 64K

ASSERT (DataLength <= (0xFFF8 - sizeof (EFI_HOB_GUID_TYPE)));
// Make sure that data length is not too long.

需要大数据量的HOB需要注意。

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

因此,只有 PEI Phase 才允许增加或改动这些 HOBs,当 HOB List 被传送给 DXE Phase 之后,他们就是只读的(Read Only)。

一个只读的 HOB List 的延伸就是 Handoff 信息,比如 Boot Mode,必须以别的方式来处理。比如,DXE Phase 想要产生一个 Recovery 条件,它不能 update Boot Mode,而是通过使用特殊方式的 reset call 来实现。

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

第一次进入 PeiCore 的时候,还是 CAR 阶段,内存没有初始化,InitializeMemoryServices 中调用 PeiCoreBuildHobHandoffInfoTable 开启 HOB 创建;位置在Car中;

PeiCore() 进入 InitializeMemoryServices()

InitializeMemoryServices() 进入 PeiCoreBuildHobHandoffInfoTable()

网上会找到很多资料说:

如何增加一个新的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~~

但是在开发中,其实我们不必操作 PHIT 中的各个堆栈指针,已经做好的有很多轮子。

代码

说了那么多,我们肯定是面向 API 编程,不用费劲心思去自己对HOB List中 PHIT的各种操作。

  1. 在PEI阶段中,需要写一个PEIM,来创建一个HOB。HOB不建议放内存地址,所以这里没用字符数组。

#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/PeimEntryPoint.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Pi/PiHob.h>
#include <Pi/PiPeiCis.h> EFI_GUID gEfiMyPeimHobGuid = {0x34ad055e, 0x1c21, 0x4b06, {0x80, 0x2f, 0xcd, 0x8a, 0x34, 0x2b, 0xa8, 0xc9}}; // define a data struct for HOB Create
typedef struct _MY_PEIM_HOB
{
EFI_HOB_GUID_TYPE EfiHobGuidType;
CHAR8 c0;
CHAR8 c1;
CHAR8 c2;
CHAR8 c3;
CHAR8 c4;
CHAR8 c5;
CHAR8 c6;
CHAR8 c7;
CHAR8 c8;
CHAR8 c9;
} MY_PEIM_HOB; // PEI Module Entry Function
EFI_STATUS
EFIAPI
MyPeimHobEntry(
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] MyPeimHobEntry Start..\n")); MY_PEIM_HOB *Hob = NULL; // create HOB by PeiServices
Status = PeiServicesCreateHob (
EFI_HOB_TYPE_GUID_EXTENSION,
sizeof(MY_PEIM_HOB),
&Hob); // fill the HOB data [Guid + 'Hello HOB!']
if (!EFI_ERROR(Status))
{
Hob->EfiHobGuidType.Name = gEfiMyPeimHobGuid;
Hob->c0 = 'H';
Hob->c1 = 'e';
Hob->c2 = 'l';
Hob->c3 = 'l';
Hob->c4 = 'o';
Hob->c5 = ' ';
Hob->c6 = 'H';
Hob->c7 = 'O';
Hob->c8 = 'B';
Hob->c9 = '!'; } DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] MyPeimHobEntry End..\n"));
return EFI_SUCCESS;
}
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyPeimHob
FILE_GUID = e1223900-9e9a-4a7d-8835-ff4f54f5cff9
MODULE_TYPE = PEIM
VERSION_STRING = 1.0
ENTRY_POINT = MyPeimHobEntry [Sources]
MyPeimHob.c [Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec [LibraryClasses]
BaseLib
PeimEntryPoint
BaseMemoryLib
DebugLib
PeiServicesLib [depex]
TRUE
  1. 在DXE阶段中,需要写一个DXE Driver,来获得之前创建好的HOB,并打印出来。

#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Pi/PiBootMode.h>
#include <Pi/PiHob.h>
#include <Library/HobLib.h>
#include <Library/UefiBootServicesTableLib.h> EFI_GUID gEfiMyPeimHobGuid = {0x34ad055e, 0x1c21, 0x4b06, {0x80, 0x2f, 0xcd, 0x8a, 0x34, 0x2b, 0xa8, 0xc9}}; typedef struct _MY_PEIM_HOB
{
EFI_HOB_GUID_TYPE EfiHobGuidType;
CHAR8 c0;
CHAR8 c1;
CHAR8 c2;
CHAR8 c3;
CHAR8 c4;
CHAR8 c5;
CHAR8 c6;
CHAR8 c7;
CHAR8 c8;
CHAR8 c9;
} MY_PEIM_HOB; EFI_STATUS
EFIAPI
MyDxeHobEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_SUCCESS;
DEBUG ((EFI_D_ERROR , "[MyHelloWorldHob] MyDxeHobEntry Start..\n"));
MY_PEIM_HOB *Hob; // get the first Hob connected with [gEfiMyPeimHobGuid] by Guid.
Hob = GetFirstGuidHob (&gEfiMyPeimHobGuid); // parse the hob and print debug info
if ( Hob != NULL)
{
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] MyDxeHob Guid:%g\n", Hob->EfiHobGuidType.Name));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c0));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c1));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c2));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c3));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c4));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c5));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c6));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c7));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c8));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c9));
} DEBUG ((EFI_D_ERROR , "[MyHelloWorldHob] MyDxeHobEntry End..\n"));
return Status;
}
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyDxeHob
FILE_GUID = 0760c3a1-a98e-4d86-8102-160d49e29e51
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = MyDxeHobEntry [Sources]
MyDxeHob.c [Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec [LibraryClasses]
UefiDriverEntryPoint
BaseLib
BaseMemoryLib
DebugLib
PrintLib
DevicePathLib
UefiBootServicesTableLib
MemoryAllocationLib
UefiLib
HobLib [depex]
TRUE
  1. 根据PEIM和DXE Driver的类型,填写OvmfPkgX64.fdf和OvmfPkgX64.dsc,来编译写好的两个模块,具体

    OvmfPkgX64.fdf

OvmfPkgX64.dsc

最后build一下,然后再在qemu目录下创建setup-qemu-x64.bat文件,里面内容

"D:\Program Files\qemu\qemu-system-x86_64.exe" -bios "D:\edk2\edk2\Build\OvmfX64\DEBUG_VS2019\FV\OVMF.fd" -M "pc" -m 256 -cpu "qemu64" -boot order=dc -serial stdio -hda fat:rw:D:\edk2\ovmf\esp -net none

其中...\ovmf\esp文件夹需要自己创建,或者改成自己想挂载的路径下。

在qemu目录下执行qemu>setup-qemu-x64.bat | findstr MyHelloWorldHob 查看结果

在这个例子里用GUID直接找HOB,实际开发中,会使用GetHobList()找到HOB头,然后循环调用GetNextHob()来找对应类型的HOB,可以参考EDK2中的原生代码,全局搜索GetNextHob()这个函数就能找到。篇幅原因,这里先Todo.......

后记:HOB 是如何跨阶段传递的?

1. 在 Cache-As-RAM 中创建

PEI 阶段在 DRAM 初始化之前运行,因此它使用 Cache-As-RAM(CAR) 作为临时内存。

HOB 就是在这个 CAR 中被创建的。

在早期 PEI 阶段,HOB 是保存在这个临时的栈空间里的。

2. 内存初始化之后,迁移 HOB

当 DRAM 初始化完成之后,PEI Core 会分配一块真实的内存区域作为 永久内存池(Permanent Memory)

此时,PEI Core 会把已经存在于 CAR 中的 HOB 数据复制到新分配的 DRAM 区域。

在 PeiMain 中,PeiDispatcher() 会调用一个专门用于初始化永久内存的PEIM,这个PEIM根据你所编译的Pkg不同而不同。这个PEIM会进行内存初始化。

这一步叫做 HOB 的重定位(HOB Relocation)。

内核会更新 HOB list 的起始地址指针。

3. DXE 阶段读取 HOB

当转交控制权给 DXE Loader 时,PEI 会通过 EFI_PEI_HOB_POINTER 将 HOB list 的新地址传递过去。

DXE Core 通过这个地址获取 HOB 列表,从而继续处理。


  1. UEFI Platform Initialization Specification

  2. UEFI 基础教程 (七) - HOB 简单使用

【UEFI】HOB 从概念到代码的更多相关文章

  1. 第三章--Win32程序的执行单元(部分概念及代码讲解)(上 -- 多线程)

    学习<Windows程序设计>记录 概念贴士: 1. 线程描述了进程内代码的执行路径. 2. _stdcall是新标准C/C++函数的调用方法.从底层来说,使用这种调用方法参数的进栈顺序和 ...

  2. 第二章--Win32程序运行原理 (部分概念及代码讲解)

    学习<Windows程序设计>记录 概念贴士: 1. 每个进程都有赋予它自己的私有地址空间.当进程内的线程运行时,该线程仅仅能够访问属于它的进程的内存,而属于其他进程的内存被屏蔽了起来,不 ...

  3. bluetooth(蓝牙) AVRCP协议概念及代码流程解析

    一 概念 AVRCP全称:The Audio/Video Remote Control Profile (AVRCP) 翻译成中文就是:音视频远程控制协议.概念:AVRCP定义了蓝牙设备之间的音视频传 ...

  4. flume原理及代码实现

    转载标明出处:http://www.cnblogs.com/adealjason/p/6240122.html 最近想玩一下流计算,先看了flume的实现原理及源码 源码可以去apache 官网下载 ...

  5. 面向对象---java代码块

    概念:代码块是指用{}括起来的一段代码. 根据位置及声明的关键字不同,代码块可分为普通代码块.构造块.静态代码块.同步代码块4种. 1.普通代码块: 直接在方法中或在语句中定义 public clas ...

  6. 我读<代码整洁之道>--读书笔记整理

    第一章 整洁代码 "我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的,整洁的代码总是看起来像是某位特别在意他的人写的.几乎没有改进的余地,代码作者设么都想到了,如果你企图改进它 ...

  7. UEFI EVENT 全解

    Event和Timer在UEFI当中是怎么实现的以及原理,我们先从Timer开始,然后细细的拨开隐藏在底层的实现. 先说Timer,那什么是Timer呢?其实在中文里面我们把它叫做定时/计数器,但是我 ...

  8. 压缩跟踪(CT)代码具体学习_模块1(样本的採集和扩充)

    本章主要具体解释的是compressive tracking框架中的第一部分:样本的採集和扩充部分. 在開始代码学习的前面,你须要知道的理论知识參见论文:Real-time Compressive T ...

  9. IOS开发之----代码块的使用(二)

    iOS4引入了一个新特性,支持代码块的使用,这将从根本上改变你的编程方式.代码块是对C语言的一个扩展,因此在Objective-C中完全支持.如果你学过Ruby,Python或Lisp编程语言,那么你 ...

  10. 【软件构造】第八章第三节 代码调优的设计模式和I/O

    第八章第三节 代码调优的设计模式和I/O 本节学习如何通过对代码的修改,消除性能瓶颈,提高系统性能?——代码调优.面向性 能的设计模式 Outline Java调优 代码调优的概念 单例模式(Sing ...

随机推荐

  1. Redmine 中,如何新增一个字段名,比如"模块名称":

    why: 用于编写测试报告时能够直接根据模块名称进行统计,不对excel 表格进行自定义拆分-----规范性 登录到 Redmine 平台,并进入你的项目页面. 在项目页面上方的导航栏中,点击 &qu ...

  2. jquery submit 解决多次提交

    jquery submit 解决多次提交 web应用中常见的问题就是多次提交,由于表单提交的延迟,有时几秒或者更长,让用户有机会多次点击提交按钮,从而导致服务器端代码的种种麻烦. 为了解决这个问题,我 ...

  3. 抓包分析:wireshark抓不到TLS1.3数据包中证书的解决方案

    近日工作中遇到需要分析使用TLS1.3协议进行通信的数据包的情况,但使用wireshark进行分析发现不能抓到服务端证书,感到诧异遂设法解决 这篇博客给出解决方案,和简单的原理分析 解决方案: 第一步 ...

  4. 【Git】在 Idea 中使用 Git

    在 Idea 中使用 Git 1 安装 Git 核心程序 根据自己的电脑操作系统从 Git 官网 https://git-scm.com/ 下载对应的 Git 核心程序. 以 git-2.21.0 为 ...

  5. study Python3【3】的函数

    Python的函数定义简单,但灵活度非常大.功能强大意味复杂.为了复习,把廖雪峰老师的该课程做个回顾. 参数有:必选参数.默认参数.可变参数.关键字参数.命名关键字参数. 计算x的n次方函数: def ...

  6. study Rust-7【使用结构体的demo】

    fn main() { let width1 = 30; let height1 = 50; println!( "The area of the rectangle is {} squar ...

  7. Web前端入门第 26 问:CSS 浏览器兼容性怎么查?

    学编码之前,当先学排查问题的能力. 在那个 IE 浏览器 当道的时代,前端开发简直就是刀耕火种一般,一个简单的圆角模块,嘿...不好意思,它不支持,用图片吧. 但凡经历过 IE 的洗礼,就会知道当时哪 ...

  8. HTTP 分段下载

    GET /user_crc.bin HTTP/1.1 Host: mnif.cn Range: bytes=0-1000

  9. zk源码—5.请求的处理过程

    大纲 1.服务器的请求处理链 (1)Leader服务器的请求处理链 一.PrepRequestProcessor请求预处理器 二.ProposalRequestProcessor事务投票处理器 三.S ...

  10. JavaScript 单线程原理与异步编程机制

    JavaScript 单线程原理与异步编程机制 为什么 JavaScript 是单线程? JavaScript 被设计成单线程,简单来说就是 -- 浏览器里干活儿只能一个接一个排着队来,没法同时多开窗 ...