张金虎 發表於 2022-6-6 15:45:35

UEFI开发实战SlimBootloader中调用FSP

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>综述</li><li>编译</li><ul class="second_class_ul"><li>PostBuild</li><li>Build</li><li>PostBuild</li><li>FSP二进制组成分析</li></ul><li>使用</li><ul class="second_class_ul"><li>Stage1A</li><li>Stage1B</li><li>Stage2</li></ul></ul></div><p class="maodian"></p><h2>综述</h2>
<p>FSP的全称是Firmware Support Package。FSP有以下的特性:</p>
<ul><li>FSP提供了Intel重要组件(包括处理器、内存控制器、芯片组等)的初始化;</li><li>FSP被编译成独立的二进制,并可以集成到Bootloader中,这里说的Bootloader可以是Slim Bootloader,coreboot,UEFI等等;</li><li>FSP的优点有免费、方便集成、可减少开发时间,等等。</li></ul>
<p>FSP中包含若干个部分,如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150245128.png" /></p>
<p>按作用来分它包含三个大的组件,分别是:</p>
<p>FSP-T:它主要用来初始化CACHE以及其它早期需要的初始化,对应提供给外部的接口是<code>TempRamInit()</code>;</p>
<p>FSP-M:它主要用来初始化内存以及其它需要的初始化,对应提供给外部的接口是<code>FspMemoryInit()</code>和<code>TempRamExit()</code>,前者用于内存初始化,后者用于处理FSP-T中使用的CACHE内容;</p>
<p>FPS-S:它主要是CPU和芯片组的初始化,对应提供给外部的接口是<code>FspSiliconInit()</code>和<code>NotifyPhase()</code>,其中后者又会在不同的阶段调用,包括PCIE扫描之后,ReadyToBoot时和EndOfBootServices的时候;</p>
<p>按功能来区分,每个组件都包含头部、配置和API三个部分。头部是固定的,配置用于一些可控的定制化,API就是功能代码。Bootloader要操作FSP,就需要完成文件配置,接口调用等。</p>
<p>BootLoader调用FSP的整个流程如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150246129.png" /></p>
<p>Bootloader中需要有相应的代码做上述的操作,以Slim Bootloader为例,有一个IntelFsp2Pkg用来处理FSP相关的内容。不过需要注意,这里的IntelFsp2Pkg并不提供FSP源代码的,只是提供了EDK与FSP之间的中间层,真正的用来初始化Intel组件的FSP的代码并没有开源,所以这里也拿不到,不过可以拿到用于QEMU的FSP源代码,后续使用的就是这个。</p>
<p class="maodian"></p><h2>编译</h2>
<p>代码主要是https://gitee.com/jiangwei0512/edk2-beni中的QemuFspPkg,它是用在QEMU上的FSP,它有源码可以下载,而其它Intel的FSP基本是不开源的,没有办法下载到,所以这里只能用QEMU的FSP作为示例。</p>
<p>QEMU对应的FSP通过BuildFsp.py进行编译得到,该脚本执行三个步骤:</p>
<ul><li>Prebuild</li><li>Build</li><li>PostBuild</li></ul>
<p class="maodian"></p><p class="maodian"></p><h3>PostBuild</h3>
<p>Prebuild的流程如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060615191901.png" /></p>
<p>1.构建FspHeader.inf实际上就是创建FSP Header的头部,它是固定的格式,位于QemuFspPkg\FspHeader\FspHeader.aslc,内容如下:</p>
<div class="jb51code"><pre class="brush:cpp;">TABLES mTable =
{
{
    FSP_INFO_HEADER_SIGNATURE,                  // UINT32Signature(FSPH)
    sizeof(FSP_INFO_HEADER),                      // UINT32HeaderLength;
    {0x00, 0x00},                                 // UINT8   Reserved1;
    FixedPcdGet8(PcdFspHeaderSpecVersion),      // UINT8   SpecVersion;
    FixedPcdGet8(PcdFspHeaderRevision),         // UINT8   HeaderRevision;
    FixedPcdGet32(PcdFspImageRevision),         // UINT32ImageRevision;
    UINT64_TO_BYTE_ARRAY(
    FixedPcdGet64(PcdFspImageIdString)),          // CHAR8   ImageId;
    0x12345678,                                 // UINT32ImageSize;
    0x12345678,                                 // UINT32ImageBase;
    FixedPcdGet16(PcdFspImageAttributes),         // UINT16ImageAttribute;
    FixedPcdGet16(PcdFspComponentAttributes),   // UINT16ComponentAttribute; Bits - 0001b: FSP-T, 0010b: FSP-M, 0011b: FSP-S
    0x12345678,                                 // UINT32CfgRegionOffset;
    0x12345678,                                 // UINT32CfgRegionSize;
    0x00000000,                                 // UINT32Reserved2;
    0x00000000,                                 // UINT32TempRamInitEntry;
    0x00000000,                                 // UINT32Reserved3;
    0x00000000,                                 // UINT32NotifyPhaseEntry;
    0x00000000,                                 // UINT32FspMemoryInitEntry;
    0x00000000,                                 // UINT32TempRamExitEntry;
    0x00000000,                                 // UINT32FspSiliconInitEntry;
},
{
    FSP_INFO_EXTENDED_HEADER_SIGNATURE,         // UINT32Signature(FSPE)
    sizeof(FSP_INFO_EXTENDED_HEADER),             // UINT32Length;
    FSPE_HEADER_REVISION_1,                     // UINT8   Revision;
    0x00,                                       // UINT8   Reserved;
    {FSP_PRODUCER_ID},                            // CHAR8   FspProducerId;
    0x00000001,                                 // UINT32FspProducerRevision;
    0x00000000,                                 // UINT32FspProducerDataSize;
},
{
    FSP_FSPP_SIGNATURE,                           // UINT32Signature(FSPP)
    sizeof(FSP_PATCH_TABLE),                      // UINT16Length;
    FSPP_HEADER_REVISION_1,                     // UINT8   Revision;
    0x00,                                       // UINT8   Reserved;
    1                                             // UINT32PatchEntryNum;
},
    0xFFFFFFFC                                    // UINT32Patch FVBASE at end of FV
};
</pre></div>
<p>里面的某些数据在之后还会被修改,通过这个头部就可以找到对应API的位置,从而进行调用。</p>
<p>2.UPD txt文件是Build\QemuFspPkg\DEBUG_VS2019\FV(根据编译工具的不同,对应的目录可能存在差异)下的如下内容:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150246130.png" /></p>
<p>txt文件名对应的是三个FSP组件的GUID(位于BuildFsp.py):</p>
<div class="jb51code"><pre class="brush:plain;">    FspGuid = {
      'FspTUpdGuid'       : '34686CA3-34F9-4901-B82A-BA630F0714C6',
      'FspMUpdGuid'       : '39A250DB-E465-4DD1-A2AC-E2BD3C0E2385',
      'FspSUpdGuid'       : 'CAE3605B-5B34-4C85-B3D7-27D54273C40F'
    }
</pre></div>
<p>里面的内容主要是一些PCD,以39A250DB-E465-4DD1-A2AC-E2BD3C0E2385.txt为例:</p>
<div class="jb51code"><pre class="brush:plain;">## @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.&lt;BR&gt;
# 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
</pre></div>
<p>txt文件的来源是QemuFspPkg\QemuFspPkg.dsc,里面有这些PCD的初始化值,位于<code></code>这个Section,其中的<code>UnusedUpdSpaceX</code>也是在dsc文件中通过PCD指定的偏移来确定的。</p>
<p>3.UPD bin文件名也对应到前面提到的GUID,以39A250DB-E465-4DD1-A2AC-E2BD3C0E2385.bin为例:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150246131.png" /></p>
<p>bin文件跟txt文件中的PCD值是一一对应的。</p>
<p>4.UPD头文件和bsf文件比较直观,不做详细说明,它们也是通过QemuFspPkg\QemuFspPkg.dsc创建的。UPD头文件中还包含一个通用的头部结构体:</p>
<div class="jb51code"><pre class="brush:cpp;">#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;
} FSP_UPD_HEADER;
#pragma pack()
</pre></div>
<p>每个平台的UPD和bsf内容都是不同的,甚至同一个平台的不同版本也可能存在差异,不同的FSP-X对应不同的UPD,分别是<code>FSPT_UPD</code>、<code>FSPM_UPD</code>和<code>FSPS_UPD</code>,它们分别存放在FsptUpd.h、FspmUpd.h和FspsUpd.h中。以<code>FSPM_UPD</code>结构体为例:</p>
<div class="jb51code"><pre class="brush:cpp;">/** 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;
/** Offset 0x007E **/
UINT16                      UpdTerminator;
} FSPM_UPD;
</pre></div>
<p>其中的内容跟前面的UPD txt中的PCD一一对应。</p>
<p>上述的文件都在Build\QemuFspPkg\DEBUG_VS2019\FV(根据编译工具的不同,对应的目录可能存在差异)创建,总的来说就是为了创建FSP的UPD配置文件和对应用在代码中的头文件,头文件会被拷贝到其它位置也是为了代码能够调用到。而UPD配置文件会通过二进制的方式包含到QemuFspPkg\QemuFspPkg.fdf,下面是一个示例:</p>
<div class="jb51code"><pre class="brush:cpp;">#
# 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
</pre></div>
<p class="maodian"></p><h3>Build</h3>
<p>该过程仅仅是执行build操作而已,对应的代码:</p>
<div class="jb51code"><pre class="brush:py;">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...')
</pre></div>
<p>可以看到执行对象是QemuFspPkg.dsc。完成这一步之后会生成FSP-M.Fv、FSP-S.Fv、FSP-T.Fv和QEMUFSP.fd。</p>
<p>到这里FSP二进制已经生成,就是QEMUFSP.fd,但是它不能直接使用,还需要后续操作。</p>
<h3>PostBuild</h3>
<p>这一步主要是通过PatchFv.py来修改前文生成的QEMUFSP.fd,Patch前后:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150246132.png" /></p>
<p>这里具体Patch了哪部分内容,需要先了解FSP二进制的组成部分,可以参考FSP二进制组成分析。这里Patch的大部分都是FSP Header中的内容,对应的默认初始化内容就是前面提到的QemuFspPkg\FspHeader\FspHeader.aslc,其结构体如下:</p>
<div class="jb51code"><pre class="brush:cpp;">///
/// 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.
///
UINT32Signature;
///
/// Byte 0x04: Length of the FSP Information Header.
///
UINT32HeaderLength;
///
/// Byte 0x08: Reserved.
///
UINT8   Reserved1;
///
/// 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.
///
UINT32ImageRevision;
///
/// Byte 0x10: Signature string that will help match the FSP Binary to a supported HW configuration.
///
CHAR8   ImageId;
///
/// Byte 0x18: Size of the entire FSP binary.
///
UINT32ImageSize;
///
/// Byte 0x1C: FSP binary preferred base address.
///
UINT32ImageBase;
///
/// Byte 0x20: Attribute for the FSP binary.
///
UINT16ImageAttribute;
///
/// Byte 0x22: Attributes of the FSP Component.
///
UINT16ComponentAttribute;
///
/// Byte 0x24: Offset of the FSP configuration region.
///
UINT32CfgRegionOffset;
///
/// Byte 0x28: Size of the FSP configuration region.
///
UINT32CfgRegionSize;
///
/// Byte 0x2C: Reserved2.
///
UINT32Reserved2;
///
/// Byte 0x30: The offset for the API to setup a temporary stack till the memory is initialized.
///
UINT32TempRamInitEntryOffset;
///
/// Byte 0x34: Reserved3.
///
UINT32Reserved3;
///
/// Byte 0x38: The offset for the API to inform the FSP about the different stages in the boot process.
///
UINT32NotifyPhaseEntryOffset;
///
/// Byte 0x3C: The offset for the API to initialize the memory.
///
UINT32FspMemoryInitEntryOffset;
///
/// Byte 0x40: The offset for the API to tear down temporary RAM.
///
UINT32TempRamExitEntryOffset;
///
/// Byte 0x44: The offset for the API to initialize the CPU and chipset.
///
UINT32FspSiliconInitEntryOffset;
///
/// Byte 0x48: Offset for the API for the optional Multi-Phase processor and chipset initialization.
///            This value is only valid if FSP HeaderRevision is &gt;= 5.
///            If the value is set to 0x00000000, then this API is not available in this component.
///
UINT32FspMultiPhaseSiInitEntryOffset;
} FSP_INFO_HEADER;
</pre></div>
<p>其中的<code>ImageSize</code>、<code>ImageBase</code>、<code>ImageAttribute</code>、<code>ComponentAttribute</code>、<code>CfgRegionOffset</code>、<code>CfgRegionSize</code>、<code>TempRamInitEntryOffset</code>、<code>FspMemoryInitEntryOffset</code>、<code>TempRamExitEntryOffset</code>、<code>FspSiliconInitEntryOffset</code>、<code>NotifyPhaseEntryOffset</code>等都需要修改。</p>
<p>因为每个FSP-X都有一个<code>FSP_INFO_HEADER</code>结构体,所以前提提到的<code>XXXOffset</code>会针对不同的FSP-X组件做对应的修改,比如FSP-T只需要<code>TempRamInitEntryOffset</code>。</p>
<p>除了FSP Header的Patch,这里还有一个点被Patch了:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150247133.png" /></p>
<p>它们对应的是模块的入口(IntelFsp2Pkg\FspSecCore\Ia32\FspHelper.nasm):</p>
<div class="jb51code"><pre class="brush:plain;">global ASM_PFX(FspInfoHeaderRelativeOff)
ASM_PFX(FspInfoHeaderRelativeOff):
   DD    0x12345678               ; This value must be patched by the build script
</pre></div>
<p>从上面的代码也可以看到这部分是需要Patch的。</p>
<p class="maodian"></p><h3>FSP二进制组成分析</h3>
<p>二进制的组成如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150247134.png" /></p>
<p>FSP每个组件都是一个FV,所以都有一个FV Header(<code>EFI_FIRMWARE_VOLUME_HEADER</code>,位于MdePkg\Include\Pi\PiFirmwareVolume.h),大小是0x48个字节,之后是一个FV Extended Header(<code>EFI_FIRMWARE_VOLUME_EXT_HEADER</code>,位于MdePkg\Include\Pi\PiFirmwareVolume.h),之后才是FSP的内容,如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150247135.png" /></p>
<p>FSP组件的第一个模块是FSP Header,对应二进制(Header的第一个成员是&quot;FSPH&quot;,最后一个成员是0xFFFFFFFC)中:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150247136.png" /></p>
<p>这里可以看到里面有一些数据比较奇怪,都是0x12345678和0x00000000,这些都是占位符,并不是真正的有效数据,是通过FspHeader.aslc生成的,在后期这些数据会被Patch成有效的值。</p>
<p>FSP组件的第二个模块是UPD数据,它在Prebuild中生成,对应的数据(UPD数据的第一个成员是Signature(本例中是QEMUPD_T),最后一个成员是0x55AA):</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150248137.png" /></p>
<p>再之后是通用的模块。以QEMU中的FSP对应的fdf文件为例:</p>
<div class="jb51code"><pre class="brush:cpp;">#
# FSP header
#
INFRuleOverride = 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
</pre></div>
<p class="maodian"></p><h2>使用</h2>
<p>用于Slim Bootloader的FSP需要放到前者指定的目录,对于QEMU来说对应的是Silicon\QemuSocPkg\FspBin,同时FSP对应的UPD头文件也需要放到指定目录。之后执行Slim Bootloader的各个阶段都会调用FSP的API接口,这里一一说明。</p>
<p class="maodian"></p><h3>Stage1A</h3>
<p>Stage1A阶段会执行FSP中的<code>FspTempRamInit()</code>接口,由于是执行阶段的早期,这里只有汇编部分的代码,具体的位置在BootloaderCorePkg\Stage1A\Ia32\SecEntry.nasm,对应代码:</p>
<div class="jb51code"><pre class="brush:plain;">globalASM_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)
</pre></div>
<p>这里<code>jmp</code>到BootloaderCorePkg\Library\FspApiLib\Ia32\FspTempRamInit.nasm:</p>
<div class="jb51code"><pre class="brush:cpp;">globalASM_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
      ;
      ; Find the fsp info header
      ; Jump to TempRamInit API
      ;
      add   eax, dword
      mov   esp, TempRamInitStack
      jmp   eax
TempRamInitDone:
      mov   esp, ebp
      jmp   esp
</pre></div>
<p>FSP中的<code>FspTempRamInit()</code>真正的入口是<code>PcdGet32(PcdFSPTBase)+ 094h + FSP_HEADER_TEMPRAMINIT_OFFSET</code>,<code>PcdFSPTBase</code>的值是:</p>
<div class="jb51code"><pre class="brush:plain;">gPlatformModuleTokenSpaceGuid.PcdFSPTBase | $(FSP_T_BASE)</pre></div>
<p><code>FSP_T_BASE</code>表示的是FSP-T.bin的开始位置,<code>094h</code>在前面也已经介绍过,其前面的内容是FV Header,该地址开始是FSP Header,而<code>FSP_HEADER_TEMPRAMINIT_OFFSET</code>是FSP Header的偏移,该位置对应成员是<code>TempRamInitEntryOffset</code>,到这里就对应起来了,<code>FspTempRamInit()</code>即是该位置的值。</p>
<p>不过对于<code>FSP_T_BASE</code>的值,它是FSP-T放到系统内存中位置的地址,可以在BootloaderCorePkg\Platform.dsc中找到:</p>
<div class="jb51code"><pre class="brush:plain;">DEFINE FSP_T_BASE = 0xFFFF0000</pre></div>
<p>这个值也跟SBL二进制产生关系:</p>
<div class="jb51code"><pre class="brush:plain;">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:
      +------------------------------------------------------------------------+
      |                              FLASHMAP                              |
      |                         (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    |
      +----------+------------------------+------------+-----------------------+
</pre></div>
<p>由于SBL会放到4G以下的空间,而FSP-T.Fv放在了SG1A中,大小是0x10000,所以位置就是0xFFFF0000。通过下述命令能够更清楚的看出来:</p>
<div class="jb51code"><pre class="brush:bash;">F:\Gitee\sbl&gt;BootloaderCorePkg\Tools\IfwiUtility.py view -i Outputs\qemu\SlimBootloader.bin
IFWI                     
BIOS                  
    NVS                  
      RSVD               
    NRD                  
      EMTY               
      VARS               
      PYLD               
      EPLD               
      IPFW               
      PTES               
    RD1                  
      EMTY               
      SG02               
      SG1B               
      FWUP               
      CNFG               
      KEYH               
    RD0                  
      EMTY               
      SG02               
      SG1B               
      FWUP               
      CNFG               
      KEYH               
    TS1                  
      SG1A               
    TS0                  
      SG1A                ---- 这里的最后就是0x100000000的位置
</pre></div>
<p>SBL二进制和FSP-T.bin的对应关系:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150248138.png" /></p>
<p>最终可以查看到<code>PcdGet32(PcdFSPTBase)+ 094h + FSP_HEADER_TEMPRAMINIT_OFFSET</code>处的值是0x473(位于0x7110C4),这也跟编译FSP时的Patch对应:</p>
<div class="jb51code"><pre class="brush:plain;">Patched offset 0x000370C4: with value 0x00000473 # TempRamInit API</pre></div>
<p>从上图可以看到该位置的值是<code>EB 0B 90 90 90</code>等等,可以确定这些就是代码了,但是它对应到的是哪个模块呢?其实可以从QemuFspPkg\QemuFspPkg.fdf中找到答案:</p>
<div class="jb51code"><pre class="brush:plain;">
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
#
INFRuleOverride = 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
</pre></div>
<p>根据前面的介绍,FspHeader.inf是FSP Header,$(FSP_T_UPD_TOOL_GUID).bin是UPD配置文件,那么FspSecCoreT.inf就应该是包含<code>FspTempRamInit()</code>函数代码的模块了。</p>
<p>这里直接找对应的模块,它被编译成一个二进制FspSecCoreT.efi,efi文件符合的是《Microsoft Portable Executable and Common Object File Format Specification》(后称Spec)规范。文档可以点击下载。在这个文档中描述了efi二进制头部的格式如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150248139.gif" /></p>
<p>通过它可以找到真正的代码入口位置,不过也不需要一一计算,可以通过对应的map文件(这里就是Build\QemuFspPkg\DEBUG_VS2019\IA32\IntelFsp2Pkg\FspSecCore\FspSecCoreT\DEBUG\FspSecCoreT.map)找到需要的入口:</p>
<div class="jb51code"><pre class="brush:plain;">Address         Publics by Value            Rva+Base       Lib:Object
0001:000001fb       _TempRamInitApi            0000041b   FspSecCoreT:FspApiEntryT.obj
</pre></div>
<p>可以看到地址是<code>0000041b</code>,查看FspSecCoreT.efi二进制:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150248140.png" /></p>
<p>可以看到数据已经对应上了。再进一步分析这些数据的话,会发现EB实际上是一个跳转指令(注意之类是16位的代码,所以是近跳转),可以参考《64-ia-32-architectures-software-developer-instruction-set-reference-manual.pdf》中的&ldquo;JMP&mdash;Jump&rdquo;章节:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/20220606150249141.png" /></p>
<p><code>cb</code>表示的是跳转偏移,这里的值是0xB,对应的代码在IntelFsp2Pkg\FspSecCore\Ia32\SaveRestoreSseNasm.inc:</p>
<div class="jb51code"><pre class="brush:plain;">%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字节对齐
</pre></div>
<p>到这里整个调用流程就完整了。</p>
<p class="maodian"></p><h3>Stage1B</h3>
<p>本阶段SBL会调用FSP中的<code>FspMemoryInit()</code>,对应的代码在BootloaderCorePkg\Stage1B\Stage1B.c:</p>
<div class="jb51code"><pre class="brush:cpp;">// Initialize memory
HobList = NULL;
DEBUG ((DEBUG_INIT, "Memory Init\n"));
AddMeasurePoint (0x2020);
Status = CallFspMemoryInit (PCD_GET32_WITH_ADJUST (PcdFSPMBase), &amp;HobList);
AddMeasurePoint (0x2030);
</pre></div>
<p><code>CallFspMemoryInit()</code>执行的最重要的代码如下:</p>
<div class="jb51code"><pre class="brush:cpp;">FspMemoryInit = (FSP_MEMORY_INIT)(UINTN)(FspHeader-&gt;ImageBase + \
                                           FspHeader-&gt;FspMemoryInitEntryOffset);
Status = FspMemoryInit (&amp;FspmUpd, HobList);
</pre></div>
<p>从这里可以看到这就是一个跳转的动作,而跳转的位置就是<code>FSP_INFO_HEADER</code>中的成员<code>FspMemoryInitEntryOffset</code>,这个在前面已经说明过。</p>
<p>FSP中对应的模块主要有:</p>
<div class="jb51code"><pre class="brush:plain;">#
# 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
</pre></div>
<p>FspSecCoreM.inf可以认为是一个伪SEC代码,主要的目的就是为了进入之后的PEI阶段,即PeiMain.inf,它跟UEFI中的PEI没有本质的区别,不过能够Dispatch的模块仅有后面的两个,Pcd.inf只是功能模块在这里并不重要,而FspmInit.inf就是内存初始化的主体。下面会简单介绍其中的主要模块。</p>
<p>FspSecCoreM.inf对应的入口:</p>
<div class="jb51code"><pre class="brush:plain;">;----------------------------------------------------------------------------
; 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)
</pre></div>
<p>对应的调用路径:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202206/2022060615191902.png" /></p>
<p>调用路径中大部分是汇编,不过有几个是C函数,因为在Stage1A中已经可以使用C函数了。<code>SecStartup()</code>位于UefiCpuPkg\SecCore\SecMain.c,是SEC的C函数入口,之后转入执行PeiMain,这个PEI阶段Dispatch的模块主要是FspmInit.inf,它完成真正的内存初始化操作。</p>
<p>FspmInit.inf模块中完成内存初始化的代码这里不多做介绍,因为真正的Intel平台中的代码要复杂的多,这里只是虚拟机的内存初始化,本身的意义不大,不过需要注意的是其中的某些代码:</p>
<div class="jb51code"><pre class="brush:cpp;">EFI_PEI_NOTIFY_DESCRIPTOR mMemoryDiscoveredNotifyList = {
(EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&amp;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 (&amp;mMemoryDiscoveredNotifyList);
</pre></div>
<p>这个操作在<code>gEfiPeiMemoryDiscoveredPpiGuid</code>被安装后被调用,而安装动作在<code>PeiCore()</code>中完成:</p>
<div class="jb51code"><pre class="brush:cpp;">    //
    // Alert any listeners that there is permanent memory available
    //
    PERF_INMODULE_BEGIN ("DisMem");
    Status = PeiServicesInstallPpi (&amp;mMemoryDiscoveredPpi);
</pre></div>
<p><code>gEfiPeiMemoryDiscoveredPpiGuid</code>对应的回调函数有很多个,这里关注的是FspmInit.inf模块中的。原因是这里有两层的跳转,其中有如下的代码:</p>
<div class="jb51code"><pre class="brush:cpp;">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 ();
}
}
</pre></div>
<p>这里的<code>FspMemoryInitDone()</code>执行之后,CPU又会跳转到SBL代码中去执行,直到SBL中再次调用FSP中的<code>TempRamExit()</code>这个API,对应SBL中的代码:</p>
<div class="jb51code"><pre class="brush:cpp;">Status = CallFspTempRamExit (PCD_GET32_WITH_ADJUST (PcdFSPMBase), NULL);</pre></div>
<p>然后会再次开始执行<code>FspMemoryInitDone()</code>之后的代码,直到<code>FspTempRamExitDone()</code>退出。</p>
<p>Stage1B中调用的两个API到这里就都介绍完毕了。</p>
<p class="maodian"></p><h3>Stage2</h3>
<p>本阶段SBL会调用FSP中<code>FspSiliconInit()</code>,对应的代码在BootloaderCorePkg\Stage2\Stage2.c:</p>
<div class="jb51code"><pre class="brush:cpp;">DEBUG ((DEBUG_INIT, "Silicon Init\n"));
AddMeasurePoint (0x3020);
Status = CallFspSiliconInit ();
AddMeasurePoint (0x3030);
FspResetHandler (Status);
ASSERT_EFI_ERROR (Status);
</pre></div>
<p>跟Stage1B中调用FSP中的API一样,这里也是一个跳转:</p>
<div class="jb51code"><pre class="brush:cpp;">FspSiliconInit = (FSP_SILICON_INIT)(UINTN)(FspHeader-&gt;ImageBase + \
                                             FspHeader-&gt;FspSiliconInitEntryOffset)
Status = FspSiliconInit (FspsUpdptr);
</pre></div>
<p>对应的FSP-S的执行过程跟FSP-M差不多,也有一个伪SEC模块,对应的模块如下所示:</p>
<div class="jb51code"><pre class="brush:plain;">#
# 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
</pre></div>
<p>不过实际上,当调用<code>FspSiliconInit()</code>之后,代码还是从Stage1B中的FSP退出的位置开始执行的,即QemuFspPkg\FspmInit\FspmInit.c中的<code>FspTempRamExitDone ()</code>之后开始执行代码,其对应的函数是<code>ReportAndInstallNewFv ()</code>,也就是说,Stage2调用FSP-S之后,还是从PeiMain开始执行,当前述的函数安装了FV之后,就又开始Dispatch,完成上述模块的执行。</p>
<p>Stage2还是调用FSP中的<code>NotifyPhase()</code>,对应SBL中的代码:</p>
<div class="jb51code"><pre class="brush:cpp;">EFI_STATUS
EFIAPI
CallFspNotifyPhase (
FSP_INIT_PHASEPhase
)
{
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-&gt;Signature == FSP_INFO_HEADER_SIGNATURE);
ASSERT (FspHeader-&gt;ImageBase == PcdGet32 (PcdFSPSBase));
if (FspHeader-&gt;NotifyPhaseEntryOffset == 0) {
    return EFI_UNSUPPORTED;
}
NotifyPhase = (FSP_NOTIFY_PHASE)(UINTN)(FspHeader-&gt;ImageBase +
                                          FspHeader-&gt;NotifyPhaseEntryOffset);
NotifyPhaseParams.Phase = Phase;
DEBUG ((DEBUG_INFO, "Call FspNotifyPhase(%02X) ... ", Phase));
if (IS_X64) {
    Status = Execute32BitCode ((UINTN)NotifyPhase, (UINTN)&amp;NotifyPhaseParams, (UINTN)0, FALSE);
    Status = (UINTN)LShiftU64 (Status &amp; ((UINTN)MAX_INT32 + 1), 32) | (Status &amp; MAX_INT32);
} else {
    Status = NotifyPhase (&amp;NotifyPhaseParams);
}
DEBUG ((DEBUG_INFO, "%r\n", Status));
return Status;
}
</pre></div>
<p>可以看到也只是一个简单的跳转。这里的参数<code>FSP_INIT_PHASE</code>对应的值:</p>
<div class="jb51code"><pre class="brush:cpp;">///
/// 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;
</pre></div>
<p>标明了调用<code>NotifyPhase()</code>的具体位置。</p>
<p>以上就是UEFI开发实战SlimBootloader中调用FSP的详细内容,更多关于UEFI开发SlimBootloader调用FSP的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>UEFI开发实战用户交互界面基础说明</li><li>UEFI开发实战用户交互界面使用说明UNI文件</li><li>UEFI开发实战用户交互界面使用说明VFR文件</li><li>UEFI开发基础HII代码示例</li><li>UEFI开发基础汇编代码的使用</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: UEFI开发实战SlimBootloader中调用FSP