UEFI源码学习02 - PeiCore

文章目录

  • 1 PeiCore Service
    • 1.1 PPI
      • 1.1.1 安装查找PPI
      • 1.1.2 安装Notify
      • 1.1.3 ProcessNotify
    • 1.2 HOB
      • 1.2.1 HobList初始化
      • 1.2.2 创建Hob
      • 1.2.3 HOB 类型
      • 1.2.4 Dump 出PEI中所有的HOB
    • 1.3 BootMode
    • 1.4 PEI Memory
      • 1.4.1 注册Pei内存信息
      • 1.4.2 分配内存
    • 1.5 Firmware Volume(TODO)
  • 2 PeiDispacther
    • 2.1 PeiCore三次进入的时机
    • 2.2 PeiDispacther流程
      • 2.2.1 PEIM布局
      • 2.2.2 主要流程
      • PeiCheckAndSwitchStack
      • 2.2.3 第三次进入PeiCore
    • 2.3 PcdPeim
    • 2.4 MemoryInitPeim
    • 2.5 DxeIplPeim (TODO)
    • 2.6 跳转到DXE (TODO)

1 PeiCore Service

1.1 PPI

PEI Foundation 提供的PPI服务主要是以下四个函数。

  PeiInstallPpi,			//安装PPI
  PeiReInstallPpi,			//重新安装PPI
  PeiLocatePpi,				//查找PPI
  PeiNotifyPpi,				//安装一个Notify

PPI和Notify的数据结构组织如图所示。PEI_CORE_INSTANCE 中PpiData维护了PPI和两种类型的Notify数组。PPI都存放在PpiList的PPiPtrs数组中。而Notify既可以放在CallbackNotifyList也可以放在DispatchNotifyList中。UEFI源码学习02 - PeiCore_第1张图片

1.1.1 安装查找PPI

PeiInstallPpi像PEI foundation安装一个PPI,其实现是通过 GUID 在 PEI PPI 数据库中安装一个接口。把PPI interface 从头部向尾部插入到PPI database 的PPI LIST 数组中。然后使用 ProcessNotify 为新安装的 PPI 处理通知。

EFI_STATUS
InternalPeiInstallPpi (
  IN CONST EFI_PEI_SERVICES        **PeiServices,
  IN CONST EFI_PEI_PPI_DESCRIPTOR  *PpiList,
  IN BOOLEAN                       Single
  )
{
  ...
  PpiListPointer = &PrivateData->PpiData.PpiList;
  Index          = PpiListPointer->CurrentCount;
  ...
  for ( ; ;) {
  //将要安装的PPI插入到PpiPtrs数组中,然后将索引index+1
    PpiListPointer->PpiPtrs[Index].Ppi = (EFI_PEI_PPI_DESCRIPTOR *)PpiList;
    Index++;
    PpiListPointer->CurrentCount++;
  ...
  }
  ...
  //使用 ProcessNotify 为新安装的 PPI 处理通知。
  ProcessNotify (
    PrivateData,
    EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK,
    LastCount,
    PpiListPointer->CurrentCount,
    0,
    PrivateData->PpiData.CallbackNotifyList.CurrentCount
    );

  return EFI_SUCCESS;
}

PeiLocatePpi根据GUID查找PPI的指针并返回。

PeiLocatePpi (
)
{
  //遍历PpiData中的所有PPI指针。
  for (Index = 0; Index < PrivateData->PpiData.PpiList.CurrentCount; Index++) {
    TempPtr   = PrivateData->PpiData.PpiList.PpiPtrs[Index].Ppi;
    CheckGuid = TempPtr->Guid;
	//如果PPI的GUID 匹配,那么就返回
    if ((((INT32 *)Guid)[0] == ((INT32 *)CheckGuid)[0]) &&
        (((INT32 *)Guid)[1] == ((INT32 *)CheckGuid)[1]) &&
        (((INT32 *)Guid)[2] == ((INT32 *)CheckGuid)[2]) &&
        (((INT32 *)Guid)[3] == ((INT32 *)CheckGuid)[3]))
    {
		...
		//返回该匹配的PPI
        *Ppi = TempPtr->Ppi;
  		...
        return EFI_SUCCESS;
      }
   ...

}

1.1.2 安装Notify

PeiNotifyPpi其流程和安装PPI差不多把PPI notify 插入PPI database 的PPI LIST 数组中,插入顺序是从PPI LIST的尾部向头部插入并使用 ProcessNotify 为所有以前安装的 PPI 处理通知。

InternalPeiNotifyPpi (
  IN CONST EFI_PEI_SERVICES           **PeiServices,
  IN CONST EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyList,
  IN BOOLEAN                          Single
  )
{
  ......
  for ( ; ;) {


    if ((NotifyList->Flags & EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK) != 0) {
      //如果是EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK,就将NotifyList节点插入CallbackNotifyListPointer链表中
	  ...
      CallbackNotifyListPointer->NotifyPtrs[CallbackNotifyIndex].Notify = (EFI_PEI_NOTIFY_DESCRIPTOR *)NotifyList;
      CallbackNotifyIndex++;
      CallbackNotifyListPointer->CurrentCount++;
    } else {
      ... 
      //否则就将NotifyList节点插入DispatchNotifyListPointer链表中
      DispatchNotifyListPointer->NotifyPtrs[DispatchNotifyIndex].Notify = (EFI_PEI_NOTIFY_DESCRIPTOR *)NotifyList;
      DispatchNotifyIndex++;
      DispatchNotifyListPointer->CurrentCount++;
    }


    if (Single) {
      break;
    } else if ((NotifyList->Flags & EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST) ==
               EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST)
    {
      break;
    }
    ...
  }
  ...
  //调用ProcessNotify 区处理Notify的回调函数
  ProcessNotify (
    PrivateData,
    EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK,
    0,
    PrivateData->PpiData.PpiList.CurrentCount,
    LastCallbackNotifyCount,
    CallbackNotifyListPointer->CurrentCount
    );

  return EFI_SUCCESS;
}

1.1.3 ProcessNotify

ProcessNotify这个函数在安装PPI和安装Notify的时候都会被调用。其作用就是从PpiData中查到对应GUID的Notify描述符,然后调用它的回调函数Notify

VOID
ProcessNotify (
  IN PEI_CORE_INSTANCE  *PrivateData,
  IN UINTN              NotifyType,
  IN INTN               InstallStartIndex,
  IN INTN               InstallStopIndex,
  IN INTN               NotifyStartIndex,
  IN INTN               NotifyStopIndex
  )
{
  ...
  //遍历所有Notify
  for (Index1 = NotifyStartIndex; Index1 < NotifyStopIndex; Index1++) {
    if (NotifyType == EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK) {
      NotifyDescriptor = PrivateData->PpiData.CallbackNotifyList.NotifyPtrs[Index1].Notify;
    } else {
      NotifyDescriptor = PrivateData->PpiData.DispatchNotifyList.NotifyPtrs[Index1].Notify;
    }
	//拿出当前Notify的Guid
    CheckGuid = NotifyDescriptor->Guid;

	//遍历所有PPI
    for (Index2 = InstallStartIndex; Index2 < InstallStopIndex; Index2++) {
      
      SearchGuid = PrivateData->PpiData.PpiList.PpiPtrs[Index2].Ppi->Guid;
	  //如果PPI的GUID和Notify的GUID匹配,那么调用回调函数Notify通知该PPI。
      if ((((INT32 *)SearchGuid)[0] == ((INT32 *)CheckGuid)[0]) &&
          (((INT32 *)SearchGuid)[1] == ((INT32 *)CheckGuid)[1]) &&
          (((INT32 *)SearchGuid)[2] == ((INT32 *)CheckGuid)[2]) &&
          (((INT32 *)SearchGuid)[3] == ((INT32 *)CheckGuid)[3]))
      {

        NotifyDescriptor->Notify (
                            (EFI_PEI_SERVICES **)GetPeiServicesTablePointer (),
                            NotifyDescriptor,
                            (PrivateData->PpiData.PpiList.PpiPtrs[Index2].Ppi)->Ppi
                            );
      }
    }
  }
}

1.2 HOB

  PeiGetHobList,	//获取HobList指针
  PeiCreateHob,		//创建一个Hob

HOB是PEI向DXE传递信息的一种方式,HOB在PEI中的实现非常简单,由PEI Foundation管理。PeiCore提供的HOB Service主要是创建HOB:PeiCreateHob。

1.2.1 HobList初始化

HOB的初始化在InitializeMemoryServices 中,首先把HobList指向SecCoreData->PeiTemporaryRamBase。这个地址是SEC阶段传过来的。然后就是建立一个HandOffInfoTable 类型的HOB,这个HOB是第一个HOB。

VOID
InitializeMemoryServices (
  ...
  )
{
    ...
    PrivateData->HobList.Raw        = SecCoreData->PeiTemporaryRamBase;
    ...
    PeiCoreBuildHobHandoffInfoTable (
      BOOT_WITH_FULL_CONFIGURATION,
      (EFI_PHYSICAL_ADDRESS)(UINTN)SecCoreData->PeiTemporaryRamBase,
      (UINTN)SecCoreData->PeiTemporaryRamSize
      );
	...
}

1.2.2 创建Hob

PeiCreateHob 会将要创建的Hob插入到HobList的尾部, HOB的组织如下图所示。
UEFI源码学习02 - PeiCore_第2张图片

PeiCreateHob (
  IN CONST EFI_PEI_SERVICES  **PeiServices,
  IN UINT16                  Type,
  IN UINT16                  Length,
  IN OUT VOID                **Hob
  )
{
  ...
  // 获取PeiCore的HobList的指针并赋值给Hob变量
  Status = PeiGetHobList (PeiServices, Hob);
  HandOffHob = *Hob;
  //拿到HobList尾节点并把它的指针赋值给Hob变量,此时就是Hob变量就是要插入的Hob节点指针
  *Hob                                        = (VOID *)(UINTN)HandOffHob->EfiEndOfHobList;
  //根据函数参数设置Hob的Type和Length
  ((EFI_HOB_GENERIC_HEADER *)*Hob)->HobType   = Type;
  ((EFI_HOB_GENERIC_HEADER *)*Hob)->HobLength = Length;
  ((EFI_HOB_GENERIC_HEADER *)*Hob)->Reserved  = 0;
  //最后更新Hob尾节点指针,Hob尾节点会放在新建Hob的后面
  HobEnd                      = (EFI_HOB_GENERIC_HEADER *)((UINTN)*Hob + Length);
  HandOffHob->EfiEndOfHobList = (EFI_PHYSICAL_ADDRESS)(UINTN)HobEnd;
  HobEnd->HobType   = EFI_HOB_TYPE_END_OF_HOB_LIST;
  HobEnd->HobLength = (UINT16)sizeof (EFI_HOB_GENERIC_HEADER);
  HobEnd->Reserved  = 0;
  HobEnd++;
  ...
}

1.2.3 HOB 类型

定义在PiHob.h

#define EFI_HOB_TYPE_HANDOFF              0x0001			//记录了PeiCore的信息
#define EFI_HOB_TYPE_MEMORY_ALLOCATION    0x0002			//记录内存分配的信息
#define EFI_HOB_TYPE_RESOURCE_DESCRIPTOR  0x0003			//记录不同resource
#define EFI_HOB_TYPE_GUID_EXTENSION       0x0004			
#define EFI_HOB_TYPE_FV                   0x0005			//记录FV的信息
#define EFI_HOB_TYPE_CPU                  0x0006			//记录关于CPU的信息
#define EFI_HOB_TYPE_MEMORY_POOL          0x0007			//记录MEMORY_POOL的信息,内存分配块
#define EFI_HOB_TYPE_FV2                  0x0009			//记录第二个FV的信息
#define EFI_HOB_TYPE_LOAD_PEIM_UNUSED     0x000A
#define EFI_HOB_TYPE_UEFI_CAPSULE         0x000B
#define EFI_HOB_TYPE_FV3                  0x000C			//记录第三个FV的信息
#define EFI_HOB_TYPE_UNUSED               0xFFFE			
#define EFI_HOB_TYPE_END_OF_HOB_LIST      0xFFFF			//尾节点标识符

这里以一个简单的EFI_HOB_TYPE_CPU举例,这个类型比较简单,通过学习这个类型其他的也可触类旁通。这个记录CPU的Memory和IO的寻址空间。这里空间的大小是以bit为单位。比如IO值如果是0x10的话,则代表其寻址空间只有16bit,也就是64KB的大小。

typedef struct {

  EFI_HOB_GENERIC_HEADER    Header;
  UINT8                     SizeOfMemorySpace;
  UINT8                     SizeOfIoSpace;
  UINT8                     Reserved[6];
} EFI_HOB_CPU;

可以看一下在实际中代码中CPU类型的HOB是如何创建的。在CpuPei初始化中又这么一句代码BuildCpuHob。这里ArmGetPhysicalAddressBits 是44, PcdPrePiCpuIoSize得到的值是16, 所以Memory空间有(2 ^ 44)B, IO空间是64KB
edk2/ArmPkg/Drivers/CpuPei/CpuPei.c

InitializeCpuPeim (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
	 BuildCpuHob (ArmGetPhysicalAddressBits (), PcdGet8 (PcdPrePiCpuIoSize));
}
VOID
EFIAPI
BuildCpuHob (
  IN UINT8  SizeOfMemorySpace,
  IN UINT8  SizeOfIoSpace
  )
{
  EFI_HOB_CPU  *Hob;
 // 创建一个HOB
  Hob = CreateHob (EFI_HOB_TYPE_CPU, sizeof (EFI_HOB_CPU));
  //根据传入的参数填充HOB的Memory和IO的空间大小
  Hob->SizeOfMemorySpace = SizeOfMemorySpace;
  Hob->SizeOfIoSpace     = SizeOfIoSpace;
}

1.2.4 Dump 出PEI中所有的HOB

我自己写了一段代码在jump到DXE之前dump出所有的HOB块。

 EFI_PEI_HOB_POINTERS        Hob;
  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_HANDOFF\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_HANDOFF) {
      DEBUG((DEBUG_INFO, "  BootMode            - 0x%x\n", Hob.HandoffInformationTable->BootMode));
      DEBUG((DEBUG_INFO, "  EfiMemoryTop        - 0x%016lx\n", Hob.HandoffInformationTable->EfiMemoryTop));
      DEBUG((DEBUG_INFO, "  EfiMemoryBottom     - 0x%016lx\n", Hob.HandoffInformationTable->EfiMemoryBottom));
      DEBUG((DEBUG_INFO, "  EfiFreeMemoryTop    - 0x%016lx\n", Hob.HandoffInformationTable->EfiFreeMemoryTop));
      DEBUG((DEBUG_INFO, "  EfiFreeMemoryBottom - 0x%016lx\n", Hob.HandoffInformationTable->EfiFreeMemoryBottom));
      DEBUG((DEBUG_INFO, "  EfiEndOfHobList     - 0x%lx\n", Hob.HandoffInformationTable->EfiEndOfHobList));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_MEMORY_ALLOCATION\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_MEMORY_ALLOCATION) {
      DEBUG ((
        DEBUG_INFO | DEBUG_LOAD,
        "  Memory Allocation 0x%08x 0x%0lx - 0x%0lx\n", \
        Hob.MemoryAllocation->AllocDescriptor.MemoryType,                      \
        Hob.MemoryAllocation->AllocDescriptor.MemoryBaseAddress,               \
        Hob.MemoryAllocation->AllocDescriptor.MemoryBaseAddress + Hob.MemoryAllocation->AllocDescriptor.MemoryLength - 1
        ));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_RESOURCE_DESCRIPTOR\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_RESOURCE_DESCRIPTOR) {
      DEBUG ((
        DEBUG_INFO | DEBUG_LOAD,
        "  Owner:%g, EFI_RESOURCE_TYPE:0x%x, ResourceAttribute:0x%x, PhysicalStart:0x%11p, ResourceLength:0x%11p\n",
         &Hob.ResourceDescriptor->Owner, 
         Hob.ResourceDescriptor->ResourceType,
         Hob.ResourceDescriptor->ResourceAttribute,
         Hob.ResourceDescriptor->PhysicalStart,
         Hob.ResourceDescriptor->ResourceLength
        ));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_GUID_CPU\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_CPU) {
      DEBUG((DEBUG_INFO, "  SizeofMem:0x%11p, SizeofIO:0x%11p\n", Hob.Cpu->SizeOfMemorySpace, Hob.Cpu->SizeOfIoSpace));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_GUID_EXTENSION\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_GUID_EXTENSION) {
      DEBUG((DEBUG_INFO, "  Length:0x%x, Guid:%g\n", GET_HOB_LENGTH(Hob), &Hob.Guid->Name));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_LOAD_PEIM_UNUSED\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_LOAD_PEIM_UNUSED) {
      DEBUG((DEBUG_INFO, "  Length:0x%x\n", GET_HOB_LENGTH(Hob)));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_UEFI_CAPSULE\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_UEFI_CAPSULE) {
      DEBUG((DEBUG_INFO, "  BaseAddr:0x%x, Length:0x%x\n", Hob.Capsule->BaseAddress, Hob.Capsule->Length));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_MEMORY_POOL\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST(Hob); Hob.Raw = GET_NEXT_HOB(Hob)) {
    if (GET_HOB_TYPE(Hob) == EFI_HOB_TYPE_MEMORY_POOL) {
      DEBUG((DEBUG_INFO, "  Length:0x%x\n", GET_HOB_LENGTH(Hob)));
    }
  }

  DEBUG((DEBUG_INFO, " EFI_HOB_TYPE_FV/2/3\n"));
  for (Hob.Raw = PrivateData->HobList.Raw; !END_OF_HOB_LIST (Hob); Hob.Raw = GET_NEXT_HOB (Hob)) {
    if (GET_HOB_TYPE (Hob) == EFI_HOB_TYPE_FV) {
      DEBUG ((
        DEBUG_INFO | DEBUG_LOAD,
        "  FV Hob            0x%0lx - 0x%0lx\n",
        Hob.FirmwareVolume->BaseAddress,
        Hob.FirmwareVolume->BaseAddress + Hob.FirmwareVolume->Length - 1
        ));
    } else if (GET_HOB_TYPE (Hob) == EFI_HOB_TYPE_FV2) {
      DEBUG ((
        DEBUG_INFO | DEBUG_LOAD,
        "  FV2 Hob           0x%0lx - 0x%0lx\n",
        Hob.FirmwareVolume2->BaseAddress,
        Hob.FirmwareVolume2->BaseAddress + Hob.FirmwareVolume2->Length - 1
        ));
      DEBUG ((
        DEBUG_INFO | DEBUG_LOAD,
        "                  %g - %g\n",
        &Hob.FirmwareVolume2->FvName,
        &Hob.FirmwareVolume2->FileName
        ));
    } else if (GET_HOB_TYPE (Hob) == EFI_HOB_TYPE_FV3) {
      DEBUG ((
        DEBUG_INFO | DEBUG_LOAD,
        "  FV3 Hob           0x%0lx - 0x%0lx - 0x%x - 0x%x\n",
        Hob.FirmwareVolume3->BaseAddress,
        Hob.FirmwareVolume3->BaseAddress + Hob.FirmwareVolume3->Length - 1,
        Hob.FirmwareVolume3->AuthenticationStatus,
        Hob.FirmwareVolume3->ExtractedFv
        ));
      if (Hob.FirmwareVolume3->ExtractedFv) {
        DEBUG ((
          DEBUG_INFO | DEBUG_LOAD,
          "                  %g - %g\n",
          &Hob.FirmwareVolume3->FvName,
          &Hob.FirmwareVolume3->FileName
          ));
      }
    }
  }

log打印

dump HobList
 EFI_HOB_TYPE_HANDOFF
  BootMode            - 0x0
  EfiMemoryTop        - 0x0000000048000000
  EfiMemoryBottom     - 0x0000000044000000
  EfiFreeMemoryTop    - 0x0000000047832000
  EfiFreeMemoryBottom - 0x0000000044021420
  EfiEndOfHobList     - 0x44021418
 EFI_HOB_TYPE_MEMORY_ALLOCATION
  Memory Allocation 0x00000004 0x47FFF000 - 0x47FFFFFF
  Memory Allocation 0x00000004 0x47FFE000 - 0x47FFEFFF
  Memory Allocation 0x00000004 0x47FFD000 - 0x47FFDFFF
  Memory Allocation 0x00000004 0x47FFC000 - 0x47FFCFFF
  Memory Allocation 0x00000004 0x47FFB000 - 0x47FFBFFF
  Memory Allocation 0x00000004 0x44000000 - 0x4401FFFF
  Memory Allocation 0x00000003 0x47FEC000 - 0x47FFAFFF
  Memory Allocation 0x00000003 0x47FE5000 - 0x47FEBFFF
  Memory Allocation 0x00000003 0x47FE2000 - 0x47FE4FFF
  Memory Allocation 0x00000003 0x47FDE000 - 0x47FE1FFF
  Memory Allocation 0x00000004 0x47EDD000 - 0x47FDDFFF
  Memory Allocation 0x00000003 0x47ED5000 - 0x47EDCFFF
  Memory Allocation 0x00000004 0x47EC5000 - 0x47ED4FFF
  Memory Allocation 0x00000004 0x47832000 - 0x47EC4FFF
 EFI_HOB_TYPE_RESOURCE_DESCRIPTOR
  Owner:00000000-0000-0000-0000-000000000000, EFI_RESOURCE_TYPE:0x0, ResourceAttribute:0x2007, PhysicalStart:0x00040000000, ResourceLength:0x00008000000
 EFI_HOB_TYPE_GUID_CPU
  SizeofMem:0x0000000002C, SizeofIO:0x00000000010
EFI_HOB_TYPE_GUID_EXTENSION
  Length:0xE0, Guid:EA296D92-0B69-423C-8C28-33B4E0A91268
  Length:0x158, Guid:9B3ADA4F-AE56-4C24-8DEA-F03B7558AE50
  Length:0x68, Guid:4C19049F-4137-4DD3-9C10-8B97A83FFDFA
  Length:0xB8, Guid:A4EE0728-E5D7-4AC5-B21E-658ED857E834
  Length:0x20, Guid:16958446-19B7-480B-B047-7485AD3F716D
  Length:0x20, Guid:B199DEA9-FD5C-4A84-8082-2F4170780305
  Length:0x238, Guid:86D70125-BAA3-4296-A62F-602BEBBB9081
  Length:0x28, Guid:EE4E5898-3914-4259-9D6E-DC7BD79403CF
 EFI_HOB_TYPE_LOAD_PEIM_UNUSED
 EFI_HOB_TYPE_UEFI_CAPSULE
 EFI_HOB_TYPE_MEMORY_
  Length:0x48
  Length:0x208
  Length:0x1C8
  Length:0x108
  Length:0x108
  Length:0x208
  Length:0x10
  Length:0x40
  Length:0xA8
  Length:0x20
  Length:0x40
  Length:0x18
  Length:0x18
  Length:0x20
  Length:0x38
  Length:0x18
  Length:0x18
  Length:0x20
 EFI_HOB_TYPE_FV/2/3
  FV Hob            0x1000 - 0x1FFFFF
  FV Hob            0x47832010 - 0x47EC458F
  FV2 Hob           0x47832010 - 0x47EC458F
                  00000000-0000-0000-0000-000000000000 - 9E21FD93-9C72-4C15-8C4B-E77F1DB2D792
  FV3 Hob           0x47832010 - 0x47EC458F - 0x0 - 0x1
                  00000000-0000-0000-0000-000000000000 - 9E21FD93-9C72-4C15-8C4B-E77F1DB2D792

1.3 BootMode

  PeiGetBootMode,		//获取BootMode
  PeiSetBootMode,		//设置BootMode

BootMode主要用来获取和设置是什么启动模式,这个PEI Service比较简单,Set就是将HandOffHob中的bootmode成员设置一下,Get就是获取HandOffHob中bootmode成员并返回。
edk2/MdeModulePkg/Core/Pei/BootMode/BootMode.c

PeiGetBootMode (
  IN  CONST EFI_PEI_SERVICES  **PeiServices,
  IN  OUT   EFI_BOOT_MODE     *BootMode
  )
{
  ...
  PrivateData = PEI_CORE_INSTANCE_FROM_PS_THIS (PeiServices);
  HandOffHob = (PrivateData->HobList.HandoffInformationTable);
  *BootMode = HandOffHob->BootMode;
  return EFI_SUCCESS;
}

PeiSetBootMode (
  IN CONST EFI_PEI_SERVICES  **PeiServices,
  IN EFI_BOOT_MODE           BootMode
  )
{
  ...
  PrivateData = PEI_CORE_INSTANCE_FROM_PS_THIS (PeiServices);
  HandOffHob = (PrivateData->HobList.HandoffInformationTable);
  HandOffHob->BootMode = BootMode;
  return EFI_SUCCESS;
}

1.4 PEI Memory

  PeiInstallPeiMemory,				//向PEI Foundation注册永久内存
  PeiAllocatePages,
  PeiAllocatePool,
  (EFI_PEI_COPY_MEM)CopyMem,
  (EFI_PEI_SET_MEM)SetMem,

1.4.1 注册Pei内存信息

永久内存的PEIM MemoryInitPeim会在内存初始化完之后调用这个函数告诉Pei Foundtaion内存信息,并且通过设置SwitchStackSignal通知Pei Foundation 切换内存栈指针到永久内存。SwitchStackSignal这个值后面在Pei Dispatcher中会讲到。

EFI_STATUS
EFIAPI
PeiInstallPeiMemory (
  IN CONST EFI_PEI_SERVICES  **PeiServices,
  IN EFI_PHYSICAL_ADDRESS    MemoryBegin,
  IN UINT64                  MemoryLength
  )
{
  //将内存信息注册到Pei Foundation中
  PrivateData->PhysicalMemoryBegin   = MemoryBegin;
  PrivateData->PhysicalMemoryLength  = MemoryLength;
  PrivateData->FreePhysicalMemoryTop = MemoryBegin + MemoryLength;
  //设置SwitchStackSignal。
  PrivateData->SwitchStackSignal = TRUE;

}

1.4.2 分配内存

PeiAllocatePages用来分配连续的内存页,从FreeMemoryTop开始向下生长。
UEFI源码学习02 - PeiCore_第3张图片

PeiAllocatePages (
  IN CONST EFI_PEI_SERVICES      **PeiServices,
  IN       EFI_MEMORY_TYPE       MemoryType,
  IN       UINTN                 Pages,
  OUT      EFI_PHYSICAL_ADDRESS  *Memory
  )
{
  ...
  PrivateData = PEI_CORE_INSTANCE_FROM_PS_THIS (PeiServices);
  Hob.Raw     = PrivateData->HobList.Raw;
  ...
  if (!PrivateData->PeiMemoryInstalled && PrivateData->SwitchStackSignal) {
  	//如果永久内存被初始化了,从永久内存中分配内存
    FreeMemoryTop    = &(PrivateData->FreePhysicalMemoryTop);
    FreeMemoryBottom = &(PrivateData->PhysicalMemoryBegin);
  } else {
    //否则从临时内存中分配内存
    FreeMemoryTop    = &(Hob.HandoffInformationTable->EfiFreeMemoryTop);
    FreeMemoryBottom = &(Hob.HandoffInformationTable->EfiFreeMemoryBottom);
  }
  ...
  //跳过一些检查和error handle。将FreeMemoryTop减去要分配的内存页
    *(FreeMemoryTop) -= Pages * EFI_PAGE_SIZE;
    //返回Memory变量给caller,分配出来的内存页起始地址就是FreeMemoryTop
    *Memory = *(FreeMemoryTop);
    //为这段内存建立Hob信息
    InternalBuildMemoryAllocationHob (
      *(FreeMemoryTop),
      Pages * EFI_PAGE_SIZE,
      MemoryType
      );
    return EFI_SUCCESS;
  ...
}

VOID
InternalBuildMemoryAllocationHob (
  IN EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN UINT64                Length,
  IN EFI_MEMORY_TYPE       MemoryType
  )
{
  //先从HobList头查找有没有可重用的的MemoryAllocationHob。
  MemoryAllocationHob = NULL;
  Hob.Raw             = GetFirstHob (EFI_HOB_TYPE_UNUSED);
  while (Hob.Raw != NULL) {
    if (Hob.Header->HobLength == sizeof (EFI_HOB_MEMORY_ALLOCATION)) {
      //查到了有可重用的Hob就跳出循环
      MemoryAllocationHob = (EFI_HOB_MEMORY_ALLOCATION *)Hob.Raw;
      break;
    }
	//不然就继续查找
 	Hob.Raw = GET_NEXT_HOB (Hob);
    Hob.Raw = GetNextHob (EFI_HOB_TYPE_UNUSED, Hob.Raw);
  }

  if (MemoryAllocationHob != NULL) {
    //如果查到可重用的MemoryAllocationHob,就把分配这段内存信息填充到这个Hob中
    MemoryAllocationHob->Header.HobType = EFI_HOB_TYPE_MEMORY_ALLOCATION;
    ZeroMem (&(MemoryAllocationHob->AllocDescriptor.Name), sizeof (EFI_GUID));
    MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress = BaseAddress;
    MemoryAllocationHob->AllocDescriptor.MemoryLength      = Length;
    MemoryAllocationHob->AllocDescriptor.MemoryType        = MemoryType;
    ZeroMem (MemoryAllocationHob->AllocDescriptor.Reserved, sizeof (MemoryAllocationHob->AllocDescriptor.Reserved));
  } else {
    //如果没有可以重用的MemoryAllocationHob,那么就新建一个MemoryAllocationHob来记录这一段分配的内存
    BuildMemoryAllocationHob (
      BaseAddress,
      Length,
      MemoryType
      );
  }
}

PeiAllocatePool与PeiAllocatePages完全不同,是直接从Hob中分配MemoryPool类型的Hob出来。

typedef struct {
  EFI_HOB_GENERIC_HEADER    Header;
} EFI_HOB_MEMORY_POOL;

PeiAl
locatePool (
  IN CONST EFI_PEI_SERVICES  **PeiServices,
  IN       UINTN             Size,
  OUT      VOID              **Buffer
  )
{
  EFI_STATUS           Status;
  EFI_HOB_MEMORY_POOL  *Hob; 
  //创建一个EFI_HOB_TYPE_MEMORY_POOL类型的HOB,
  Status = PeiServicesCreateHob (
             EFI_HOB_TYPE_MEMORY_POOL,
             (UINT16)(sizeof (EFI_HOB_MEMORY_POOL) + Size),
             (VOID **)&Hob
  //因为EFI_HOB_MEMORY_POOL类型只有EFI_HOB_GENERIC_HEADER一个成员,所以Hob+1就是Header之后的实际数据
  //所以分配的地址就是指向Hob的数据区域。
  *Buffer = Hob + 1;
  return Status;
}

1.5 Firmware Volume(TODO)

2 PeiDispacther

UEFI源码学习02 - PeiCore_第4张图片

2.1 PeiCore三次进入的时机

  1. 第一次进入是退出SEC,进入PeiCore
  2. 进入PeiDispatcher先执行MemoryInit.efi的dependency:PcdPeim,然后执行MemoryInit
  3. 进入PeiCheckAndSwitchStack
    a) 通过TemporaryRamSupportPpi->TemporaryRamMigration把栈和堆都copy到永久内存上。
    b) 进入第二次PeiCore
  4. 把PeiCore 从ROM shadow(copy)到RAM上,然后进入第三次PeiCore
  5. 进入PeiDispacther将剩下的PEIM全部分发完
  6. 解压缩压缩Volumn,获取DXE entry地址,最后跳转到DXE。大致的流程如下所示
    UEFI源码学习02 - PeiCore_第5张图片

2.2 PeiDispacther流程

如上图所示,PeiDispacther有两个循环,第一个循环是遍历所有的FV, 第二个循环是遍历FV中所有的PEIM,然后根据PEIM的Dependency进行加载。

2.2.1 PEIM布局

PEIM必须是位置无关代码或者带有重定位信息。下图是一个PEI的布局。如果PEIM是位置相关代码,那么重定位信息被放在PE32+文件中。
UEFI源码学习02 - PeiCore_第6张图片
最下面有一个可选段,是DEPEX。记录了该PEIM的Dependcy。PeiDispachter在加载PEIM之前会去检查该PEIM的Dependency,如果Dependency中的PEIM还未被加载,则该PEIM就需要等到所依赖的PEIM加载完成后再加载。

2.2.2 主要流程

VOID
PeiDispatcher (
  IN CONST EFI_SEC_PEI_HAND_OFF  *SecCoreData,
  IN PEI_CORE_INSTANCE           *Private
  )
{
  ...
  do {
    ... 
    //第一个循环:遍历FFS中所有的FV
    for (FvCount = Private->CurrentPeimFvCount; FvCount < Private->FvCount; FvCount++) {
      CoreFvHandle = FindNextCoreFvHandle (Private, FvCount);
      //判断这个FV的FvPpi是否是存在的,如果不存在,则没法加载,就跳过
      if (CoreFvHandle->FvPpi == NULL) 
        continue;

      Private->CurrentPeimFvCount = FvCount;

      if (Private->CurrentPeimCount == 0) {
		//去查看这个FV里面有没有Apriori文件,如果存在,则根据Apriori文件堆PEIM加载顺序将PEIM加载顺序排序。
        DiscoverPeimsAndOrderWithApriori (Private, CoreFvHandle);
      }

      //第二个循环: 遍历FV中所有的PEIM。
      for (PeimCount = Private->CurrentPeimCount;
           PeimCount < Private->Fv[FvCount].PeimCount;
           PeimCount++)
      {
        Private->CurrentPeimCount = PeimCount;
        PeimFileHandle            = Private->CurrentFileHandle = Private->CurrentFvFileHandles[PeimCount];
		//如果该PEIM还没有被加载,则加载它
        if (Private->Fv[FvCount].PeimState[PeimCount] == PEIM_STATE_NOT_DISPATCHED) {
          //检查PEIM的Dependcy是否满足,如果不满足,则把PeimNeedingDispatch设置为TRUE,同时先不加载这个PEIM。
          if (!DepexSatisfied (Private, PeimFileHandle, PeimCount)) {
            Private->PeimNeedingDispatch = TRUE;
          } else {
            //这条路是PEIM满足Dependcy的情况,需要加载该PEIM
            Status = CoreFvHandle->FvPpi->GetFileInfo (CoreFvHandle->FvPpi, PeimFileHandle, &FvFileInfo);
            if (FvFileInfo.FileType == EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE) {
              //如果是EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE文件,那么需要对它进行处理,比如解压缩再加载。
              Status = ProcessFvFile (Private, &Private->Fv[FvCount], PeimFileHandle);
              if (Status == EFI_SUCCESS) {
                Private->Fv[FvCount].PeimState[PeimCount]++;
                Private->PeimDispatchOnThisPass = TRUE;
              } else {
                Private->PeimNeedingDispatch = TRUE;
              }
            } else {
              //否则则是PEIM类型的文件,则开始进行PEIM的加载
              //第一步:调用PeiLoadImage加载Image并从中获取Image的EntryPoint
              Status = PeiLoadImage (
                         PeiServices,
                         PeimFileHandle,
                         PEIM_STATE_NOT_DISPATCHED,
                         &EntryPoint,
                         &AuthenticationState
                         );
              ...
              if (Status == EFI_SUCCESS) {
				//第二步,校验PEIM,如果PEIM校验过了,才进行下一步,主要用来secure boot的
                Status = VerifyPeim (Private, CoreFvHandle->FvHandle, PeimFileHandle, AuthenticationState);
                if (Status != EFI_SECURITY_VIOLATION) {
                  Private->Fv[FvCount].PeimState[PeimCount]++;
                  PeimEntryPoint = (EFI_PEIM_ENTRY_POINT2)(UINTN)EntryPoint;
                  PeimEntryPoint (PeimFileHandle, (const EFI_PEI_SERVICES **)PeiServices);
                  Private->PeimDispatchOnThisPass = TRUE;
                } else {
                  //校验不通过就直接跳出循环了,进行出错处理
                  Private->PeimNeedingDispatch = TRUE;
                }
                ...
              }
            }
			//该函数会检查永久内存是否已经安装,如果已经安装则会切换堆栈到永久内存上,并且第二次进入PeiCore
            PeiCheckAndSwitchStack (SecCoreData, Private);
            ProcessDispatchNotifyList (Private);
  			PeiCheckAndSwitchStack (SecCoreData, Private);
			
            if ((Private->PeiMemoryInstalled) && (Private->Fv[FvCount].PeimState[PeimCount] == PEIM_STATE_REGISTER_FOR_SHADOW) &&   \
                (PcdGetBool (PcdMigrateTemporaryRamFirmwareVolumes) ||
                 (Private->HobList.HandoffInformationTable->BootMode != BOOT_ON_S3_RESUME) ||
                 PcdGetBool (PcdShadowPeimOnS3Boot))
                )
            {
              
              if ((Private->HobList.HandoffInformationTable->BootMode != BOOT_ON_S3_RESUME) && !PcdGetBool (PcdShadowPeimOnBoot) &&
                  !PcdGetBool (PcdMigrateTemporaryRamFirmwareVolumes))
              {
                //如果永久内存安装了,并且该PEIM的状态是PEIM_STATE_REGISTER_FOR_SHADOW的话,
              	//那么就需要重新加载PEIM到永久内存上并获取它的EntryPoint
                Status = PeiLoadImage (
                           PeiServices,
                           PeimFileHandle,
                           PEIM_STATE_REGISTER_FOR_SHADOW,
                           &EntryPoint,
                           &AuthenticationState
                           );
                if (Status == EFI_SUCCESS) {
                  PeimEntryPoint = (EFI_PEIM_ENTRY_POINT2)(UINTN)EntryPoint;
                }
              }
			  //调用PEIM的EntryPoint,一般来说会初始化该PEIM并且安装PEIM内的PPI Service。
              PeimEntryPoint (PeimFileHandle, (const EFI_PEI_SERVICES **)PeiServices);
              Private->Fv[FvCount].PeimState[PeimCount]++;
              //处理一下新安装的PPI的Notify
              ProcessDispatchNotifyList (Private);
            }
          }
        }
      }
	  //初始化临时变量进入下一次循环
      Private->CurrentFileHandle    = NULL;
      Private->CurrentPeimCount     = 0;
      Private->CurrentFvFileHandles = NULL;
    }
	
    Private->CurrentPeimFvCount = 0;
  } while (Private->PeimNeedingDispatch && Private->PeimDispatchOnThisPass);
}

PeiCheckAndSwitchStack

PeiCheckAndSwitchStack检查SwitchStackSignal是否为TRUE,如果是TRUE,则把栈从临时内存上切换到永久内存上。在永久内存上的栈叫做NewStack,在临时内存上的栈叫做OldStack

PeiCheckAndSwitchStack (
  IN CONST EFI_SEC_PEI_HAND_OFF  *SecCoreData,
  IN PEI_CORE_INSTANCE           *Private
  )
{
  ...
  PeiServices = (CONST EFI_PEI_SERVICES **)&Private->Ps;

  if (Private->SwitchStackSignal) {
    UINT32                *StackPointer;
    EFI_PEI_HOB_POINTERS  Hob;

    //计算栈顶地址
    for (  StackPointer = (UINT32 *)SecCoreData->StackBase;
           (StackPointer < (UINT32 *)((UINTN)SecCoreData->StackBase + SecCoreData->StackSize)) \
        && (*StackPointer == PcdGet32 (PcdInitValueInTempStack));
           StackPointer++)
    {
    }
    ...

	//计算NewStack的大小和栈顶地址
    NewStackSize = RShiftU64 (Private->PhysicalMemoryLength, 1);
    NewStackSize = ALIGN_VALUE (NewStackSize, EFI_PAGE_SIZE);
    NewStackSize = MIN (PcdGet32 (PcdPeiCoreMaxPeiStackSize), NewStackSize);
    TopOfOldStack = (UINTN)SecCoreData->StackBase + SecCoreData->StackSize;
    TopOfNewStack = Private->PhysicalMemoryBegin + NewStackSize;
    //计算NewStack相对于OldStack的偏移
    if (TopOfNewStack >= TopOfOldStack) {
      StackOffsetPositive = TRUE;
      StackOffset         = (UINTN)(TopOfNewStack - TopOfOldStack);
    } else {
      StackOffsetPositive = FALSE;
      StackOffset         = (UINTN)(TopOfOldStack - TopOfNewStack);
    }

    Private->StackOffsetPositive = StackOffsetPositive;
    Private->StackOffset         = StackOffset;
    //查找gEfiTemporaryRamSupportPpiGuid用于搬运临时内存到永久内存上
    Status = PeiServicesLocatePpi (
               &gEfiTemporaryRamSupportPpiGuid,
               0,
               NULL,
               (VOID **)&TemporaryRamSupportPpi
               );
    if (!EFI_ERROR (Status)) {
	  //计算堆的偏移
      BaseOfNewHeap = TopOfNewStack;
      if (BaseOfNewHeap >= (UINTN)SecCoreData->PeiTemporaryRamBase) {
        Private->HeapOffsetPositive = TRUE;
        Private->HeapOffset         = (UINTN)(BaseOfNewHeap - (UINTN)SecCoreData->PeiTemporaryRamBase);
      } else {
        Private->HeapOffsetPositive = FALSE;
        Private->HeapOffset         = (UINTN)((UINTN)SecCoreData->PeiTemporaryRamBase - BaseOfNewHeap);
      }
	  //使用gEfiTemporaryRamSupportPpiGuid->TemporaryRamMigration 将OldStack数据搬到NewStack中
      TemporaryRamSupportPpi->TemporaryRamMigration (
                                PeiServices,
                                TemporaryRamBase,
                                (EFI_PHYSICAL_ADDRESS)(UINTN)(TopOfNewStack - TemporaryStackSize),
                                TemporaryRamSize
                                );

      //把分配在临时内存上的Pages搬到永久内存上。
      MigrateMemoryPages (Private, TRUE);
      //第二次调用PeiCore
      PeiCore (SecCoreData, NULL, Private);
    } else {
		...
		//错误处理,这里不分析了,只分析正常流程
	}

    //
    // Code should not come here
    //
    ASSERT (FALSE);
  }
}

2.2.3 第三次进入PeiCore

上一篇SEC分析 UEFI学习01-ARM AARCH64编译、ArmPlatformPriPeiCore(SEC)中分析了第一次进入PeiCore
PeiCheckAndSwitchStack 第二次进入PeiCore
而第三次PeiCore进入实际是在所有存在于临时内存的数据包括PeiCore自身被搬运到永久内存后。最终调用OldCoreData->ShadowedPeiCore (SecCoreData, PpiList, OldCoreData);跳转到永久内存上。

PeiCore (
  IN CONST EFI_SEC_PEI_HAND_OFF    *SecCoreDataPtr,
  IN CONST EFI_PEI_PPI_DESCRIPTOR  *PpiList,
  IN VOID                          *Data
  )
{
  ...
  if (OldCoreData == NULL) {
	...
	//这是第一次进入PeiCore的路径
  } else {
    //这是第二次和第三次进入PeiCore的路径
    if (OldCoreData->ShadowedPeiCore == NULL) {
      //ShadowedPeiCore = NULL说明是第二次进入PeiCore
      OldCoreData->Ps    = &OldCoreData->ServiceTableShadow;
      OldCoreData->CpuIo = &OldCoreData->ServiceTableShadow.CpuIo;
      
       //修正_PEI_CORE_INSTANCE 中的各个指针的地址,如下面节选的这些即是将HobList,Ppi指针,Fv指针从临时内存中加上偏移,指向永久内存中的HobList
       OldCoreData->HobList.Raw = (VOID *)(OldCoreData->HobList.Raw + OldCoreData->HeapOffset);
       ...
       OldCoreData->PpiData.DispatchNotifyList.NotifyPtrs = (PEI_PPI_LIST_POINTERS *)((UINT8 *)OldCoreData->PpiData.DispatchNotifyList.NotifyPtrs + OldCoreData->HeapOffset);
       OldCoreData->Fv = (PEI_CORE_FV_HANDLE *)((UINT8 *)OldCoreData->Fv + OldCoreData->HeapOffset);
       //更新FV中PEIM的状态
       for (Index = 0; Index < OldCoreData->FvCount; Index++) {
         if (OldCoreData->Fv[Index].PeimState != NULL) {
           OldCoreData->Fv[Index].PeimState = (UINT8 *)OldCoreData->Fv[Index].PeimState + OldCoreData->HeapOffset;
         }

         if (OldCoreData->Fv[Index].FvFileHandles != NULL) {
           OldCoreData->Fv[Index].FvFileHandles = (EFI_PEI_FILE_HANDLE *)((UINT8 *)OldCoreData->Fv[Index].FvFileHandles + OldCoreData->HeapOffset);
         }
       }

       OldCoreData->TempFileGuid    = (EFI_GUID *)((UINT8 *)OldCoreData->TempFileGuid + OldCoreData->HeapOffset);
       OldCoreData->TempFileHandles = (EFI_PEI_FILE_HANDLE *)((UINT8 *)OldCoreData->TempFileHandles + OldCoreData->HeapOffset)...
      //根据物理内存更新HandOffHob里的参数
      HandoffInformationTable = OldCoreData->HobList.HandoffInformationTable;
      HandoffInformationTable->EfiEndOfHobList = HandoffInformationTable->EfiEndOfHobList + OldCoreData->HeapOffset;
      HandoffInformationTable->EfiMemoryTop        = OldCoreData->PhysicalMemoryBegin + OldCoreData->PhysicalMemoryLength;
      HandoffInformationTable->EfiMemoryBottom     = OldCoreData->PhysicalMemoryBegin;
      HandoffInformationTable->EfiFreeMemoryTop    = OldCoreData->FreePhysicalMemoryTop;
      HandoffInformationTable->EfiFreeMemoryBottom = HandoffInformationTable->EfiEndOfHobList + sizeof (EFI_HOB_GENERIC_HEADER);
  	  //将MemoryAllocationHob中的参数进行修正到永久内存中
      ConvertMemoryAllocationHobs (OldCoreData);
      //将Ppi指针进行修正到永久内存中
      ConvertPpiPointers (SecCoreData, OldCoreData);
      OldCoreData->PeiMemoryInstalled = TRUE;
      OldCoreData->PeimDispatcherReenter = TRUE;

      OldCoreData->ShadowedPeiCore = (PEICORE_FUNCTION_POINTER)(UINTN)PeiCore;
      if (PcdGetBool (PcdMigrateTemporaryRamFirmwareVolumes) ||
          ((HandoffInformationTable->BootMode == BOOT_ON_S3_RESUME) && PcdGetBool (PcdShadowPeimOnS3Boot)) ||
          ((HandoffInformationTable->BootMode != BOOT_ON_S3_RESUME) && PcdGetBool (PcdShadowPeimOnBoot)))
      {
        //将PeiCore从临时内存搬到永久内存并返回shadow后的PeiCore地址
        OldCoreData->ShadowedPeiCore = ShadowPeiCore (OldCoreData);
      }
	  //第三次跳转到永久内存上的PeiCore
      OldCoreData->ShadowedPeiCore (SecCoreData, PpiList, OldCoreData);


      ASSERT (FALSE);
      CpuDeadLoop ();
      UNREACHABLE ();
    }
  ...
}

2.3 PcdPeim

再来看QEMU_EFI.FD的布局,这里面一共有6个PEIM。其中PcdPeim, MemoryInit和DxeIpl跟PeiCore的流程相关,CpuPei和PlatfromPei和硬件平台相关, PeiVariablePei在这里没用到。这里就挑三个和PeiCore流程相关的PEIM来分析。
UEFI源码学习02 - PeiCore_第7张图片

edk2/MdeModulePkg/Universal/PCD/Pei

PcdPeim在这个FV中是第一个运行的PEIM,其他的PEIM都依赖于PcdPeim。PcdPeim提供了一个访问动态PCD的接口。从Pcd.inf中可以看到PcdPeim的EntryPoint是PcdPeimInit

  PCD_IS_DRIVER                  = PEI_PCD_DRIVER
  ENTRY_POINT                    = PcdPeimInit

PcdPeimInit实现很简单,第一就是建立了PCD Database,第二就是注册了PCD的PPI提供给其他PEIM使用。PcdPeimInit在PEI阶段会重入,这里不考虑重入的部分,就考虑第一次初始化的时候。

PcdPeimInit (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  //建立Pcd Database
  BuildPcdDatabase (FileHandle);
  //安装两个PCD PPI gPcdPpiGuid和gGetPcdInfoPpiGuid
  Status = PeiServicesInstallPpi (&mPpiList[0]);
  Status = PeiServicesInstallPpi (&mPpiList2[0]);
  //注册回调函数和通知
  Status = PeiServicesNotifyPpi (&mEndOfPeiSignalPpiNotifyList[0]);
  Status = PeiRegisterCallBackOnSet (
             &gEfiMdeModulePkgTokenSpaceGuid,
             PcdToken (PcdSetNvStoreDefaultId),
             PcdSetNvStoreDefaultIdCallBack
             );

  return Status;
}

BuildPcdDatabase 从FV文件中把Pcd的二进制文件读出来,然后开辟一段空间,将读出来的二进制复制到这段内存中。这段内存就叫做PCD Database。

PEI_PCD_DATABASE *
BuildPcdDatabase (
  IN EFI_PEI_FILE_HANDLE  FileHandle
  )
{
  //获取PCD Database在FV上的指针
  PeiPcdDbBinary = LocateExPcdBinary (FileHandle);
  //创建一个HOB用于PCD Database
  Database = BuildGuidHob (&gPcdDataBaseHobGuid, PeiPcdDbBinary->Length + PeiPcdDbBinary->UninitDataBaseSize);
  ZeroMem (Database, PeiPcdDbBinary->Length  + PeiPcdDbBinary->UninitDataBaseSize);
  //把Pcd从FV中复制到开辟的PCD Database中
  CopyMem (Database, PeiPcdDbBinary, PeiPcdDbBinary->Length);
  //同样的操作把PCD中的callback来一遍。
  SizeOfCallbackFnTable = Database->LocalTokenCount * sizeof (PCD_PPI_CALLBACK) * PcdGet32 (PcdMaxPeiPcdCallBackNumberPerPcdEntry);
  CallbackFnTable = BuildGuidHob (&gEfiCallerIdGuid, SizeOfCallbackFnTable);
  ZeroMem (CallbackFnTable, SizeOfCallbackFnTable);

  return Database;
}

再来看gPcdPpiGuid对应PPI其实就是一组设置和获取PCD的接口。

EFI_PEI_PPI_DESCRIPTOR  mPpiList[] = {
  {
    EFI_PEI_PPI_DESCRIPTOR_PPI,
    &gPcdPpiGuid,
    &mPcdPpiInstance
  },
....
PCD_PPI  mPcdPpiInstance = {
  PeiPcdSetSku,

  PeiPcdGet8,
  ...
  PeiPcdGet8Ex,
...
  PeiPcdSet8,
...
  PeiPcdSet8Ex,
...
  PeiRegisterCallBackOnSet,
  PcdUnRegisterCallBackOnSet,
  PeiPcdGetNextToken,
  PeiPcdGetNextTokenSpace
};

当PcdPeim被加载完之后,PEI阶段其他的PEIM就可以使用其接口对动态PCD进行读写了。这里不对PcdPeim的读写进行展开,原理不复杂,网上也有很多分析PcdPeim的读写实现。

2.4 MemoryInitPeim

edk2/ArmPlatformPkg/MemoryInitPei
真实的芯片上在这之前应该要初始化DDR Controller/PHY和DDR内存。但在qemu上不需要,所以进到MemoryInitPei中。MemoryInitPei从PCD上读出内存相关的信息:起始地址和长度然后上报给Pei Foundation,然后建立相关的HOB以及初始化MMU。

InitializeMemory (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  //从PCD读出内存的基地址和长度
  SystemMemoryBase = (UINTN)PcdGet64 (PcdSystemMemoryBase);
  SystemMemoryTop  = SystemMemoryBase + PcdGet64 (PcdSystemMemorySize);
  //从PCD读出QEMU_EFI.FD的基地址和长度
  FdBase = (UINTN)PcdGet64 (PcdFdBaseAddress);
  FdTop  = FdBase + (UINTN)PcdGet32 (PcdFdSize);
  ...
  //计算出UefiMemoryBase的地址,UefiMemory位于内存的最上端
  UefiMemoryBase = SystemMemoryTop - FixedPcdGet32 (PcdSystemMemoryUefiRegionSize);
  ...
  //调用PeiServicesInstallPeiMemory 向PeiCore注册Uefi所使用的内存地址和长度
  //PeiServicesInstallPeiMemory最终会调用到PeiInstallPeiMemory,这个函数之前讲过, 会把内存信息填到HandOffHob中然后
  //把SwitchStackSignal设置为TRUE
  Status = PeiServicesInstallPeiMemory (UefiMemoryBase, FixedPcdGet32 (PcdSystemMemoryUefiRegionSize));
  //MemoryPeim 建立内存相关的Hob并且初始化MMU。
  Status = MemoryPeim (UefiMemoryBase, FixedPcdGet32 (PcdSystemMemoryUefiRegionSize));
  ...
}

edk2/ArmVirtPkg/Library/ArmVirtMemoryInitPeiLib/ArmVirtMemoryInitPeiLib.c


MemoryPeim (
  IN EFI_PHYSICAL_ADDRESS  UefiMemoryBase,
  IN UINT64                UefiMemorySize
  )
{

  ResourceAttributes = (
                        EFI_RESOURCE_ATTRIBUTE_PRESENT |
                        EFI_RESOURCE_ATTRIBUTE_INITIALIZED |
                        EFI_RESOURCE_ATTRIBUTE_WRITE_BACK_CACHEABLE |
                        EFI_RESOURCE_ATTRIBUTE_TESTED
                        );

  SystemMemoryTop = PcdGet64 (PcdSystemMemoryBase) +
                    PcdGet64 (PcdSystemMemorySize);
  ...
  //创建EFI_RESOURCE_SYSTEM_MEMORY 类型的RESOURCE HOB块
  BuildResourceDescriptorHob (
     EFI_RESOURCE_SYSTEM_MEMORY,
     ResourceAttributes,
     PcdGet64 (PcdSystemMemoryBase),
     PcdGet64 (PcdSystemMemorySize)
     );

  //把Uefi用到的内存 的DCache Invalidu一下
  InvalidateDataCacheRange ((VOID *)(UINTN)UefiMemoryBase, UefiMemorySize);

  // 初始化MMU
  InitMmu ();
  ...
}
InitMmu (
  VOID
  )
{
  ...
  // 获取物理地址和虚拟地址,这里edk2采用了平坦映射,因此物理地址等于虚拟地址。
  ArmVirtGetMemoryMap (&MemoryTable);
  //配置页表并使能MMU。从此就可以使用dcache。
  Status = ArmConfigureMmu (MemoryTable, &TranslationTableBase, &TranslationTableSize);
  ...
}

到这里,MemoryInit PEIM就初始化完了。当MemoryInit初始化完成,SwitchStackSignal被设置成1,就意味着DDR内存可以使用了。这个时候PeiDispacther就会把所有在临时内存上的数据全部搬到永久内存上去。下图是
原始memorymap
UEFI源码学习02 - PeiCore_第8张图片
初始化完memory后
UEFI源码学习02 - PeiCore_第9张图片

2.5 DxeIplPeim (TODO)

2.6 跳转到DXE (TODO)

你可能感兴趣的:(UEFI,驱动开发)