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 } }