2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ASUS X205TA 実機で UEFI アプリによる "Hello, World!" in Rust

Posted at

動機

みかん本(通称:ゼロからの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 から起動するように設定変更しておくこと)

...

IMG_1121_.JPG

やったぜ:relaxed:

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.fdOVMF_VARS.fdが出来ておれば成功です。これを QEMU に読み込ませれば OK です。

今年中に USB マウス動かすところぐらいまでは進めたいな。
おわり。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?