Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ jobs:
if: matrix.arch == 'x86_64'
- run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.arch }}-multiboot --microvm --release
if: matrix.arch == 'x86_64'
- run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.arch }}-uefi --esp
if: matrix.arch == 'x86_64'
- run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.arch }}-uefi --esp --release
if: matrix.arch == 'x86_64'
- run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.arch }}-uefi
if: matrix.arch == 'x86_64'
- run: cargo xtask ci qemu ${{ matrix.flags }} --target ${{ matrix.arch }}-uefi --release
Expand Down
20 changes: 7 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,7 @@ qemu-system-x86_64 \

#### UEFI Boot

For booting from UEFI, we have to set up an EFI system partition (ESP).
OVMF will automatically load and execute the loader if placed at `\EFI\BOOT\BOOTX64.EFI` in the ESP.
The Hermit application has to be next to the loader with the filename `hermit-app`.
You can set the ESP up with the following commands:

```bash
$ mkdir -p esp/EFI/BOOT
$ cp <LOADER> esp/EFI/BOOT/BOOTX64.EFI
$ cp <APP> esp/EFI/BOOT/hermit-app
```

Then, you can boot Hermit like this:
When using UEFI, you can boot Hermit in a similar way:

```bash
qemu-system-x86_64 \
Expand All @@ -66,9 +55,14 @@ qemu-system-x86_64 \
-display none -serial stdio \
-drive if=pflash,format=raw,readonly=on,file=<OVMF_CODE.fd> \
-drive if=pflash,format=raw,readonly=on,file=<OVMF_VARS.fd> \
-drive format=raw,file=fat:rw:esp
-kernel <LOADER> \
-initrd <APP>
```

This is possible thanks to a build-time transformation of the loader, applied when building using `xtask`.
If this transformation breaks, or if you are not building with `xtask`, you can instead make use of an EFI system partition.
Refer to a [previous version](https://github.com/hermit-os/loader/blob/4ba86e4048ce770b8584727f8fc0f1f8f1b7f510/README.md#uefi-boot) of this file for precise instructions.

#### No KVM

If you want to emulate x86-64 instead of using KVM, omit `-enable-kvm` and set the CPU explicitly to a model of your choice, for example `-cpu Skylake-Client`.
Expand Down
2 changes: 1 addition & 1 deletion src/os/uefi/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static ALLOCATOR: LockedAllocator = LockedAllocator::uefi();
pub fn exit_boot_services() {
assert!(matches!(*ALLOCATOR.0.lock(), GlobalAllocator::Uefi));

let mem = vec![MaybeUninit::uninit(); 4096].leak();
let mem = vec![MaybeUninit::uninit(); 0x2000].leak();

let bump = BumpAllocator::from(mem);

Expand Down
66 changes: 59 additions & 7 deletions src/os/uefi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ use hermit_entry::boot_info::{
};
use hermit_entry::elf::{KernelObject, LoadedKernel};
use log::{error, info};
use uefi::boot::{AllocateType, MemoryType, PAGE_SIZE};
use uefi::CString16;
use uefi::boot::{AllocateType, MemoryType, PAGE_SIZE, open_protocol_exclusive};
use uefi::fs::{self, FileSystem, Path};
use uefi::prelude::*;
use uefi::proto::loaded_image::LoadedImage;
use uefi::table::cfg::ConfigTableEntry;

pub use self::console::CONSOLE;
Expand All @@ -29,9 +31,18 @@ fn main() -> Status {
uefi::helpers::init().unwrap();
crate::log::init();

let mut esp = Esp::new().unwrap();

let kernel_image = esp.read_app();
let kernel_args = KernelArguments::new().unwrap();
let mut esp = BootPartition::new().unwrap();

let kernel_image = if let Some(path) = kernel_args
.as_ref()
.and_then(|arg| arg.initrd_path.as_ref())
{
esp.read_app_at(path.as_ref())
.expect("Could not open kernel image provided in initrd")
} else {
esp.read_app()
};
let kernel = KernelObject::parse(&kernel_image).unwrap();

let kernel_memory = alloc_page_slice(kernel.mem_size()).unwrap();
Expand All @@ -48,7 +59,9 @@ fn main() -> Status {
.rsdp(u64::try_from(rsdp.expose_provenance()).unwrap())
.unwrap();

if let Some(bootargs) = esp.read_bootargs() {
if let Some(kernel_args) = kernel_args {
fdt = fdt.bootargs(kernel_args.hermit_args).unwrap();
} else if let Some(bootargs) = esp.read_bootargs() {
fdt = fdt.bootargs(bootargs).unwrap();
}

Expand Down Expand Up @@ -122,11 +135,11 @@ fn rsdp() -> *const c_void {
})
}

pub struct Esp {
pub struct BootPartition {
fs: FileSystem,
}

impl Esp {
impl BootPartition {
pub fn new() -> uefi::Result<Self> {
let image_handle = boot::image_handle();
let fs = boot::get_image_file_system(image_handle)?;
Expand Down Expand Up @@ -186,3 +199,42 @@ impl Esp {
inner(&mut self.fs, path.as_ref())
}
}

/// Reads arguments passed when using Kernel Direct Boot (`-kernel -initrd` arguments with UEFI
/// support)
#[derive(Debug)]
struct KernelArguments {
/// Arguments that should be forwarded to Hermit
hermit_args: String,

/// Image path, overriding default option
initrd_path: Option<CString16>,
}

impl KernelArguments {
pub fn new() -> uefi::Result<Option<Self>> {
let image_handle = boot::image_handle();
let loaded_image = open_protocol_exclusive::<LoadedImage>(image_handle)?;
let Some(raw_options) = loaded_image.load_options_as_cstr16().ok() else {
return Ok(None);
};

let raw_options: String = raw_options.into();
let args = if let Some(rest) = raw_options.strip_prefix("initrd=") {
let (initrd, rest) = rest.split_once(' ').unwrap();
Self {
hermit_args: rest.into(),
initrd_path: Some(initrd.try_into().unwrap()),
}
} else {
Self {
hermit_args: raw_options,
initrd_path: None,
}
};

info!("Read QEMU kernel arguments: {args:?}");

Ok(Some(args))
}
}
10 changes: 10 additions & 0 deletions xtask/src/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ impl Artifact {
fn release_args(&self) -> &'static [&'static str] {
if self.release { &["--release"] } else { &[] }
}

/// Applies any required post-build transformation on this artifact
pub fn post_build(&self) {
if self.target != Target::X86_64Uefi {
return;
}

// EFI files need to be transformed slightly to allow booting with `-kernel` in QEMU
super::pe::PEFile::load_from_path(self.build_object().as_ref()).rewrite();
}
}

pub trait CmdExt {
Expand Down
2 changes: 2 additions & 0 deletions xtask/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ impl Build {
.cargo_build_args(&self.artifact)
.run()?;

self.artifact.post_build();

let build_object = self.artifact.build_object();
let dist_object = self.artifact.dist_object();
eprintln!(
Expand Down
44 changes: 42 additions & 2 deletions xtask/src/ci/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pub struct Qemu {
#[arg(long)]
u_boot: bool,

/// Run with an EFI System Partition.
/// Only used for x86_64-uefi
#[arg(long)]
esp: bool,

#[command(flatten)]
build: Build,

Expand Down Expand Up @@ -82,7 +87,7 @@ impl Qemu {
let sh = crate::sh()?;

match self.build.target() {
Target::X86_64Uefi => {
Target::X86_64Uefi if self.esp => {
sh.remove_path("target/esp")?;

// Spec: https://uefi.org/specs/UEFI/2.11/03_Boot_Manager.html#removable-media-boot-behavior
Expand Down Expand Up @@ -190,7 +195,7 @@ impl Qemu {
.unwrap(),
);
}
Target::X86_64Uefi => {
Target::X86_64Uefi if self.esp => {
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};

let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf")
Expand All @@ -211,6 +216,41 @@ impl Qemu {
cpu_args.push("-drive".to_string());
cpu_args.push("format=raw,file=fat:rw:target/esp".to_string());
}
Target::X86_64Uefi => {
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};

let prebuilt = Prebuilt::fetch(Source::LATEST, "target/ovmf")
.expect("failed to update prebuilt");
let code = prebuilt.get_file(Arch::X64, FileType::Code);
let vars = prebuilt.get_file(Arch::X64, FileType::Vars);

cpu_args.push("-drive".to_string());
cpu_args.push(format!(
"if=pflash,format=raw,readonly=on,file={}",
code.display()
));
cpu_args.push("-drive".to_string());
cpu_args.push(format!(
"if=pflash,format=raw,readonly=on,file={}",
vars.display()
));
cpu_args.push("-kernel".to_string());
cpu_args.push(
self.build
.dist_object()
.into_os_string()
.into_string()
.unwrap(),
);
cpu_args.push("-initrd".to_string());
cpu_args.push(
self.build
.ci_image(self.image.as_deref().unwrap())
.into_os_string()
.into_string()
.unwrap(),
);
}
_ => unreachable!(),
}
cpu_args
Expand Down
1 change: 1 addition & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod build;
mod ci;
mod clippy;
mod object;
mod pe;
mod target;

use std::env;
Expand Down
Loading