有关于将 edk2 移植到新的 ARM64 平台的指南吗?

bru*_*uin 3 arm64 edk2

我是 EDK2 的新手。

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

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

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

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

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

bru*_*uin 5

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

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

    • “超越 Bios——使用统一可扩展固件接口进行开发”,第三版,作者: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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    • readme.md里面https://github.com/pftf/RPi4/releases/download/v1.17/RPi4_UEFI_Firmware_v1.32.zip。顺便说一句,我需要用 zip 文件中的内容替换原来的start4.elffixup4.dat,否则,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
      
      Run Code Online (Sandbox Code Playgroud)
    • RPI_EFI.fd通过使用一些 UEFI 实用程序来对内容进行某种程度的分析是值得的。我主要使用GUI版本UEFIToolsudo apt install uefitool uefitool-cli. 还可以使用其他工具。阅读 EDK2 构建规范以检查对概念的理解时,剖析RPI_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.fdArmPlatformPkg/PrePi/PeiUniCore.infMODULE_TYPE = SEC

      • 这个组件是什么:这是 RPi4 中的第一个(也是唯一的)SEC(安全)模块。名字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
            
            Run Code Online (Sandbox Code Playgroud)
          • 检查入口点.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
            
            Run Code Online (Sandbox Code Playgroud)
          • 比较不同偏移量的两个文件的内容(我们得到相同的内容):

            $ 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
            
            Run Code Online (Sandbox Code Playgroud)
  3. 准备一个空的pkg,并使其构建正常。主要目的是使用 EDK2 构建系统进行一些练习,并使用空 pkg 作为新平台的起点。

    • 复制一份RaspberryPi.dec,全部gRaspberry改为gMyPlatform

    • 复制 和RPi4.dscRPi4.fdf并注释掉DSCFDF文件中的所有内容。

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

    • 请注意,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
      
      Run Code Online (Sandbox Code Playgroud)
  4. 添加第一个组件ArmPlatformPrePiUniCore。该组件用于为 DXE phae 准备 HOB。主要目的是使串行端口正常工作并正确配置内存。此步骤的另一个目的是熟悉添加组件/模块/lib 的步骤。以下是步骤的简要总结:

    • INF取消对DSC([Components]部分) 和FDF( [FV.FVMAIN_COMPACT])中模块的注释。
    • 重建 pkg,并Instance of library class [xxxLib] is not found通过[LibraryClasses]更新DSC.
      • 这个步骤是一个重复几十次的过程。
      • 某些 lib-class 有多个 lib-instances,请确保选择适当的 lib-instance(参考 RPi4 的构建报告)。
      • 如果遇到ModuleEntryPoint.iiii:31: Error: immediate out of range:启用gArmTokenSpaceGuid.PcdFdBaseAddressgArmTokenSpaceGuid.PcdFdSizeFDF
      • 如果遇到undefined reference to _gPcd_BinaryPatch_PcdSerialClockRate:在 中的部分PcdSerialClockRate中设置。修复我:为什么?参考[PcdsPatchableInModule]DSC
    • 检查构建日志中列出的 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为适当的值才能查看更多调试信息。