Skip to content

Commit

Permalink
Barebones EFI implementation for mach-virt/arm64
Browse files Browse the repository at this point in the history
This is a minimal implementation in Rust of those parts of the EFI spec
that are actually needed to boot a arm64 Linux kernel straight from QEMU's
mach-virt.

It uses the efiloader crate which implements the bare minimum of EFI's
boot and runtime services that are needed to boot a Linux kernel, UKI
image or GRUB/shim loader stage.

It is intended to be minimal and secure:
- file data is never copied around - the kernel image and initrd are
  DMA'd into their final location using QEMU's fwcfg device,
- the MMU is enabled straight out of reset, and all memory accesses and
  code execution occurs with the appropriate memory and permission
  attributes,
- written in Rust,
- only a reduced subset of boot and runtime services is implemented,
- ACPI and SMBIOS tables are obtained from QEMU fwcfg directly and not
  modified,

How to use:

- build using 'cargo build'
  (ELF file ends up in target/aarch64-unknown-linux-gnu/.../efilite)

- use 'objcopy -O binary <in> <out>' to convert the ELF file into a raw binary

- run using something like the below

$ qemu-system-aarch64 \
    -M virt -cpu max -smp 4 \
    -net none -nographic -m 900m -bios efilite.bin -kernel path/to/Image \
    -drive if=virtio,file=path/to/hda.xxx,format=xxx -initrd <initrd>
  • Loading branch information
ardbiesheuvel committed Nov 7, 2023
1 parent f624ec4 commit 7f37776
Show file tree
Hide file tree
Showing 16 changed files with 1,922 additions and 2 deletions.
12 changes: 12 additions & 0 deletions .cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[target.aarch64-unknown-linux-gnu]
rustflags = [
"-C", "relocation-model=static",
"-C", "linker-flavor=ld.lld",
"-C", "link-arg=-Tsrc/efilite.lds",
"-C", "link-arg=--orphan-handling=error",
"-C", "target-feature=+rand,+bti",
# "-Z", "branch-protection=bti"
]

[build]
target = "aarch64-unknown-linux-gnu"
20 changes: 20 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Rust

on:
push:
branches: [ "mach-virt" ]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- run: rustup target add aarch64-unknown-linux-gnu
- run: sudo apt-get update && sudo apt-get install -y lld
- name: Build
run: cargo build --verbose
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/target
*.bin
.*.swp
152 changes: 152 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,19 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
linked_list_allocator = "0.10.5"
log = { version = "0.4.14", features = ["max_level_debug", "release_max_level_warn"] }
mmio = "2.1.0"
fdt = "0.1.5"
aarch64-intrinsics = { git = "https://github.com/ardbiesheuvel/aarch64-intrinsics" }
spinning_top = "0.2.5"
once_cell = { version = "1.18.0", default-features = false, features = ["alloc"] }
efiloader = "0.0.1"
aarch64-paging = "0.5.0"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
#lto = true # breaks BTI
84 changes: 84 additions & 0 deletions src/console.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright 2022-2023 Google LLC
// Author: Ard Biesheuvel <[email protected]>

use core::fmt::Write;
use core::mem::MaybeUninit;
use core::ops::Range;
use fdt::node::FdtNode;
use log::{Metadata, Record};
use mmio::{Allow, Deny, VolBox};
use spinning_top::Spinlock;

pub struct DumbSerialConsole {
pub base: usize,
out: Spinlock<VolBox<u32, Deny, Allow>>,
}

struct DumbSerialConsoleWriter<'a> {
console: &'a DumbSerialConsole,
}

pub fn init(base: &Range<usize>) -> &'static DumbSerialConsole {
// Statically allocated so we can init the console before the heap
static mut CON: MaybeUninit<DumbSerialConsole> = MaybeUninit::uninit();

unsafe {
let v = VolBox::<u32, Deny, Allow>::new(base.start as *mut u32);
CON.write(DumbSerialConsole {
base: base.start,
out: Spinlock::new(v),
})
}
}

pub fn init_from_fdt_node(node: FdtNode) -> Option<&'static DumbSerialConsole> {
let reg = node.reg()?.nth(0)?;
let base = reg.starting_address as usize;
let size = reg.size?;
Some(init(&(base..base + size)))
}

impl DumbSerialConsole {
fn puts(&self, s: &str) {
let mut out = self.out.lock();

for b in s.as_bytes().iter() {
if *b == b'\n' {
out.write(b'\r' as u32);
}
out.write(*b as u32)
}
}
}

impl efiloader::SimpleConsole for DumbSerialConsole {
fn write_string(&self, s: &str) {
self.puts(s)
}

fn read_byte(&self) -> Option<u8> {
None
}
}

impl Write for DumbSerialConsoleWriter<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
Ok(self.console.puts(s))
}
}

impl log::Log for DumbSerialConsole {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= log::max_level()
}

fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut out = DumbSerialConsoleWriter { console: &self };
write!(&mut out, "efilite {} - {}", record.level(), record.args()).ok();
}
}

fn flush(&self) {}
}
Loading

0 comments on commit 7f37776

Please sign in to comment.