Crate r_efi

source ·
Expand description

UEFI Reference Specification Protocol Constants and Definitions

This project provides protocol constants and definitions as defined in the UEFI Reference Specification. The aim is to provide all these constants as C-ABI compatible imports to rust. Safe rust abstractions over the UEFI API are out of scope of this project. That is, the purpose is really just to extract all the bits and pieces from the specification and provide them as rust types and constants.

While we strongly recommend using safe abstractions to interact with UEFI systems, this project serves both as base to write those abstractions, but also as last resort if you have to deal with quirks and peculiarities of UEFI systems directly. Therefore, several examples are included, which show how to interact with UEFI systems from rust. These serve both as documentation for anyone interested in how the system works, but also as base for anyone implementing safe abstractions on top.

§Target Configuration

Rust code can be compiled natively for UEFI systems. However, you are quite unlikely to have a rust compiler running in an UEFI environment. Therefore, you will most likely want to cross compile your rust code for UEFI systems. To do this, you need a target-configuration for UEFI systems. As of rust-1.61, upstream rust includes the following UEFI targets:

  • aarch64-unknown-uefi: A native UEFI target for aarch64 systems (64bit ARM).
  • i686-unknown-uefi: A native UEFI target for i686 systems (32bit Intel x86).
  • x86_64-unknown-uefi: A native UEFI target for x86-64 systems (64bit Intel x86).

If none of these targets match your architecture, you have to create the target specification yourself. Feel free to contact the r-efi project for help.

§Transpose Guidelines

The UEFI specification provides C language symbols and definitions of all its protocols and features. Those are integral parts of the specification and UEFI programming is often tightly coupled with the C language. For better compatibility to existing UEFI documentation, all the rust symbols are transposed from C following strict rules, aiming for close similarity to specification. This section gives a rationale on some of the less obvious choices and tries to describe as many of those rules as possible.

  • no enums: Rust enums do not allow random discriminant values. However, many UEFI enumerations use reserved ranges for vendor defined values. These cannot be represented with rust enums in an efficient manner. Hence, any enumerations are turned into rust constants with an accompanying type alias.

    A detailed discussion can be found in:

        commit 401a91901e860a5c0cd0f92b75dda0a72cf65322
        Author: David Rheinsberg <david.rheinsberg@gmail.com>
        Date:   Wed Apr 21 12:07:07 2021 +0200
    
            r-efi: convert enums to constants
    
  • no incomplete types: Several structures use incomplete structure types by using an unbound array as last member. While rust can easily represent those within its type-system, such structures become DSTs, hence even raw pointers to them become fat-pointers, and would thus violate the UEFI ABI.

    Instead, we use const-generics to allow compile-time adjustment of the variable-sized structures, with a default value of 0. This allows computing different sizes of the structures without any runtime overhead.

  • nullable callbacks as Option: Rust has no raw function pointers, but just normal Rust function pointers. Those, however, have no valid null value. The Rust ABI guarantees that Option<fn ...> is an C-ABI compatible replacement for nullable function pointers, with None being mapped to NULL. Hence, whenever UEFI APIs require nullable function pointers, we use Option<fn ...>.

  • prefer *mut over *const: Whenever we transpose pointers from the specification into Rust, we prefer *mut in almost all cases. *const should only be used if the underlying value is known not to be accessible via any other mutable pointer type. Since this is rarely the case in UEFI, we avoid it.

    The reasoning is that Rust allows coercing immutable types into *const pointers, without any explicit casting required. However, immutable Rust references require that no other mutable reference exists simultaneously. This is not a guarantee of const-pointers in C / UEFI, hence this coercion is usually ill-advised or even wrong.

    Lastly, note that *mut and *const and be as-casted in both directions without violating any Rust guarantees. Any UB concerns always stem from the safety guarantees of the surrounding code, not of the raw-pointer handling.

§Examples

To write free-standing UEFI applications, you need to disable the entry-point provided by rust and instead provide your own. Most target-configurations look for a function called efi_main during linking and set it as entry point. If you use the target-configurations provided with upstream rust, they will pick the function called efi_main as entry-point.

The following example shows a minimal UEFI application, which simply returns success upon invocation. Note that you must provide your own panic-handler when running without libstd. In our case, we use a trivial implementation that simply loops forever.

#![no_main]
#![no_std]

use r_efi::efi;

#[panic_handler]
fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[export_name = "efi_main"]
pub extern fn main(_h: efi::Handle, _st: *mut efi::SystemTable) -> efi::Status {
    efi::Status::SUCCESS
}

Modules§

  • UEFI Base Environment
  • Flat EFI Namespace
  • Human Interface Infrastructure (HII)
  • UEFI Protocols
  • UEFI System Integration
  • UEFI Vendor Protocols

Macros§

  • Annotate function with UEFI calling convention