Easyboot互換カーネルの作成
==========================

[Easyboot](https://gitlab.com/bztsrc/easyboot) は、[プラグイン](plugins.md) を使用してさまざまなカーネルをサポートします。ただし、
適切なプラグインが見つからない場合は、Multiboot2 プロトコルの簡略化された (何も埋め込む必要がない) バリアントを使用して、ELF64 または PE32+
バイナリにフォールバックします。

これは、[Simpleboot](https://gitlab.com/bztsrc/simpleboot) が使用するのとまったく同じプロトコルであり、そのリポジトリ内のすべてのサンプルカーネルは
**Easyboot** でも動作するはずです。

GRUB のリポジトリにあるオリジナルの multiboot2.h ヘッダー、または使いやすい typedef を備えた [easyboot.h](../../easyboot.h) C/C++ ヘッダー ファイルを使用できます。
低レベルのバイナリ形式は同じで、C 以外の言語でも、この [Rust](https://github.com/rust-osdev/multiboot2/tree/main/multiboot2/src) ライブラリなどの既存の
Multiboot2 ライブラリを使用できます (注: 私はこれらの開発者とは一切関係ありません。単に「Rust Multiboot2」を検索して、それが最初の結果だっただけです)。

[[_TOC_]]

ブートシーケンス
----------------

### ローダーのブートストラップ

*BIOS* マシンでは、ディスクの最初のセクターがファームウェアによって 0:0x7C00 にロードされ、制御が渡されます。このセクターには、 **Easyboot** の
[boot_x86.asm](../../src/boot_x86.asm) があり、これは第 2 ステージ ローダーを見つけてロードし、そのローダーにロング モードを設定するのに十分なほどスマートです。

*UEFI* マシンでは、 `EFI/BOOT/BOOTX64.EFI` と呼ばれるまったく同じ第 2 ステージ ファイルがファームウェアによって直接ロードされます。このローダーのソースは
[loader_x86.c](../../src/loader_x86.c) にあります。つまり、 **Easyboot** は GRUB でも syslinux でもありません。どちらもディスク上に多数のシステム ファイルを必要とします。
ここではこれ以上のファイルは必要ありません。必要なのはこれだけです (プラグインはオプションで、Multiboot2 互換性を提供するために必要なものはありません)。

*Raspberry Pi* では、ローダーは `KERNEL8.IMG` と呼ばれ、[loader_rpi.c](../../src/loader_rpi.c) からコンパイルされます。

### ローダー

このローダーは、複数の構成で動作するように非常に注意深く作成されています。ディスクから GUID パーティション テーブルをロードし、「EFI システム パーティション」
を検索します。見つかったら、そのブート パーティションで `easyboot/menu.cfg` 構成ファイルを検索します。ブート オプションが選択され、カーネルのファイル名が判明すると、
ローダーはそれを見つけてロードします。

次に、カーネルのフォーマットを自動検出し、何をどこにロードするかに関するセクションとセグメントの情報を解釈するのに十分なスマートさを備えています
(必要な場合はいつでもオンデマンドのメモリ マッピングを行います)。次に、検出されたブート プロトコル (Multiboot2 / Linux などの保護モードまたはロング モード、
ABI 引数など) に応じて適切な環境を設定します。マシンの状態が安定して明確に定義された後、最後の動作として、ローダーはカーネルのエントリ ポイントにジャンプします。

マシンの状態
------------

汎用レジスタを除き、[Multiboot2 仕様](https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html) に記述されているマシン状態に関する内容はすべて有効です。
**Easyboot** は、SysV ABI と Microsoft fastcall ABI に従って、カーネルのエントリ ポイントに 2 つの引数を渡します。最初のパラメーターはマジックで、2
番目は物理メモリ アドレスで、Multiboot Information タグリスト (以下、MBI と略記、以下を参照) を指します。

また、上位半分のカーネルを処理するために、Multiboot2 プロトコルに少し違反しています。Multiboot2 では、メモリが必ず ID マップされることが義務付けられています。
まあ、 **Easyboot** では、これは部分的にしか当てはまりません。すべての物理 RAM が確実に期待どおりに ID マップされることを保証するだけです。ただし、
それより上の一部の領域 (カーネルのプログラム ヘッダーによって異なります) はまだ利用できる可能性があります。これは、
使用可能な物理 RAM の外部のメモリにアクセスしないことになっている通常の Multiboot2 準拠カーネルに悪影響を及ぼしません。

カーネルは、BIOS と UEFI システムの両方、および RPi でまったく同じ方法でロードされます。ファームウェアの違いは、単に「他人の問題」です。カーネルが認識するのは、
MBI に EFI システム テーブル タグが含まれているかどうかだけです。作業を簡素化するために、 **Easyboot** は EFI メモリ マップ (タイプ 17) タグも生成せず、
すべてのプラットフォームで [メモリ マップ](#memory_map) (タイプ 6) タグのみを無差別に提供します (UEFI システムでも、メモリ マップは単純に変換されるため、
カーネルは 1 種類のメモリ リスト タグのみを処理する必要があります)。古い、廃止されたタグも省略され、このブート マネージャーによって生成されることはありません。

カーネルはスーパーバイザー レベル (x86 ではリング 0、ARM では EL1) で実行され、すべての CPU コアで並列に実行される可能性があります。

GDT は指定されていませんが、有効です。スタックは最初の 640k に設定され、下に向かって増加します (ただし、できるだけ早く、適切と思われるスタックに変更する必要があります)。
SMP が有効になっている場合、すべてのコアに独自のスタックがあり、コア ID はスタックの最上部にあります (ただし、cpuid / mpidr などを使用して、
通常のプラットフォーム固有の方法でコア ID を取得することもできます)。

IDT は未指定と見なしてください。IRQ、NMI、およびソフトウェア割り込みは無効です。ダミー例外ハンドラは、最小限のダンプを表示してマシンを停止するように設定されています。
これらは、独自の IDT とハンドラを設定する前にカーネルが大混乱に陥った場合の報告にのみ依存するべきです (できるだけ早く)。ARM では、
vbar_el1 は同じダミー例外ハンドラを呼び出すように設定されています (もちろん、異なるレジスタをダンプしますが)。

フレームバッファもデフォルトで設定されます。設定で解像度を変更できますが、指定しない場合はフレームバッファが設定されたままになります。

カーネルから戻らないことが重要です。メモリ内のローダーの任意の部分を自由に上書きできるため (MBI タグの処理が終わったらすぐに)、戻る場所がまったくありません。
"Der Mohr hat seine Schuldigkeit getan, der Mohr kann gehen."

カーネルに渡されるブート情報 (MBI)
----------------------------------

最初は明らかではありませんが、Multiboot2 仕様では実際には 2 つの完全に独立したタグ セットが定義されています。

- 最初のセットは、Multiboot2 準拠のカーネルにインライン化されるはずで、OS イメージの Multiboot2 ヘッダー (セクション 3.1.2) と呼ばれ、したがって *カーネルによって提供されます*。
  **Easyboot** はこれらのタグを気にせず、カーネルを解析することもありません。**Easyboot** では、カーネル ファイルに埋め込まれた特別な魔法のデータは必要ありません。
  ELF ヘッダーと PE ヘッダーで十分です。

- 2 番目のセットはブート時に動的に *カーネルに渡され*、**Easyboot** はこれらのタグのみを使用します。ただし、Multiboot2 が指定するすべてのタグが生成されるわけではありません
  (古いタグ、廃止されたタグ、またはレガシータグは単に省略されます)。これらのタグは MBI タグと呼ばれます。[ブート情報](https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html#Boot-information-format)
  (セクション 3.6) を参照してください。

注意: MBI タグの Multiboot2 仕様にはバグが多すぎます。GRUB のソース リポジトリにある multiboot2.h ヘッダー ファイルと一致する修正バージョンが以下にあります。

カーネルの最初のパラメータはマジック 0x36d76289 です (`rax`、`rcx`、`rdi` 内)。2 番目のパラメータ (`rbx`、`rdx`、`rsi` 内) を使用して MBI
タグを見つけることができます。ARM プラットフォームでは、マジックは `x0` にあり、アドレスは `x1` にあります。RISC-V および MIPS では、それぞれ `a0` と `a1`
が使用されます。このローダーが別のアーキテクチャに移植される場合は、常に関数引数に SysV ABI で指定されたレジスタを使用する必要があります。SysV ABI
と干渉しない他の一般的な ABI がプラットフォーム上に存在する場合は、それらの ABI のレジスタ (またはスタックの最上部) でも値を複製する必要があります。

### ヘッダー

渡されるアドレスは常に 8 バイト境界に配置され、MBI ヘッダーで始まります。

```
        +-------------------+
u32     | total_size        |
u32     | reserved          |
        +-------------------+
```

これに続いて、8 バイトで整列された一連のタグが続きます。すべてのタグは次のタグ ヘッダー フィールドで始まります。

```
        +-------------------+
u32     | type              |
u32     | size              |
        +-------------------+
```

`type` には、タグの残りの内容の識別子が含まれます。`size` には、ヘッダー フィールドを含むがパディングを含まないタグのサイズが含まれます。
タグは、各タグが 8 バイトのアラインメント アドレスから開始されるように、必要に応じてパディングされて次々に続きます。

### ターミネーター

```
        +-------------------+
u32     | type = 0          |
u32     | size = 8          |
        +-------------------+
```

タグは、タイプ `0` およびサイズ `8` のタグで終了します。

### ブートコマンドライン

```
        +-------------------+
u32     | type = 1          |
u32     | size              |
u8[n]   | string            |
        +-------------------+
```

`string` には、*menuentry* の `kernel` 行で指定されたコマンドライン (カーネルのパスとファイル名なし) が含まれます。コマンドラインは、
通常の C スタイルのゼロ終了 UTF-8 文字列です。

### ブートローダー名

```
        +----------------------+
u32     | type = 2             |
u32     | size = 17            |
u8[n]   | string "Easyboot"    |
        +----------------------+
```

`string` にはカーネルを起動するブートローダーの名前が含まれます。名前は通常の C スタイルの UTF-8 ゼロ終了文字列です。

### モジュール

```
        +-------------------+
u32     | type = 3          |
u32     | size              |
u32     | mod_start         |
u32     | mod_end           |
u8[n]   | string            |
        +-------------------+
```

このタグは、カーネルイメージとともにロードされたブートモジュールとその場所をカーネルに示します。`mod_start` と `mod_end` には、
ブートモジュール自体の開始物理アドレスと終了物理アドレスが含まれます。**Easyboot** はそれらを透過的に解凍するため、gzip 圧縮されたバッファを取得することはありません
(プラグインを提供する場合は、gzip 圧縮以外のデータでも動作します)。`string` フィールドは、特定のブートモジュールに関連付けられる任意の文字列を提供します。
これは、通常の C スタイルのゼロ終端 UTF-8 文字列です。*menuentry* の `module` 行で指定され、その正確な使用方法はオペレーティングシステムに固有です。
ブートコマンドラインタグとは異なり、モジュールタグにはモジュールのパスとファイル名も *含まれます*。

モジュールごとに 1 つのタグが表示されます。このタグ タイプは複数回表示されることがあります。初期 RAM ディスクがカーネルとともにロードされた場合、
それが最初のモジュールとして表示されます。

特別なケースとして、ファイルが DSDT ACPI テーブル、FDT (dtb)、または GUDT ブロブである場合、モジュールとして表示されず、ACPI 旧 RSDP (タイプ 14) または
ACPI 新 RSDP (タイプ 15) がパッチされ、その DSDT がこのファイルの内容に置き換えられます。

### メモリマップ

このタグはメモリマップを提供します。

```
        +-------------------+
u32     | type = 6          |
u32     | size              |
u32     | entry_size = 24   |
u32     | entry_version = 0 |
varies  | entries           |
        +-------------------+
```

`size` には、このフィールド自体を含むすべてのエントリのサイズが含まれます。`entry_size` は常に 24 です。`entry_version` は `0` に設定されます。
各エントリの構造は次のとおりです。

```
        +-------------------+
u64     | base_addr         |
u64     | length            |
u32     | type              |
u32     | reserved          |
        +-------------------+
```

`base_addr` は開始物理アドレスです。`length` はメモリ領域のサイズ (バイト単位) です。`type` は表されるアドレス範囲の種類です。`1` の値は使用可能な RAM、
`3` の値は ACPI 情報を保持する使用可能なメモリ、`4` の値は休止状態で保持する必要がある予約済みメモリ、`5` の値は欠陥のある RAM モジュールによって占有されているメモリ、
その他の値はすべて現在予約済み領域であることを示します。`reserved` は BIOS ブート時に `0` に設定されます。

MBI が UEFI マシンで生成されると、さまざまな EFI メモリ マップ エントリがタイプ `1` (使用可能な RAM) または `2` (予約済み RAM) として保存され、必要に応じて、
元の EFI メモリ タイプが `reserved` フィールドに配置されます。

提供されるマップは、通常の使用に使用できるすべての標準 RAM をリストすることが保証されており、常に `base_addr` の昇順で並べられています。ただし、
この使用可能な RAM タイプには、カーネル、mbi、セグメント、およびモジュールによって占有される領域が含まれます。カーネルは、これらの領域を上書きしないように注意する必要があります
(**Easyboot** はこれらの領域を簡単に除外できますが、Multiboot2 との互換性が損なわれます)。

### フレームバッファ情報

```
        +----------------------------------+
u32     | type = 8                         |
u32     | size = 38                        |
u64     | framebuffer_addr                 |
u32     | framebuffer_pitch                |
u32     | framebuffer_width                |
u32     | framebuffer_height               |
u8      | framebuffer_bpp                  |
u8      | framebuffer_type = 1             |
u16     | reserved                         |
u8      | framebuffer_red_field_position   |
u8      | framebuffer_red_mask_size        |
u8      | framebuffer_green_field_position |
u8      | framebuffer_green_mask_size      |
u8      | framebuffer_blue_field_position  |
u8      | framebuffer_blue_mask_size       |
        +----------------------------------+
```

フィールド `framebuffer_addr` には、フレームバッファの物理アドレスが含まれます。フィールド `framebuffer_pitch` には、1 行の長さがバイト単位で含まれます。
フィールド `framebuffer_width`、`framebuffer_height` には、フレームバッファの寸法がピクセル単位で含まれます。フィールド `framebuffer_bpp` には、
ピクセルあたりのビット数が含まれます。現在の仕様バージョンでは、`framebuffer_type` は常に 1 に設定され、`reserved` には常に 0 が含まれ、
OS イメージでは無視される必要があります。残りのフィールドは、パックされたピクセル形式、チャネルの位置とサイズをビット単位で記述します。
式 `((~(0xffffffff << size)) << position) & 0xffffffff` を使用して、UEFI GOP のようなチャネル マスクを取得できます。

### EFI 64 ビット システム テーブル ポインター

このタグは、 **Easyboot** が UEFI マシンで実行されている場合にのみ存在します。BIOS マシンでは、このタグは生成されません。

```
        +-------------------+
u32     | type = 12         |
u32     | size = 16         |
u64     | pointer           |
        +-------------------+
```

このタグにはEFI system tableへのポインターが含まれています。

### EFI 64 ビット イメージ ハンドル ポインター

このタグは、 **Easyboot** が UEFI マシンで実行されている場合にのみ存在します。BIOS マシンでは、このタグは生成されません。

```
        +-------------------+
u32     | type = 20         |
u32     | size = 16         |
u64     | pointer           |
        +-------------------+
```

このタグにはEFI image handleへのポインターが含まれています。通常はブートローダーイメージハンドルです。

### SMBIOS テーブル

```
        +-------------------+
u32     | type = 13         |
u32     | size              |
u8      | major             |
u8      | minor             |
u8[6]   | reserved          |
        | smbios テーブル   |
        +-------------------+
```

このタグには、SMBIOS テーブルのコピーとそのバージョンが含まれています。

### ACPI 旧 RSDP

```
        +-------------------+
u32     | type = 14         |
u32     | size              |
        | RSDPv1 のコピー   |
        +-------------------+
```

このタグには、ACPI 1.0 仕様に従って定義された RSDP のコピーが含まれています (32 ビット アドレスを使用)。

### ACPI 新 RSDP

```
        +-------------------+
u32     | type = 15         |
u32     | size              |
        | RSDPv2 のコピー   |
        +-------------------+
```

このタグには、ACPI 2.0 以降の仕様に従って定義された RSDP のコピーが含まれています (おそらく 64 ビット アドレスを使用します)。

これら (タイプ 14 および 15) は、`FACP` テーブルへのポインターを持つ `RSDT` または `XSDT` テーブルを指し、`FACP` テーブルには、マシンを説明する `DSDT`
テーブルへの 2 つのポインターが含まれています。**Easyboot** は、ACPI をサポートしないマシンでこれらのテーブルを偽装します。また、DSDT テーブル、FDT (dtb)、
または GUDT ブロブをモジュールとして提供する場合、**Easyboot** は、そのユーザー提供のテーブルを指すようにポインターを修正します。これらのテーブルを解析するには、
依存関係のない単一ヘッダーの [hwdet](https://gitlab.com/bztsrc/hwdet) ライブラリ (または肥大化した [apcica](https://github.com/acpica/acpica) および
[libfdt](https://github.com/dgibson/dtc)) を使用できます。

カーネル固有のタグ
------------------

`type` が 256 以上のタグは Multiboot2 仕様の一部ではありませんが、**Easyboot** によって提供されます。これらは、カーネルで必要な場合に、
オプションの [プラグイン](plugins.md) によってリストに追加されることがあります。

### EDID

```
        +-------------------+
u32     | type = 256        |
u32     | size              |
        | EDID のコピー     |
        +-------------------+
```

このタグには、EDID 仕様に従ってサポートされているモニター解像度リストのコピーが含まれています。

### SMP

```
        +-------------------+
u32     | type = 257        |
u32     | size              |
u32     | numcores          |
u32     | running           |
u32     | bspid             |
        +-------------------+
```

このタグは、`multicore` ディレクティブが指定された場合に表示されます。`numcores` にはシステム内の CPU コアの数が含まれます。`running` には正常に初期化され、
同じカーネルを並列で実行しているコアの数が含まれます。`bspid` には BSP コアの識別子 (x86 lAPIC id 上) が含まれるため、カーネルは AP を区別し、
それらに対して異なるコードを実行できます。すべての AP には独自のスタックがあり、スタックの上部には現在のコアの ID があります。

### パーティション識別子

```
        +-------------------+
u32     | type = 258        |
u32     | size = 24 / 40    |
u128    | bootuuid          |
u128    | rootuuid          |
        +-------------------+
```

このタグには、ブート パーティションとルート パーティションの GPT 内の一意の識別子フィールドが含まれます。ブート時に GUID パーティション テーブルが使用されない場合は、
`bootuuid` は `54524150-(デバイス コード)-(パーティション番号)-616F6F7400000000` として生成されます。

メモリレイアウト
----------------

### BIOSマシン

| 開始     | 終了    | 説明                                                            |
|---------:|--------:|-----------------------------------------------------------------|
|      0x0 |   0x400 | 割り込みベクターテーブル（使用可能、リアルモードIDT）           |
|    0x400 |   0x4FF | BIOS データ領域 (使用可能)                                      |
|    0x4FF |   0x500 | BIOS ブート ドライブ コード (おそらく 0x80、使用可能)           |
|    0x500 |   0x5A0 | SMPの同期データ（使用可能）                                     |
|    0x5A0 |  0x1000 | 例外ハンドラ スタック (IDT を設定した後に使用可能)              |
|   0x1000 |  0x8000 | ページング テーブル (ページング テーブルを設定した後に使用可能) |
|   0x8000 | 0x20000 | ローダー コードとデータ (IDT を設定した後に使用可能)            |
|  0x20000 | 0x40000 | 構成 + タグ (MBI 解析後に使用可能)                              |
|  0x40000 | 0x90000 | プラグインID; 上から下へ: カーネルのスタック                    |
|  0x90000 | 0x9A000 | Linuxカーネルのみ: zero page + cmdline                          |
|  0x9A000 | 0xA0000 | 拡張 BIOS データ領域 (触れない方が良い)                         |
|  0xA0000 | 0xFFFFF | VRAM および BIOS ROM (使用不可)                                 |
| 0x100000 |       x | カーネルセグメント、続いてモジュール、各ページが整列            |

### UEFI マシン

誰にも分かりません。UEFI はメモリを好きなように割り当てます。何でもありです。すべての領域は、メモリ マップにタイプ = 1 (`MULTIBOOT_MEMORY_AVAILABLE`)
および予約 = 2 (`EfiLoaderData`) として確実にリストされますが、これは排他的ではなく、他の種類のメモリもそのようにリストされる場合があります (たとえば、
ブート マネージャーの bss セクション)。

### Raspberry Pi

| 開始     | 終了    | 説明                                                                |
|---------:|--------:|---------------------------------------------------------------------|
|      0x0 |   0x500 | ファームウェアによって予約されています（触れない方が良い）          |
|    0x500 |   0x5A0 | SMPの同期データ（使用可能）                                         |
|    0x5A0 |  0x1000 | 例外ハンドラ スタック (VBAR を設定した後に使用可能)                 |
|   0x1000 |  0x9000 | ページング テーブル (ページング テーブルを設定した後に使用可能)     |
|   0x9000 | 0x20000 | ローダー コードとデータ (VBAR を設定した後に使用可能)               |
|  0x20000 | 0x40000 | 構成 + タグ (MBI 解析後に使用可能)                                  |
|  0x40000 | 0x80000 | ファームウェアが提供するFDT（dtb）; 上から下へ：カーネルのスタック  |
| 0x100000 |       x | カーネルセグメント、続いてモジュール、各ページが整列                |

最初の数バイトは [armstub](https://github.com/raspberrypi/tools/blob/master/armstubs/armstub8.S) 用に予約されています。コア 0 のみが起動するため、
アプリケーション プロセッサを起動するには、関数のアドレスをこの領域にある 0xE0 (コア 1)、0xE8 (コア 2)、0xF0 (コア 3) に書き込みます。
`multicore` ディレクティブが使用されている場合はこれは無関係で、すべてのコアがカーネルを実行します。

RPi ではネイティブにはサポートされていませんが、偽のテーブルを含む ACPI の古い RSDP (タイプ 14) タグが引き続き提供されます。`APIC` テーブルは、
使用可能な CPU コアの数をカーネルに伝えるために使用されます。スタートアップ関数のアドレスは、RSD PTR-> RSDT -> APIC -> cpu\[x].apic_id フィールドに格納されます
 (コア ID は cpu\[x].acpi_id に格納されます。BSP は常に cpu\[0].acpi_id = 0 および cpu\[0].apic_id = 0xD8 です。"acpi" と "apic" は非常によく似ているので注意してください)。

有効な FDT BLOB がファームウェアによって渡された場合、またはモジュールの 1 つが .dtb、.gud、または .aml ファイルである場合は、FADT (マジック `FACP` 付き) テーブルも追加されます。
このテーブルでは、DSDT ポインター (32 ビット、オフセット 40) は、提供されたフラット化されたデバイス ツリー BLOB を指しています。

ファームウェアではメモリ マップ機能は提供されていませんが、検出された RAM と MMIO 領域をリストするメモリ マップ (タイプ 6) タグも取得できます。これを使用して、
RPi3 と RPi4 で異なる MMIO のベース アドレスを検出できます。
