在 $300 的手机上启动 Windows 10 ARM64
imbushuo
Please mind the gap
这是上一篇文章的续集。总的来说,差异不是特别大,但是也有一些细节上的不同。
这是这只 $300 的手机。价格刚刚从美亚截图的众所周知,Windows Phone 8 和 Windows 10 Mobile 没有发布过正式的 AArch64 版本。所以即使在 Lumia 950, 950XL, HP Elite x3 之类的设备上,他们也是以 AArch32 模式运行的(向下兼容)。
从具体实现细节说,Snapdragon 808 和 810 以 AArch32 模式启动第一个核心并初始化(但是 TZ 是 AArch64 的),根据需求通过 Secure Monitor Call 来切换 PL1/EL1 的 bitness。Snapdragon 820 以 AArch64 模式启动第一个核心,根据需求在启动 Windows Phone UEFI 时进入 AArch32 的 EL1。
所以在给 Lumia 移植了 LK 之后,我们就有相对充足的 EL1 控制权(在高通设备上 EL2 和 EL3 是不受我们控制的)。因此我们也可以载入我们自己的 TianoCore Payload 到内存,然后通过 SMC 切换 bitness 并执行。具体怎么从 Boot Manager 启动 LK,看这里。
UEFI 相关
首先在 ARM 上 UEFI 的初始化工作比 x86 通常要小很多,因为 UEFI 一般被作为一个 Secondary Bootloader 被载入,载入的时候,DDR 训练已经完成了,内存可以直接拿来用了。而且这些嵌入式设备有很多硬编码的参数。
剩下的内容并不是特别麻烦,EFIDroid 项目提供了很多不错的参考实现,直接拿来用就好。不过我重写了内存初始化(其实是 MMU 和 HOB)部分来适应 Windows 的启动要求。
USB 还没来得及做,但是都是 Synposys 的 USB IP,可以参考 RaspberryPiPkg 里的相关内容。估计 Clock 部分要和 EFIDroid 的驱动做集成。
一个正在进行中的 Lumia 950 XL 参考 UEFI 实现看这里。目前代码还很乱。
UART
很不幸 Lumia 950XL 没有外置的(唯一一个接了蓝牙)。所以我们用屏幕当串口来诊断一些问题。我把这个选项做成了一个 PCD,在我的实现发布里默认没开。其实其他 Lumia 非 x50 代的都有能用的 UART 测试点,使用的是 1.8V UART(高通常见)。
多核启动
可以先看一下老狼的这篇文章,对多核启动有一个了解:兄弟阋墙,CPU内核们是如何争当老大的?
在 ARM 平台上,目前通行的几种多核启动办法如下:
- 固件初始化所有核心,交给 OS 时全部可用(好像没看到谁用了)
- 固件启动在 BSP 核心,固件在 DXE 阶段把所有核心上电并定向到指定内存地址并 Hold(其实是执行一段代码),等待 OS 启动后通过 GIC 或其他手段通知核心启动并开始运作。这种办法也称之为 Parking。除了 Snapdragon 820 外的 Windows Phone, Windows RT 和 Windows 10 IoT 设备用的是这种办法,具体实现规范参考这份文件。
- 固件启动在 BSP 核心,将其他核心初始化到某个预期状态(或者不管),OS 启动后通过 PSCI 来启动核心。Snapdragon 820 的 Windows Phone 和 Windows 10 ARM 平板用的是这种办法。具体实现规范参考这份文件。
第二种办法在核心运行的 Hold 伪代码如下:
Hold:
等一个中断过来,没有来就睡觉
判断一下是不是我要起床了(检查核心 ID),是的话跳转到指定内存地址并继续执行
不是我起床,回到 Hold 重复
第三种办法直接向 EL2/3 发送 Hypervisor Call 或者 Secure Monitor Call。虽然 Lumia 950 XL 所用的 Snapdragon 810 没有标注 PSCI 支持,但是实际上支持了一部分 PSCI 内容。PSCI CPU_ON 是没问题的,但是其他电源操作还是需要通过 SPMI 给 PMIC 发指令。所以我们在 ACPI 里标注了 PSCI 支持(并要求 Hypervisor Call),但是 UEFI 库里的其他电源操作还是通过 PMIC 完成。
不过看上去 HVC PSCI 没法让 Linux 带起来。Linux 只能单核启动,尚未调查具体原因。
完成了这些内容后,Windows 就能启动了。不过把各种外设带起来,还需要挺长的时间。后面是几张图。