在RK3588上体验UEFI
Unified Extensible Firmware Interface
UEFI是一种固件技术,源于英特尔,最初的开发代号叫Tiano。项目的开始时间大约在本世纪初,当时的固件代码都很陈旧,主要用汇编语言和C语言编写,繁杂拖沓,难以维护扩展。与此同时,英特尔当时投巨资的安腾项目使用了众多开创性的创新设计,需要在固件层做很多修改,要在陈旧的BIOS上代码做这些修改难度很大。为此,Tiano项目应需而生,引入当时流行的COM技术精华,普遍使用接口技术,使用GUID来表示接口,使用C++代替C语言。
# 大哉,固件
2004年,Tinao项目初步完成,以EFI为正式名字对外发布。在Intel网站,还可以找到当年的一则新闻稿:
Intel Invites Industry In Design Of Technology To Succeed BIOS,Industry Collaboration Key in Addressing Oldest Software Technology in PC Platforms。
标题大意是,Intel引领产业设计取代BIOS的新技术,针对PC平台上的最古老软件技术开展产业合作。
这个标题把BIOS称为PC平台的最古老软件技术是很合适的。的确,BIOS源于已经成为历史的CP/M操作系统和DRI公司,它的发明者是DRI的创始人Gary Arlen Kildall。我在《软件简史》第六篇《亢龙有悔》中详细描述了Gary的传奇人生和他对PC产业和系统软件所作的巨大贡献。


《软件简史》中关于PC软件
和BIOS技术的追根溯源
Kildall是PC技术的开路者,他在Intel开发8086时,就是Intel的软件顾问。他成名时,比尔·盖茨还在找方向,比尔·盖茨曾多次到Kildall的家里登门求教。从下面这张照片可以看出一些微妙的信息。

比尔·盖茨(中)与Kildall(右)的合影
固件技术的核心价值是隔离硬件差异,让操作系统可以以统一的接口来管理五花八门、不断变化的硬件。这也正是Gary Arlen Kildall当初发明BIOS技术的初衷。这个技术的价值也被比尔·盖茨所心领神会,凭借这个技术,微软的Windows操作系统在PC时代畅通无阻,只要是有BIOS的盒子,就可以安装Windows。


# 林纳斯大神怒批ARM
2005年,Intel连同几家伙伴一起成立UEFI论坛(UEFI Forum),从此,Tiano技术的成果开始以UEFI的形式发展。UEFI论坛的最初成员有:
AMD, American Megatrends, Inc., Dell Inc., HP, Intel Corporation, International Business Machines Corporation, Insyde Software Corp., Microsoft Corp., and Phoenix Technologies Ltd. to Collaborate
注意,上面的成员列表中没有ARM,原因当然有很多。从ARM玩家的角度看,当时ARM的主要场景还是嵌入式和垂直领域。对于这些场景,通常是像苹果那样,一家公司做整个软件栈,软件兼容的压力不是很大,所以对固件层的需要不是那么迫切。


但随着ARM系统的硬件越来越强大,越来越多的ARM系统开始使用通用的LINUX内核,这时兼容问题便日益严重了。太多厂商不停往Linux内核的代码树上合并看似相同却又不同的适配代码。这种现象持续了几年,导致Linux内核的arch/arm目录不断膨胀,一大堆以mach开头的目录,一眼看不到边,直到今天,这个目录仍然如此。
终于有一天,当一位名叫Tony Lindgren的人想把自己的代码合并到内核代码树时,遇上了林纳斯大神心情不好,大发雷霆。
FromLinus Torvalds <>
DateThu, 17 Mar 2011 19:50:36 -0700
SubjectRe: [GIT PULL] omap changes for v2.6.39 merge window share 292
On Thu, Mar 17, 2011 at 11:30 AM, Tony Lindgrenwrote:
>
> Please pull omap changes for this merge window from:
Gaah. Guys, this whole ARM thing is a f*cking pain in the ass. You need to stop stepping on each others toes. There is no way that your changes to those crazy clock-data files should constantly result in those annoying conflicts, just because different people in different ARM trees do some masturbatory renaming of some random device. Seriously. That usb_musb_init() thing in arch/arm/mach-omap2/usb-musb.c also seems to be totally insane. I wonder what kind of insanity I'm missing just because I don't happen to see the merge conflicts, just because people were lucky enough to happen to not touch the same file within a few lines.
林纳斯是诗人后裔,说话言简意赅。“整个ARM的东西就是x。你们必须停止相互挖坑,踩别人的脚趾头。”

# 十年能否磨出一剑
林纳斯大神的怒批很有效果。大约在2013年,ARM加入了UEFI论坛。

2014年8月12日,UEFI论坛发布ACPI 5.1标准中,在这个标准中,加入了对ARM的支持,包括ARM的中断控制器GIC,以及ARMv8的众多特征。当年新闻稿的标题为:
UEFI FORUM’S NEW ACPI 5.1 SPECIFICATION ADAPTS CONFIGURATION AND POWER INTERFACE TO 64-BIT FOCUSED FEATURES OF THE ARMV8-A ARCHITECTURES
UEFI的新ACPI 5.1规约采纳了支持ARMv8 64位特征的配置和电源接口
有了标准支持,是必要条件,但却不是充分条件。
为了在ARM生态中推动UEFI技术,ARM一直在努力,比如ARM名为SystemReady的认证技术就把UEFI作为重点内容。

ARM的Dong Wei院士领衔推动这个方向。

从2013年算起,已经10年了。ARM平台上的UEFI到底是什么状况呢?
昨天,格蠹的小伙伴将来自硬件伙伴的UEFI代码编译通过,刷到了幽兰代码本上,第一次没有刷成功,第二次刷成功了,熟悉的UEFI风格设置界面在幽兰上出现了。

听到小伙伴说刷好后一开机能进UEFI设置界面,我首先很高兴,因为没花什么时间就把UEFI的代码在幽兰上跑起来了,这反映了ARM的十年努力是有成果的。ARM平台的固件技术积贫积弱,能在固件阶段显示出图形界面,这是值得纪念的一个里程碑。放眼整个ARM生态,ARM系统的总量以百亿记也不过分,但是能有界面的固件凤毛麟角,难得一见,不足百万分之一。
但听说一开机就进了UEFI界面,我也知道情况不妙。凭借我多年来对UEFI的了解,自动进图形界面意味着正常启动失败了。从整个系统的角度讲,固件主要在启动阶段运行,它的主要使命就是为操作系统铺平道路。它把准备工作做好,就应该把执行权交给操作系统。从这个角度来看,它很像是帮助越王勾践灭吴后就泛舟五湖、功成隐退的陶朱公(范蠡)。
可是当下,UEFI没能把控制权成功交给OS。这既是意外,也在情理之中。

# 交接为何失败?
幽兰本来是使用u-boot作为固件的,它负责加载LINUX内核,并把控制权平稳过渡。u-boot和Linux都存储在幽兰的EMMC闪存上面。闪存的分区情况如下:

以下是小伙伴抓的串口输出:
DDR V1.09 a930779e06 typ 22/11/21-17:50:56
LPDDR4X, 2112MHz
channel[0] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
channel[1] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
channel[2] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
channel[3] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
Manufacturer ID:0xff
CH0 RX Vref:27.7%, TX Vref:20.8%,0.0%
CH1 RX Vref:26.7%, TX Vref:20.8%,0.0%
CH2 RX Vref:26.7%, TX Vref:23.8%,0.0%
CH3 RX Vref:28.7%, TX Vref:21.8%,0.0%
change to F1: 528MHz
change to F2: 1068MHz
change to F3: 1560MHz
change to F0: 2112MHz
out
U-Boot SPL board init
U-Boot SPL 2017.09-gc060f28d70-220414 # (Apr 18 2022 - 18:13:34)
Failed to set cpub01
Failed to set cpub23
Trying to boot from MMC2
MMC: no card present
mmc_init: -123, time 0
spl: mmc init failed with error: -123
Trying to boot from MMC1
Trying fit image at 0x4000 sector
## Verified-boot: 0
## Checking atf-1 0x00040000 ... sha256(909ea14106...) + OK
## Checking uboot 0x00200000 ... sha256(36148e13ec...) + OK
## Checking fdt 0x002f0000 ... sha256(c07f4a4d71...) + OK
## Checking atf-2 0x000f0000 ... sha256(6a970ae6b4...) + OK
## Checking atf-3 0xff100000 ... sha256(3ea8cf0d7e...) + OK
## Checking optee 0x08400000 ... sha256(fde0860845...) + OK
Jumping to U-Boot(0x00200000) via ARM Trusted Firmware(0x00040000)
Total: 542.534 ms
INFO: Preloader serial: 2
NOTICE: BL31: v2.3():v2.3-468-ge529a2760:derrick.huang
NOTICE: BL31: Built : 09:59:49, Nov 21 2022
INFO: spec: 0x1
INFO: ext 32k is valid
INFO: ddr: stride-en 4CH
INFO: GICv3 without legacy support detected.
INFO: ARM GICv3 driver initialized in EL3
INFO: valid_cpu_msk=0xff bcore0_rst = 0x0, bcore1_rst = 0x0
INFO: system boots from cpu-hwid-0
INFO: idle_st=0x21fff, pd_st=0x11fff9, repair_st=0xfff70001
INFO: dfs DDR fsp_params[0].freq_mhz= 2112MHz
INFO: dfs DDR fsp_params[1].freq_mhz= 528MHz
INFO: dfs DDR fsp_params[2].freq_mhz= 1068MHz
INFO: dfs DDR fsp_params[3].freq_mh
上面的串口打印信息来自两个软件,一个是ATF,即ARM的可信固件,运行在ARMv8的最高特权级别,里面执行高安全级别的敏感逻辑,包括缓解CPU的缺陷和设计不足。另一个是u-boot,u-boot在ARM生态上使用多年,根深蒂固。实际上,我们拿到的固件包叫uboot_uefi,它其实是uefi和uboot的混杂版本,使用时仍需要使用u-boot,只不过是u-boot很快就把执行权交给uefi。
对于上面的打印信息,还没有看到uefi。我实际参与调试整个问题后,终于看到了uefi的输出信息:
add-symbol-file /extd/guolin/uefi_sdk/uefi_emmc/Build/RK3588/DEBUG_GCC5/AARCH64/EmbeddedPkg/Application/AndroidBoot/AndroidBootApp/DEBUG/AndroidBootApp.dll 0x39828000
Loading driver at 0x00039827000 EntryPoint=0x▒Failed to get AndroidBootImg Size: Invalid Parameter
Error: Image at 00039827000 start failed: Invalid Parameter
remove-symbol-file /extd/guolin/uefi_sdk/uefi_emmc/Build/RK3588/DEBUG_GCC5/AARCH64/EmbeddedPkg/Application/AndroidBoot/AndroidBootApp/DEBUG/AndroidBootApp.dll 0x39828000
Image Return Status = Invalid Parameter
[Bds]Booting Android Fastboot
add-symbol-file /extd/guolin/uefi_sdk/uefi_emmc/Build/RK3588/DEBUG_GCC5/AARCH64/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp/DEBUG/AndroidFastbootApp.dll 0x39829000
Loading driver at 0x00039828000 EntryPoint=0x0▒Fastboot: Couldn't open Fastboot Transport Protocol: Not Found
Error: Image at 00039828000 start failed: Not Found
remove-symbol-file /extd/guolin/uefi_sdk/uefi_emmc/Build/RK3588/DEBUG_GCC5/AARCH64/EmbeddedPkg/Application/AndroidFastboot/AndroidFastbootApp/DEBUG/AndroidFastbootApp.dll 0x39829000
Image Return Status = Not Found
Process PlatformRecovery0000 (Default PlatformRecovery) ...
[Bds] Unable to boot!
[Bds]Booting UiApp
add-symbol-file /extd/guolin/uefi_sdk/uefi_emmc/Build/RK3588/DEBUG_GCC5/AARCH64/MdeModulePkg/Application/UiApp/UiApp/DEBUG/UiApp.dll 0x30509000
Loading driver at 0x00030508000 EntryPoint=0x0003050FE30 UiApp▒[HiiDatabase]: Memory allocation is required after ReadyToBoot, which may change memory map and cause S4 resume issue.
上面的多种信息都代表了UEFI的风格,或者说代表了来自Tiano的基因(原始设计)。
首先,其中的add-symbol-file语句是供调试使用的,打印整个信息代表了UEFI具有与调试器配套的调试支持。
其次,上面的文件路径指示的文件后缀都是dll,因为Tiano的最初开发环境是Windows,最初的编译工具也是微软的Visual C++。所以UEFI的模块是windows平台上流行的DLL,对应的调试符号是微软使用的PDB格式。
上面的信息中包含了三个DLL的名字,分别是:
AndroidBootApp.dll
AndroidFastbootApp.dll
UiApp.dll
从App的名字来看,这几个模块都属于扩展性质的App模块,不属于Tiano的核心框架(比如DXECORE)。
上面三个DLL其实代表了UEFI在完成基本工作后,准备移交执行权时所做的三种努力:
先尝试以Android Boot方式引导内核。
再尝试以Android FastBoot方式引导内核。
都不成功,只好运行UiApp,呈现图形界面。

# 安卓魔术码,你在何处?
根据错误信息查找uefi的源代码,可以看到是下面的函数报错:

这个函数是获取内核大小。所谓的Android启动方式,就是在闪存的固定位置放一段描述信息,这段信息需要符合如下格式:
#pragma pack(1)
/* https://android.googlesource.com/platform/system/core/+/master/mkbootimg/bootimg.h */
typedef struct {
UINT8 BootMagic[ANDROID_BOOT_MAGIC_LENGTH];
UINT32 KernelSize;
UINT32 KernelAddress;
UINT32 RamdiskSize;
UINT32 RamdiskAddress;
UINT32 SecondStageBootloaderSize;
UINT32 SecondStageBootloaderAddress;
UINT32 KernelTaggsAddress;
UINT32 PageSize;
UINT32 Reserved[2];
CHAR8 ProductName[16];
CHAR8 KernelArgs[ANDROID_BOOTIMG_KERNEL_ARGS_SIZE];
UINT32 Id[32];
} ANDROID_BOOTIMG_HEADER;
#pragma pack ()
上面信息的开头是个魔术码:ANDROID!
出错的代码就是没有匹配到这个魔术码,所以因为参数错误。
其实格蠹的GDK就是使用的这种启动方式来引导LINUX内核的。使用od命令查看GDK8的boot分区,就可以看到ANDROID!魔术码。

上图中的4e41就分别是N和A的ASCII代码。

# GRUB打岔
在调试这个问题的过程中,团队中出现了两种不同的意见,一种意见是制作带有GRUB的启动盘,然后让UEFI把控制权移交给GRUB,GRUB再加载LINUX内核。另一种意见是不用GRUB,直接加载LINUX内核。
我是支持第二种的意见的,已经有了UEFI,而且里面已经包含了一个还没有完全退休的u-boot,没有必要再把GRUB拉进来。太多人同台唱戏,会让系统很复杂,而且影响启动时间。
但是持第一种观点的人很快取得了进展,使用UEFI中做好的多启动目标支持,在幽兰上成功跑起了GRUB。

但是我仍坚持第二种方式,第一种方式即使成功了,第二种方式也有价值,它的启动速度会更快。于是我继续坚持调试第二种方法。
失败的根源是找不到魔术码,那只要按这个线索排查就可以了,一种简单的方式是向源代码里加打印信息。这种调试方法如果不成功,那么还可以动用幽兰上具有的更强大调试设施——CoreSight。


# 穷追不舍查魔码
读不到魔术码,那可能是分区位置指定错了。查看源代码,出问题的函数是通过PCD配置技术来读分区的。

查找对应的PCD设置,果然发现有两种设置,一种是设置为\\EFI\\BOOT\\GRUBAA64.EFI。另一种是设置为UUID的分区表形式,我们应该选用第二种。而实际使用的是第一种。

UEFI具有非常好的可配置性,调整INF文件就可以改变PCD设置。

调整后,通过串口打印的信息可以确认,UEFI已经使用正确的分区了。

但是无效参数错误还是有,也就是UEFI还没有找到合适魔术码。为此,我让小伙伴增加打印,打印读到数据,发现魔术码位置都是0。

因为打印时套用了UEFI的%r格式符,所以上图中的四个Success其实是四个0。
这时,目标已经很明确,UEFI读的分区位置不对,所以读到的都是0。有了确凿的数据作为支撑后,小伙伴仔细核对起PCD参数,发现参数中的分区起始位置是错误的。
PCD串很长,前面一直把注意力集中在核对UUID上,没有注意UUID后面的起始位置和分区大小部分。
L"VenHw(100C2CFA-B586-4198-9B4C-1683D195B1DA)/HD(3,GPT,7A3F0000-0000-446A-8000-702F00006273,0x8000,0x20000)"
把分区信息纠正后,UEFI成功把执行权交给了内核,UEFI的界面不自动出现了,取而代之的是幽兰图标,一朵兰花在黑色屏幕中闪现,鲜绿的叶子,金黄色的花瓣,红黄两色的花蕊,它傲然绽放,又略带娇羞,因为它幽然独立,与众不同。


在上文提到的Intel新闻稿中,特意描述了Tiano项目有超过200人参与开发,他们在Intel的上海软件中心,以及位于Oregon和华盛顿的实验室。
The project represents more than 200 person years of development by Intel's China Software Center in Shanghai, and Intel software labs in Oregon and Washington.
https://www.intel.com/pressroom/archive/releases/2004/20040601corp_a.htm
的确,Tiano的很多初始代码诞生在上海。某种意义上说,Tiano项目是Intel最早在上海进行的著名项目。2003年我加入Intel时,还有很多同事在做Tiano项目。
Tiano项目是成功的,它使用C++语言重新构建了固件层的框架和核心逻辑,在固件领域搭建了一座软件大厦,这座大厦使用新世纪的技术,新世纪的思想,具有新世纪的特征。先进的接口思想让它充分模块化,非常容易维护和扩展。
回看历史,当年投巨资的安腾项目并不成功,慢慢被历史所淡忘。为安腾铺路的Tiano项目却很成功,具有持久的生命力,随着时代的发展而不断扩大领地。不仅成为X86平台的特色技术,服务于今天的X86桌面和X86服务器领域,而且已经延展到ARM平台,作为X86平台的技术精华输送给日新月异的ARM平台。

在RK3588上体验UEFI

购买幽兰代码本即可成为兰舍会员,与众多技术高手一起成长。
购买可前往淘宝格友小店:
https://m.tb.cn/h.Uuv7fit?tk=N1iIdn8t4CI


盛格塾是格蠹科技旗下的知识分享平台,是以“格物致知”为教育理念的现代私塾。
本着为先圣继绝学的思想,盛格塾努力将传统文化中的精华与现代科技密切结合,以传统文化和人文情怀阐释现代科技,用现代科技传播传统文化。
访问方式
手机端:微信小程序搜索“盛格塾”
电脑端:下载Nano Code社区版客户端
https://nanocode.cn/#/download

格友公众号

盛格塾小程序

喜欢此内容的人还喜欢



2023杭州研习班回顾

左右滑动查看下一篇
本文介绍了UEFI在ARM平台,尤其是RK3588上的应用,探讨了UEFI作为固件技术在解决硬件兼容性和系统启动问题上的作用。文章讲述了UEFI的发展历程,包括其在X86和ARM平台的推广,以及在RK3588上实现UEFI遇到的挑战和解决方法,如魔术码定位错误的调试过程。



4550

被折叠的 条评论
为什么被折叠?



