29
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LinuxAdvent Calendar 2019

Day 21

Linuxカーネルがx86 microcodeを扱う処理について

Last updated at Posted at 2019-12-21

はじめに

お久しぶりです。@akachichonです。

さて、当初はルートファイルシステム周辺に関する記事を書こうとしましたが色々あり、今回はマイクロコードアップデートについて書きました。
なお、引用したソースコードもしくは確認に使った環境は、「Ubuntu Server 19.10」です。
Ubuntu Server19.10のソースコード取得方法については、補足「Ubuntu Linuxのカーネルソースコード取得方法」を参照してください。

ルートファイルシステムイメージについて

Ubuntu Server19.10のルートファイルシステムイメージは、/boot/initrd.img-5.3.0-24-genericです。ルートファイルシステムイメージはUbuntuに限らず、多くのLinuxディストリビューションで共通だと思います。
さて、中身を確認しましょう。ルートファイルシステムイメージ内のファイル一覧を見るだけなら、lsinitramfsを使うのが楽です。

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ディレクトリのみです。

cpio実行結果
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のドキュメントに行き当たりました。

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にあります。ファイル名称がそのまま過ぎて、とてもわかりやすいです。

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()です。

arch/x86/kernel/cpu/microcode/core.c
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レジスタに格納された情報」です。実装は以下の通りです。

arch/x86/include/asm/processor.h
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」だとわかります。

cpuid_eax_1.png

また、eaxに格納される情報は以下のフォーマットに従うことも分かります。
cpuid_eax_2.png

arch/x86/kernel/cpu/microcode/core.c
    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()は、おおよそ以下の関数呼び出し構成となっています。

calltree.png

それぞれの関数概要は以下の通りです。

関数名 概要
__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の簡単な解説と図」を参考にしてください。

arch/x86/kernel/cpu/microcode/intel.c
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);

パス名は、以下の通りです。

arch/x86/kernel/cpu/microcode/intel.c
static const char ucode_path[] = "kernel/x86/microcode/GenuineIntel.bin";

cpioイメージを検索する

find_cpio_data()は、先のパスに相当するファイルをRAM上のcpioイメージから探し、それが置かれている領域の先頭アドレスとサイズを返します。
これらの情報は、以下の構造体で表現されます。dataはデータブロックを含むファイルの先頭位置で、sizeはファイルサイズです。

include/linux/earlycpio.h
struct cpio_data {
    void *data;
    size_t size;
    char name[MAX_CPIO_FILE_NAME];
};

initrd内のファイルを調べ、適用すべきデータブロックを見つける

ファイル内の構成は以下の通りです。
genuineintel.png

Intel SDM Vol.3によると、個々のデータブロックのフォーマットは以下の通りです。

structure_of_microcode.png

データブロックのヘッダのうち、最も大切なパラメータは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に渡します。

arch/x86/kernel/cpu/microcode/intel.c
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」を参照してください。

wrmsr0x79.png

おわりに

駆け足でしたが、ルートファイルシステムイメージからデータブロックを探し、それを適用する仕組みについて見ていきました。
当初書きたいネタから脇道にそれることは多々あります。お仕事で脇道にそれるのは良くないのですが、プライベートで技術調査をするときにはそれが楽しいのです。やはりコードや文献を読んでいろいろな事柄を知ることは楽しいものですね。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にはお世話になりました。やはり必携です。また、まめだぬき氏の記事は日本語で読める大変貴重な記事だと思います。私も楽しく読ませて頂きました。
また、文中で引用したその他記事についても、著者の方々に改めてお礼申し上げます。

29
22
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?