在PEI和DXE之间通过HOB 传递参数。例如从VOID EFIAPI DxeMain ( IN VOID *HobStart ) 的入口函数也可以看到输入参数只有一个,这个参数是在PEI阶段准备好的。
拿到HOB 后就可以按照下面的code从HOB中拿到PEI传递过来的数据
VectorInfoList = NULL;
GuidHob = GetNextGuidHob (&gEfiVectorHandoffInfoPpiGuid, HobStart);
if (GuidHob != NULL) {
VectorInfoList = (EFI_VECTOR_HANDOFF_INFO *) (GET_GUID_HOB_DATA(GuidHob));
}
调用GetNextGuidHob 来找从HobStart 开始第一个匹配gEfiVectorHandoffInfoPpiGuid的GuidHob。
VOID *
EFIAPI
GetNextGuidHob (
IN CONST EFI_GUID *Guid,
IN CONST VOID *HobStart
)
{
EFI_PEI_HOB_POINTERS GuidHob;GuidHob.Raw = (UINT8 *) HobStart;
while ((GuidHob.Raw = GetNextHob (EFI_HOB_TYPE_GUID_EXTENSION, GuidHob.Raw)) != NULL) {
if (CompareGuid (Guid, &GuidHob.Guid->Name)) {
break;
}
GuidHob.Raw = GET_NEXT_HOB (GuidHob);
}
return GuidHob.Raw;
}
可见会遍历整个GuidHob.Raw,如果找到guid的name等于gEfiVectorHandoffInfoPpiGuid 就停止,然后返回 GuidHob.Raw。
然后通过
#define GET_GUID_HOB_DATA(HobStart)
(VOID )((UINT8 **)&(HobStart) + sizeof (EFI_HOB_GUID_TYPE))
就可以拿到想要的gEfiVectorHandoffInfoPpiGuid
OVERVIEW
本篇补充相关PEI到DXE阶段的一写知识
在PEI阶段,PEIM 、PPI、 HOB组成了PEI阶段的最重要的部分,PEI阶段的module也可以理解为Driver就是PEIM,PEI阶段就是由一个一个的PEIM组成的;PPI是PEIM之间互相调用的接口,由惟一的GUID引导,内部也包含一些接口,HOB相当于信件在PEI阶段创建,会记载当前系统的信息,可以自定义HOB,然后在DXE阶段读取。框图如下:
简单解读:
PEI Core主要包括Core Services和Core Dispatcher。
Core Services包括PEI及后面phase要用到的各种Services, 比如Status Code, HOBs,Memory Services,Boot Mode Services等。在PEi阶段get当前计算机启动的boot Mode是有直接定义好的PEI Service函数,在DXE以及后面的阶段要通过HOB方式,通过get HOB LIST然后拆解信息进行get启动的boot Mode。
Core Dispatcher负责派发各PEIMs,意思是将PEIM按照既定的顺序Load并执行。这里的既定顺序即Dependency顺序,就是inf文件里面的depx,只有满足条件才会执行。
各PEIM Entry可能使用其它PEIM的PPI, 也可能使用自己的PPI。
PEI Core最后会找到DXE IPL PPI,(IPL是一个很重要的知识点)进入下一阶段DXE。
DXE获得之前phase Data的是从HOB里拿,PEI Core会创建HOB,PEI和DXE都可以使用HOB的Data。
PEIM
PEIM(PEI Module), 会被编译成efi binary,在一套完整的BIOS code编译完之后进入到build目录就可以找到这个PEIM具体的efi,.inf + .c +.h >build> .efi是为了硬件相关的初始化,PEIM 也提供各自提供接口(interface)给别的PEIM使用
PPI
PPIs (PEIM-to-PEIM Interfaces)
PEIMs被调用是通过PPI,Interface。Interface是UEFI重要的概念,会经常出现。简单来说,PPI只是一个接口,接口里面有成员函数,想调用某个函数必须通过该接口。
PPI的名字:GUID (128-bits value)
PPIs被定义成结构体的形式,在code里看PPI就是一个Struct,其中可能包括功能、数据,或者两者的混合。
PEIM会把它的PPI注册到PEI Foundation,PEI Foundation管理着庞大的PPI数据库
USE PPI
几个重要的PPI Services
InstallPpi() 安装PPI到PEI foundation,protocol install完后是放到Handle Datebase里面
LocatePpi() 根据PPI名字GUID从PEI foundation找Interface
NotifyPpi() PPI里的function不会在派发时就执行,会有一个判定条件,通知系统这个PPI会在某个PPI被安装时才执行。
Install PPI
/**Install PPI services. It is implementation of EFI_PEI_SERVICE.InstallPpi.
这是个service,PEI foundation提供的。 通过GUID安装。目的是让别人调用。
@param PeiServices An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
标准格式,入口第一人参数是铁定的EFI_PEI_SERVICES指针
@param PpiList Pointer to PPI array that want to be installed.
第二个参数是PPI List, LIST里包括Flag、GUID和函数 参考.h里的EFI_PEI_PPI_DESCRIPTOR定义@retval EFI_SUCCESS if all PPIs in PpiList are successfully installed.
@retval EFI_INVALID_PARAMETER if PpiList is NULL pointer
if any PPI in PpiList is not valid
@retval EFI_OUT_OF_RESOURCES if there is no more memory resource to install PPI**/
EFI_STATUS
EFIAPI
PeiInstallPpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList
);
实例:
以下面的PPI为例,一个给Capsule服务的PPI,里面有三个成员 CapsuleCoalesce, CheckCapsuleUpdate, CreateState。
CONST EFI_PEI_CAPSULE_PPI mCapsulePpi = {
CapsuleCoalesce,
CheckCapsuleUpdate,
CreateState
};
给InstallPPI传递的第二个参数:*PpiList。Descriptor有三个值,第一个为属性,第二个为绑定的GUID用于后面locate调用,第三个参数放入struct接口,这样就定义好了ppiList。
安装PpiList, 通常代码会用Library封装(*PeiServices)->InstallPpi 成PeiServicesInstallPpi
PeiServicesInstallPpi原型函数,还是那个标准的InstallPpi,传了两个参数This指针和PpiList
EFI_PEI_PPI_DESCRIPTOR
//
// PEI Ppi Services List Descriptors
//
#define EFI_PEI_PPI_DESCRIPTOR_PIC 0x00000001
#define EFI_PEI_PPI_DESCRIPTOR_PPI 0x00000010
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK 0x00000020
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH 0x00000040
#define EFI_PEI_PPI_DESCRIPTOR_NOTIFY_TYPES 0x00000060
#define EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST 0x80000000
///
/// The data structure through which a PEIM describes available services to the PEI Foundation.
///
typedef struct {
///
/// This field is a set of flags describing the characteristics of this imported table entry.
/// All flags are defined as EFI_PEI_PPI_DESCRIPTOR_***, which can also be combined into one.
/// 上面描述的属性。
UINTN Flags;
///
/// The address of the EFI_GUID that names the interface.
/// 所绑定的GUID
EFI_GUID *Guid;
///
/// A pointer to the PPI. It contains the information necessary to install a service.
/// 所需要install的PPI
VOID *Ppi;
} EFI_PEI_PPI_DESCRIPTOR;LocatePPI()
/**
Locate a given named PPI.
用GUID从Database中找想要的PPI@param PeiServices An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
@param Guid Pointer to GUID of the PPI.
@param Instance Instance Number to discover.
@param PpiDescriptor Pointer to reference the found descriptor. If not NULL,
returns a pointer to the descriptor (includes flags, etc)
@param Ppi Pointer to reference the found PPI@retval EFI_SUCCESS if the PPI is in the database
@retval EFI_NOT_FOUND if the PPI is not in the database**/
EFI_STATUS
EFIAPI
PeiLocatePpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_GUID *Guid,
IN UINTN Instance,
IN OUT EFI_PEI_PPI_DESCRIPTOR **PpiDescriptor,
IN OUT VOID **Ppi
);
//第一个参数是固定的EFI_PEI_SERVICES第二个参数为GUID,第三个参数通常为0,第四个参数为NULL最重要是拿到第五个参数,通过这个指针可以得到Interface。
实例:
以Capsule为例:Guid为gEfiPeiCapsulePpiGuid,通过这个GUID从Database中找想要的PPI, 想要的PPI都在这个Capsule指针里,Locate之后就可以调用PPI里的函数实现CheckCapsuleUpdate功能
这也是封装过的LocatePpi,原型函数是下面这样,标准的LocatePpi,当然直接用PeiService->也是可以的
Notifyppi()
/**Install a notification for a given PPI.
注册一个notification服务,当给定的那个PPI被安装或者再安装时,执行notify里的函数。@param PeiServices An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
@param NotifyList Pointer to list of Descriptors to notify upon.@retval EFI_SUCCESS if successful
@retval EFI_OUT_OF_RESOURCES if no space in the database
@retval EFI_INVALID_PARAMETER if not a good descriptor
NotifyList里包括Flag,GUID和Notify Routine**/
EFI_STATUS
EFIAPI
PeiNotifyPpi (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN CONST EFI_PEI_NOTIFY_DESCRIPTOR *NotifyList
);
实例:当这个gEfiEndOfPeiSignalPpiGuid指向的PPI被安装时才会触发S3EndOfPeiNotify()
1->首先将要做的事情写到这个函数中,
2->定义好Notify Descriptor :FLAG 、所需加载的PPI GUID、需要执行的函数
3->调用封装后的PeiServiceNotifyPpi => (*PeiServices)->NotifyPpi(PeiServices,NotifyList);
注:在自定义的函数中在确定的点运行相关ppi最好自己写一个Ppi作为触发条件,否则在使用code中定义好的Ppi时需确保不会被其他函数影响或影响其它函数
HOB
HOB (Hand-Off Blocks )传输信息的载体,相比于其他Phase之间的联系,Pei到DXE之间联系比较薄弱,PEI一些初始化硬件、内存的数据等,DXE需要知道,HOB便作为桥梁应运而生。
HOB producer phase (PEI phase)
HOB consumer phase (PEI & DXE phase) (如getbootmodehob就是在PEI阶段调用的,但DXE阶段是不能产生HOB的)
HOB实际上就是一个链表,当我们找到一个hoblist的头,那么整个链表的数据都能get到,比如说GetHobList(),会直接获取hoblist的指针,而且第一个HOB总是为PHIT == Phase Handoff Information Table,里面是boot mode
其它HOB可能出现在List任意位置, 最重要的是System Memory HOB & Firmware Volumes, HOB列表总是会以END_OF_HOB_LIST结束
所以判定是否是HOBlist的最后一个一般都是while (!END_OF_HOB_LIST (Hob))。
下图中没有显示的另一个HOB类型是GUID HOB,它允许PEIM将私有数据传递给DXE驱动程序。
HOB TYPE
GUID类型的HOB是自定义HOB时候会用到的类型,通常用于自己写一些HOB信息。
HOB的使用
/**
Add a new HOB to the HOB List.@param PeiServices An indirect pointer to the EFI_PEI_SERVICES table published by the PEI Foundation.
@param Type Type of the new HOB.
@param Length Length of the new HOB to allocate.
@param Hob Pointer to the new HOB.@return EFI_SUCCESS Success to create HOB.
@retval EFI_INVALID_PARAMETER if Hob is NULL
@retval EFI_NOT_AVAILABLE_YET if HobList is still not available.
@retval EFI_OUT_OF_RESOURCES if there is no more memory to grow the Hoblist.**/
EFI_STATUS
EFIAPI
PeiCreateHob (
IN CONST EFI_PEI_SERVICES **PeiServices,
IN UINT16 Type, //对于自定义的HOB 一般使用EFI_HOB_GUID_TYPE
IN UINT16 Length,
IN OUT VOID **Hob
);
GetHobList()返回整个HOB列表中匹配GUID HOB的第一个实例
由于阶段不一样,PEI直接拿到HOB List Pointer, DXE通过System Configuration Table (gST)拿。
传入的参数第一个为Hob的GUID,所有的UEFI元素都有自己的GUID,传入得到的返回结果就是HOBlist ,通过的是configration table去抓的(属于systemtable的成员),每个DXE Driver的entrypoint都有两个参数imaginehandle 和systemtable,故而可以通过systemtable可以访问整个系统的资源。