25. Linux 微代码加载器

作者:

内核具有一个 x86 微代码加载机制,旨在为操作系统提供微代码加载方法。潜在的使用场景包括:在超出 OEM 生命周期(End-Of-Life)支持的平台上更新微代码,以及在无需重启的长期运行系统上更新微代码。

加载器支持三种加载方法:

25.1. 早期微代码加载

内核可以在引导的极早期更新微代码。尽早加载微代码可以在内核引导期间观察到 CPU 问题之前将其修复。

微代码存储在 initrd 文件中。在引导期间,微代码从该文件中读取并加载到 CPU 核心中。

组合后的 initrd 镜像的格式是:(未压缩的)cpio 格式的微代码,后跟(可能经过压缩的)initrd 镜像。加载器在引导期间解析组合后的 initrd 镜像。

cpio 命名空间中的微代码文件为:

在 Intel 平台上:

kernel/x86/microcode/GenuineIntel.bin

在 AMD 平台上:

kernel/x86/microcode/AuthenticAMD.bin

在 BSP(引导处理器,BootStrapping Processor)引导期间(SMP 启动前),内核扫描 initrd 中的微代码文件。如果找到与 CPU 匹配的微代码,它将首先应用于 BSP,随后应用于所有 AP(应用处理器,Application Processors)。

加载器还将与 CPU 匹配的微代码保存在内存中。因此,当 CPU 从睡眠状态恢复时,将应用缓存的微代码补丁。

下面是如何准备包含微代码的 initrd 的一个粗略示例(这通常由发行版在重新创建 initrd 时自动完成,因此你实际上不需要自己去做。此处记录仅供将来参考)。

#!/bin/bash

if [ -z "$1" ]; then
    echo "You need to supply an initrd file"
    exit 1
fi

INITRD="$1"

DSTDIR=kernel/x86/microcode
TMPDIR=/tmp/initrd

rm -rf $TMPDIR

mkdir $TMPDIR
cd $TMPDIR
mkdir -p $DSTDIR

if [ -d /lib/firmware/amd-ucode ]; then
        cat /lib/firmware/amd-ucode/microcode_amd*.bin > $DSTDIR/AuthenticAMD.bin
fi

if [ -d /lib/firmware/intel-ucode ]; then
        cat /lib/firmware/intel-ucode/* > $DSTDIR/GenuineIntel.bin
fi

find . | cpio -o -H newc >../ucode.cpio
cd ..
mv $INITRD $INITRD.orig
cat ucode.cpio $INITRD.orig > $INITRD

rm -rf $TMPDIR

系统需要将微代码软件包安装到 /lib/firmware 中,或者如果你的微代码存放在其他地方,和/或你是直接从处理器厂商网站下载的,则需要修正上述路径。

25.2. 延迟加载

你只需安装发行版提供的微代码软件包,并运行

# echo 1 > /sys/devices/system/cpu/microcode/reload

以 root 身份。

加载机制在 /lib/firmware/{intel-ucode,amd-ucode} 中寻找微代码 blob。默认的发行版安装包已经将它们放在了那里。

自内核 5.19 起,默认不再启用延迟加载。

/dev/cpu/microcode 方法已在 5.19 中被移除。

25.3. 为什么延迟加载是危险的?

25.3.1. 同步所有 CPU

接收微代码更新的微代码引擎在 SMT 系统的两个逻辑线程之间是共享的。因此,当在一个核心的某个 SMT 线程上执行更新时,其同胞线程会“自动”获得更新。

由于微代码也可以“模拟” MSR,在微代码更新进行期间,这些被模拟的 MSR 会短暂地不复存在。如果 SMT 同胞线程刚好正在访问此类 MSR,这可能会导致不可预测的结果。通常观察到的现象是,此类 MSR 访问会引发 #GP 以指示前者不存在。

消失的 MSR 只是观察到的常见问题之一。任何其他正在被修补且被另一个 SMT 同胞线程并发执行的指令,也可能导致类似、不可预测的行为。

为了消除这种情况,引入了基于 stop_machine() 的 CPU 同步机制,以确保所有逻辑 CPU 都不执行任何代码,而只是在一个自旋循环中等待,轮询一个原子变量。

虽然这处理了设备中断或外部中断、包括 LVT 在内的 IPI(如 CMCI 等),但它无法解决其他无法关闭的特殊中断。这些特殊中断包括:机器检查(#MC)、系统管理中断(#SMI)和不可屏蔽中断(#NMI)。

25.3.2. 机器检查

机器检查(#MC)是不可屏蔽的。MCE 有两种。致命的不可恢复 MCE 和可恢复 MCE。虽然不可恢复的错误是致命的,但在内核上下文中发生的可恢复错误也被内核视为致命错误。

在某些 Intel 机器上,MCE 也会广播给系统中的所有线程。如果一个线程正处于执行 WRMSR 的过程中,MCE 将在流程结束时被捕获。无论哪种情况,它们都会等待执行 wrmsr(0x79) 的线程在 MCE 处理程序中汇合,如果系统中的任何线程未能向 MCE 汇合点签到,最终将导致关机。

为了力求严谨并获得可预测的行为,操作系统可以选择设置 MCG_STATUS.MCIP。由于系统中最多只能有一个 MCE,如果发出了 MCE 信号,上述条件将自动提升为系统重置。操作系统可以在该核心更新结束时关闭 MCIP。

25.3.3. 系统管理中断

SMI 也会广播到平台中的所有 CPU。微代码更新在写入 MSR 0x79 之前,会请求对核心的独占访问。因此,如果确实发生了一个线程处于 WRMSR 流程中、而第二个线程收到了 SMI 的情况,该线程将在 SMI 处理程序的第一条指令处被停止。

由于辅助线程在 SMI 的第一条指令处停止,它几乎不可能正处于执行正在被修补的指令的过程中。此外,操作系统无法阻止 SMI 的发生。

25.3.4. 不可屏蔽中断

当某个核心的 thread0 正在进行微代码更新时,如果 thread1 被拉入 NMI,由于上述原因,这可能会导致不可预测的行为。

操作系统可以选择各种方法来避免陷入这种状况。

25.3.5. 微代码是否适合延迟加载?

延迟加载是在系统完全运行并承载实际工作负载时进行的。延迟加载的行为取决于升级到新补丁之前 CPU 上的基础补丁是什么。

对于 Intel CPU 而言,情况确实如此。

例如,考虑某个 CPU 具有补丁级别 1,而更新的目标是补丁级别 3。

在补丁 1 和补丁 3 之间,补丁 2 可能已经弃用了某个软件可见的特性。

如果软件甚至有可能正在使用该特性,这是不可接受的。例如,假设更新后 MSR_X 不再可用,访问该 MSR 将导致 #GP 错误。

基本上,没有办法声明某个新的微代码更新适合延迟加载。这是导致延迟加载默认不被启用的又一个问题。

25.4. 内置微代码

加载器还支持加载通过常规内置固件方法 CONFIG_EXTRA_FIRMWARE 提供的内置微代码。当前仅支持 64 位。

以下是一个示例

CONFIG_EXTRA_FIRMWARE="intel-ucode/06-3a-09 amd-ucode/microcode_amd_fam15h.bin"
CONFIG_EXTRA_FIRMWARE_DIR="/lib/firmware"

这基本上意味着,你在本地具有以下树形结构

/lib/firmware/
|-- amd-ucode
...
|   |-- microcode_amd_fam15h.bin
...
|-- intel-ucode
...
|   |-- 06-3a-09
...

这样构建系统就可以找到这些文件并将它们集成到最终的内核镜像中。早期加载器会找到它们并进行应用。

不用说,这种方法不是最灵活的,因为它要求每当 CPU 厂商提供更新的微代码时都要重新构建内核。