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 uefi

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

#![no_main]
#![no_std]

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

#[entry]
fn main(_image_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
    uefi::helpers::init(&mut system_table).unwrap();
    info!("Hello world!");
    system_table.boot_services().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 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(_image_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {

The main function in a Uefi application always takes two arguments, the image handle and the system table. The image handle represents the currently-running executable, and the system table provides access to many different UEFI services. The main function returns a Status, which is essentially a numeric error (or success) code defined by UEFI.

The first thing we do inside of main is initialize uefi_services:

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

The uefi_services crate is not strictly required to make a UEFI application with the uefi crate, but it makes things much simpler by setting a simple memory allocator, initializing the logger, and providing a panic handler.

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!");
    system_table.boot_services().stall(10_000_000);
}

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

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