Creating a UEFI application

Install dependencies

Follow the Rust installation instructions to set up Rust.

Create a minimal application

Create an empty application and change to that directory:

cargo new my-uefi-app
cd my-uefi-app

Add a few dependencies:

cargo add log
cargo add uefi --features logger,panic_handler

to your Cargo.toml. The resulting Cargo.toml should look like that:

[dependencies]
log = "0.4.21"
uefi = { version = "0.33.0", features = [ "panic_handler", "logger" ] }

Replace the contents of src/main.rs with this:

#![no_main]
#![no_std]

use log::info;
use uefi::prelude::*;

#[entry]
fn main() -> Status {
    uefi::helpers::init().unwrap();
    info!("Hello world!");
    boot::stall(10_000_000);
    Status::SUCCESS
}

Walkthrough

Let's look a quick look at what each part of the program is doing, starting with the #![...] lines at the top:

#![allow(unused)]
#![no_main]
#![no_std]
fn main() {
}

This is some boilerplate that all Rust UEFI applications will need. no_main is needed because the UEFI application entry point is different from the standard Rust main function. no_std is needed to turn off the std library; the core and alloc crates can still be used.

Next up are some use lines. Nothing too exciting here; the uefi::prelude module is intended to be glob-imported, and exports a number of commonly-used macros, modules, and types.

#![allow(unused)]
fn main() {
use log::info;
use uefi::prelude::*;
}

Now we get to the UEFI application main function, and here things look a little different from a standard Rust program.

#[entry]
fn main() -> Status {

The main function in a UEFI application takes no arguments and returns a Status, which is essentially a numeric error (or success) code defined by UEFI. The main function must be marked with the #[entry] macro.

The first thing we do inside of main is initialize the helpers module, which initializes logging:

#![allow(unused)]
fn main() {
    uefi::helpers::init().unwrap();
}

Next we use the standard log crate to output "Hello world!". Then we call stall to make the system pause for 10 seconds. This just ensures you have enough time to see the output.

#![allow(unused)]
fn main() {
    info!("Hello world!");
    boot::stall(10_000_000);
}

Finally we return Status::SUCCESS indicating that everything completed successfully:

#![allow(unused)]
fn main() {
    Status::SUCCESS
}
}