# 微代码Microcode

所有现代CPU厂商都有设计和生产瑕疵的历史，从相关设计稳定性问题到潜在的安全漏洞。虽然大多数CPU的勘误表中列出的bug都被标记为"不修复"，Intel依然提供了能够通过microcode更新的方式提供CPU稳定性和安全性补丁。

microcode就是处理器厂商发布稳定性和安全性升级的处理器微代码。虽然微代码可以通过BIOS来升级，但是Linux内核也提供了一种通过在启动时更新的方法。这些更新提供了bug fix来确保系统的稳定性。没有这些更新，你可能会遇到奇怪的宕机或者异常的系统挂起，而且非常难以跟踪。

**对于使用Intel Haswell和Broadwell处理器系列的用户，必须要安装这些microcode来取保系统稳定。**

不幸的是，microcode更新格式是没有文档说明的（似乎没法获得microcode格式的理解方法），也无法清晰理解microcode补丁修复的具体bug。

## microcode更新工作原理

microcode更新是通过在一个称为`IA32_UCODE_WRITE`的特定型号寄存器（model-specific register, MSR）中写入intel提供的无文档说明的二进制内容的虚拟地址。这个私有的操作通常是在系统启动时通过系统BIOS来执行的，但是现代操作系统内核已经包含了支持microcode更新的功能。

BIOS（或操纵系统）在尝试WRMSR操作之前将校验匹配运行硬件的正确更新包。为了实现这个目的，每个microcode更新包的头部有一个更新元信息。这个文件头部在Intel开发手册卷3描述。包含3个相关校验信息：microcode修订，处理器签名和处理器标志。

microcode修订是一个递增的版本号 - 你只能更新更高版本的软件包（只能升级不能降级）。BIOS通常通过一个称为`IA32_UCODE_REV`的`RDMSR`调用来展开当前microcode订正，然后对比更新包头部的订正号。

处理器签名是一个microcode将要补丁的硬件型号的唯一标识。这个运行硬件的签名可以通过使用`CPUID`指令来获得，然后对比microcode头部的处理器卡名。对应Intel，"每个microcode更新是对于给定处理器家族，扩展模型，类型，型号等来签名"。处理器标志字段类似，Intel说"BIOS使用处理器标志为来结合MSR(17H)的平台ID来确定更新是否符合当前处理器"。

一旦microcode更新通过`IA32_UCODE_WRITE`完成，BIOS通常发出`CPUID`指令然后读取`IA32_UCODE_REV`的`MSR`。如果获得的修订版本号增加了，就表示补丁正确完成。

## 更新微码概述

从2008年开始，Intel周期性发布包含每个处理器的最新更新的DAT文件。在此之前，microcode更新数据包是通过开源工具`microcode_ctl`的一部分来发布的。

Red Hat Enterprise Linux系统有一个`microcode_ctl`软件包，提供了更新 `x86/x86-64` CPU microcode的工具，默认系统已经安装。这个软件包包含了操作系统发行版本更新的Intel microcode，文件存储位置在 `/lib/firmware/intel-ucode/` 目录下。（根据发行版本，也可能将更新的microcode存放在`/etc/firmware`目录）。未来版本会采用`kernel-firmware`方式来发布。(参考[microcode\_ctl: tool to update x86/x86-64 CPU microcode](https://fedorahosted.org/microcode_ctl/))

Intel驱动下载中心提供了[Linux Processor Microcode Data File](https://downloadcenter.intel.com/search?keyword=Linux+Processor+Microcode+Data+File)下载，可以及时更新Linux系统的处理器微代码。下载的微代码解压缩后是一个 `microcode.dat` 文件。

参考[Gentoo Linux wiki: Intel microcode](https://wiki.gentoo.org/wiki/Intel_microcode)，对于Intel Haswell处理器，要求先加载microcode，所以不能使用模块编译，要直接编译进内核，内核配置要求

```bash
CONFIG_MICROCODE_EARLY=y
```

micorcode需要通过`bootloader`加载

## 更新微码详解

内核有一个x86 microcode loader机制支持在OS内部加载微码。这样可以在主机厂商停止OEM支持之后继续更新微码，并且支持无需重启就更新微码。

x86 microcode loader支持3种加载模式：

### 更新微码方式一： Early load microcode

内核可以在启动过程的早期更新微码。Early load microcode 可以在内核启动时在出现CPU问题之前修复CPU错误。

Early load microcode是存储在`initrd`文件中，在内核启动时，从initrd文件读取微码加载到CPU核心。

在结合到initrd镜像中的microcode格式是非压缩的cpio格式，是跟随着initrd镜像（有可能压缩）。此时loader会在启动时处理这种结合格式的initrd镜像。

在cpio中的microcode命名：

```
on Intel: kernel/x86/microcode/GenuineIntel.bin
on AMD : kernel/x86/microcode/AuthenticAMD.bin
```

在BSP（BootStrapping Processor）启动过程（pre-SMP），内核扫描initrd中的微码文件。如果微码匹配主机的CPU，就会在BSP过程中应用到所有应用程序处理器（APs, Application Processors）。

loader也会保存匹配上的微码到内存中，这样，当CPU从睡眠状态恢复时，就会将缓存的微码补丁应用上去。

以下时一个如何处理initrd微码的脚本，不过通常是由发行版本来处理的，不需要自己执行：

```bash
#!/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`目录，或者直接从处理器厂商网站下载微码。

### 更新微码方式二： late loading

有两种传统的用户空间接口可以用来加载微码，或者通过 `/dev/cpu/micorcode` 或者通过sysfs的文件入口 `/sys/devices/system/cpu/microcode/reload`。

当前`/dev/cpu/microcode`方式已经被废弃，因为它需要一个特殊的用户空间工具来实现。

较为简单的方法是，将发行版的微码包安装好以后，然后以`root`用户身份执行如下命令：

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

此时加载机制将查看 `/lib/firmware/{intel-ucode,amd-ucode}`目录，也就是默认发行版已经将微码存放在这些目录下。

也可以手工安装微码，如果拿到的是rpm包，可以使用以下命令将rpm解开：

```
rpm2cpio microcode_XXXX_el7.noarch.rpm | cpio -idmv
```

然后将解压缩之后`lib/firmware/XX-xx`子目录手工复制到 `/lib/firmware/intel-ucode` 目录下。

不过，此时微码尚未生效，需要执行一次

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

执行以后使用 `dmesg | grep microcode` 可以看到类似

```
...
[300889.237741] microcode: CPU0 sig=0x406f1, pf=0x1, revision=0xb000021
[300889.242919] microcode: CPU0 updated to revision 0xb00002e, date = 2018-04-19
...
```

此时通过`cat /proc/cpuinfo`可以看到升级微码前版本

```
microcode    : 0xb000021
```

升级后微码版本

```
microcode    : 0xb00002e
```

#### `late loading`更新微码的注意点

`late loading`更新微码是在用户空间实现的，所以系统启动后，会在较晚的时候加载。这可能会带来一个问题： 如果某个内核模块依赖于修复微码以后特性，并且内核模块先于microcode生效，则可能会引起系统异常。

### 更新微码方式三： Builtin microcode

loader也支持builtin microcode，也就是常规的嵌入firmware方式，即 `CONFIG_EXTRA_FIRMWARE`，当前仅支持64位系统。

以下是案例：

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

不过，上述方法不灵活，要求重新编译内核来升级微码，对于CPU厂商经常更新微码的情况，非常麻烦。

## 验证microcode在启动时已经更新

使用`dmesg`命令检查系统启动时是否更新了microcode

```bash
dmesg | grep microcode
```

显示输出类似如下

```bash
[    0.855704] microcode: CPU0 sig=0x206d7, pf=0x1, revision=0x710
[    0.855898] microcode: CPU1 sig=0x206d7, pf=0x1, revision=0x710
[    0.856194] microcode: CPU2 sig=0x206d7, pf=0x1, revision=0x710
...
[    0.860201] microcode: CPU22 sig=0x206d7, pf=0x1, revision=0x710
[    0.860403] microcode: CPU23 sig=0x206d7, pf=0x1, revision=0x710
[    0.860635] microcode: Microcode Update Driver: v2.00 <tigran@aivazian.fsnet.co.uk>, Peter Oruba
```

使用命令 `cat /proc/cpuinfo` 可以看到cpu信息

```bash
processor    : 23
vendor_id    : GenuineIntel
cpu family    : 6
model        : 45
model name    : Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
stepping    : 7
microcode    : 0x710
cpu MHz        : 2299.910
```

其中`microcode`版本显示`0x710`就是操作系统启动时加载的microcode更新的版本。

### 启动时microcode更新阶段

操作系统启动时，微码更新可能会分2个阶段，例如微码`0x200004a`刷新可以看到分为`updated early`阶段，大约时启动时0.6秒\~2秒内，到7秒时候开始第二阶段：

```
#dmesg | grep microcode
[    0.000000] CPU0 microcode updated early to revision 0x200004a, date = 2018-03-28
[    0.664059] CPU1 microcode updated early to revision 0x200004a, date = 2018-03-28
...
[    1.975245] CPU47 microcode updated early to revision 0x200004a, date = 2018-03-28
...
[    7.053195] microcode: CPU0 sig=0x50654, pf=0x80, revision=0x200004a
[    7.059795] microcode: CPU1 sig=0x50654, pf=0x80, revision=0x200004a
```

## 参考

* [Notes on Intel Microcode Updates](http://inertiawar.com/microcode/) - 这篇文档非常详尽，建议阅读
* [ArchLinux wiki: Microcode](https://wiki.archlinux.org/index.php/microcode)
* [Intel microcode](https://wiki.gentoo.org/wiki/Intel_microcode)
* [linux/Documentation/x86/microcode.txt](https://github.com/torvalds/linux/blob/master/Documentation/x86/microcode.txt)
* [How to update CPU microcode in Linux](https://www.pcsuggest.com/update-cpu-microcode-in-linux/)
* [archlinux: Microcode](https://wiki.archlinux.org/index.php/microcode)
* [debian: Microcode](https://wiki.debian.org/Microcode)
* [gentoo linux: Intel microcode](https://wiki.gentoo.org/wiki/Intel_microcode)
* [How to load new Intel microcode](https://www.linuxquestions.org/questions/slackware-14/how-to-load-new-intel-microcode-4175621053/) - 有关处理Slackware 64平台的microcode，解决Meltdown和Spectre


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huataihuang.gitbook.io/cloud-atlas-draft/os/linux/kernel/cpu/microcode.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
