2

我是 EDK2 的新手。

要将 ekd2 固件移植到新的 ARM64 平台,最好先获得一个至少可以运行 UEFI Shell 的最小 edk2 端口,可以在此基础上逐步添加改进。

第一步似乎相当陡峭,例如,如何确定平台中的最小“项目”集.dsc.fdf文件?就我而言,我想.fd为我的平台构建并将其视为 TF-A 的 BL33,实际上我想构建一个 edk2 固件来替换 u-boot。

在网上似乎很难找到这样的指南。我发现了一个旧版本的 edk2,其中包含一些指令,但显然它们已经过时(在最新master分支中不存在,而可以在 UDK 分支中找到,例如UDK2014),我不确定为什么这些文档会从master分支中删除。

目前我可以.fd为 FVP ( edk2-platforms/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.dsc) 构建,似乎构建输出FVP_AARCH64_EFI.fd应该被视为 BL33。理论上这可能是我的新 ARM64 平台的原型,但对我来说它太复杂了:固件大小约为 2.5MiB(与 500K 的 u-boot 相比),所以我想它远非“最小“ 版本。但是很难弄清楚要删除哪些功能(以及如何删除)。

我想知道是否有关于此类主题的详细指南...

4

1 回答 1

1

经过 1 个月的反复试验,今天我成功地将我的 ARM64 平台带入了 UEFI Shell 环境。我将其视为我在 EDK2 旅程中的第一个里程碑。下面我将尝试总结我到目前为止所采取的步骤,作为对上述问题的初步回答。欢迎指导/更正/评论。

  1. 通过阅读书籍/规范/文章来熟悉 UEFI/PI 规范和 EDK2 实现。好吧,UEFI/PI 规范长达数千页……如何开始?我的主要阅读清单是:

    • “Beyond Bios——使用统一可扩展固件接口进行开发”,第 3 版,作者 Vincent Zimmer 等人。正如作者所解释的,这本书是对数千页规范的一种高级总结。而且我发现这本书组织得很好,适合新手熟悉各种 UEFI 相关概念。第一次阅读(在使用 edk2 代码库之前)的主要目的是熟悉概念和架构思想,而不是细节。稍后阅读 EDK2 实现时需要参考相关章节。
    • EDK2 规格,包括:
      • EDKII 用户手册
      • EDKII 构建规范
      • EDKII DSC/FDF/DEC/INF 文件规范
    • 网上各种文章...
  2. 获取一个可以正确启动从最新 EDK2 源构建的 FD 映像的参考平台,并稍微使用启动管理器和 Shell 环境。就我而言,我选择了 RPi4B。对我来说,这很重要,因为参考平台在整个过程中起到了扶手的作用,每当我遇到错误或有疑问时,我都会查看参考平台的源/日志。这解决了我遇到的大部分问题。顺便说一句,总是为参考平台和目标平台生成“构建日志”和“构建报告”,因为这两个文件包含非常详细的信息以供比较和检查。请参阅 EDK2 构建规范,了解如何在构建期间生成这两个文件。

    我使用以下脚本为 RPi4B 平台构建:

    #!/bin/bash
    
    # https://github.com/tianocore/edk2-platforms#how-to-build-linux-environment
    
    export WORKSPACE=/home/bruin/work/tianocore
    export PACKAGES_PATH=$WORKSPACE/edk2:$WORKSPACE/edk2-platforms:$WORKSPACE/edk2-non-osi
    
    pushd $WORKSPACE
    
    rm -rf ./Build/RPi4
    
    source edk2/edksetup.sh
    
    echo "Building BaseTools..."
    make -C edk2/BaseTools all
    
    #sudo apt install acpica-tools    # iasl
    # pip install antlr4-python3-runtime   # -Y EXECUTION_ORDER
    
    echo "Building firmware for Pi4B..."
    GCC5_AARCH64_PREFIX=aarch64-none-linux-gnu- build \
        -n 4 \
        -a AARCH64 \
        -p Platform/RaspberryPi/RPi4/RPi4.dsc \
        -t GCC5 \
        -b NOOPT \
        -v -d 9 -j RPi4-build.log \
        -y RPi4-build-report.txt \
        -Y PCD \
        -Y LIBRARY \
        -Y DEPEX \
        -Y HASH \
        -Y BUILD_FLAGS \
        -Y FLASH \
        -Y FIXED_ADDRESS \
        -Y EXECUTION_ORDER \
        all
    

    如何RPI_EFI.fd在 RPi4B 上使用构建结果,请参考以下内容:

    • edk2-platforms/Platform/RaspberryPi/RPi4/Readme.md

    • readme.mdhttps://github.com/pftf/RPi4/releases/download/v1.17/RPi4_UEFI_Firmware_v1.32.zip里面。顺便说一句,我需要替换原始文件start4.elffixup4.datzip 文件中的文件,否则,RPi4 的启动将失败,抱怨如下:

      RpiFirmwareGetClockRate: Get Clock Rate return: ClockRate=0 ClockId=C
      ASSERT [ArasanMMCHost] /home/bruin/work/tianocore/edk2-platforms/Platform/RaspberryPi/
      Drivers/ArasanMmcHostDxe/ArasanMmcHostDxe.c(263): BaseFrequency != 0
      
    • RPI_EFI.fd通过使用一些 UEFI 实用程序,在某种程度上分析内容是值得的。我主要使用 GUI 版本UEFIToolsudo apt install uefitool uefitool-cli. 也可以使用其他工具。在阅读EDK2RPI_EFI.fd构建规范以检查对概念的理解时,解剖学很有帮助。

      • 一个特殊的方面RPI_EFI.fd是第一个 128K 是bl31.bin来自 ATF 的二进制文件。我猜这是由于 RPi 的特殊引导配置方法。对于我的平台,我不需要这样的打包,我只需要构建 UEFI 镜像MY.fd,它被视为 BL33 镜像,fip.bin并通过 ATF 构建脚本与 BL2 和 BL31 镜像打包成一起。
      • .fd另一个需要注意的方面是文件开头的“重置向量” 。这涉及到 UEFI 镜像的入口点(以及每个 EDK2 模块的入口点),以及解释BLAArch64 的指令。基本上可以总结如下:

      第一个[Components]是,它RPI_EFI.fd是。ArmPlatformPkg/PrePi/PeiUniCore.infMODULE_TYPE = SEC

      • 这是什么组件:这是SECRPi4 中的第一个(也是唯一一个)(安全)模块。名称PrePiPei含义是什么?

        ... PI 规范与 edk2 PEIM 无关,我看不出 EDKII PEI 模块在哪里是目前唯一“公认”的硅初始化环境。edk2 树本身似乎包含根本不使用 edk2 PEI 模块集的平台,但(IIRC)从 SEC 跳转到 DXE。我相信“ArmPlatformPkg/PrePi”和“ArmVirtPkg/PrePi”与此有关。

        --- https://listman.redhat.com/archives/edk2-devel-archive/2020-November/msg00021.html

      • 它的入口点:所有 UEFI 组件都有相同的入口点 ( _ModuleEntryPoint)。

        • “组件”是指 UEFI 驱动程序和 UEFI 应用程序,两者都是 PE32 可执行文件,通常后缀为.efi.
        • .efis 是通过工具从 ELF 可执行文件 ( .dll)转换而来的GenFw:修改文件头。
        • 要验证“所有组件的入口点是_ModuleEntryPoint”:
          • 检查.dll构建报告()中的生成命令行build -y <BUILD_REPORT_FILE>,我们有两个标志"aarch64-none-linux-gnu-gcc" -o xxx.dll -u _ModuleEntryPoint -Wl,-e,_ModuleEntryPoint ...
            • -u:gcc --help -v|grep "undefined SYMBOL"-u SYMBOL --undefined SYMBOL: star with undefined reference to SYMBOL
            • Wl,-e:ld --help|grep "entry"-e ADDRESS, --entry ADDRESS Set start address
          • 检查所有.dll文件Entry point address == _ModuleEntryPointfind . -type f -name "*.dll" -exec sh -c "readelf -a {} |grep -E 'Entry point address|_ModuleEntryPoint'" \;
      • 它的入口点是整个 UEFI FD 映像的入口点(即从bl33_base_addr跳转到 this _ModuleEntryPoint):

        UEFI 固件文件的拓扑结构

        UEFI 固件文件(实际上是 UEFI 固件设备 - FD 文件)是封装到单个映像中的 UEFI 二进制文件的集合。此图像的格式由平台初始化规范第 3 卷定义。向量表位于此文件的底部。固件底部的“BL”分支指令(向量表中复位条目的位置)将跳转到 UEFI 固件映像的第一个“SEC”模块。

        --- https://github.com/lzeng14/tianocore/wiki/ArmPkg-Debugging

        • 要验证上述陈述:

          • .FD分解生成的(我们得到 offset= )的重置向量(即第一个词0x360):

            $ xxd -l 4 -e TEST.fd       <== dump 4 bytes in little endian
            00000000: 140000d8          <== BL   {PC}+(0xd8<<2); offset=0x360
            
          • 检查入口点.dll(我们得到了 offset= 0x240):

            $ aarch64-none-elf-objdump -t ArmPlatformPrePiUniCore.dll|grep _ModuleEntryPoint
            0000000000000240 g     F .text  0000000000000000 _ModuleEntryPoint
            $ readelf -h ArmPlatformPrePiUniCore.dll|grep Entry
            Entry point address:               0x240
            
          • 比较不同偏移量的两个文件的内容(我们得到相同的内容):

            $ xxd -s 0x360 -l 64 TEST.fd                      <== skip 0x360 bytes, dump 64 bytes
            00000360: 901e 0094 050a 0094 ea03 00aa a1cd 0a58  ...............X
            00000370: 0200 e0d2 2200 c0f2 0240 a0f2 0200 80f2  ...."....@......
            00000380: c303 a0d2 e3ff 9ff2 6304 00d1 6300 028b  ........c...c...
            00000390: 0400 a1d2 0400 80f2 2000 03eb 8400 0054  ........ ......T
            $ xxd -s 0x240 -l 64 ArmPlatformPrePiUniCore.dll  <== skip 0x240 bytes
            00000240: 901e 0094 050a 0094 ea03 00aa a1cd 0a58  ...............X
            00000250: 0200 e0d2 2200 c0f2 0240 a0f2 0200 80f2  ...."....@......
            00000260: c303 a0d2 e3ff 9ff2 6304 00d1 6300 028b  ........c...c...
            00000270: 0400 a1d2 0400 80f2 2000 03eb 8400 0054  ........ ......T
            
  3. 准备一个的pkg,并使其构建正常。主要目的是使用 EDK2 构建系统做一些练习,并使用空 pkg 作为新平台的起点。

    • 复制一份,RaspberryPi.dec全部改成。gRaspberrygMyPlatform

    • 复制RPi4.dscand RPi4.fdf,并注释掉DSCandFDF文件中的所有内容。

    • 替换 // 文件中的所有 GUID,DSC使用FDF在线guid 生成器DEC生成新的。

    • 请注意,PCD 在DEC文件中声明,DEC 文件由模块(INF文件)引用。由于空包不包含任何模块,因此在FDF. 因此,为了成功构建空包,我们需要注释掉FDF.

    • NOOPT构建命令MyPlatform如下:

      #!/bin/bash
      
      export WORKSPACE=/home/bruin/work/tianocore
      export PACKAGES_PATH=$WORKSPACE/edk2:$WORKSPACE/edk2-platforms:$WORKSPACE/edk2-non-osi
      pushd $WORKSPACE
      source edk2/edksetup.sh
      
      echo "Building BaseTools..."
      make -C edk2/BaseTools all
      
      echo "Building UEFI firmware for MyPlatform..."
      GCC5_AARCH64_PREFIX=aarch64-none-linux-gnu- build \
            -n 4 \
            -a AARCH64 \
            -p Platform/MyCorp/MyPlatform/MyPlatform.dsc \
            -t GCC5 \
            -b NOOPT \
            -v -d 9 -j MyPlatform-build.log \
            -y MyPlatform-build-report.txt \
            -Y EXECUTION_ORDER \
            -Y PCD \
            -Y LIBRARY \
            -Y DEPEX \
            -Y HASH \
            -Y BUILD_FLAGS \
            -Y FLASH \
            -Y FIXED_ADDRESS \
            all
      popd
      
  4. 添加第一个组件ArmPlatformPrePiUniCore。该组件用于为 DXE phae 准备 HOB。主要目的是让串口工作和内存配置正确。此步骤的另一个目的是熟悉添加组件/模块/lib 的步骤。以下是步骤的简要摘要:

    • 取消注释模块的INF( DSCsection [Components]) 和FDF( [FV.FVMAIN_COMPACT])。
    • 通过 Instance of library class [xxxLib] is not found更新[LibraryClasses]. _DSC
      • 这一步是重复几十次的过程。
      • 一些库类有多个库实例,确保选择适当的库实例(参考 RPi4 的构建报告)。
      • 如果遇到ModuleEntryPoint.iiii:31: Error: immediate out of range:enablegArmTokenSpaceGuid.PcdFdBaseAddressgArmTokenSpaceGuid.PcdFdSizein FDF
      • 如果遇到undefined reference to _gPcd_BinaryPatch_PcdSerialClockRate:设置PcdSerialClockRate[PcdsPatchableInModule]节中DSC。FIXME:为什么?参考
    • 检查构建日志中列出的 PCD:检查任何异常的 PCD 值,并提供正确的值。
    • 自定义特定于平台的驱动程序或库。
      • SerialPortLib: 通过.定位 lib-class 头文件 ( MdePkg/Include/Library/SerialPortLib.h) find edk2 -type f -name "*.dec" -exec grep -Hn SerialPortLib需要以下功能:
        • SerialPortInitialize()
        • SerialPortWrite()
        • SerialPortRead()
        • SerialPortPoll()
        • SerialPortSetControl(): RETURN_UNSUPPORTED
        • SerialPortGetControl(): RETURN_UNSUPPORTED
        • SerialPortSetAttributes(): RETURN_UNSUPPORTED
      • ArmPlatformLib: 接口头在Include/Library/ArmPlatformLib.h. 需要以下功能:
        • ArmPlatformGetCorePosition(): 在给定 MPIDR 值的情况下返回集群中的 cpu idx。此函数_ModuleEntryPoint用于设置辅助内核的堆栈。现在假设一个集群。
        • ArmPlatformIsPrimaryCore()
        • ArmPlatformGetPrimaryCoreMpId()
        • ArmPlatformGetBootMode()
        • ArmPlatformPeiBootAction()
        • ArmPlatformInitialize()
        • ArmPlatformGetVirtualMemoryMap()
        • ArmPlatformGetPlatformPpiList()
      • ETC...
  5. 取消注释 DSC/FDF 中的更多模块,逐个模块...对于特定于 RPi 平台的驱动程序/库,我们可以:

    • edk2在/中搜索edk2-platform类似的驱动程序或 lib 实例,或者
    • 复制RPi4实现并注释掉大部分内容,先使pkg构建成功,然后再修复bug。
  6. 调试:我目前主要的调试方法是添加“printf()”,即edk2宏DEBUG((DEBUG_INFO,))。需要设置gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel为适当的值才能查看更多调试信息。

于 2021-12-09T06:41:36.393 回答