はじめに
お久しぶりです。@akachichonです。
さて、当初はルートファイルシステム周辺に関する記事を書こうとしましたが色々あり、今回はマイクロコードアップデートについて書きました。
なお、引用したソースコードもしくは確認に使った環境は、「Ubuntu Server 19.10」です。
Ubuntu Server19.10のソースコード取得方法については、補足「Ubuntu Linuxのカーネルソースコード取得方法」を参照してください。
ルートファイルシステムイメージについて
Ubuntu Server19.10のルートファイルシステムイメージは、/boot/initrd.img-5.3.0-24-genericです。ルートファイルシステムイメージはUbuntuに限らず、多くのLinuxディストリビューションで共通だと思います。
さて、中身を確認しましょう。ルートファイルシステムイメージ内のファイル一覧を見るだけなら、lsinitramfsを使うのが楽です。
fyoshida@fyoshida:~/lab/initramfs$ cp /boot/initrd.img-5.3.0-24-generic .
fyoshida@fyoshida:~/lab/initramfs$ lsinitramfs initrd.img-5.3.0-24-generic
.
kernel
kernel/x86
kernel/x86/microcode
kernel/x86/microcode/AuthenticAMD.bin
kernel
(略)
var
var/lib
var/lib/dhcp
もう少し中身を確認したいので、cpioコマンドを使いinitramfsイメージを展開します。ところが、lsinitramfsで見たとおりのファイル群を展開できません。展開できたのは、kernelディレクトリのみです。
fyoshida@fyoshida:~/lab/initramfs$ cat initrd.img-5.3.0-24-generic | cpio -id
62 blocks
fyoshida@fyoshida:~/lab/initramfs$ ls
initrd.img-5.3.0-24-generic kernel
この理由を調べると、kernel.orgのドキュメントに行き当たりました。
The microcode is stored in an initrd file. During boot, it is read from it and loaded into the CPU cores.
The format of the combined initrd image is microcode in (uncompressed) cpio format followed by the (possibly compressed) initrd image. The loader parses the combined initrd image during boot.
なるほど。initrdのイメージは「combined initrd image(結合されたinitrd image)」なので、cpioコマンドでは最初のマイクロコードが含まれたcpioフォーマット部分のみが展開できたと推測できます。そういえば、マイクロコードについては相当あやふやな知識しかない事に気づきました。
そういうことで、今回のAdvent Calendarでは「Linuxがルートファイルシステム内のマイクロコードをどう扱うか」をネタにして記事を書きます。
なお、全国のAMDファンの皆様、ごめんなさい。今回はIntelのみを記事の範囲とします。
Intel SDMをもとにした説明
マイクロコードアップデートを知るためには、Intel SDM Vol.3 「9.11 MICROCODE UPDATE FACILITIES」の最初の数文が重要となります。まとめると以下のようになります。
- Intelが提供するデータブロックをプロセッサにロードすることによりエラッタを修正する機能があります。この機能のことをマイクロコードアップデートと呼びます。
- BIOSは、マイクロコードアップデートをシステム初期化時に行うための仕組みを提供する必要があります。
- この仕組み(update loader)を持つBIOSは、システム初期化時にアップデートをプロセッサにロードする責務があります。プロセッサにロードするステップは2段階です。最初のステップは必要なアップデートデータブロック(※1)をBIOSに渡すこと、2つめのステップはBIOSがアップデートデータブロックをプロセッサにロードすることです。
※1.「マイクロコードアップデートデータブロック」を示す単語として、以後「データブロック」を使います。
この文書では、最初のステップ「データブロックをBIOSに渡す」ためのLinux実装を記事にします。
Linuxカーネルのコードを読む
ソース群は、arch/x86/kernel/cpu/microcodeにあります。ファイル名称がそのまま過ぎて、とてもわかりやすいです。
fyoshida@fyoshida:~/source/ubuntu-kernel/linux-source-5.3.0$ ag microcode arch/x86/kernel/cpu/microcode/
amd.c core.c intel.c Makefile
始まりはload_ucode_bsp()です。
void __init load_ucode_bsp(void)
{
// 略
cpuid_1_eax = native_cpuid_eax(1);
cpuidとは、Intel SDMの言葉を借りると「To obtain processor identification information」な命令です。取得したい情報を示す番号をeaxレジスタにセットした上でcpuid命令を実行します。native_cpuid_eaxの引数は、取得したい情報を示す番号です。そして、戻り値は「cpuidを実行した結果のうち、eaxレジスタに格納された情報」です。実装は以下の通りです。
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
/* ecx is often an input as well as an output. */
asm volatile("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx)
: "memory");
}
#define native_cpuid_reg(reg) \
static inline unsigned int native_cpuid_##reg(unsigned int op) \
{ \
unsigned int eax = op, ebx, ecx = 0, edx; \
\
native_cpuid(&eax, &ebx, &ecx, &edx); \
\
return reg; \
}
以下、Intel SDMからの抜粋情報により、ここで取得する情報は「Type, Family, Model, Stepping ID」だとわかります。
また、eaxに格納される情報は以下のフォーマットに従うことも分かります。
switch (x86_cpuid_vendor()) {
case X86_VENDOR_INTEL:
if (x86_family(cpuid_1_eax) < 6)
return;
break;
ここでは、自身が動くCPUでマイクロコードアップデートをサポートするか判定しています。マイクロコードアップデートをサポートするCPUは、「P6以降」であることを思い出してください。ついで、load_ucode_intel_bsp()が呼ばれます。
void __init load_ucode_intel_bsp(void)
{
struct microcode_intel *patch;
struct ucode_cpu_info uci;
patch = __load_ucode_intel(&uci);
if (!patch)
return;
uci.mc = patch;
apply_microcode_early(&uci, true);
}
load_ucode_intel_bsp()は、おおよそ以下の関数呼び出し構成となっています。
それぞれの関数概要は以下の通りです。
関数名 | 概要 |
---|---|
__load_ucode_intel | 適用すべきデータブロックを探します |
load_builtin_intel_microcode | Firmware Loaderを用いて適用しようとするデータブロックの有無を判定します(※2) |
find_microcode_in_initrd | メモリ上に展開されたinitrdからデータブロックを含むファイルを検索します |
collect_cpu_info_early | cpuid命令などを用いて、自身が動作するCPUの情報を取得します |
scan_microcode | ファイルに含まれるデータブロック群から最も適切なデータブロックを見つけます |
apply_microcode_early | ここまでの処理で見つけたデータブロックをBIOSに渡します |
※2. Firmware Loaderについては、こちらの良記事が詳しいです。
なお、この私の記事では、「Firmware Loaderを用いて適用しようとするデータブロックはない」ケースを前提とします。
initrdからデータブロックを探す
ブートローダによって展開されたinitrdイメージの位置とサイズを取得します。
このRAM Diskイメージが、冒頭に書いた/boot/initrd.img-5.3.0-24-generic です。また、このイメージの先頭はcpioフォーマットで始まっています。よって、cpioフォーマットに従い、データブロックを含むファイルをパス名で検索します。
cpioフォーマットについては、手前味噌ですが私の書いた記事の「cpioの簡単な解説と図」を参考にしてください。
struct cpio_data find_microcode_in_initrd(const char *path, bool use_pa)
{
/* 略 */
size = (unsigned long)boot_params.ext_ramdisk_size << 32;
size |= boot_params.hdr.ramdisk_size;
if (size) {
start = (unsigned long)boot_params.ext_ramdisk_image << 32;
start |= boot_params.hdr.ramdisk_image;
start += PAGE_OFFSET;
}
/* 略 */
return find_cpio_data(path, (void *)start, size, NULL);
パス名は、以下の通りです。
static const char ucode_path[] = "kernel/x86/microcode/GenuineIntel.bin";
cpioイメージを検索する
find_cpio_data()は、先のパスに相当するファイルをRAM上のcpioイメージから探し、それが置かれている領域の先頭アドレスとサイズを返します。
これらの情報は、以下の構造体で表現されます。dataはデータブロックを含むファイルの先頭位置で、sizeはファイルサイズです。
struct cpio_data {
void *data;
size_t size;
char name[MAX_CPIO_FILE_NAME];
};
initrd内のファイルを調べ、適用すべきデータブロックを見つける
Intel SDM Vol.3によると、個々のデータブロックのフォーマットは以下の通りです。
データブロックのヘッダのうち、最も大切なパラメータはProcessor Signatureです。これは、該当マイクロコードアップデートの適用対象となるプロセッサ種別を表現した値で、「Extended family, extended model, type, family, model, stepping」から構成される値です。それぞれのデータブロックは特定のプロセッサ種別ごとにデザインされたものになります。
「特定のプロセッサ向けにデザインされている」ため、1つのデータブロックは1種類のプロセッサ種別に適用可能です。しかし、複数種類のプロセッサ種別に適用可能なデータブロックもあります。そんなときはどうするのでしょうか。
それを解決するのが、「Optional Extended Signature Table」です。平たく言いますと、このTableに2つ目以降の「適用可能なプロセッサ種別」が列挙されます。詳細は、Intel SDM Vol.3の「9.11.2 Optional Extended Signature Table」を読んでください。
また、ヘッダに関して押さえるべきポイントは以下の通りです。
- Data Sizeは純粋なマイクロコードのサイズです。サイズはDWORDの倍数となります。また、0が格納されている場合、ヘッダを除去した純粋なマイクロコードのサイズは2000byteになります。
- Total Sizeはヘッダのサイズ + 純粋なマイクロコードのサイズ + Optional Extended Signature Tableのサイズです。この値は1024の倍数となります。
- Update Revisionはデータブロックのバージョンです。
scan_microcode()の処理を一言で言えば、「メモリ上のGenuineIntel.binを先頭からパースし、動いているプロセッサ種別に適用可能なデータブロックのうち、最新のものを見つけ、そのアドレスとサイズを返す」です。Intel SDM Vol.3の「9.11.3 Processor Identification」も参考にしてコードを読まれると良いです。
データブロックをBIOSに渡す
いよいよデータブロックをBIOSに渡します。
static int apply_microcode_early(struct ucode_cpu_info *uci, bool early)
{
/* 略 */
/* write microcode via MSR 0x79 */
native_wrmsrl(MSR_IA32_UCODE_WRITE, (unsigned long)mc->bits);
native_wrmsrl()は何をしているのでしょうか。実は、Intel SDM Vol.3 「9.11.6 Microcode Update Loader」に対応した処理になります。これでデータブロックをBIOSに渡すことができます。なお、詳しい条件については、Intel SDM Vol.3 「9.11.6 Microcode Update Loader」を参照してください。
おわりに
駆け足でしたが、ルートファイルシステムイメージからデータブロックを探し、それを適用する仕組みについて見ていきました。
当初書きたいネタから脇道にそれることは多々あります。お仕事で脇道にそれるのは良くないのですが、プライベートで技術調査をするときにはそれが楽しいのです。やはりコードや文献を読んでいろいろな事柄を知ることは楽しいものですね。Advent Calendarはそんな機会を与えてくれる良い機会だと思います。
それでは、今年もいろいろありましたが、残り少ない今年も、そして来年以降もHappy Hacking!
補足
Ubuntu Linuxのカーネルソースコード取得方法
少なくともUbuntu Server19.10の場合、aptコマンドでlinux-sourceを取得するのが一番楽です。ソースコードはtarで固められていますので、展開は必要です。
fyoshida@fyoshida:~$ sudo apt install linux-source
fyoshida@fyoshida:~$ cd /usr/src/linux-source-5.3.0/
fyoshida@fyoshida:/usr/src/linux-source-5.3.0$ ls
debian debian.master linux-source-5.3.0.tar.bz2
fyoshida@fyoshida:/usr/src/linux-source-5.3.0$ sudo tar xf linux-source-5.3.0.tar.bz2
fyoshida@fyoshida:/usr/src/linux-source-5.3.0$ ls
debian debian.master linux-source-5.3.0 linux-source-5.3.0.tar.bz2
fyoshida@fyoshida:/usr/src/linux-source-5.3.0/linux-source-5.3.0$ ls
arch COPYING Documentation fs ipc kernel MAINTAINERS net scripts sound update-version-dkms
block CREDITS drivers include Kbuild lib Makefile README security tools usr
certs crypto dropped.txt init Kconfig LICENSES mm samples snapcraft.yaml ubuntu virt
参考文献、ページ
全般的にIntel SDMにはお世話になりました。やはり必携です。また、まめだぬき氏の記事は日本語で読める大変貴重な記事だと思います。私も楽しく読ませて頂きました。
また、文中で引用したその他記事についても、著者の方々に改めてお礼申し上げます。