UEFI开发实战SlimBootloader中调用FSP
作者:jiangwei0512
综述
FSP的全称是Firmware Support Package。FSP有以下的特性:
- FSP提供了Intel重要组件(包括处理器、内存控制器、芯片组等)的初始化;
- FSP被编译成独立的二进制,并可以集成到Bootloader中,这里说的Bootloader可以是Slim Bootloader,coreboot,UEFI等等;
- FSP的优点有免费、方便集成、可减少开发时间,等等。
FSP中包含若干个部分,如下图所示:
按作用来分它包含三个大的组件,分别是:
FSP-T:它主要用来初始化CACHE以及其它早期需要的初始化,对应提供给外部的接口是TempRamInit()
;
FSP-M:它主要用来初始化内存以及其它需要的初始化,对应提供给外部的接口是FspMemoryInit()
和TempRamExit()
,前者用于内存初始化,后者用于处理FSP-T中使用的CACHE内容;
FPS-S:它主要是CPU和芯片组的初始化,对应提供给外部的接口是FspSiliconInit()
和NotifyPhase()
,其中后者又会在不同的阶段调用,包括PCIE扫描之后,ReadyToBoot时和EndOfBootServices的时候;
按功能来区分,每个组件都包含头部、配置和API三个部分。头部是固定的,配置用于一些可控的定制化,API就是功能代码。Bootloader要操作FSP,就需要完成文件配置,接口调用等。
BootLoader调用FSP的整个流程如下图所示:
Bootloader中需要有相应的代码做上述的操作,以Slim Bootloader为例,有一个IntelFsp2Pkg用来处理FSP相关的内容。不过需要注意,这里的IntelFsp2Pkg并不提供FSP源代码的,只是提供了EDK与FSP之间的中间层,真正的用来初始化Intel组件的FSP的代码并没有开源,所以这里也拿不到,不过可以拿到用于QEMU的FSP源代码,后续使用的就是这个。
编译
代码主要是https://gitee.com/jiangwei0512/edk2-beni中的QemuFspPkg,它是用在QEMU上的FSP,它有源码可以下载,而其它Intel的FSP基本是不开源的,没有办法下载到,所以这里只能用QEMU的FSP作为示例。
QEMU对应的FSP通过BuildFsp.py进行编译得到,该脚本执行三个步骤:
- Prebuild
- Build
- PostBuild
PostBuild
Prebuild的流程如下:
1.构建FspHeader.inf实际上就是创建FSP Header的头部,它是固定的格式,位于QemuFspPkg\FspHeader\FspHeader.aslc,内容如下:
TABLES mTable = { { FSP_INFO_HEADER_SIGNATURE, // UINT32 Signature (FSPH) sizeof(FSP_INFO_HEADER), // UINT32 HeaderLength; {0x00, 0x00}, // UINT8 Reserved1[2]; FixedPcdGet8(PcdFspHeaderSpecVersion), // UINT8 SpecVersion; FixedPcdGet8(PcdFspHeaderRevision), // UINT8 HeaderRevision; FixedPcdGet32(PcdFspImageRevision), // UINT32 ImageRevision; UINT64_TO_BYTE_ARRAY( FixedPcdGet64(PcdFspImageIdString)), // CHAR8 ImageId[8]; 0x12345678, // UINT32 ImageSize; 0x12345678, // UINT32 ImageBase; FixedPcdGet16(PcdFspImageAttributes), // UINT16 ImageAttribute; FixedPcdGet16(PcdFspComponentAttributes), // UINT16 ComponentAttribute; Bits[15:12] - 0001b: FSP-T, 0010b: FSP-M, 0011b: FSP-S 0x12345678, // UINT32 CfgRegionOffset; 0x12345678, // UINT32 CfgRegionSize; 0x00000000, // UINT32 Reserved2; 0x00000000, // UINT32 TempRamInitEntry; 0x00000000, // UINT32 Reserved3; 0x00000000, // UINT32 NotifyPhaseEntry; 0x00000000, // UINT32 FspMemoryInitEntry; 0x00000000, // UINT32 TempRamExitEntry; 0x00000000, // UINT32 FspSiliconInitEntry; }, { FSP_INFO_EXTENDED_HEADER_SIGNATURE, // UINT32 Signature (FSPE) sizeof(FSP_INFO_EXTENDED_HEADER), // UINT32 Length; FSPE_HEADER_REVISION_1, // UINT8 Revision; 0x00, // UINT8 Reserved; {FSP_PRODUCER_ID}, // CHAR8 FspProducerId[6]; 0x00000001, // UINT32 FspProducerRevision; 0x00000000, // UINT32 FspProducerDataSize; }, { FSP_FSPP_SIGNATURE, // UINT32 Signature (FSPP) sizeof(FSP_PATCH_TABLE), // UINT16 Length; FSPP_HEADER_REVISION_1, // UINT8 Revision; 0x00, // UINT8 Reserved; 1 // UINT32 PatchEntryNum; }, 0xFFFFFFFC // UINT32 Patch FVBASE at end of FV };
里面的某些数据在之后还会被修改,通过这个头部就可以找到对应API的位置,从而进行调用。
2.UPD txt文件是Build\QemuFspPkg\DEBUG_VS2019\FV(根据编译工具的不同,对应的目录可能存在差异)下的如下内容:
txt文件名对应的是三个FSP组件的GUID(位于BuildFsp.py):
FspGuid = { 'FspTUpdGuid' : '34686CA3-34F9-4901-B82A-BA630F0714C6', 'FspMUpdGuid' : '39A250DB-E465-4DD1-A2AC-E2BD3C0E2385', 'FspSUpdGuid' : 'CAE3605B-5B34-4C85-B3D7-27D54273C40F' }
里面的内容主要是一些PCD,以39A250DB-E465-4DD1-A2AC-E2BD3C0E2385.txt为例:
## @file # # THIS IS AUTO-GENERATED FILE BY BUILD TOOLS AND PLEASE DO NOT MAKE MODIFICATION. # # This file lists all VPD informations for a platform collected by build.exe. # # Copyright (c) 2022, Intel Corporation. All rights reserved.<BR> # This program and the accompanying materials # are licensed and made available under the terms and conditions of the BSD License # which accompanies this distribution. The full text of the license may be found at # http://opensource.org/licenses/bsd-license.php # # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. # gQemuFspPkgTokenSpaceGuid.Signature|DEFAULT|0x0000|8|0x4D5F4450554D4551 gQemuFspPkgTokenSpaceGuid.Revision|DEFAULT|0x0008|1|0x01 gQemuFspPkgTokenSpaceGuid.Reserved|DEFAULT|0x0009|23|{0x00} gQemuFspPkgTokenSpaceGuid.Revision|DEFAULT|0x0020|1|0x01 gQemuFspPkgTokenSpaceGuid.Reserved|DEFAULT|0x0021|3|{0x00} gQemuFspPkgTokenSpaceGuid.NvsBufferPtr|DEFAULT|0x0024|4|0x00000000 gQemuFspPkgTokenSpaceGuid.StackBase|DEFAULT|0x0028|4|0x00070000 gQemuFspPkgTokenSpaceGuid.StackSize|DEFAULT|0x002C|4|0x00010000 gQemuFspPkgTokenSpaceGuid.BootLoaderTolumSize|DEFAULT|0x0030|4|0x00000000 gPlatformFspPkgTokenSpaceGuid.Bootmode|DEFAULT|0x0034|4|0x00000000 gQemuFspPkgTokenSpaceGuid.Reserved1|DEFAULT|0x0038|8|{0x00} gQemuFspPkgTokenSpaceGuid.SerialDebugPortAddress|DEFAULT|0x0040|4|0x00000000 gQemuFspPkgTokenSpaceGuid.SerialDebugPortType|DEFAULT|0x0044|1|0x02 gQemuFspPkgTokenSpaceGuid.SerialDebugPortDevice|DEFAULT|0x0045|1|0x02 gQemuFspPkgTokenSpaceGuid.SerialDebugPortStrideSize|DEFAULT|0x0046|1|0x02 gQemuFspPkgTokenSpaceGuid.UnusedUpdSpace0|DEFAULT|0x0047|0x0031|{0} gQemuFspPkgTokenSpaceGuid.ReservedFspmUpd|DEFAULT|0x0078|4|{0x00} gQemuFspPkgTokenSpaceGuid.UnusedUpdSpace1|DEFAULT|0x007C|0x0002|{0} gQemuFspPkgTokenSpaceGuid.UpdTerminator|DEFAULT|0x007E|2|0x55AA
txt文件的来源是QemuFspPkg\QemuFspPkg.dsc,里面有这些PCD的初始化值,位于[PcdsDynamicVpd.Upd]
这个Section,其中的UnusedUpdSpaceX
也是在dsc文件中通过PCD指定的偏移来确定的。
3.UPD bin文件名也对应到前面提到的GUID,以39A250DB-E465-4DD1-A2AC-E2BD3C0E2385.bin为例:
bin文件跟txt文件中的PCD值是一一对应的。
4.UPD头文件和bsf文件比较直观,不做详细说明,它们也是通过QemuFspPkg\QemuFspPkg.dsc创建的。UPD头文件中还包含一个通用的头部结构体:
#pragma pack(1) /// /// FSP_UPD_HEADER Configuration. /// typedef struct { /// /// UPD Region Signature. This signature will be /// "XXXXXX_T" for FSP-T /// "XXXXXX_M" for FSP-M /// "XXXXXX_S" for FSP-S /// Where XXXXXX is an unique signature /// UINT64 Signature; /// /// Revision of the Data structure. /// For FSP spec 2.0/2.1 value is 1. /// For FSP spec 2.2 value is 2. /// UINT8 Revision; UINT8 Reserved[23]; } FSP_UPD_HEADER; #pragma pack()
每个平台的UPD和bsf内容都是不同的,甚至同一个平台的不同版本也可能存在差异,不同的FSP-X对应不同的UPD,分别是FSPT_UPD
、FSPM_UPD
和FSPS_UPD
,它们分别存放在FsptUpd.h、FspmUpd.h和FspsUpd.h中。以FSPM_UPD
结构体为例:
/** Fsp M UPD Configuration **/ typedef struct { /** Offset 0x0000 **/ FSP_UPD_HEADER FspUpdHeader; /** Offset 0x0020 **/ FSPM_ARCH_UPD FspmArchUpd; /** Offset 0x0040 **/ FSP_M_CONFIG FspmConfig; /** Offset 0x007C **/ UINT8 UnusedUpdSpace1[2]; /** Offset 0x007E **/ UINT16 UpdTerminator; } FSPM_UPD;
其中的内容跟前面的UPD txt中的PCD一一对应。
上述的文件都在Build\QemuFspPkg\DEBUG_VS2019\FV(根据编译工具的不同,对应的目录可能存在差异)创建,总的来说就是为了创建FSP的UPD配置文件和对应用在代码中的头文件,头文件会被拷贝到其它位置也是为了代码能够调用到。而UPD配置文件会通过二进制的方式包含到QemuFspPkg\QemuFspPkg.fdf,下面是一个示例:
# # Project specific configuration data files # !ifndef $(CFG_PREBUILD) FILE RAW = $(FSP_M_UPD_FFS_GUID) { SECTION RAW = $(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/FV/$(FSP_M_UPD_TOOL_GUID).bin } !endif
Build
该过程仅仅是执行build操作而已,对应的代码:
def Build (target, toolchain): cmd = '%s -p QemuFspPkg/QemuFspPkg.dsc -a IA32 -b %s -t %s -y Report%s.log' % ( 'build' if os.name == 'posix' else 'build.bat', target, toolchain, target) ret = subprocess.call(cmd.split(' ')) if ret: Fatal('Failed to do Build QEMU FSP!') print('End of Build...')
可以看到执行对象是QemuFspPkg.dsc。完成这一步之后会生成FSP-M.Fv、FSP-S.Fv、FSP-T.Fv和QEMUFSP.fd。
到这里FSP二进制已经生成,就是QEMUFSP.fd,但是它不能直接使用,还需要后续操作。
PostBuild
这一步主要是通过PatchFv.py来修改前文生成的QEMUFSP.fd,Patch前后:
这里具体Patch了哪部分内容,需要先了解FSP二进制的组成部分,可以参考FSP二进制组成分析。这里Patch的大部分都是FSP Header中的内容,对应的默认初始化内容就是前面提到的QemuFspPkg\FspHeader\FspHeader.aslc,其结构体如下:
/// /// FSP Information Header as described in FSP v2.0 Spec section 5.1.1. /// typedef struct { /// /// Byte 0x00: Signature ('FSPH') for the FSP Information Header. /// UINT32 Signature; /// /// Byte 0x04: Length of the FSP Information Header. /// UINT32 HeaderLength; /// /// Byte 0x08: Reserved. /// UINT8 Reserved1[2]; /// /// Byte 0x0A: Indicates compliance with a revision of this specification in the BCD format. /// UINT8 SpecVersion; /// /// Byte 0x0B: Revision of the FSP Information Header. /// UINT8 HeaderRevision; /// /// Byte 0x0C: Revision of the FSP binary. /// UINT32 ImageRevision; /// /// Byte 0x10: Signature string that will help match the FSP Binary to a supported HW configuration. /// CHAR8 ImageId[8]; /// /// Byte 0x18: Size of the entire FSP binary. /// UINT32 ImageSize; /// /// Byte 0x1C: FSP binary preferred base address. /// UINT32 ImageBase; /// /// Byte 0x20: Attribute for the FSP binary. /// UINT16 ImageAttribute; /// /// Byte 0x22: Attributes of the FSP Component. /// UINT16 ComponentAttribute; /// /// Byte 0x24: Offset of the FSP configuration region. /// UINT32 CfgRegionOffset; /// /// Byte 0x28: Size of the FSP configuration region. /// UINT32 CfgRegionSize; /// /// Byte 0x2C: Reserved2. /// UINT32 Reserved2; /// /// Byte 0x30: The offset for the API to setup a temporary stack till the memory is initialized. /// UINT32 TempRamInitEntryOffset; /// /// Byte 0x34: Reserved3. /// UINT32 Reserved3; /// /// Byte 0x38: The offset for the API to inform the FSP about the different stages in the boot process. /// UINT32 NotifyPhaseEntryOffset; /// /// Byte 0x3C: The offset for the API to initialize the memory. /// UINT32 FspMemoryInitEntryOffset; /// /// Byte 0x40: The offset for the API to tear down temporary RAM. /// UINT32 TempRamExitEntryOffset; /// /// Byte 0x44: The offset for the API to initialize the CPU and chipset. /// UINT32 FspSiliconInitEntryOffset; /// /// Byte 0x48: Offset for the API for the optional Multi-Phase processor and chipset initialization. /// This value is only valid if FSP HeaderRevision is >= 5. /// If the value is set to 0x00000000, then this API is not available in this component. /// UINT32 FspMultiPhaseSiInitEntryOffset; } FSP_INFO_HEADER;
其中的ImageSize
、ImageBase
、ImageAttribute
、ComponentAttribute
、CfgRegionOffset
、CfgRegionSize
、TempRamInitEntryOffset
、FspMemoryInitEntryOffset
、TempRamExitEntryOffset
、FspSiliconInitEntryOffset
、NotifyPhaseEntryOffset
等都需要修改。
因为每个FSP-X都有一个FSP_INFO_HEADER
结构体,所以前提提到的XXXOffset
会针对不同的FSP-X组件做对应的修改,比如FSP-T只需要TempRamInitEntryOffset
。
除了FSP Header的Patch,这里还有一个点被Patch了:
它们对应的是模块的入口(IntelFsp2Pkg\FspSecCore\Ia32\FspHelper.nasm):
global ASM_PFX(FspInfoHeaderRelativeOff) ASM_PFX(FspInfoHeaderRelativeOff): DD 0x12345678 ; This value must be patched by the build script
从上面的代码也可以看到这部分是需要Patch的。
FSP二进制组成分析
二进制的组成如下:
FSP每个组件都是一个FV,所以都有一个FV Header(EFI_FIRMWARE_VOLUME_HEADER
,位于MdePkg\Include\Pi\PiFirmwareVolume.h),大小是0x48个字节,之后是一个FV Extended Header(EFI_FIRMWARE_VOLUME_EXT_HEADER
,位于MdePkg\Include\Pi\PiFirmwareVolume.h),之后才是FSP的内容,如下图所示:
FSP组件的第一个模块是FSP Header,对应二进制(Header的第一个成员是"FSPH",最后一个成员是0xFFFFFFFC)中:
这里可以看到里面有一些数据比较奇怪,都是0x12345678和0x00000000,这些都是占位符,并不是真正的有效数据,是通过FspHeader.aslc生成的,在后期这些数据会被Patch成有效的值。
FSP组件的第二个模块是UPD数据,它在Prebuild中生成,对应的数据(UPD数据的第一个成员是Signature(本例中是QEMUPD_T),最后一个成员是0x55AA):
再之后是通用的模块。以QEMU中的FSP对应的fdf文件为例:
# # FSP header # INF RuleOverride = FSPHEADER $(FSP_PACKAGE)/FspHeader/FspHeader.inf # # Project specific configuration data files # !ifndef $(CFG_PREBUILD) FILE RAW = $(FSP_T_UPD_FFS_GUID) { SECTION RAW = $(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/FV/$(FSP_T_UPD_TOOL_GUID).bin } !endif INF RuleOverride = RELOC IntelFsp2Pkg/FspSecCore/FspSecCoreT.inf
使用
用于Slim Bootloader的FSP需要放到前者指定的目录,对于QEMU来说对应的是Silicon\QemuSocPkg\FspBin,同时FSP对应的UPD头文件也需要放到指定目录。之后执行Slim Bootloader的各个阶段都会调用FSP的API接口,这里一一说明。
Stage1A
Stage1A阶段会执行FSP中的FspTempRamInit()
接口,由于是执行阶段的早期,这里只有汇编部分的代码,具体的位置在BootloaderCorePkg\Stage1A\Ia32\SecEntry.nasm,对应代码:
global ASM_PFX(_ModuleEntryPoint) ASM_PFX(_ModuleEntryPoint): movd mm0, eax ; ; Read time stamp ; rdtsc mov esi, eax mov edi, edx ; ; Early board hooks ; mov esp, EarlyBoardInitRet jmp ASM_PFX(EarlyBoardInit) EarlyBoardInitRet: mov esp, FspTempRamInitRet jmp ASM_PFX(FspTempRamInit)
这里jmp
到BootloaderCorePkg\Library\FspApiLib\Ia32\FspTempRamInit.nasm:
global ASM_PFX(FspTempRamInit) ASM_PFX(FspTempRamInit): ; ; This hook is called to initialize temporay RAM ; ESI, EDI need to be preserved ; ESP contains return address ; ECX, EDX return the temprary RAM start and end ; ; ; Get FSP-T base in EAX ; mov ebp, esp mov eax, dword [ASM_PFX(PcdGet32(PcdFSPTBase))] ; ; Find the fsp info header ; Jump to TempRamInit API ; add eax, dword [eax + 094h + FSP_HEADER_TEMPRAMINIT_OFFSET] mov esp, TempRamInitStack jmp eax TempRamInitDone: mov esp, ebp jmp esp
FSP中的FspTempRamInit()
真正的入口是PcdGet32(PcdFSPTBase)+ 094h + FSP_HEADER_TEMPRAMINIT_OFFSET
,PcdFSPTBase
的值是:
gPlatformModuleTokenSpaceGuid.PcdFSPTBase | $(FSP_T_BASE)
FSP_T_BASE
表示的是FSP-T.bin的开始位置,094h
在前面也已经介绍过,其前面的内容是FV Header,该地址开始是FSP Header,而FSP_HEADER_TEMPRAMINIT_OFFSET
是FSP Header的偏移,该位置对应成员是TempRamInitEntryOffset
,到这里就对应起来了,FspTempRamInit()
即是该位置的值。
不过对于FSP_T_BASE
的值,它是FSP-T放到系统内存中位置的地址,可以在BootloaderCorePkg\Platform.dsc中找到:
DEFINE FSP_T_BASE = 0xFFFF0000
这个值也跟SBL二进制产生关系:
Flash Map Information: +------------------------------------------------------------------------+ | FLASH MAP | | (RomSize = 0x00721000) | +------------------------------------------------------------------------+ | NAME | OFFSET (BASE) | SIZE | FLAGS | +----------+------------------------+------------+-----------------------+ +------------------------------------------------------------------------+ | TOP SWAP A | +------------------------------------------------------------------------+ | SG1A | 0x711000(0xFFFF0000) | 0x010000 | Uncompressed, TS_A | +------------------------------------------------------------------------+ | TOP SWAP B | +------------------------------------------------------------------------+ | SG1A | 0x701000(0xFFFE0000) | 0x010000 | Uncompressed, TS_B | +------------------------------------------------------------------------+ | REDUNDANT A | +------------------------------------------------------------------------+ | KEYH | 0x700000(0xFFFDF000) | 0x001000 | Uncompressed, R_A | | CNFG | 0x6ff000(0xFFFDE000) | 0x001000 | Uncompressed, R_A | | FWUP | 0x6e7000(0xFFFC6000) | 0x018000 | Compressed , R_A | | SG1B | 0x6b7000(0xFFF96000) | 0x030000 | Compressed , R_A | | SG02 | 0x69f000(0xFFF7E000) | 0x018000 | Compressed , R_A | | EMTY | 0x681000(0xFFF60000) | 0x01e000 | Uncompressed, R_A | +------------------------------------------------------------------------+ | REDUNDANT B | +------------------------------------------------------------------------+ | KEYH | 0x680000(0xFFF5F000) | 0x001000 | Uncompressed, R_B | | CNFG | 0x67f000(0xFFF5E000) | 0x001000 | Uncompressed, R_B | | FWUP | 0x667000(0xFFF46000) | 0x018000 | Compressed , R_B | | SG1B | 0x637000(0xFFF16000) | 0x030000 | Compressed , R_B | | SG02 | 0x61f000(0xFFEFE000) | 0x018000 | Compressed , R_B | | EMTY | 0x601000(0xFFEE0000) | 0x01e000 | Uncompressed, R_B | +------------------------------------------------------------------------+ | NON REDUNDANT | +------------------------------------------------------------------------+ | PTES | 0x600000(0xFFEDF000) | 0x001000 | Uncompressed, NR | | IPFW | 0x5f0000(0xFFECF000) | 0x010000 | Uncompressed, NR | | EPLD | 0x3e3000(0xFFCC2000) | 0x20d000 | Uncompressed, NR | | PYLD | 0x2e3000(0xFFBC2000) | 0x100000 | Compressed , NR | | VARS | 0x2e1000(0xFFBC0000) | 0x002000 | Uncompressed, NR | | EMTY | 0x001000(0xFF8E0000) | 0x2e0000 | Uncompressed, NR | +------------------------------------------------------------------------+ | NON VOLATILE | +------------------------------------------------------------------------+ | RSVD | 0x000000(0xFF8DF000) | 0x001000 | Uncompressed, NV | +----------+------------------------+------------+-----------------------+Flash Map Information: +------------------------------------------------------------------------+ | FLASH MAP | | (RomSize = 0x00721000) | +------------------------------------------------------------------------+ | NAME | OFFSET (BASE) | SIZE | FLAGS | +----------+------------------------+------------+-----------------------+ +------------------------------------------------------------------------+ | TOP SWAP A | +------------------------------------------------------------------------+ | SG1A | 0x711000(0xFFFF0000) | 0x010000 | Uncompressed, TS_A | +------------------------------------------------------------------------+ | TOP SWAP B | +------------------------------------------------------------------------+ | SG1A | 0x701000(0xFFFE0000) | 0x010000 | Uncompressed, TS_B | +------------------------------------------------------------------------+ | REDUNDANT A | +------------------------------------------------------------------------+ | KEYH | 0x700000(0xFFFDF000) | 0x001000 | Uncompressed, R_A | | CNFG | 0x6ff000(0xFFFDE000) | 0x001000 | Uncompressed, R_A | | FWUP | 0x6e7000(0xFFFC6000) | 0x018000 | Compressed , R_A | | SG1B | 0x6b7000(0xFFF96000) | 0x030000 | Compressed , R_A | | SG02 | 0x69f000(0xFFF7E000) | 0x018000 | Compressed , R_A | | EMTY | 0x681000(0xFFF60000) | 0x01e000 | Uncompressed, R_A | +------------------------------------------------------------------------+ | REDUNDANT B | +------------------------------------------------------------------------+ | KEYH | 0x680000(0xFFF5F000) | 0x001000 | Uncompressed, R_B | | CNFG | 0x67f000(0xFFF5E000) | 0x001000 | Uncompressed, R_B | | FWUP | 0x667000(0xFFF46000) | 0x018000 | Compressed , R_B | | SG1B | 0x637000(0xFFF16000) | 0x030000 | Compressed , R_B | | SG02 | 0x61f000(0xFFEFE000) | 0x018000 | Compressed , R_B | | EMTY | 0x601000(0xFFEE0000) | 0x01e000 | Uncompressed, R_B | +------------------------------------------------------------------------+ | NON REDUNDANT | +------------------------------------------------------------------------+ | PTES | 0x600000(0xFFEDF000) | 0x001000 | Uncompressed, NR | | IPFW | 0x5f0000(0xFFECF000) | 0x010000 | Uncompressed, NR | | EPLD | 0x3e3000(0xFFCC2000) | 0x20d000 | Uncompressed, NR | | PYLD | 0x2e3000(0xFFBC2000) | 0x100000 | Compressed , NR | | VARS | 0x2e1000(0xFFBC0000) | 0x002000 | Uncompressed, NR | | EMTY | 0x001000(0xFF8E0000) | 0x2e0000 | Uncompressed, NR | +------------------------------------------------------------------------+ | NON VOLATILE | +------------------------------------------------------------------------+ | RSVD | 0x000000(0xFF8DF000) | 0x001000 | Uncompressed, NV | +----------+------------------------+------------+-----------------------+
由于SBL会放到4G以下的空间,而FSP-T.Fv放在了SG1A中,大小是0x10000,所以位置就是0xFFFF0000。通过下述命令能够更清楚的看出来:
F:\Gitee\sbl>BootloaderCorePkg\Tools\IfwiUtility.py view -i Outputs\qemu\SlimBootloader.bin IFWI [O:0x00000000 L:0x00721000] BIOS [O:0x00000000 L:0x00721000] NVS [O:0x00000000 L:0x00001000] RSVD [O:0x00000000 L:0x00001000] NRD [O:0x00001000 L:0x00600000] EMTY [O:0x00001000 L:0x002E0000] VARS [O:0x002E1000 L:0x00002000] PYLD [O:0x002E3000 L:0x00100000] EPLD [O:0x003E3000 L:0x0020D000] IPFW [O:0x005F0000 L:0x00010000] PTES [O:0x00600000 L:0x00001000] RD1 [O:0x00601000 L:0x00080000] EMTY [O:0x00601000 L:0x0001E000] SG02 [O:0x0061F000 L:0x00018000] SG1B [O:0x00637000 L:0x00030000] FWUP [O:0x00667000 L:0x00018000] CNFG [O:0x0067F000 L:0x00001000] KEYH [O:0x00680000 L:0x00001000] RD0 [O:0x00681000 L:0x00080000] EMTY [O:0x00681000 L:0x0001E000] SG02 [O:0x0069F000 L:0x00018000] SG1B [O:0x006B7000 L:0x00030000] FWUP [O:0x006E7000 L:0x00018000] CNFG [O:0x006FF000 L:0x00001000] KEYH [O:0x00700000 L:0x00001000] TS1 [O:0x00701000 L:0x00010000] SG1A [O:0x00701000 L:0x00010000] TS0 [O:0x00711000 L:0x00010000] SG1A [O:0x00711000 L:0x00010000] ---- 这里的最后就是0x100000000的位置
SBL二进制和FSP-T.bin的对应关系:
最终可以查看到PcdGet32(PcdFSPTBase)+ 094h + FSP_HEADER_TEMPRAMINIT_OFFSET
处的值是0x473(位于0x7110C4),这也跟编译FSP时的Patch对应:
Patched offset 0x000370C4:[00000000] with value 0x00000473 # TempRamInit API
从上图可以看到该位置的值是EB 0B 90 90 90
等等,可以确定这些就是代码了,但是它对应到的是哪个模块呢?其实可以从QemuFspPkg\QemuFspPkg.fdf中找到答案:
[FV.FSP-T] BlockSize = $(FLASH_BLOCK_SIZE) FvAlignment = 16 ERASE_POLARITY = 1 MEMORY_MAPPED = TRUE STICKY_WRITE = TRUE LOCK_CAP = TRUE LOCK_STATUS = TRUE WRITE_DISABLED_CAP = TRUE WRITE_ENABLED_CAP = TRUE WRITE_STATUS = TRUE WRITE_LOCK_CAP = TRUE WRITE_LOCK_STATUS = TRUE READ_DISABLED_CAP = TRUE READ_ENABLED_CAP = TRUE READ_STATUS = TRUE READ_LOCK_CAP = TRUE READ_LOCK_STATUS = TRUE FvNameGuid = 52F1AFB6-78A6-448f-8274-F370549AC5D0 # # FSP header # INF RuleOverride = FSPHEADER $(FSP_PACKAGE)/FspHeader/FspHeader.inf # # Project specific configuration data files # !ifndef $(CFG_PREBUILD) FILE RAW = $(FSP_T_UPD_FFS_GUID) { SECTION RAW = $(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/FV/$(FSP_T_UPD_TOOL_GUID).bin } !endif INF RuleOverride = RELOC IntelFsp2Pkg/FspSecCore/FspSecCoreT.inf
根据前面的介绍,FspHeader.inf是FSP Header,$(FSP_T_UPD_TOOL_GUID).bin是UPD配置文件,那么FspSecCoreT.inf就应该是包含FspTempRamInit()
函数代码的模块了。
这里直接找对应的模块,它被编译成一个二进制FspSecCoreT.efi,efi文件符合的是《Microsoft Portable Executable and Common Object File Format Specification》(后称Spec)规范。文档可以点击下载。在这个文档中描述了efi二进制头部的格式如下:
通过它可以找到真正的代码入口位置,不过也不需要一一计算,可以通过对应的map文件(这里就是Build\QemuFspPkg\DEBUG_VS2019\IA32\IntelFsp2Pkg\FspSecCore\FspSecCoreT\DEBUG\FspSecCoreT.map)找到需要的入口:
Address Publics by Value Rva+Base Lib:Object 0001:000001fb _TempRamInitApi 0000041b FspSecCoreT:FspApiEntryT.obj
可以看到地址是0000041b
,查看FspSecCoreT.efi二进制:
可以看到数据已经对应上了。再进一步分析这些数据的话,会发现EB实际上是一个跳转指令(注意之类是16位的代码,所以是近跳转),可以参考《64-ia-32-architectures-software-developer-instruction-set-reference-manual.pdf》中的“JMP—Jump”章节:
cb
表示的是跳转偏移,这里的值是0xB,对应的代码在IntelFsp2Pkg\FspSecCore\Ia32\SaveRestoreSseNasm.inc:
%macro ENABLE_SSE 0 ; ; Initialize floating point units ; jmp NextAddress align 4 ; 需要4字节对齐,所以后面补充了90,表示的是nop指令,总共3个字节 ; ; Float control word initial value: ; all exceptions masked, double-precision, round-to-nearest ; FpuControlWord DW 027Fh ; 占据2个字节 ; ; Multimedia-extensions control word: ; all exceptions masked, round-to-nearest, flush to zero for masked underflow ; MmxControlWord DD 01F80h ; 占据2个字节 SseError: ; ; Processor has to support SSE ; jmp SseError ; 对应的机器码是EB FE,因为是循环执行,相当于跳转回去执行同一条命令,而该命令是2个字节,所以就是-2,等于0xFE NextAddress: ; 理论上到这里只有9个字节,但是代码中跳转了0xB,也就是有11个字节,多出来的2个字节用0补上了,应该也是为了4字节对齐
到这里整个调用流程就完整了。
Stage1B
本阶段SBL会调用FSP中的FspMemoryInit()
,对应的代码在BootloaderCorePkg\Stage1B\Stage1B.c:
// Initialize memory HobList = NULL; DEBUG ((DEBUG_INIT, "Memory Init\n")); AddMeasurePoint (0x2020); Status = CallFspMemoryInit (PCD_GET32_WITH_ADJUST (PcdFSPMBase), &HobList); AddMeasurePoint (0x2030);
CallFspMemoryInit()
执行的最重要的代码如下:
FspMemoryInit = (FSP_MEMORY_INIT)(UINTN)(FspHeader->ImageBase + \ FspHeader->FspMemoryInitEntryOffset); Status = FspMemoryInit (&FspmUpd, HobList);
从这里可以看到这就是一个跳转的动作,而跳转的位置就是FSP_INFO_HEADER
中的成员FspMemoryInitEntryOffset
,这个在前面已经说明过。
FSP中对应的模块主要有:
# # It is important to keep the proper order for these PEIMs # for this implementation # INF RuleOverride = RELOC IntelFsp2Pkg/FspSecCore/FspSecCoreM.inf INF MdeModulePkg/Core/Pei/PeiMain.inf INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf # # Project specific PEIMs # INF $(FSP_PACKAGE)/FspmInit/FspmInit.inf
FspSecCoreM.inf可以认为是一个伪SEC代码,主要的目的就是为了进入之后的PEI阶段,即PeiMain.inf,它跟UEFI中的PEI没有本质的区别,不过能够Dispatch的模块仅有后面的两个,Pcd.inf只是功能模块在这里并不重要,而FspmInit.inf就是内存初始化的主体。下面会简单介绍其中的主要模块。
FspSecCoreM.inf对应的入口:
;---------------------------------------------------------------------------- ; FspMemoryInit API ; ; This FSP API is called after TempRamInit and initializes the memory. ; ;---------------------------------------------------------------------------- global ASM_PFX(FspMemoryInitApi) ASM_PFX(FspMemoryInitApi): mov eax, 3 ; FSP_API_INDEX.FspMemoryInitApiIndex jmp ASM_PFX(FspApiCommon)
对应的调用路径:
调用路径中大部分是汇编,不过有几个是C函数,因为在Stage1A中已经可以使用C函数了。SecStartup()
位于UefiCpuPkg\SecCore\SecMain.c,是SEC的C函数入口,之后转入执行PeiMain,这个PEI阶段Dispatch的模块主要是FspmInit.inf,它完成真正的内存初始化操作。
FspmInit.inf模块中完成内存初始化的代码这里不多做介绍,因为真正的Intel平台中的代码要复杂的多,这里只是虚拟机的内存初始化,本身的意义不大,不过需要注意的是其中的某些代码:
EFI_PEI_NOTIFY_DESCRIPTOR mMemoryDiscoveredNotifyList = { (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), &gEfiPeiMemoryDiscoveredPpiGuid, MemoryDiscoveredPpiNotifyCallback }; // // Now that all of the pre-permanent memory activities have // been taken care of, post a call-back for the permanent-memory // resident services, such as HOB construction. // PEI Core will switch stack after this PEIM exit. After that the MTRR // can be set. // Status = PeiServicesNotifyPpi (&mMemoryDiscoveredNotifyList);
这个操作在gEfiPeiMemoryDiscoveredPpiGuid
被安装后被调用,而安装动作在PeiCore()
中完成:
// // Alert any listeners that there is permanent memory available // PERF_INMODULE_BEGIN ("DisMem"); Status = PeiServicesInstallPpi (&mMemoryDiscoveredPpi);
gEfiPeiMemoryDiscoveredPpiGuid
对应的回调函数有很多个,这里关注的是FspmInit.inf模块中的。原因是这里有两层的跳转,其中有如下的代码:
EFI_STATUS EFIAPI MemoryDiscoveredPpiNotifyCallback ( IN EFI_PEI_SERVICES **PeiServices, IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, IN VOID *Ppi ) { // // Migrate FSP-M UPD data before destroying CAR // MigrateFspmUpdData (); // // Give control back after MemoryInitApi // FspMemoryInitDone (HobListPtr); if (GetFspApiCallingIndex() == TempRamExitApiIndex) { DEBUG ((DEBUG_INFO | DEBUG_INIT, "Memory Discovered Notify completed ...\n")); // // Give control back after TempRamExitApi // FspTempRamExitDone (); } }
这里的FspMemoryInitDone()
执行之后,CPU又会跳转到SBL代码中去执行,直到SBL中再次调用FSP中的TempRamExit()
这个API,对应SBL中的代码:
Status = CallFspTempRamExit (PCD_GET32_WITH_ADJUST (PcdFSPMBase), NULL);
然后会再次开始执行FspMemoryInitDone()
之后的代码,直到FspTempRamExitDone()
退出。
Stage1B中调用的两个API到这里就都介绍完毕了。
Stage2
本阶段SBL会调用FSP中FspSiliconInit()
,对应的代码在BootloaderCorePkg\Stage2\Stage2.c:
DEBUG ((DEBUG_INIT, "Silicon Init\n")); AddMeasurePoint (0x3020); Status = CallFspSiliconInit (); AddMeasurePoint (0x3030); FspResetHandler (Status); ASSERT_EFI_ERROR (Status);
跟Stage1B中调用FSP中的API一样,这里也是一个跳转:
FspSiliconInit = (FSP_SILICON_INIT)(UINTN)(FspHeader->ImageBase + \ FspHeader->FspSiliconInitEntryOffset) Status = FspSiliconInit (FspsUpdptr);
对应的FSP-S的执行过程跟FSP-M差不多,也有一个伪SEC模块,对应的模块如下所示:
# # It is important to keep the proper order for these PEIMs # for this implementation # INF RuleOverride = RELOC IntelFsp2Pkg/FspSecCore/FspSecCoreS.inf INF MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf INF RuleOverride = PE32 $(FSP_PACKAGE)/FspsInit/FspsInit.inf INF RuleOverride = PE32 $(FSP_PACKAGE)/QemuVideo/QemuVideo.inf INF RuleOverride = PE32 IntelFsp2Pkg/FspNotifyPhase/FspNotifyPhasePeim.inf
不过实际上,当调用FspSiliconInit()
之后,代码还是从Stage1B中的FSP退出的位置开始执行的,即QemuFspPkg\FspmInit\FspmInit.c中的FspTempRamExitDone ()
之后开始执行代码,其对应的函数是ReportAndInstallNewFv ()
,也就是说,Stage2调用FSP-S之后,还是从PeiMain开始执行,当前述的函数安装了FV之后,就又开始Dispatch,完成上述模块的执行。
Stage2还是调用FSP中的NotifyPhase()
,对应SBL中的代码:
EFI_STATUS EFIAPI CallFspNotifyPhase ( FSP_INIT_PHASE Phase ) { FSP_INFO_HEADER *FspHeader; FSP_NOTIFY_PHASE NotifyPhase; NOTIFY_PHASE_PARAMS NotifyPhaseParams; EFI_STATUS Status; FspHeader = (FSP_INFO_HEADER *)(UINTN)(PcdGet32 (PcdFSPSBase) + FSP_INFO_HEADER_OFF); ASSERT (FspHeader->Signature == FSP_INFO_HEADER_SIGNATURE); ASSERT (FspHeader->ImageBase == PcdGet32 (PcdFSPSBase)); if (FspHeader->NotifyPhaseEntryOffset == 0) { return EFI_UNSUPPORTED; } NotifyPhase = (FSP_NOTIFY_PHASE)(UINTN)(FspHeader->ImageBase + FspHeader->NotifyPhaseEntryOffset); NotifyPhaseParams.Phase = Phase; DEBUG ((DEBUG_INFO, "Call FspNotifyPhase(%02X) ... ", Phase)); if (IS_X64) { Status = Execute32BitCode ((UINTN)NotifyPhase, (UINTN)&NotifyPhaseParams, (UINTN)0, FALSE); Status = (UINTN)LShiftU64 (Status & ((UINTN)MAX_INT32 + 1), 32) | (Status & MAX_INT32); } else { Status = NotifyPhase (&NotifyPhaseParams); } DEBUG ((DEBUG_INFO, "%r\n", Status)); return Status; }
可以看到也只是一个简单的跳转。这里的参数FSP_INIT_PHASE
对应的值:
/// /// Enumeration of FSP_INIT_PHASE for NOTIFY_PHASE. /// typedef enum { /// /// This stage is notified when the bootloader completes the /// PCI enumeration and the resource allocation for the /// PCI devices is complete. /// EnumInitPhaseAfterPciEnumeration = 0x20, /// /// This stage is notified just before the bootloader hand-off /// to the OS loader. /// EnumInitPhaseReadyToBoot = 0x40, /// /// This stage is notified just before the firmware/Preboot /// environment transfers management of all system resources /// to the OS or next level execution environment. /// EnumInitPhaseEndOfFirmware = 0xF0 } FSP_INIT_PHASE;
标明了调用NotifyPhase()
的具体位置。
以上就是UEFI开发实战SlimBootloader中调用FSP的详细内容,更多关于UEFI开发SlimBootloader调用FSP的资料请关注脚本之家其它相关文章!