RustでUEFIのBIOSにHello World!を出す

# はじめに

UEFI仕様のBIOSに`Hello World!`を表示するブートファイルをRustで作成します。
今回使用した開発環境は以下です。

- CPUのアーキテクチャ：x86_64
- 開発環境のホストOS：Windows11
- 実行環境：devcontainer
- エミュレーター：QEMU

# 環境構築

実行環境にはvscodeの拡張機能「devcontainer」を使っています。
検証はしておりませんが、devcontainerを使わない場合でも必要なものをインストールすれば、各自の環境でも開発は可能と思われます。

devcontainerの説明は割愛し、設定ファイルのみ公開します。

```json:devcontainer.json
{
	"name": "Rust",
	"build": {
		"dockerfile": "Dockerfile"
	}
}
```

```docker:Dockerfile
FROM mcr.microsoft.com/devcontainers/rust:1-1-bullseye

RUN sudo apt update && apt upgrade -y && apt install qemu-system -y \
    && rustup target add x86_64-unknown-uefi && cargo install uefi-run
```

- エミュレーター「QEMU」をインストールするために`apt install qemu-system`します。
- RustでUEFIでの実行形式をターゲットとしてコンパイルするために`rustup target add x86_64-unknown-uefi`します。
- Rustランナーをインストールするために`cargo install uefi-run`します。

3つ目の`uefi-run`のインストールは任意ですが、こちらを使えばターミナルで`cargo run`を実行するだけで、自動的にQEMUでコンパイルしたブートファイルが起動します。

# 各種設定

devcontainerを起動後、Rustの設定をしていきます。
まずは`cargo init`を実行して、作業ディレクトリに`.cargo/config.toml`を追加してください。

```toml:.cargo/config.toml
[build]
target = "x86_64-unknown-uefi"

[target.x86_64-unknown-uefi]
runner = "uefi-run"
```

# 実装

`src`ディレクトリで必要なプログラムを実装していきます。

```rust:src/uefi.rs
use core::ffi::c_void;

pub type EfiStatus = usize;
pub type EfiHandle = *const c_void;

#[repr(C)]
struct EfiTableHeader {
    signature: u64,
    revision: u32,
    header_size: u32,
    crc32: u32,
    reserved: u32,
}

#[repr(C)]
pub struct EfiSystemTable {
    hdr: EfiTableHeader,
    firmware_vendor: *const u16,
    firmware_revision: u32,
    console_in_handle: EfiHandle,
    con_in: usize,
    console_out_handle: EfiHandle,
    con_out: *const EfiSimpleTextOutputProtocol,
}

impl EfiSystemTable {
    pub fn con_out(&self) -> &EfiSimpleTextOutputProtocol {
        unsafe { &(*self.con_out) }
    }
}

#[repr(C)]
pub struct EfiSimpleTextOutputProtocol {
    reset: unsafe fn(this: &Self, extended_verification: bool) -> EfiStatus,
    output_string: unsafe fn(this: &Self, string: *const u16) -> EfiStatus,
}

impl EfiSimpleTextOutputProtocol {
    pub fn reset(&self, extended_verification: bool) -> EfiStatus {
        unsafe { (self.reset)(self, extended_verification) }
    }

    pub fn output_string(&self, string: *const u16) -> EfiStatus {
        unsafe { (self.output_string)(self, string) }
    }
}
```

以下のUEFIの仕様書を参考にして、必要なプロトコルの呼び出し部分だけ実装しています。

https://uefi.org/specs/UEFI/2.10_A/

```rust:src/main.rs
#![no_std]
#![no_main]

mod uefi;

#[no_mangle]
pub fn efi_main(
    _image_handle: uefi::EfiHandle,
    system_table: &uefi::EfiSystemTable,
) -> uefi::EfiStatus {
    system_table.con_out().reset(false);
    system_table.con_out().output_string(
        [
            0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0,
        ]
        .as_ptr(),
    );

    loop {}
}

#[cfg(not(test))]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}
```

`0x48, 0x65, 0x6c, 0x6c, 0x6f, ...`はそれぞれ表示する一文字に対応しています。

# 実行

`cargo run`で実行して、しばらく待つと`Hello World!`が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3532049/2aea1ccf-c93c-e078-d8db-c78076b37973.png)

# おわりに
ソースコードの説明が少なくて申し訳ありませんが、気が向いたときに追加するかもしれません。
RustでのOS開発を進めていく予定ですので、進捗がありましたら共有いたします。

参考書

https://zero.osdev.jp/