23. Linux 微代码加载器

作者:

内核具有 x86 微代码加载工具,旨在提供操作系统中的微代码加载方法。潜在的用例包括在 OEM 寿命终止支持之外的平台上更新微代码,以及在无需重启的情况下在长时间运行的系统上更新微代码。

加载器支持三种加载方法

23.1. 早期加载微代码

内核可以在启动期间非常早的时候更新微代码。在内核启动时观察到 CPU 问题之前,早期加载微代码可以修复这些问题。

微代码存储在 initrd 文件中。在启动期间,它从中读取并加载到 CPU 内核中。

组合 initrd 映像的格式为:(未压缩的) cpio 格式的微代码,后跟(可能已压缩的)initrd 映像。加载器在启动期间解析组合的 initrd 映像。

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

在 Intel 上

kernel/x86/microcode/GenuineIntel.bin

在 AMD 上

kernel/x86/microcode/AuthenticAMD.bin

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

加载器还将 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 中,如果您的路径在其他位置和/或您已直接从处理器供应商的站点下载了它们,则需要修复上面的路径。

23.2. 后期加载

您只需安装发行版提供的微代码包并运行

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

作为 root 用户。

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

自内核 5.19 以来,默认情况下不启用后期加载。

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

23.3. 为什么后期加载很危险?

23.3.1. 同步所有 CPU

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

由于微代码也可以“模拟” MSR,因此在微代码更新进行期间,这些模拟的 MSR 会暂时消失。如果 SMT 同级线程恰好正在访问此类 MSR,则可能会导致不可预测的结果。通常的观察结果是,此类 MSR 访问会导致引发 #GP 以指示以前的访问不存在。

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

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

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

23.3.2. 机器检查

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

在某些 Intel 机器上,MCE 也会广播到系统中的所有线程。如果一个线程正在执行 WRMSR,则将在流程结束时发生 MCE。无论哪种方式,它们都会等待执行 wrmsr(0x79) 的线程在 MCE 处理程序中会合,并且如果系统中任何线程未能签入 MCE 会合,最终将关闭。

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

23.3.3. 系统管理中断

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

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

23.3.4. 不可屏蔽中断

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

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

23.3.5. 微代码适合后期加载吗?

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

这对于 Intel CPU 来说是正确的。

例如,考虑一个 CPU 的补丁级别为 1,并且更新为补丁级别 3。

在补丁 1 和补丁 3 之间,补丁 2 可能已弃用软件可见功能。

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

基本上,无法声明新的微代码更新适合后期加载。这是导致默认情况下不启用后期加载的另一个问题。

23.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 供应商提供更新的微代码时都重新构建内核。