UEFI Boot Servicesによるコンソール出力

UEFIアプリケーションから画面に文字列を表示するところまでを扱います。UEFIアプリケーションのビルド設定の記事で作成したmain()は成功ステータスを返すだけでしたが、これにBoot Servicesを使ったコンソール出力を追加し、QEMU+OVMFと実機の両方で表示を確認します。

Simple Text Output Protocol

UEFIでは、レガシーBIOSのようにINT 10hを呼んだりVGAテキストバッファへ直接書き込んだりするのではなく、ファームウェアが提供する「プロトコル」というインターフェース経由でコンソール出力を行います。文字出力を担うのは Simple Text Output Protocol で、System Table の ConOut フィールドからそのインスタンスへのポインタを取得できます。

このプロトコルが持つメソッドのうち、文字列描画に使うのは OutputString です。null終端されたUTF-16(LE)文字列を受け取り、現在のカーソル位置から描画します。UTF-16を要求するのは、UEFI全体の文字列表現がWindowsのAPI設計を踏襲しているためで、UEFIアプリケーションのビルド設定の記事build.zigのABI指定(.msvc)に触れた際のPE/COFF系の設計とも一貫しています。

Zigではstd.os.uefi.system_table.con_outがこのConOutに対応し、SimpleTextOutputProtocol.outputString()というラッパーメソッドとして呼び出せます。

コンソール文字列出力の実装

// boot/main.zig
const std = @import("std");
const uefi = std.os.uefi;

pub fn main() uefi.Status {
    const con_out = uefi.system_table.con_out.?;
    con_out.clearScreen() catch {};
    _ = con_out.outputString(std.unicode.utf8ToUtf16LeStringLiteral("Hello, zigos!\r\n")) catch {};

    while (true) {}
}

utf8ToUtf16LeStringLiteralはコンパイル時にUTF-8のソース文字列をUTF-16LEへ変換するZig標準ライブラリの関数で、実行時の変換処理を書かずに済みます。outputStringの戻り値は「未知のグリフに遭遇したか」を示すboolですが、今回は結果を無視しています(存在しない文字コードを渡すことはなく、実際に起こり得ないケースへのエラー処理は不要と判断しました)。

clearScreen()を呼んでいるのは表示崩れへの対処で、経緯は後述します。

文字列出力のあとはwhile (true) {}で無限ループに入り、処理を戻しません。UEFIアプリケーションは本来、処理が終わると呼び出し元のブートマネージャーに制御を返す仕様になっていますが、今回はまだ自作カーネルへのハンドオフを実装していないため、main()から戻ると表示直後にブートマネージャーの画面に切り替わってしまい、出力を確認できません。ハンドオフの実装は後の作業で扱うため、それまでの暫定的な措置です。

QEMU+OVMFでの表示確認

UEFIアプリケーションのビルド設定の記事での検証では、VMが実行中の状態のままであることしか確認できず、実際に文字列が描画されたかどうかまでは分かりませんでした。今回はコンソール出力そのものが実装対象なので、フレームバッファの内容を画像として取得し、直接確認しました。

取得した画像には、OVMFのブートマネージャーが出力するログ(BdsDxe: starting Boot0002 ...)に続けてHello, zigos!が表示されており、実装したコンソール出力が実際に実行されたことが確認できました。

実機での表示崩れと原因

QEMUでの確認後、実機(MousePro CR-I1U01)のUSBブートでも同様の手順で動作を確認したところ、次のような崩れが発生しました。

  • 現象Hello, zigos!の先頭のHだけが画面右端に表示され、そこで改行されたあとello, zigos!が次の行の先頭から表示される
  • 原因:QEMU+OVMFでは、ファームウェア自身の起動ログの最終行がちょうど改行で終わっていたため、outputStringを呼び出す時点でカーソルは行頭にありました。実機のファームウェア(AMI Aptio系)ではログの終端位置が異なり、カーソルが行の途中に残った状態でoutputStringが呼ばれたため、Hを出力した直後に行末に達して自動改行が発生したと考えられます
  • 対応outputStringの前にclearScreen()を呼ぶよう変更しました。Simple Text Output Protocolの仕様上、ClearScreenは画面をクリアするとともにカーソル位置を(0, 0)へリセットするため、ファームウェアがそれまでにどこまでログを出力していたかに依存せず、常に行頭から描画できます

この修正後、QEMU・実機の両方でHello, zigos!が折り返しなく表示されることを確認しました。ファームウェアごとに起動ログの出方が異なるという、実機ならではの差異が表面化した形です。

参考リンク