Protocols

Working with UEFI protocols - the interface system for devices and services.

Understanding Protocols

Protocols are UEFI’s object-oriented interfaces:

flowchart TB
    subgraph handle["Handle"]
        A["Protocol A<br/>(BlockIO)"]
        B["Protocol B<br/>(DiskIO)"]
        C["Protocol C<br/>(DevicePath)"]
    end

Each protocol:

  • Has a unique GUID identifier
  • Provides a set of functions
  • Is associated with handles
  • Can have multiple instances (one per handle)

Common Protocols

Protocol GUID Purpose
SimpleTextOutput 387477c2-… Console output
SimpleTextInput 387477c1-… Console input
GraphicsOutput 9042a9de-… Graphics framebuffer
BlockIO 964e5b21-… Block device access
SimpleFileSystem 964e5b22-… File system access
LoadedImage 5b1b31a1-… Loaded image info
DevicePath 09576e91-… Device identification

Locating Protocols

Single Instance

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

fn find_graphics(bt: &BootServices) -> uefi::Result {
    // Find the first instance
    let gop = bt.locate_protocol::<GraphicsOutput>()?;

    log::info!("Found Graphics Output Protocol");
    log::info!("  Modes: {}", gop.modes().count());

    Ok(())
}

All Instances

use uefi::proto::media::block::BlockIO;
use uefi::table::boot::SearchType;

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

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

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

        let media = block_io.media();
        let size_mb = (media.last_block + 1) * media.block_size as u64 / 1024 / 1024;

        log::info!("  Device {}: {} MB, removable: {}",
            i, size_mb, media.removable_media);
    }

    Ok(())
}

Opening Protocols

Exclusive Access

fn open_exclusive(bt: &BootServices, handle: Handle) -> uefi::Result {
    // Exclusive access prevents others from using it
    let protocol = bt.open_protocol_exclusive::<BlockIO>(handle)?;

    // Protocol is automatically closed when dropped

    Ok(())
}

Scoped Protocol

fn use_protocol(bt: &BootServices, handle: Handle) -> uefi::Result {
    // ScopedProtocol manages lifetime
    let block_io = bt.open_protocol_exclusive::<BlockIO>(handle)?;

    // Use the protocol
    let media = block_io.media();
    log::info!("Block size: {}", media.block_size);

    // Automatically closed here
    Ok(())
}

SimpleTextOutput Protocol

Console text output:

use uefi::proto::console::text::{Output, Color};

fn text_output_demo(output: &mut Output) -> uefi::Result {
    // Clear screen
    output.clear()?;

    // Set colors
    output.set_color(Color::Yellow, Color::Blue)?;

    // Output text
    output.output_string(cstr16!("Yellow on Blue\r\n"))?;

    // Reset to default
    output.set_color(Color::LightGray, Color::Black)?;

    // Query modes
    for mode in output.modes() {
        let (cols, rows) = mode.columns();
        log::info!("Mode: {}x{}", cols, rows);
    }

    // Set a specific mode
    // output.set_mode(output.modes().next().unwrap())?;

    Ok(())
}

SimpleTextInput Protocol

Keyboard input:

use uefi::proto::console::text::{Input, Key, ScanCode};

fn text_input_demo(bt: &BootServices, input: &mut Input) -> uefi::Result {
    // Reset input
    input.reset(false)?;

    log::info!("Press a key...");

    // Wait for key event
    let event = input.wait_for_key_event()?;
    bt.wait_for_event(&mut [event])?;

    // Read the key
    if let Some(key) = input.read_key()? {
        match key {
            Key::Printable(c) => {
                let ch: char = c.into();
                log::info!("Pressed: '{}'", ch);
            }
            Key::Special(scan) => {
                match scan {
                    ScanCode::ESCAPE => log::info!("Escape pressed"),
                    ScanCode::UP => log::info!("Up arrow"),
                    ScanCode::DOWN => log::info!("Down arrow"),
                    _ => log::info!("Special key: {:?}", scan),
                }
            }
        }
    }

    Ok(())
}

BlockIO Protocol

Low-level block device access:

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

fn read_blocks(bt: &BootServices) -> uefi::Result {
    let handles = bt.locate_handle_buffer(
        SearchType::ByProtocol(&BlockIO::GUID)
    )?;

    if let Some(&handle) = handles.first() {
        let block_io = bt.open_protocol_exclusive::<BlockIO>(handle)?;

        let media = block_io.media();
        let block_size = media.block_size as usize;

        // Allocate buffer for one block
        let mut buffer = alloc::vec![0u8; block_size];

        // Read first block (LBA 0)
        block_io.read_blocks(media.media_id, 0, &mut buffer)?;

        log::info!("First block (first 16 bytes):");
        log::info!("  {:02x?}", &buffer[..16]);

        // Check for MBR signature
        if buffer[510] == 0x55 && buffer[511] == 0xAA {
            log::info!("  MBR signature found!");
        }
    }

    Ok(())
}

DiskIO Protocol

Higher-level disk access without block alignment:

use uefi::proto::media::disk::DiskIo;

fn read_disk(bt: &BootServices) -> uefi::Result {
    let handles = bt.locate_handle_buffer(
        SearchType::ByProtocol(&DiskIo::GUID)
    )?;

    if let Some(&handle) = handles.first() {
        let disk_io = bt.open_protocol_exclusive::<DiskIo>(handle)?;

        // Get media ID from BlockIO
        let block_io = bt.open_protocol_exclusive::<BlockIO>(handle)?;
        let media_id = block_io.media().media_id;

        // Read 512 bytes starting at offset 0
        let mut buffer = [0u8; 512];
        disk_io.read_disk(media_id, 0, &mut buffer)?;

        log::info!("Read {} bytes", buffer.len());
    }

    Ok(())
}

LoadedImage Protocol

Information about loaded executables:

use uefi::proto::loaded_image::LoadedImage;

fn 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!("Loaded Image Info:");
    log::info!("  Base address: {:?}", base);
    log::info!("  Size: {} bytes", size);

    // Get device we were loaded from
    if let Some(device) = loaded_image.device() {
        log::info!("  Device handle: {:?}", device);
    }

    // Get file path
    if let Some(path) = loaded_image.file_path() {
        log::info!("  File path: {:?}", path);
    }

    Ok(())
}

DevicePath Protocol

Device identification and hierarchy:

use uefi::proto::device_path::{DevicePath, DeviceType, DeviceSubType};
use uefi::proto::device_path::text::DevicePathToText;

fn print_device_path(bt: &BootServices, handle: Handle) -> uefi::Result {
    let device_path = bt.open_protocol_exclusive::<DevicePath>(handle)?;

    // Convert to text if protocol available
    if let Ok(to_text) = bt.locate_protocol::<DevicePathToText>() {
        let text = to_text.convert_device_path_to_text(
            &device_path,
            true,  // display only
            false, // allow shortcuts
        )?;
        log::info!("Device path: {}", text);
    }

    // Manual traversal
    for node in device_path.node_iter() {
        log::info!("  Type: {:?}, SubType: {:?}",
            node.device_type(), node.sub_type());
    }

    Ok(())
}

Creating Protocol Wrappers

For cleaner code, wrap protocols:

struct Disk<'a> {
    block_io: ScopedProtocol<'a, BlockIO>,
    media_id: u32,
    block_size: u32,
    total_blocks: u64,
}

impl<'a> Disk<'a> {
    fn open(bt: &'a BootServices, handle: Handle) -> uefi::Result<Self> {
        let block_io = bt.open_protocol_exclusive::<BlockIO>(handle)?;

        let media = block_io.media();

        Ok(Disk {
            block_io,
            media_id: media.media_id,
            block_size: media.block_size,
            total_blocks: media.last_block + 1,
        })
    }

    fn read_sector(&self, lba: u64, buffer: &mut [u8]) -> uefi::Result {
        self.block_io.read_blocks(self.media_id, lba, buffer)
    }

    fn size_bytes(&self) -> u64 {
        self.total_blocks * self.block_size as u64
    }
}

Protocol Registration

Creating and installing custom protocols (advanced):

use uefi::proto::Protocol;
use uefi::Guid;

// Define a custom protocol
#[repr(C)]
struct MyProtocol {
    revision: u64,
    get_value: unsafe extern "efiapi" fn() -> u32,
}

unsafe impl Protocol for MyProtocol {
    const GUID: Guid = Guid::from_values(
        0x12345678, 0x1234, 0x1234,
        [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]
    );
}

unsafe extern "efiapi" fn get_value_impl() -> u32 {
    42
}

fn install_protocol(bt: &BootServices, handle: Handle) -> uefi::Result {
    let protocol = MyProtocol {
        revision: 1,
        get_value: get_value_impl,
    };

    // Install on handle
    unsafe {
        bt.install_protocol_interface(
            Some(handle),
            &MyProtocol::GUID,
            &protocol as *const _ as *mut core::ffi::c_void,
        )?;
    }

    Ok(())
}

Complete Example

#![no_main]
#![no_std]

extern crate alloc;

use uefi::prelude::*;
use uefi::proto::console::gop::GraphicsOutput;
use uefi::proto::media::block::BlockIO;
use uefi::table::boot::SearchType;

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

    let bt = st.boot_services();

    // Graphics Output Protocol
    log::info!("=== Graphics ===");
    if let Ok(gop) = bt.locate_protocol::<GraphicsOutput>() {
        let mode = gop.current_mode_info();
        log::info!("Resolution: {}x{}",
            mode.resolution().0,
            mode.resolution().1);
    } else {
        log::info!("No graphics available");
    }

    // Block devices
    log::info!("\n=== Block Devices ===");
    if let Ok(handles) = bt.locate_handle_buffer(SearchType::ByProtocol(&BlockIO::GUID)) {
        for (i, &handle) in handles.iter().enumerate().take(5) {
            if let Ok(block_io) = bt.open_protocol_exclusive::<BlockIO>(handle) {
                let media = block_io.media();
                let size = (media.last_block + 1) * media.block_size as u64;
                log::info!("Device {}: {} bytes, block_size: {}",
                    i, size, media.block_size);
            }
        }
    }

    Status::SUCCESS
}

Summary

Protocol Purpose
SimpleTextOutput Console text display
SimpleTextInput Keyboard input
GraphicsOutput Framebuffer access
BlockIO Block device I/O
DiskIO Unaligned disk I/O
LoadedImage Executable information
DevicePath Device identification

Next Steps

Learn about File System access using UEFI protocols.


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.