Rust で UEFI を使う最近の方法

uefi-rs を使うとかなりサクっと使えて良い。cargo build をするだけで UEFI Application が吐ける。 まず,cargo-xbuild をインストール。
$ cargo install xbuild
これは私が以前から使っていた xargo の fork のようだ。 以下のようにそれぞれ準備する。

x86_64-uefi.json
{
  "llvm-target": "x86_64-pc-windows-msvc",
  "target-endian": "little",
  "target-pointer-width": "64",
  "target-c-int-width": "32",
  "os": "uefi",
  "arch": "x86_64",
  "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
  "linker": "rust-lld",
  "linker-flavor": "lld-link",
  "pre-link-args": {
    "lld-link": [
      "/Subsystem:EFI_Application",
      "/Entry:efi_main"
    ]
  },
  "panic-strategy": "abort",
  "default-hidden-visibility": true,
  "executables": true,
  "position-independent-executables": true,
  "exe-suffix": ".efi",
  "is-like-windows": true,
  "emit-debug-gdb-scripts": false
}
Cargo.toml
cargo-features = ["edition"]

[package]
name = "uefi_app"
version = "0.1.0"
authors = ["foo "]

[dependencies]
uefi = { path = "../external/uefi-rs/" }
uefi-services = { path = "../external/uefi-rs/uefi-services" }
uefi-exts = { path = "../external/uefi-rs/uefi-exts" }

log = { version = "0.4", default-features = false }

src/main.rs
#![no_std]
#![no_main]

#![feature(slice_patterns)]
#![feature(alloc)]
#![feature(asm)]
#![feature(extern_prelude)]

extern crate uefi;
extern crate uefi_exts;
#[macro_use]
extern crate log;
#[macro_use]
extern crate alloc;

use uefi::prelude::*;
use uefi::table::boot::MemoryDescriptor;
use uefi::proto::console::gop::GraphicsOutput;
use uefi_exts::BootServicesExt;
use core::mem;
use alloc::vec::Vec;

#[no_mangle]
pub extern "C" fn efi_main(handle: uefi::Handle, system_table: &'static SystemTable) -> Status {
    uefi_services::init(system_table);
    let graphic_mode = system_table.stdout().modes().last().unwrap();
    system_table.stdout().set_mode(graphic_mode).expect("Failed");
    gop_init(system_table.boot);
    memmap(system_table.boot);
    loop{}
    return uefi::Status::Success;
}

fn gop_init(bt: &BootServices) {
    if let Some(mut gop_proto) = bt.find_protocol::() {
        let gop = unsafe { gop_proto.as_mut() };
        let mode = gop.modes()
            .find(|ref mode| {
                let mode_info = mode.info();
                mode_info.resolution() == (1280, 720)
            }).unwrap();
        gop.set_mode(&mode).expect("failed");
    } else {
        warn!("UEFI GOP is not supported");
    }
}

fn memmap(bt: &BootServices) {
    let map_size = bt.memory_map_size();
    let buffer_size = map_size + 8 * mem::size_of::();
    let mut buffer = Vec::with_capacity(buffer_size);

    unsafe {
        buffer.set_len(buffer_size);
    }

    let (_key, mut descriptor_iter) = bt.memory_map(&mut buffer)
        .expect("failed");

    assert!(descriptor_iter.len() > 0, "memory map is empty");

    let first_descriptor = descriptor_iter.next().unwrap();
    let physical_address_start = first_descriptor.phys_start;

    assert_eq!(physical_address_start, 0, "memory does not start at address 0x0");
}
以上のファイルが用意できたら次のコマンドでビルド
$ RUST_TARGET_PATH=`pwd` cargo xbuild --target x86_64-uefi
すると,build/target/x86_64-uefi/uefi_app.efi が生成される。 実行するときは UEFI の image である OVMF について OVMF_CODE.fd と OVMF_VARS.fd をそれぞれ用意し,次のような構成にする

uefi_app
   |
   +--- Cargo.toml
   |
   +--- x86_64-uefi.json
   |
   +--- OVMF_CODE.fd
   |
   +--- OVMF_VARS.fd
   |
   +--- src
   |     |
   |     +--- main.rs
   |
   +--- esp
         |
         +--- efi
               |
               +--- boot
                     |
                     +--- bootx64.efi (uefi_app.efi をコピー)
生成されたバイナリはプロジェクトディレクトリの esp/efi/boot/bootx64.efi として配置する。
qemu-system-x86_64 -nodefaults -vga std -machine q35,accel=kvm:tcg -m 1024 \
       -drive if=pflash,format=raw,file=OVMF_CODE.fd ,readonly=on \
       -drive if=pflash,format=raw,file=OVMF_VARS.fd ,readonly=on \
       -drive format=raw,file=fat:rw:esp