飞腾D2000 UEFI编译与固件打包全流程实战解析

发布时间:2026/9/23 7:41:38
飞腾D2000 UEFI编译与固件打包全流程实战解析 做固件的人都知道飞腾D2000这套平台跟x86那套玩法完全是两码事。拿到板卡第一件事往往就是对着默认固件发愁版本太老、想调内存参数、想换启动Logo、想把自己的驱动编进UEFI里甚至只是想让串口日志更全一点。网上关于D2000 UEFI编译的资料零零散散环境怎么搭、命令怎么跑、编出来的fd文件跟最终烧录的BIOS镜像到底是什么关系坑多得离谱。我前后在D2000平台上折腾了好几轮从环境搭建到编出可刷镜像每一步都踩过坑这次把整套流程和排查思路完整写出来算是给自己做个记录也给后面接手的人省点时间。这篇文章适合手里有D2000开发板或整机、需要定制固件的开发者也适合刚接触ARM服务器平台、想搞明白UEFI编译和固件打包之间关系的人。文章里不会出现什么高深的理论全是实际跑过的命令、路径、报错和解决办法。为了说清楚编译产物和最终BIOS镜像的关系我会先从D2000固件的组成讲起这部分搞不明白后面打包环节一定会出问题。1. 先弄清D2000固件里到底装了什么才不会把“UEFI编译”和“BIOS打包”混为一谈1.1 D2000的“BIOS”不是一个文件而是一组镜像的合体很多人第一次接触D2000固件会下意识地认为UEFI源码编译完生成一个fd文件那就是BIOS了直接烧进去就行。这个理解错得非常离谱。D2000板卡上有一颗SPI NOR Flash容量通常从16MB到64MB不等里面装的是好几个不同功用的镜像UEFI主固件只是其中之一。我这边以常见的32MB SPI Flash布局为例拆开看典型分区分区起始偏移十六进制大小内容说明BootRom保留区0x000000001MB芯片BootROM引导参数、FIP等由厂商出厂固化UEFI主镜像0x00100000约20MBUEFI Award/EDK2编译产出的FD镜像含PEI、DXE、BDS等UEFI变量区0x01F0000064KB ~ 256KB存BootOrder、Boot####、SecureBoot密钥、平台配置等BMC固件部分服务器板卡0x01F400008MB左右带外管理控制器固件D2000部分开发板没有厂商配置区0x01C000001MBMAC地址、序列号、板级设置、Logo等注意这张表里的偏移是我实际工程里的一个参考值不同板卡差异很大尤其是变量区的位置和大小。这个偏移跟芯片内部BootROM的硬编码有关BootROM上电后会从固定的地址去加载下一级引导代码所以打包的时候偏移一旦出错烧进去板子直接砖没有任何协商余地。1.2 源码编译真正产出的是什么在EDK2工程里编译命令执行完之后你会得到一个或多个FD文件比如D2000.fd。FD是一个完整的固件卷Firmware Device里面包含了PEI阶段的固件文件系统FFS、DXE驱动、BDS启动管理器、UEFI Shell等一堆东西。EDK2的构建系统会在最后阶段用GenFv、GenFds这类工具把分散的模块封装成FV再拼成FD。但注意这个FD通常不包括UEFI变量区的初始内容也不包括BMC固件更不包含厂商写在Flash其他位置的配置数据。它只是“BIOS”里体积最大、逻辑最核心的那一部分。所以一次完整的固件发布需要把UEFI主镜像、变量区初始化模板、可能存在的BMC固件、Logo、DTB等按硬件设计好的Flash布局拼到同一个文件里这一步叫打包产出的最终文件才是能烧录的完整BIOS镜像。很多新手卡在“编译成功了但烧进去不启动”这个状态十有八九是把fd文件直接当完整BIOS烧了把Flash里其他区域的数据冲掉了。所以说动手编译之前先弄清楚整条链路能省下大把救砖的时间。1.3 从源码到烧录完整流程分四步以我在D2000平台上的实践来说一次完整的固件定制要经历下面四个阶段源码准备拿到对应板卡的UEFI BSP源码包确认平台目录和默认配置。交叉编译在x86主机上用aarch64工具链编译出FD固件镜像。镜像打包用打包脚本把FD、变量区模板、DTB、Logo等按Flash布局拼成最终烧录镜像。刷写验证通过UEFI Shell、BMC或SPI烧录器把镜像写入Flash然后上电验证。这四个阶段各有各的坑下面按顺序逐个展开。2. 编译环境搭建工具链和依赖版本比想象中更挑2.1 宿主系统的选择D2000的UEFI源码是基于TianoCore EDK2的通常在x86_64的Linux主机上做交叉编译就行。操作系统方面我试过Ubuntu 18.04和20.04都是可以跑通的。20.04默认的GCC版本是9.x但很多老一批的D2000 BSP包默认用的是GCC5工具链配置编到一半会因为GCC版本太新出现各种奇怪的警告和报错。比较稳妥的做法是用Ubuntu 18.04配合GCC 7.5或者直接用20.04但手动把工具链切到aarch64-linux-gnu-gcc-7。如果你拿到的是新版本的BSP包官方一般会写清楚推荐的GCC版本优先遵循官方建议。我这边用的比较多的是Ubuntu 20.04 GCC 7.5交叉工具链的组合兼容性最好踩坑最少。2.2 安装基础依赖包先刷新索引然后把编译过程中会用到的工具一次性装齐sudo apt update sudo apt install -y build-essential git uuid-dev nasm python3 python3-distutils \ acpica-tools iasl bison flex ccache这些包的作用不一样nasm和iasl是给ACPI表和x86汇编用的在ARM平台上一般不直接参与D2000的编译但EDK2的全局构建系统有可能在某些环节调用它们缺了会直接中断。uuid-dev是用来生成GUID的工具库EDK2里所有模块的GUID处理都依赖它。2.3 交叉编译工具链安装与验证D2000是ARMv8架构所以编译目标是aarch64sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu如果官方BSP要求GCC 7可以这样装sudo apt install -y gcc-7-aarch64-linux-gnu g-7-aarch64-linux-gnu装完之后确认工具链有效aarch64-linux-gnu-gcc --version然后写一个最简单的C程序交叉编译一下确认环境没问题// hello.c #include stdio.h int main(void) { printf(hello d2000\n); return 0; }aarch64-linux-gnu-gcc -o hello hello.c file hello如果file命令输出里出现了“ELF 64-bit LSB executable, ARM aarch64”说明工具链工作正常。这一步千万别跳过很多环境问题都是在这里提前暴露的。如果file输出是x86-64说明gcc命令解析到了宿主系统的原生编译器上需要检查PATH顺序或者用完整路径调用交叉编译器。2.4 为什么我强烈建议你用虚拟机或容器UEFI编译过程中会生成大量中间文件Build目录轻松超过10GB而且EDK2的构建系统对环境的依赖非常敏感同一个工程在不同机器上编出来的行为可能不一样。我之前就遇到过因为宿主系统里装了一个特殊版本的Python库导致build脚本执行到一半崩掉的情况排查了很久才发现是系统环境的问题重新搭一台干净环境很快就编过了。所以动手之前先准备一个快照或者独立的Docker容器环境搞坏了直接回滚不要在自己的主力开发机上硬刚。这个习惯在固件开发里特别值钱因为你不知道哪次实验会把系统库改乱。3. D2000 UEFI源码编译从edksetup到生成FD文件3.1 拿到源码后第一件事检查平台目录结构我用的D2000 UEFI BSP包是厂商提供的一个EDK2工程解压之后目录结构大致是edk2/ ├── edksetup.sh ├── BaseTools/ ├── MdeModulePkg/ ├── MdePkg/ ├── ArmPkg/ ├── ArmPlatformPkg/ ├── Platform/ │ └── Phytium/ │ ├── D2000/ │ │ ├── D2000.dsc │ │ ├── D2000.fdf │ │ └── Library/ │ └── Common/ ├── Silicon/ │ └── Phytium/ │ └── FT2000/ └── ...关键的三个文件分别是.dsc文件平台描述文件定义要编译哪些模块、使用什么PCD平台配置数据库策略。.fdf文件固件描述文件定义固件的Flash布局、FD里包含哪些FV、各模块如何放置。build.sh或README厂商给的编译入口脚本。打开D2000.dsc先看两个东西PLATFORM_NAME和TARGET_ARCHITECTURE。正常情况TARGET_ARCHITECTURE应该包含AARCH64。如果这里写的还是ARM说明这个BSP是给32位ARM平台用的跟D2000完全不搭得换包。3.2 source edksetup.sh之后发生了什么进入UEFI源码根目录第一步永远是source edksetup.sh这个脚本会做两件事设置EDK2的环境变量把编译工具路径加入PATH编译生成BaseTools底下的工具比如GenFv、GenFds、VfrCompile这些。如果之前没有编译过BaseTools脚本运行时会自动编译。老版本EDK2这里有个坑如果系统Python版本是3.x但缺少distutils模块脚本会直接报错。Ubuntu 20.04下可以提前执行sudo apt install -y python3-distutilssource edksetup.sh没有报错之后再执行build命令才会进入正常流程。3.3 修改编译配置target.txt不能忽略EDK2的默认配置文件在Conf/target.txt这个文件控制着编译目标。打开后重点改这几项ACTIVE_PLATFORM Platform/Phytium/D2000/D2000.dsc TARGET_ARCH AARCH64 TOOL_CHAIN_CONF Conf/tools_def.txt TOOL_CHAIN_TAG GCC5 TARGET RELEASETOOL_CHAIN_TAG是EDK2对交叉编译工具链的封装标签。GCC5在EDK2的tools_def.txt里定义好了aarch64交叉编译器的调用规则它会根据CROSS_COMPILE环境变量去拼编译器路径。所以执行build之前通常还需要export CROSS_COMPILEaarch64-linux-gnu-如果你装了多个版本的交叉编译器可以用绝对路径指定比如export GCC5_AARCH64_PREFIX/usr/bin/aarch64-linux-gnu-注意EDK2不同版本的变量名不太一样有些版本用的是GCC5_AARCH64_PREFIX有些则直接认CROSS_COMPILE。我这里习惯两个都设置上防止遗漏。TARGET建议第一次编译用RELEASE原因后面单独讲。3.4 执行编译第一次编不过太正常了配置改好后执行build -n 16-n 16是多线程编译参数按你机器CPU核数来。EDK2的构建系统对多线程支持还可以但我遇到过一些老的BSP包用超过8线程编译会随机出现链接错误这时候改成-n 4就能过。BSP包如果自带build.sh脚本优先用脚本脚本里通常会包含厂商调过的参数比手动敲build命令稳得多。第一次编译耗时取决于机器性能通常在10到30分钟之间。等待的时候有一点可以留意EDK2的编译输出日志信息量非常大但绝大多数都不需要关注只要没有红色的error:字样就可以让它继续跑。编完之后产物在Build/目录下Build/PhytiumD2000/RELEASE_GCC5/ ├── FV/ │ ├── D2000.fd │ ├── D2000.fd.map │ └── FVMAIN.fv └── AARCH64/ ├── MdeModulePkg/ ├── ShellPkg/ └── ...D2000.fd就是UEFI主镜像但离可烧录还有一段距离这个我下一节详细讲。3.5 RELEASE和DEBUG怎么选直接影响你的调试验体验TARGET有两种常见值RELEASE和DEBUG。DEBUG编译优化等级低包含大量调试日志代码生成的fd文件更大启动速度慢但串口会输出非常详细的模块加载信息。RELEASE优化等级高裁剪掉调试日志启动快体积小但出问题的时候只能靠POST码和少量串口信息判断。我个人的建议是第一次接触一块新板卡先用RELEASE编一版烧进去确认系统能正常起来。如果起不来再切DEBUG版本用串口日志定位。因为DEBUG版本的日志量太大了对新手来说反而容易被淹没在无用信息里。等你能熟练看懂日志中自己关心的那几行之后再切DEBUG也不迟。4. 从FD到完整BIOS镜像打包过程才是大量坑的源头4.1 准备打包素材不只是fd文件打包这一步中文资料里讲得最少但恰恰是实际问题最多的地方。我最初也觉得把D2000.fd直接丢给烧录工具不就完了吗后来发现太天真了。一个可烧录的完整BIOS镜像在打包阶段至少需要准备这些素材UEFI主镜像D2000.fd这个刚刚编译出来的。变量区初始化模板VarStore.fd或类似的二进制文件。它覆盖了Flash的NVRAM区域里面会写入默认的BootOrder、Timeout等。这个文件通常由BSP包附带不用自己编译。板级设备树*.dtb。D2000 UEFI引导U-Boot或者Linux的时候会用到设备树但UEFI阶段的版本也可能需要。Logo图片可选的启动LogoBMP格式最稳妥。厂商配置块包含MAC地址、序列号、板卡型号等这部分通常不用动。把这些素材按Flash布局拼起来才是完整镜像。4.2 打包脚本的核心逻辑偏移对齐和填充厂商BSP包一般会提供一个GenFirmware.sh或pack_linux.sh之类的打包脚本脚本的核心内容本质上就是多次调用dd命令往一个大镜像文件里按偏移写入各分区的内容。如果你的BSP没有脚本那需要手动做这件事。写一个参考脚本注意这里的偏移是示例值必须按你实际板卡的原理图或BSP默认配置来#!/bin/bash # D2000 打包参考脚本32MB SPI Flash set -e BLOCK_SIZE4096 FLASH_SIZE0x2000000 # 32MB UEFI_FDBuild/PhytiumD2000/RELEASE_GCC5/FV/D2000.fd VAR_STOREVarStore.fd DTB_FILEboard.dtb OUT_IMAGEbios_full.bin # 1. 生成全0填充的底图 dd if/dev/zero of$OUT_IMAGE bs$BLOCK_SIZE \ count$((FLASH_SIZE / BLOCK_SIZE)) # 2. 写入UEFI主镜像起始偏移0x00100000 dd if$UEFI_FD of$OUT_IMAGE bs$BLOCK_SIZE \ seek$((0x00100000 / BLOCK_SIZE)) convnotrunc # 3. 写入设备树起始偏移0x01C00000 dd if$DTB_FILE of$OUT_IMAGE bs$BLOCK_SIZE \ seek$((0x01C00000 / BLOCK_SIZE)) convnotrunc # 4. 写入UEFI变量区模板起始偏移0x01F00000 dd if$VAR_STORE of$OUT_IMAGE bs$BLOCK_SIZE \ seek$((0x01F00000 / BLOCK_SIZE)) convnotrunc # 5. 打印输出文件大小确认 ls -lh $OUT_IMAGEseek的单位是块所以要除以块大小。如果你改过BLOCK_SIZE所有计算都要保持一致。convnotrunc很关键表示只覆盖对应偏移位置的数据不清空文件其他部分。4.3 偏移错误导致的典型故障这块我必须多说几句因为它是打包阶段最隐蔽的坑。如果你把UEFI FD写到了错误的偏移或者变量区覆盖了UEFI主镜像的一部分表现出来的故障非常迷惑板子完全无输出电流表显示上电了但串口一个字符都不出。能进UEFI但保存设置重启后所有配置都丢失每次都是默认值。启动到一半卡死偶尔又能过去完全随机。网络启动选项异常PXE菜单乱码。这些情况不用怀疑大概率都是Flash布局错位。排查方法就是把你的完整镜像用hexdump之类工具导出来对照原理图上标注的偏移确认每一个分区头部的magic number是否出现在正确位置。做固件这行的都知道一个原则烧录之前double check偏移是比任何技术都重要的基本功。4.4 确认产物完整性烧录前做一个最终检查打包脚本执行完之后不要急着烧先做三个检查第一检查镜像文件大小。32MB的Flash最终镜像应该精确等于0x2000000字节多一个字节少一个字节都说明脚本有问题。stat -c %s bios_full.bin第二检查关键偏移处是否有预料中的数据特征。比如UEFI FD的头部通常有_FVH标记UEFI变量区头部有NVAR之类的结构特征不同EDK2版本可能不同。用xxd查看即可xxd -l 256 -s 0x00100000 bios_full.bin xxd -l 256 -s 0x01F00000 bios_full.bin第三对比一下打包前后FD文件在镜像里对应区间的内容是否完全一致。使用cmp按偏移比对# 从完整镜像中取出UEFI区域与原始FD比较 dd ifbios_full.bin ofextract_uefi.bin bs4096 \ skip$((0x00100000 / 4096)) count$((0x01400000 / 4096)) cmp D2000.fd extract_uefi.bin如果cmp没有输出说明UEFI主镜像在打包过程中没有被破坏。这三个步骤做完完整镜像才算可以进入刷写阶段。5. 刷写进板卡三种常用方式与验证手段5.1 刷写前第一件事备份原始固件不管用哪种方式刷写动手之前必须先把当前板卡里的固件完整备份出来。万一刷挂了至少还有原始固件可以用来恢复。备份方法取决于你的板卡支持哪种接口在UEFI Shell里用fpt命令、在BMC里用固件升级页面的导出功能、或者直接用SPI烧录器读取整颗Flash都行。备份的完整镜像一定要留好并且标注好来源板卡和备份日期。我见过有人备份了但没有做校验等到要用的时候才发现备份文件损坏那种绝望感不想经历第二次。5.2 在UEFI Shell中刷写最常用的方式D2000板卡上电后通常会进入UEFI引导界面。如果你的板卡支持UEFI Shell可以把完整镜像放到FAT32格式的U盘里插到板卡上进入Shell后执行刷写命令。不同板卡的刷写命令不一样有些板卡自带一个Flash.nsh脚本放到U盘里在Shell里执行# 在UEFI Shell中执行 # 假设文件系统映射为 FS0: FS0: flash.nsh bios_full.bin如果板卡没有现成脚本有些UEFI固件会内置fpt命令也就是Intel的Flash Programming ToolD2000平台如果有对应的efi版本可以这样fpt -f bios_full.bin -D注意在UEFI Shell下刷写时刷写过程中绝对不能断电否则Flash里的内容处于半写状态很容易变砖。刷写完成后不要直接强制断电要等固件自己完成收尾工作按提示重启板卡。5.3 用BMC带外刷写服务器板卡常见的路子如果你的D2000板卡带BMC那么刷写路径通常更安全。BMC一般会提供一个Web管理页面里面有一个固件升级/BIOS升级的功能入口上传完整镜像BMC会负责把镜像写入Flash。这种方式的优势在于即使系统OS挂了只要BMC还在就还能救回来。尤其在企业级设备上BMC刷写是首选方案因为带外通道完全独立于CPU刷写过程不依赖UEFI状态。5.4 SPI烧录器最后一道救命稻草如果UEFI已经刷挂了Shell进不去BMC也没有那只能用SPI烧录器直接在线烧录Flash芯片。市面上常见的USB SPI烧录器比如CH341A配上一根测试夹或者插座就能直接读写Flash。操作步骤简述如下断开板卡电源拆出Flash芯片或找到板载烧录座。用烧录器软件读取芯片型号确认容量。备份当前内容如果还能读出来。加载完整镜像执行擦除和写入。写入完成后校验。断电装回芯片上电。这个方式虽然土但却是救砖成功率最高的方法。需要注意D2000板卡的Flash芯片封装可能比较小测试夹夹不好容易接触不良。另外烧录器软件里“芯片类型”必须选对选错了轻则读取内容不对重则可能把芯片锁死。5.5 刷写后如何判断固件真的起来了刷写完成后上电观察这几个信号基本能判断是否成功串口日志D2000 UEFI启动过程中串口UART0波特率通常115200会输出一段日志。只要能看到UEFI v2.7或Press DEL to enter setup之类的字样说明UEFI主镜像已经被正确加载。显示输出接上显示器或显卡看是否能出现UEFI Logo或HII设置界面。POST码如果板卡上有数码管或POST卡接口可以看到两位十六进制的POST码走到0x00或特定数值说明启动流程走完。进入系统UEFI引导U盘或NVMe上的系统能进入GRUB菜单或Shell说明整个引导链路都通了。如果上面任何一项没反应不要慌按后面一节给的排查顺序逐个确认。6. 从我自己踩过的坑里提炼的检查清单第三个大坑编译环境问题。有一个典型的项目报错出现在我第一次编老版本BSP时。报错信息是/usr/bin/ld: cannot find crt1.o: No such file or directory collect2: error: ld returned 1 exit status这个报错看着像链接器出问题实际上是因为系统缺少32位库或者是交叉编译器的sysroot路径不对。我当时的解决办法是确认aarch64-linux-gnu-gcc能被正确调用并且安装了libc6-dev-arm64-cross。如果你是Ubuntu系统可以这样装sudo apt install -y libc6-dev-arm64-cross第二个常见的坑是build命令执行时报Unknown tool chain tag GCC5这说明tools_def.txt里没有匹配的配置通常是因为Conf/target.txt里的TOOL_CHAIN_CONF路径写错或者BSP不支持当前EDK2版本。这时候优先查看BSP的README确认它依赖的是哪个EDK2基线的版本。不要自己乱改tools_def很容易把整个编译环境搞乱。6.2 启动阶段遇到的黑屏卡死怎么定位是哪一层的问题刷完固件上电黑屏先按以下顺序排查第一步看串口有没有输出。如果没有输出考虑BootROM没有加载到UEFI多半是镜像偏移错了或者Flash芯片本身没有正确识别。第二步串口有输出但停在某一行不动通常是某个DXE驱动加载失败或者变量区配置导致驱动初始化异常。这时候用DEBUG版本固件重新编译一遍日志会详细很多。第三步能进UEFI但进不了系统那是引导项的问题跟固件本身关系不大。第四步如果UEFI和系统都能进但过一段时间随机死机考虑内存参数、散热或供电问题固件层面能调的只有内存相关PCD配置。黑屏类问题我见过最多的还是打包偏移错误。每次遇到黑屏先做两件事确认串口有没有电确认是否用备份固件能正常启动。如果备份固件正常而新固件黑屏那就是新固件本身的问题按上面四步往下查。6.3 引导系统时提示“磁盘布局不受UEFI支持”怎么办这个报错实际上不是D2000特有的Windows安装器在检测磁盘分区表时会报类似的提示意思是当前磁盘是MBR分区或者没有EFI系统分区与UEFI固件的引导方式不匹配。D2000的UEFI固件是纯ARM UEFI实现不支持传统BIOS/CSM兼容模式所以引导介质必须是GPT分区表并且存在一个FAT格式的ESP分区。如果你是要在D2000上装Linux制作启动U盘时注意用GPT分区表不要用MBR。必须有一个EFI System Partition文件系统格式为FAT16或FAT32。引导文件必须放在ESP分区下GRUB镜像或者UEFI Shell文件按标准路径放置。如果没有ESP分区固件会认为这个设备不可引导表现就是启动菜单里看不到U盘或者系统盘。这不是固件Bug而是磁盘布局不符合UEFI规范。6.4 刷写回退方案永远保留一个“安全版本”固件调试过程本质上是反复试错回退能力一定要提前准备好。我的习惯是每拿到一块新板卡立即备份一份出厂固件镜像存到多个位置。每次要烧新固件之前把当前正常可用的版本再备份一次。所有自定义镜像的文件名都带上日期、版本号和修改内容比如bios_full_d2000_v0.3_20250115_增加立创串口补丁.bin这样即使过了几个月翻目录也能知道这个镜像当时改了什么东西。不要嫌麻烦你永远不知道几个月后那个“当时随手改了一点”的镜像会是什么状态。6.5 我特别想提醒的一个心态问题固件开发跟普通软件开发最大的区别在于犯错代价高排查链路长。普通软件挂了重启进程就行固件挂了可能得动用SPI烧录器。所以整个流程中最重要的习惯就是动手之前想清楚动手之后慢一点。每执行一个可能影响Flash内容的操作前都问自己一句备份了吗偏移对吗真有必要刷吗这个习惯帮我至少挽救了四五块板卡的命也让我少走了很多冤枉路。最后分享一个我自己总结的小技巧在UEFI工程里改完PCD配置重新编译之后如果改动量很小我通常不会立刻打包整个镜像而是先在DEBUG版本下编一次用串口日志确认模块加载顺序没有异常再返回RELEASE版本打包。这样虽然多花了几分钟编译时间但能省下反复刷机救砖的几小时。D2000平台固件开发这条路耐心比聪明值钱多了。