動機
みかん本(通称:ゼロからのOS自作入門)を参考にしながら Rust でぼちぼちと自作 OS に取り組んでいます。QEMU でしか動かしてなかったので、実機で動かしたいなと思い立ち、大昔に買った ASUS X205TA というノート PC を引っ張り出してきました。X205TA は CPU が Intel ATOM Z3735F、メモリ 2G、eMMC 32GB という鬼スペックの PC です。しかもこいつは CPU アーキテクチャは x64 なのに、UEFI は 32bit という変わりもの。まあ、この PC なら文鎮になっても構わないので、実験台になってもらうことにしました。
開発機
Linux penguin on Chromebook x86_64(これも ASUS 製 PC。ASUS 好きですね。)
Rust の設定
toolchain
nightly を使います。rustc 1.67.0-nightly (e1d819583 2022-12-05)
rust-analyzer
VSCode + rust-analyzer で開発しています。下記設定を追加して#![no_std]
でエラーが出ないようにします。
"rust-analyzer.checkOnSave.allTargets": false
実装
kernel をロードするところはまだ未実装ですが、そのうち実装すると思うのでloader
という名前でクレートを作ります。
cargo new loader
loader/.cargo/config.toml
core クレートのためのおまじないと、target の指定です。
[unstable]
build-std = ["core"]
build-std-features = ["compiler-builtins-mem"]
[build]
target = "i686-unknown-uefi"
loader/src/uefi.rs
uefi-rs というクレートがありますが、UEFI Spec 見ながら自分でゴリゴリと書いていきます。やっぱ自分で書きたいですよね。何回かパディングサイズ間違いました(あるある)。短いので全部載せます。
use core::fmt;
pub struct EfiStatus(pub usize);
impl EfiStatus {
pub fn is_success(&self) -> bool {
self.0 == 0
}
}
#[repr(C)]
pub struct EfiSystemTable<'a> {
header: [u8; 24],
pub firmware_vendor: *const u16,
_padding0: [u8; 16],
pub con_out: &'a EfiSimpleTextOutputProtocol,
}
#[repr(C)]
pub struct EfiSimpleTextOutputProtocol {
_padding0: [u8; 4],
output_string: fn(&EfiSimpleTextOutputProtocol, *const u16) -> EfiStatus,
_padding1: [u8; 16],
clear_screen: fn(&EfiSimpleTextOutputProtocol) -> EfiStatus,
}
impl EfiSimpleTextOutputProtocol {
fn output_string(&self, string: *const u16) -> EfiStatus {
(self.output_string)(self, string)
}
pub fn clear_screen(&self) -> EfiStatus {
(self.clear_screen)(self)
}
fn write_char(&self, c: u8) {
let buf = [c as u16, 0];
self.output_string(buf.as_ptr());
}
}
pub struct TextWriter<'a>(&'a EfiSimpleTextOutputProtocol);
impl<'a> TextWriter<'a> {
pub fn new(stop: &'a EfiSimpleTextOutputProtocol) -> Self {
Self(stop)
}
}
impl<'a> fmt::Write for TextWriter<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
s.bytes().for_each(|c| {
if c.eq(&b'\n') {
self.0.write_char(b'\r');
}
self.0.write_char(c);
});
Ok(())
}
}
loader/src/main.rs
core::fmt::Write
を実装するだけでwriteln!
マクロが使えるのが便利すぎますね。
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
use core::arch::asm;
use core::fmt::Write;
use core::panic::PanicInfo;
mod uefi;
use uefi::*;
#[no_mangle]
pub extern "efiapi" fn efi_main(_image: *const u8, system_table: &EfiSystemTable) -> ! {
system_table.con_out.clear_screen();
let mut writer = TextWriter::new(system_table.con_out);
writeln!(&mut writer, "Hello, World!").unwrap();
loop {
unsafe { asm!("hlt") };
}
}
#[panic_handler]
fn panic_impl(_info: &PanicInfo) -> ! {
loop {
unsafe { asm!("hlt") };
}
}
結果
cargo build --release
して出来上がったtarget/i686-unknown-uefi/release/loader.efi
を USB メモリの/efi/boot/bootia32.efi
にコピーします。その USB を X205TA に差し込んで起動!(BIOS で USB から起動するように設定変更しておくこと)
...
やったぜ
QEMU
さすがに X205TA 実機でデバッグするのは大変なので、開発機の QEMU でも動くように環境を整えます。私の環境の Chromebook Linux だとmount /mnt
でエラーが出るのでmtools
を導入しました。
loader/run-qemu.sh
#!/bin/bash -ex
qemu-img create -f raw disk.img 200M
sudo mkfs.fat -n 'CHAOS' -s 2 -f 2 -R 32 -F 32 disk.img
mmd -i disk.img ::/EFI
mmd -i disk.img ::/EFI/BOOT
mcopy -i disk.img ./target/i686-unknown-uefi/release/loader.efi ::/EFI/BOOT/BOOTIA32.EFI
qemu-system-i386 \
-m 1G \
-drive if=pflash,format=raw,readonly=on,file=./ovmf/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=./ovmf/OVMF_VARS.fd \
-drive if=ide,index=0,media=disk,format=raw,file=disk.img \
-monitor stdio
OVMF
OVMF を 32bit 用に自分でビルドします。mikanos-build の手順に従っておれば、ビルド環境は整っているはずです。下記でビルドが始まります。
cd ~/edk2/OvmfPkg
./build.sh -a IA32 -b RELEASE -n 4
~/edk2/Build/OvmfIa32/RELEASE_GCC5/FV/
にOVMF_CODE.fd
とOVMF_VARS.fd
が出来ておれば成功です。これを QEMU に読み込ませれば OK です。
今年中に USB マウス動かすところぐらいまでは進めたいな。
おわり。