健忘的 CPU(M4 上的 Linux)

Lobsters Hottest 新闻

摘要

一篇详细的技术文章,讲述如何在 M4 Mac mini 上启动主线 Linux,克服新引入且强制启用的 SPTM(安全页表监控器)——它破坏了现有的基于 m1n1 虚拟机管理程序的工作流程,并让 SoC 启动到足以通过串口控制台进行调试的程度。作者对 Asahi Linux 团队表示感谢,并记录了被锁定的 GXF/RVBAR 寄存器以及早期 Linux 启动的进展。

<p><a href="https://lobste.rs/s/s4wm69/forgetful_cpu_linux_on_m4">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/02 14:37

# 健忘的 CPU(M4 上的 Linux)- 博客 来源:https://yuka.dev/blog-2026-10-02-linux-m4.html *这篇博文相当详细地讲述了我最初如何在 M4 Mac mini 上启动 Linux 的过程。我建议你查一查文中提到的你不熟悉的术语和概念,因为我无法在本文中解释所有背景知识 ;)* 在继续说下去之前,我需要感谢 Asahi Linux (https://asahilinux.org/) 团队之前所做的全部工作,以及在这段旅程中给予我的帮助。如果你想看到更多主线 Linux 在 Apple Silicon 上的工作,请考虑向 Asahi Open Collective (https://opencollective.com/asahilinux) 捐款! ### 起源 2024 年 11 月,我买了一台 M4 Mac mini,赌它会和 M1-M3 的 Apple Silicon 机器相似,从而能在 Asahi Linux 中很快得到支持。在 M4 于我桌上放了几个月的期间,关于这颗 SoC 的更多细节开始浮出水面。 [![Sven Peter 于 2025 年 4 月发布的 Mastodon 帖子截图:"看起来 Asahi Linux 的 M4 支持将会相当痛苦 [...]"](https://yuka.dev/files/sven-m4-sptm.png)](https://social.treehouse.systems/@sven/114278224116678776) 事实证明它确实更加困难,因为 M4 机器是第一代强制要求 SPTM(Secure Page Table Monitor,安全页表监视器)的 Apple Silicon,SPTM 可以加固对 macOS XNU 内核漏洞的防护。在前几代中,Linux 的移植工作主要基于使用 m1n1 hypervisor 捕获的 MMIO 追踪,从而得以分析原始 macOS 驱动与硬件之间的交互。而在 SPTM 的情况下,要在 hypervisor 下运行 macOS,需要对 m1n1 进行重大修改,这些修改显然超出了我作为一个该领域新手所能想到的范围。 不过,这也不算完全白费。在 hypervisor 工作的同时,我开始了在 M4 上启动 Linux 的初步尝试。这意味着要禁用严格的启动安全机制,通过 macOS 恢复 [1](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn1) 将 m1n1 作为自定义启动对象安装,并获取串行控制台 [2](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn2) 以检查日志。 ### 被锁定的寄存器 起初,m1n1 只能在 BRINGUP 模式下启动,否则在尝试初始化 GXF [3](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn3) 时会立即崩溃。事实证明,在这些 M4+ SoC 的原始启动模式下,GXF 功能被禁用/锁定了,因此将其初始化设为条件性操作、在这些机器上跳过它才是正确的做法。 除了被禁用的 GXF 功能外,还有 RVBAR(复位向量基地址寄存器):内存中的一处位置(每个 CPU 核心一个),用于决定核心上电时从哪里开始执行代码。m1n1 的代码在引导内核或链式加载另一个 m1n1 时,会为每个核心将其入口地址写入 RVBAR。写入该寄存器在 M4 上同样导致了崩溃,但长话短说——它里面已经包含了正确的值,所以这个写入同样需要跳过。 ``` 2024-12-01 17:37 确认这个状态可以得到可用的 usb 代理,因为 lsusb 中出现了一个 "Generic m1n1 uartproxy v1.4.17-61-ga24ff77",而且我可以打开 shell 了 :) ``` ### `debug_putc` 终端截图。显示一个 hyfetch NixOS 的跨性别配色横幅。主机:Apple Mac Mini (M4, 2024),CPU:1 截图还显示了 `ip a` 的输出(除 loopback 外没有任何网络接口)、`lsblk`(除从 loop0 挂载的 Nix store 外没有任何块设备),以及只显示单核的 cpuinfo(https://fedi.yuka.dev/notice/B2aXPkzrd81n12Anqq) 在那之后,我有很长一段时间没有碰过那台 Mac mini。2025 年底的混沌通信大会(Chaos Communication Congress)上,我找到了新的动力。我终于拼凑出了一个非常简化的设备树,只包含 CPU 核心和 AIC 中断控制器,并使用 m1n1 的 `linux.py` 加载 Linux 内核(带着 `earlycon` 参数),但在 "Vectoring to next stage" 之后我什么输出都没看到。 既然从内核那里得不到任何有用的输出,我就只能瞎猜哪里出了问题,对吧?我决定尝试暴力方法:老办法 `println` 调试。我从 m1n1 中取出了 `debug_putc` 汇编例程,把它修改成打印单个 'a' 字符 [4](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn4)。我把它插入到 Linux 内核代码中非常早期的位置,果然,在 "Vectoring to next stage" 之后我得到了一个 'a'! 基本上,我对 Linux 启动代码进行了二分查找,最终落在了 MMU 初始化代码上(这仍然*非常*早期,仍然处于 `arch/arm64/kernel/head.S` 中的汇编代码里)。 是 MMU 初始化以某种方式使 CPU 崩溃了吗?并不完全是:UART 是通过内存映射 I/O 访问的。一旦 MMU 被启用,所有内存访问都会被定向到虚拟地址,而这些虚拟地址通过页表映射到相应的物理地址。虽然 m1n1 创建了映射,将 MMIO 地址空间暴露在相同的虚拟地址上,但 Linux 并不这样做,这意味着一旦 MMU 被启用,我们最终访问到的将是未映射的空间而不是 UART。 我修改了初始页表,为 MMIO 空间添加了这种 1:1 映射,于是我的 `debug_putc` 在启动过程中的更深处也能工作了。 再次二分查找后,打印这次一直成功到中断控制器初始化的某个地方!我把它缩小到一次对实现特定的 CPU 寄存器 `SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2` 的写入,这触发了新的崩溃。在这次写入被注释掉之后,内核启动到了一个 shell。太棒了! `SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2` 与虚拟化相关,它在新的 iBoot 版本中已被解锁,因此不再需要注释掉那次写入。 既然我已经知道 Linux 代码确实在被实际执行,我又重新查看了为什么之前尽管有 `earlycon` 启动参数,却一直没有在串行控制台上得到任何 `printk` 输出: ``` 2026-01-23 12:35 在我的 bootargs 中添加了 earlycon=s5l,0x3ad200000 2026-01-23 12:36 现在我终于在崩溃发生之前得到了有用的输出! ``` 果然,设备树只是缺少了 `stdout-path = "serial0"`,添加之后,我就在早期崩溃时从 Linux 得到了完整的寄存器转储和堆栈回溯。 ### 次要核心与 WFI [](https://fedi.yuka.dev/notice/B5HtAViL7sPUGmyoTI) 目前,m1n1 没有启动次要核心,因为 `smp_start_offset` 缺失(这是 m1n1 中硬编码的一个偏移量;没有它,`smp_init` 就会被跳过)。我尝试了用于基础款 M1-M3 的偏移量,成功启动了次要核心。 当我再次尝试加载 Linux 时,又来到了另一个神秘的崩溃。之前的 Apple Silicon CPU 在 WFI 指令方面已经有了已知的怪异行为。根据 XNU 开源代码中名为 `ARM64_REG_CYC_OVRD_ok2pwrdn_force_mask` 的*鸡位*(chicken bit,寄存器中的一位,允许厂商禁用某些 CPU 优化或功能,或者说*认怂*)的状态,WFI 指令在这些前代 SoC 上会导致 CPU 寄存器 x0-x31 被清零。XNU 在 WFI 之前将这些寄存器保存到栈中,之后再恢复它们。在 M1-M3 SoC 中,m1n1 会禁用这种行为,使 CPU 表现得和其他 arm64 处理器一样。Asahi 内核后来会专门重新启用这种行为,以便 CPU 核心能够进入更深的睡眠状态并节省电力。这也有助于允许集群中的一个核心在其他所有核心都处于这种更深的 WFI 睡眠状态时,提升到更高的时钟速度。 看起来这个鸡位在 M4 上要么被锁定了,要么已被移除,其默认行为并不符合 ARM64 规范(具体来说:"如果系统被配置为可以完成 WFI 指令,那么 WFI 指令绝不能导致架构状态的丢失。"[5](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn5))。 2026 年 4 月,我通过将我内核中的所有 WFI 和 WFIT(带超时等待中断)指令替换为 NOP(空操作),成功在启用了所有核心的情况下启动了 Linux。由此,一场朝着为 WFI 问题提供上游解决方案的漫长旅程开始了。 起初,我研究了如何处理 errata(芯片的异常行为)。Linux 中有一整套框架,允许它在早期启动期间在内存中修补自己。然而,这种方案被放弃了,因为很难准确检测出哪些情况需要将 WFI 替换为 NOP。也就是说,在 macOS hypervisor 下运行的虚拟机同样会触发 errata 逻辑,但 WFI 会被 macOS hypervisor 捕获,并用于高效地调度不同的客户机。检测虚拟化(尤其是当嵌套虚拟化被启用时)也很复杂,因此 Will Deacon 提出了一个替代方案:内核应该支持通过一个新的 bootarg [6](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn6) 来禁用 WFI 空闲,然后 m1n1 可以在已知 WFI 损坏的裸机上启动时条件性地添加相应的 bootargs。随后我们将添加一种机制,允许 Linux 将核心置于睡眠状态。目前可以使用下游的 cpuidle-apple 驱动来完成这一工作,但 Sven 的 PSCI EFI conduit 工作为未来的上游解决方案提供了非常有希望的方向。 我很高兴地报告,防止 Linux 在 WFI 和 WFIT 指令上崩溃的机制已被合入主线 Linux [7](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn7) [8](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn8) 和 m1n1 [9](https://yuka.dev/blog-2026-10-02-linux-m4.html#fn9),因此这些项目的最新版本可以在 M4 Mac 上以原生方式启动并使用次要核心! ### 后续步骤 到目前为止,这项工作已经发现并解决了在 M4 及后续 Apple Silicon 芯片上运行 Linux 的一个根本性问题,使得 Linux 能够以所有核心可用的方式启动到 shell(同样的 WFI 变通方法也被发现适用于 M4 Pro、M4 Max 和 M5 芯片!)。至于外设的逆向工程,我在这里不会详细展开,目前进展缓慢但稳步前进。Sven 在使 m1n1 hypervisor 能够在这些设备上启动并追踪 macOS 方面的不懈工作,将对解决诸如内置摄像头、显示控制器和 GPU 初始化等更复杂的组件大有帮助。 大部分情况下,我会将我的工作直接提交给相应的上游项目,让每个人都能从中受益。有时,如果某些获得大量资金的项目能更透明地说明它们如何从上游项目的进步中获益,那就再好不过了。 --- **如果你想支持我关于 M4 上 Linux 的工作,我可以在 LiberaPay (https://liberapay.com/Yureka/) 或 GitHub Sponsors (https://github.com/sponsors/yuyuyureka) 接受捐赠。也请考虑向 Asahi Open Collective (https://opencollective.com/asahilinux) 捐款,以推动更多 Linux 在 Apple Silicon 上进入主线!** 一如既往,希望你学到了一些东西 :) --- 1. m1n1 用户指南 - Asahi Linux 文档 (https://asahilinux.org/docs/sw/m1n1-user-guide/#installation)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref1) 2. 通过串行控制台调试 - Asahi Linux 文档 (https://asahilinux.org/docs/hw/soc/serial-debug/#getting-a-serial-console)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref2) 3. Apple Silicon 硬件秘密:SPRR 和受保护的异常级别 (GXF) \| Sven Peter (https://blog.svenpeter.dev/posts/m1_sprr_gxf/)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref3) 4. HACK: DEBUG: t8132 上早期启动阶段的 debug_putc (8c86bd8c) · Commits · Yureka Lilian / linux · GitLab (https://cyberchaos.dev/yuka/linux/-/commit/8c86bd8c76bbe02dbf20f3e0f8fb0c311be30faf)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref4) 5. Wait For Interrupt - 文档 - Arm Developer (https://support.arm.com/documentation/ddi0487/mc/-Part-G-The-AArch32-System-Level-Architecture/-Chapter-G1-The-AArch32-System-Level-Programmers--Model/-G1-19-Mechanisms-for-entering-a-low-power-state/-G1-19-2-Wait-For-Interrupt?lang=en#cfibbgjg)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref5) 6. [Re: \[PATCH v2\] arm64: errata: Handle Apple WFI State Loss - Will Deacon](https://lore.kernel.org/all/ajAT9fWgOd9HY7ei@willie-the-truck/)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref6) 7. arch: arm64: add early_param idle= - kernel/git/torvalds/linux.git - Linux 内核源码树 (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d97afae6f16a4f8ac7d50af0070816270b5380c1)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref7) 8. arm64: Add override for WFxT - kernel/git/torvalds/linux.git - Linux 内核源码树 (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=baaa9b126b875b40ffd9356da52c7c871917e5bb)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref8) 9. kboot: 如果 WFI 丢失状态则禁用 wfi/wfit,作者 yuyuyureka · Pull Request \#672 · AsahiLinux/m1n1 (https://github.com/AsahiLinux/m1n1/pull/672)↩︎ (https://yuka.dev/blog-2026-10-02-linux-m4.html#fnref9)

相似文章

在一个月内为M4 Mac Mini开发Linux GPU驱动程序

Hacker News Top

Cody Ho和Niklas通过洁净室逆向工程,在一个月内为M4 Mac Mini构建了一个完全符合OpenGL ES 3.0标准的GPU驱动程序,使得Linux能够以高帧率运行如Minecraft等图形密集型应用程序。

Haiku OS 现可在 M1 Mac 上运行

Lobsters Hottest

Haiku OS 已移植至 Apple M1 Mac,可直接启动无需虚拟机。ARM 移植尚处早期阶段,但能启动到桌面,所有八个核心均正常工作。