Boot Services

Memory management, event handling, and image loading during boot.

Boot Services Overview

Boot services are available only during the pre-boot phase:

flowchart TB
    subgraph boot["Boot Phase"]
        BS["Boot Services Available<br/>• Memory allocation<br/>• Protocol management<br/>• Event handling<br/>• Image loading<br/>• Timer services"]
    end

    BS -->|"ExitBootServices()"| RT

    subgraph runtime["Runtime Phase"]
        RT["Runtime Services Only<br/>(Boot Services NO LONGER AVAILABLE)"]
    end

Memory Services

Memory Types

Type Description After ExitBootServices
CONVENTIONAL Free memory for OS use Available
LOADER_CODE Boot loader code Available
LOADER_DATA Boot loader data Available
BOOT_SERVICES_CODE UEFI boot code Available
BOOT_SERVICES_DATA UEFI boot data Available
RUNTIME_SERVICES_CODE UEFI runtime code Reserved
RUNTIME_SERVICES_DATA UEFI runtime data Reserved
ACPI_RECLAIM ACPI tables Reclaimable
ACPI_NVS ACPI NVS memory Reserved
RESERVED Firmware reserved Reserved

Allocating Memory

use uefi::prelude::*;
use uefi::table::boot::{AllocateType, MemoryType};

fn allocate_memory(bt: &BootServices) -> uefi::Result<*mut u8> {
    // Allocate pages (4KB each)
    let num_pages = 10;
    let address = bt.allocate_pages(
        AllocateType::AnyPages,
        MemoryType::LOADER_DATA,
        num_pages,
    )?;

    log::info!("Allocated {} pages at {:#x}", num_pages, address);

    Ok(address as *mut u8)
}

fn free_memory(bt: &BootServices, address: u64, pages: usize) -> uefi::Result {
    unsafe {
        bt.free_pages(address, pages)?;
    }
    Ok(())
}

Pool Allocation

For smaller, arbitrary-sized allocations:

fn allocate_pool(bt: &BootServices) -> uefi::Result<*mut u8> {
    // Allocate arbitrary bytes
    let size = 1024;
    let ptr = bt.allocate_pool(MemoryType::LOADER_DATA, size)?;

    log::info!("Allocated {} bytes at {:?}", size, ptr);

    Ok(ptr)
}

fn free_pool(bt: &BootServices, ptr: *mut u8) -> uefi::Result {
    unsafe {
        bt.free_pool(ptr)?;
    }
    Ok(())
}

Memory Map

The memory map describes all physical memory regions:

use uefi::table::boot::{MemoryDescriptor, MemoryType};

fn print_memory_map(bt: &BootServices) -> uefi::Result {
    // Get required size
    let map_size = bt.memory_map_size();
    let buffer_size = map_size.map_size + 2 * map_size.entry_size;

    // Allocate buffer
    let mut buffer = alloc::vec![0u8; buffer_size];

    // Get memory map
    let (map_key, descriptors) = bt.memory_map(&mut buffer)?;

    log::info!("Memory Map (key: {:?}):", map_key);

    for desc in descriptors {
        let start = desc.phys_start;
        let pages = desc.page_count;
        let end = start + pages * 4096;
        let attr = desc.att;

        log::info!(
            "  {:#012x}-{:#012x} ({:4} pages) {:?} {:?}",
            start, end, pages, desc.ty, attr
        );
    }

    Ok(())
}

Memory for OS Handoff

When preparing to exit boot services:

fn prepare_for_os(st: SystemTable<Boot>) -> uefi::Result {
    // Allocate memory for OS use
    let bt = st.boot_services();

    // Get final memory map
    let map_size = bt.memory_map_size();
    let mut buffer = alloc::vec![0u8; map_size.map_size + 512];
    let (map_key, _descriptors) = bt.memory_map(&mut buffer)?;

    // Exit boot services - CRITICAL: No boot services after this!
    let (_runtime_table, _memory_map) = st.exit_boot_services(map_key);

    // Now only runtime services available
    // Cannot allocate memory, use protocols, etc.

    Ok(())
}

Event Services

Timer Events

use uefi::table::boot::{EventType, TimerTrigger, Tpl};

fn create_timer_event(bt: &BootServices) -> uefi::Result {
    // Create a timer event
    let event = unsafe {
        bt.create_event(
            EventType::TIMER | EventType::NOTIFY_SIGNAL,
            Tpl::CALLBACK,
            None,
            None,
        )?
    };

    // Set periodic timer (100ms intervals = 1,000,000 * 100ns)
    bt.set_timer(&event, TimerTrigger::Periodic(1_000_000))?;

    // Later: close the event
    // bt.close_event(event)?;

    Ok(())
}

Waiting for Events

fn wait_for_key(st: &mut SystemTable<Boot>) -> uefi::Result {
    let bt = st.boot_services();

    // Get the key event from stdin
    let key_event = st.stdin().wait_for_key_event()?;

    // Wait for the event
    let mut events = [key_event];
    let index = bt.wait_for_event(&mut events)?;

    log::info!("Event {} signaled", index);

    Ok(())
}

Multiple Events

fn wait_for_multiple(bt: &BootServices, events: &mut [Event]) -> uefi::Result<usize> {
    // Wait for any event in the array
    let index = bt.wait_for_event(events)?;

    log::info!("Event at index {} triggered", index);

    Ok(index)
}

Protocol Services

Locating Protocols

use uefi::proto::console::gop::GraphicsOutput;

fn find_gop(bt: &BootServices) -> uefi::Result<&mut GraphicsOutput> {
    // Locate protocol by type
    let gop = bt.locate_protocol::<GraphicsOutput>()?;

    log::info!("Found Graphics Output Protocol");

    Ok(gop)
}

Locating All Instances

use uefi::proto::media::block::BlockIO;

fn find_all_block_devices(bt: &BootServices) -> uefi::Result {
    // Get all handles with BlockIO protocol
    let handles = bt.locate_handle_buffer(
        SearchType::ByProtocol(&BlockIO::GUID)
    )?;

    log::info!("Found {} block devices", handles.len());

    for handle in handles.iter() {
        let block_io = bt.open_protocol_exclusive::<BlockIO>(*handle)?;

        let media = block_io.media();
        log::info!(
            "  Block size: {}, Blocks: {}",
            media.block_size,
            media.last_block + 1
        );
    }

    Ok(())
}

Opening Protocols

use uefi::table::boot::OpenProtocolParams;

fn open_protocol_safe<P: Protocol>(
    bt: &BootServices,
    handle: Handle,
) -> uefi::Result<ScopedProtocol<P>> {
    // Exclusive access - prevents other opens
    let protocol = bt.open_protocol_exclusive::<P>(handle)?;

    Ok(protocol)
}

Image Services

Loading Images

use uefi::proto::device_path::DevicePath;
use uefi::proto::loaded_image::LoadedImage;

fn load_and_start_image(
    bt: &BootServices,
    image_handle: Handle,
    path: &DevicePath,
) -> uefi::Result<Status> {
    // Load the image
    let loaded_handle = bt.load_image(
        image_handle,
        LoadImageSource::FromDevicePath {
            device_path: path,
            from_boot_manager: false,
        },
    )?;

    log::info!("Image loaded at handle {:?}", loaded_handle);

    // Get loaded image info
    let loaded_image = bt.open_protocol_exclusive::<LoadedImage>(loaded_handle)?;
    log::info!("Image base: {:?}", loaded_image.info().0);
    log::info!("Image size: {}", loaded_image.info().1);

    // Start the image
    let status = bt.start_image(loaded_handle)?;

    log::info!("Image returned with status {:?}", status);

    Ok(status)
}

Getting Current Image Info

fn get_image_info(bt: &BootServices, image_handle: Handle) -> uefi::Result {
    let loaded_image = bt.open_protocol_exclusive::<LoadedImage>(image_handle)?;

    let (base, size) = loaded_image.info();
    log::info!("Current image:");
    log::info!("  Base: {:?}", base);
    log::info!("  Size: {} bytes", size);

    // Get the device handle this image was loaded from
    if let Some(device) = loaded_image.device() {
        log::info!("  Loaded from device: {:?}", device);
    }

    Ok(())
}

Stall and Watchdog

Stalling (Delays)

fn delay_example(bt: &BootServices) {
    log::info!("Waiting 2 seconds...");

    // Stall for 2 seconds (2,000,000 microseconds)
    bt.stall(2_000_000);

    log::info!("Done waiting");
}

Watchdog Timer

fn configure_watchdog(bt: &BootServices) -> uefi::Result {
    // Set watchdog to 5 minutes (300 seconds)
    // If not reset, system will reboot
    bt.set_watchdog_timer(300, 0x10000, None)?;

    log::info!("Watchdog set to 5 minutes");

    // Disable watchdog
    bt.set_watchdog_timer(0, 0, None)?;

    log::info!("Watchdog disabled");

    Ok(())
}

TPL (Task Priority Level)

use uefi::table::boot::Tpl;

fn critical_section(bt: &BootServices) {
    // Raise TPL to prevent interrupts
    let old_tpl = bt.raise_tpl(Tpl::NOTIFY);

    // Critical code here - no interrupts or events
    // ...

    // Restore TPL
    unsafe {
        bt.restore_tpl(old_tpl);
    }
}

Complete Example

#![no_main]
#![no_std]

extern crate alloc;

use alloc::vec;
use uefi::prelude::*;
use uefi::table::boot::{MemoryType, AllocateType};

#[entry]
fn main(image: Handle, st: SystemTable<Boot>) -> Status {
    uefi::helpers::init().unwrap();

    let bt = st.boot_services();

    // Demonstrate memory allocation
    log::info!("=== Memory Allocation ===");

    // Allocate pages
    let pages = 4;
    match bt.allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, pages) {
        Ok(addr) => {
            log::info!("Allocated {} pages at {:#x}", pages, addr);

            // Use the memory
            unsafe {
                let ptr = addr as *mut u8;
                ptr.write_bytes(0xAA, pages * 4096);
            }

            // Free the memory
            unsafe {
                bt.free_pages(addr, pages).unwrap();
            }
            log::info!("Freed {} pages", pages);
        }
        Err(e) => log::error!("Allocation failed: {:?}", e),
    }

    // Print memory map summary
    log::info!("\n=== Memory Map Summary ===");
    let map_size = bt.memory_map_size();
    let mut buffer = vec![0u8; map_size.map_size + 256];

    if let Ok((_key, descriptors)) = bt.memory_map(&mut buffer) {
        let mut conventional = 0u64;
        let mut reserved = 0u64;

        for desc in descriptors {
            let bytes = desc.page_count * 4096;
            match desc.ty {
                MemoryType::CONVENTIONAL => conventional += bytes,
                MemoryType::RESERVED => reserved += bytes,
                _ => {}
            }
        }

        log::info!("Conventional: {} MB", conventional / 1024 / 1024);
        log::info!("Reserved: {} MB", reserved / 1024 / 1024);
    }

    // Demonstrate timer
    log::info!("\n=== Timer Demo ===");
    for i in (1..=3).rev() {
        log::info!("{}...", i);
        bt.stall(1_000_000);
    }
    log::info!("Done!");

    Status::SUCCESS
}

Summary

Service Purpose
allocate_pages Allocate page-aligned memory
allocate_pool Allocate arbitrary-sized memory
memory_map Get system memory map
locate_protocol Find protocol instance
wait_for_event Wait for event signals
stall Busy-wait delay
set_watchdog_timer Configure system watchdog
exit_boot_services Transition to runtime

Next Steps

Learn about Protocols for device and service interaction.


Back to top

Rust Programming Guide is not affiliated with the Rust Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.