Asahi Linux Progress Report: Linux 7.2

Asahi Linux Progress Report: Linux 7.2

Asahi Linux 进展报告:Linux 7.2

Linux 7.2 has been released! That was fast. Let’s dive in to yet another Asahi Linux progress report. We’ve got a lot of interesting developments for you today, so make yourself a cuppa and enjoy. Linux 7.2 发布了!速度真快。让我们深入了解一下 Asahi Linux 的又一份进展报告。今天我们有很多有趣的进展要分享,所以请泡上一杯茶,慢慢享用。

Think Different… again

再次“不同凡想”(Think Different)

The Apple Silicon platform’s power management infrastructure is complicated. Responsibilities are divided between multiple hardware blocks, including the SMC, PMGR, and PMP, all of which have featured before on this blog. While supporting these blocks is important for power use, one of the biggest obstacles to improving battery life has been the application cores themselves. Apple Silicon 平台的电源管理架构非常复杂。其职责被分配在多个硬件模块之间,包括 SMC、PMGR 和 PMP,这些模块在之前的博客中都曾提到过。虽然支持这些模块对于功耗控制很重要,但提升电池续航能力的最大障碍之一其实是应用核心本身。

There are multiple ways to “sleep” a CPU core, and each one should be used in a specific context. The most basic way to sleep an ARM CPU core is to use a Wait For Interrupt (WFI) instruction. This tells the core to stop doing things until it is woken up by an interrupt from an interrupt source. While this does save power by virtue of stopping the core from executing any code, the core stays powered up and retains enough state for it to resume work extremely quickly. As such, WFI is typically only used for parking a core on a running system. Apple cores include a “deep” WFI mode, which shuts down more of the core at the expense of losing its state. Our downstream cpuidle driver operates by setting WFI up in this mode, saving the core’s state, then issuing a WFI loop. 让 CPU 核心“休眠”有多种方式,每种方式都应在特定场景下使用。让 ARM CPU 核心休眠最基本的方法是使用“等待中断”(WFI)指令。这会告诉核心停止工作,直到被中断源发出的中断唤醒。虽然这确实通过停止核心执行代码来节省了功耗,但核心仍处于通电状态,并保留了足够的状态以便极快地恢复工作。因此,WFI 通常仅用于在运行中的系统中挂起核心。Apple 的核心包含一种“深度”WFI 模式,它会关闭核心的更多部分,代价是丢失其状态。我们下游的 cpuidle 驱动程序通过将 WFI 设置为这种模式来运行,保存核心状态,然后发出 WFI 循环。

Vendor-specific power management oddities like this are quite common. Thankfully for kernel maintainers, there is a standard way to deal with them: the Power State Coordination Interface. PSCI defines a standard interface that allows an operating system to call into a defined set of CPU core power management functions implemented by system firmware, including to prepare them for sleep. 这类特定于供应商的电源管理怪癖非常普遍。幸运的是,对于内核维护者来说,有一种处理它们的标准方法:电源状态协调接口(PSCI)。PSCI 定义了一个标准接口,允许操作系统调用由系统固件实现的一组定义的 CPU 核心电源管理功能,包括为休眠做准备。

To avoid a proliferation of vendor-specific power management hacks inside the Linux kernel, the maintainers of the arm64 arch-specific code have mandated that all upstream hardware must use PSCI for power management. As such, we are not able to upstream our Apple-specific cpuidle driver. Why are we still using it then? 为了避免 Linux 内核中出现大量特定于供应商的电源管理补丁(hacks),arm64 架构代码的维护者强制要求所有上游硬件必须使用 PSCI 进行电源管理。因此,我们无法将 Apple 专用的 cpuidle 驱动程序合入上游。那么,为什么我们还在使用它呢?

PSCI defines “conduits” through which calls to firmware are to be dispatched from the kernel. The two currently supported conduits in the kernel are the SMC (Secure Monitor Call) and HVC (Hypervisor Call) instructions, which are used to yield execution to a higher Exception Level. The Linux kernel expects to be running at EL2, which means that its PSCI calls must yield to firmware running in EL3. Except Apple’s cores do not implement EL3… PSCI 定义了“通道”(conduits),内核通过这些通道将调用分发给固件。内核目前支持的两个通道是 SMC(安全监控调用)和 HVC(管理程序调用)指令,它们用于将执行权限移交给更高的异常级别(Exception Level)。Linux 内核期望运行在 EL2,这意味着它的 PSCI 调用必须移交给运行在 EL3 的固件。但问题是,Apple 的核心并没有实现 EL3……

With the kernel already running in EL2 and no firmware running in EL3 to talk to, we are a bit stuck. Linux cannot issue an SMC or HVC instruction since there is no EL3 to yield execution to, which means we cannot make use of PSCI. Being able to properly power-manage the CPU cores is vital for battery life and efficiency, so the status quo simply will not do. One quick and dirty solution would be to have m1n1 load the kernel into EL1, and host a PSCI implementation in EL2. While this would theoretically work, it would also break a lot of architectural features, such as virtualisation. There must be something else we can do… 由于内核已经在 EL2 运行,且没有运行在 EL3 的固件可供通信,我们陷入了困境。Linux 无法发出 SMC 或 HVC 指令,因为没有 EL3 来接收执行权限,这意味着我们无法使用 PSCI。能够正确管理 CPU 核心的电源对于电池续航和效率至关重要,因此现状是不可接受的。一个快速且粗糙的解决方案是让 m1n1 将内核加载到 EL1,并在 EL2 中托管一个 PSCI 实现。虽然这在理论上可行,但它会破坏许多架构特性,例如虚拟化。一定还有其他办法……

If you think about it, m1n1 is almost like our own firmware for Apple Silicon. mBoot (formerly iBoot) starts it in EL2, it does its job, then jumps to whatever payload is attached to it. m1n1 does not reserve any memory for itself and does not have any code that must stay resident, so its payload is free to reclaim and overwrite that memory. 仔细想想,m1n1 几乎就像是我们为 Apple Silicon 开发的固件。mBoot(前身为 iBoot)在 EL2 启动它,它完成工作后,就会跳转到附加在其上的任何负载(payload)。m1n1 不会为自己保留任何内存,也没有任何必须常驻的代码,因此它的负载可以自由回收并覆盖那部分内存。

On production Asahi Linux systems, m1n1 loads U-Boot rather than the kernel directly. We do this to make use of U-Boot’s UEFI implementation, allowing distros and users to utilise whichever standard UEFI bootloader (GRUB, systemd-boot, etc.) they want. UEFI also provides another feature, Runtime Services. Much as the BIOS interrupts of old did, UEFI Runtime Services provide a way for the operating system to access code originating in system firmware. 在生产环境的 Asahi Linux 系统上,m1n1 加载的是 U-Boot 而不是直接加载内核。我们这样做是为了利用 U-Boot 的 UEFI 实现,允许发行版和用户使用他们想要的任何标准 UEFI 引导加载程序(GRUB、systemd-boot 等)。UEFI 还提供了另一个功能:运行时服务(Runtime Services)。就像旧时代的 BIOS 中断一样,UEFI 运行时服务为操作系统提供了一种访问系统固件中代码的方法。

Reading the PSCI standard as published by Arm, one will notice that it deliberately defines the API without reference to any specific conduit, and only lists SMC and HVC as examples. If we take a broad interpretation of this, we could conclude that this means other conduits are allowed by the spec… 阅读 Arm 发布的 PSCI 标准,你会注意到它在定义 API 时特意没有引用任何特定的通道,仅将 SMC 和 HVC 列为示例。如果我们对此进行广泛解读,可以得出结论:该规范允许使用其他通道……

To this end, Sven has been working on implementing a UEFI Runtime Service based PSCI conduit. With m1n1’s memory region carved out like other firmware regions, this will allow the kernel to call back into it for PSCI services, even though it is running at the same Exception Level. Sven has already modified m1n1 to reserve its memory and leave behind a PSCI implementation, and the patches to the kernel enabling its use are already on the mailing list as an RFC! 为此,Sven 一直致力于实现基于 UEFI 运行时服务的 PSCI 通道。通过像其他固件区域一样划分出 m1n1 的内存区域,这将允许内核回调它以获取 PSCI 服务,即使它们运行在相同的异常级别。Sven 已经修改了 m1n1 以保留其内存并留下一个 PSCI 实现,而启用该功能的内核补丁已经作为 RFC 发送到了邮件列表!

Please stop Thinking Different

请停止“不同凡想”

Given that the cpuidle situation saw no progress until very recently, one might assume that some event has catalysed work in this space. One would be correct. 鉴于 cpuidle 的情况直到最近才取得进展,人们可能会认为某些事件催化了该领域的工作。你是对的。

The ARM specification mandates that cores in WFI loops should preserve all state. This is not the default mode on Apple Silicon. On M1 through M3 series SoCs, state retention can be configured on a per-core basis using chicken bits. ARM 规范要求处于 WFI 循环中的核心应保留所有状态。但这在 Apple Silicon 上并非默认模式。在 M1 到 M3 系列 SoC 上,可以使用“chicken bits”按核心配置状态保留。

Due to a number of reasons that are not worth mentioning, Apple now sets each core’s chicken bits in mBoot and then locks down the registers controlling them starting with the M4 series. This makes our life a little easier as m1n1 now has marginally less work to do, however it also means that we cannot fine tune low level CPU behaviour. This is an issue on M4 particularly, as calling WFI causes the core to lose its state and crash whatever was running on it. 由于一些不值得一提的原因,Apple 现在在 mBoot 中设置每个核心的 chicken bits,并从 M4 系列开始锁定了控制它们的寄存器。这让我们的工作稍微轻松了一些,因为 m1n1 现在要做的工作稍微少了一点,但这也意味着我们无法微调底层的 CPU 行为。这在 M4 上尤其是一个问题,因为调用 WFI 会导致核心丢失状态,并使运行在其上的任何程序崩溃。

Yureka noticed this while doing M4 bringup work, and added a kernel command line parameter to make idle loop behaviour configurable. The parameter allows us to tell the kernel how it should park cores in idle loops, including by doing a basic no-op loop. This prevents M4 machines from crashing during early kernel initialisation, bef Yureka 在进行 M4 启动工作时注意到了这一点,并添加了一个内核命令行参数,使空闲循环行为可配置。该参数允许我们告诉内核应如何挂起空闲循环中的核心,包括执行基本的空转循环(no-op loop)。这防止了 M4 机器在内核早期初始化期间崩溃……