SeaBIOS Boot Sequence

About

この文書はSeaBIOSのコードリーディングをした際の記録です。 使用したコミットは 81ec9ec0bcf45df11fb7f98339ec9036b546fca0 です。

随時更新していきます。

更新日: 2026/11/14

起動

CPUコアが初期化されて最初に実行されるメモリアドレスは通常 0xFFF0 で、いわゆるリセットベクタと言われる領域です。 正確にはCode Segment(CS)レジスタが 0xFFFF で命令ポインタ(IP)が 0x0000 の状態になっています。 リアルモードでは機械語の実効アドレスを計算する際に、自動的にCSの値を4bit左シフトした値を加算しているため、最初に実行されるメモリアドレスは 0xFFF0 になります。

初手の命令は src/romlayout.S の reset_vector にある ljmpw $SEG_BIOS, $entry_post で、CSレジスタを入れ替えて、一気にエントリポイントまで飛びます。 飛び先は同じファイルの上の方にある entry_post で、ResumeかReboot(通常起動)かを判定してそれぞれのエントリポイントに飛んでいます。

通常起動の際は ENTRY_INTO32 _cfunc32flat_handle_post で、C言語で書かれたハンドラに飛んでいくようです。 ENTRY_INTO32 マクロは src/entryfunc.S にあります。 中身は transition32 を経由して指定した関数にジャンプする命令です。

transition32 自体はよくある16bit real modeから32bit protected modにジャンプするコードで、ここでは説明を省略します。 BIOSからIPLに飛んでくる際は16bit real modeになっていますが、SeaBIOS自体は32bit protected modeで動作するようです。

見落としがちなのは transition32 に飛ぶ前に movl $ BUILD_STACK_ADDR , %esp でスタックポインタにデフォルトスタックをセットしています。 すなわちBIOSが起動した時点でRAMは使用できる前提となっています。

コンピュータの起動時は様々なデバイスが初期化されておらず利用できない状態になっています。 これはメモリも同様で、通常メモリトレーニングと呼ばれる処理を行ってメモリコントローラとDRAMを初期化します。 これが行われるまでCPUは内部にある汎用レジスタか、通常キャッシュとして使われているSRAMだけを使用して処理を行う必要があります。 これらの処理は各メーカ独自の仕様に基づいており、今ではBIOSが呼ばれる前に完了していることになっています。 これはEDKIIやU-Bootでも同じ前提となっていて、リセットベクタをCPUが実行する時点ではDRAMは使用できる状態になっています。

話を戻して次に向かいます。 handle_post は src/post.c に定義されていて以下のようになっています。

void VISIBLE32FLAT
handle_post(void)
{
    if (!CONFIG_QEMU && !CONFIG_COREBOOT)
        return;

    serial_debug_preinit();
    debug_banner();

    // Check if we are running under Xen.
    xen_preinit();

    // Allow writes to modify bios area (0xf0000)
    make_bios_writable();

    // Now that memory is read/writable - start post process.
    dopost();
}

最初のifを見るに、SeaBIOSはQEMUかcoreboot専用になっているようです。 実機で動かす場合は実質coreboot一択のようですね。

最初にシリアルポートを初期化しています。ここでは飛ばして次のXenの初期化を見ます。 xen_preinit は src/fw/xen.c にあります。 XenのシグネチャをCPUID命令を使用して探しています。

シグネチャが見つかった場合は、 validate_info を呼んでいます。 この関数では INFO_PHYSICAL_ADDRESS を struct xen_seabios_info にキャストして検証しています。

struct xen_seabios_info {
    char signature[14]; /* XenHVMSeaBIOS\0 */
    u8 length;     /* Length of this struct */
    u8 checksum;   /* Set such that the sum over bytes 0..length == 0 */
    /*
     * Physical address of an array of tables_nr elements.
     *
     * Each element is a 32 bit value contianing the physical address
     * of a BIOS table.
     */
    u32 tables;
    u32 tables_nr;
    /*
     * Physical address of the e820 table, contains e820_nr entries.
     */
    u32 e820;
    u32 e820_nr;
} PACKED;

Xen環境の場合は INFO_PHYSICAL_ADDRESS 、4KiBの場所にXenの構造体があるようです。 この構造体には仮想マシンの情報が格納されているテーブルやメモリマップであるE820テーブルへのポインタがあります。 具体的な中身については後ほど見ていきます。

handle_post に戻ると make_bios_writable が呼ばれています。

// Make the 0xc0000-0x100000 area read/writable.
void
make_bios_writable(void)
{
    if (!CONFIG_QEMU || runningOnXen())
        return;

    dprintf(3, "enabling shadow ram\n");

    // At this point, statically allocated variables can't be written,
    // so do this search manually.
    int bdf;
    pci_ioconfig_foreachbdf(bdf, 0) {
        u32 vendev = pci_ioconfig_readl(bdf, PCI_VENDOR_ID);
        u16 vendor = vendev & 0xffff, device = vendev >> 16;
        if (vendor == PCI_VENDOR_ID_INTEL
            && device == PCI_DEVICE_ID_INTEL_82441) {
            make_bios_writable_intel(bdf, I440FX_PAM0);
            code_mutable_preinit();
            ShadowBDF = bdf;
            return;
        }
        if (vendor == PCI_VENDOR_ID_INTEL
            && device == PCI_DEVICE_ID_INTEL_Q35_MCH) {
            make_bios_writable_intel(bdf, Q35_HOST_BRIDGE_PAM0);
            code_mutable_preinit();
            ShadowBDF = bdf;
            return;
        }
    }
    dprintf(1, "Unable to unlock ram - bridge not found\n");
}

最初のifにあるようにこの処理はQEMU専用のようです。 QEMUではマシンタイプに応じてMemory Controller Hubが存在しているようです。 make_bios_writable_intel ではBARを見てQEMUの場合にのみ特殊な処理をして __make_bios_writable_intel を呼んでいます。

// Enable shadowing and copy bios.
static void
__make_bios_writable_intel(u16 bdf, u32 pam0)
{
    // Read in current PAM settings from pci config space
    union pamdata_u pamdata;
    pamdata.data32[0] = pci_ioconfig_readl(bdf, ALIGN_DOWN(pam0, 4));
    pamdata.data32[1] = pci_ioconfig_readl(bdf, ALIGN_DOWN(pam0, 4) + 4);
    u8 *pam = &pamdata.data8[pam0 & 0x03];

    // Make ram from 0xc0000-0xf0000 writable
    int i;
    for (i=0; i<6; i++)
        pam[i + 1] = 0x33;

    // Make ram from 0xf0000-0x100000 writable
    int ram_present = pam[0] & 0x10;
    pam[0] = 0x30;

    // Write PAM settings back to pci config space
    pci_ioconfig_writel(bdf, ALIGN_DOWN(pam0, 4), pamdata.data32[0]);
    pci_ioconfig_writel(bdf, ALIGN_DOWN(pam0, 4) + 4, pamdata.data32[1]);

    if (!ram_present)
        // Copy bios.
        memcpy(VSYMBOL(code32flat_start)
               , VSYMBOL(code32flat_start) + BIOS_SRC_OFFSET
               , SYMBOL(code32flat_end) - SYMBOL(code32flat_start));
}

pam0 はそれぞれ I440FX_PAM0 か Q35_HOST_BRIDGE_PAM0 となります。 どちらもPCI Configuration Spaceの中にあるようで、ここから pamdata_u で表現される8バイトの値を取り出します。

PAMはProgrammable Attribute Mapの略で、BIOS領域のメモリ属性の設定をしているようです。 (12th Generation Intel® Core™ Processor Datasheet Volume 2 of 2 Programmable Attribute Map 0 (PAM0_0_0_0_PCI) – Offset 80、リンク切れの可能性あり)

それぞれ8ビットの値で、Bit 0がWrite Lockで1にすると二度と書き込めなくなります。Bit 5/4が属性を設定するもので、 0b00 は DMIへのRWアクセス、 0b01 はReadがDRAMでWriteはDRAMに、 0b10 はReadがDMIで書き込みがDRAMに、 0b11 がDRAMへのアクセスになります。(TODO: DMIとは?)

PAM0が 0xF0000 から64KiB分、 PAM1からPAM6が 0xC0000 からそれぞれ32KiB分を担当するようです。 すべての領域をDRAM転送にした後、元々DRAMがなかった場合BIOSをDRAMに改めてコピーするようです。

BIOSがWritableになったあとは dopost が呼ばれます。 dopost では malloc の初期化をしています。大抵のシステムソフトウェアはまず利用できるメモリ領域を確認して動的メモリ確保をできるようにします。

QEMUの場合は qemu_preinit でRAMの検出が行われます。