Skip to content

Exit BootServices fails on real machine if custom memory type is used. #1375

@hannahfluch

Description

@hannahfluch

When allocating memory for a UEFI application using BootServices with a custom memory type, the ExitBootServices call later freezes on a real machine, but it works fine in QEMU.

Everything works as expected on the real machine as well, if MemoryType::LOADER_DATA is used.

System Information:

Tested on: Lenovo V15 G3 IAP
CPU: 12th Gen Intel(R) Core(TM) i5-1235U
Rust Toolchain: nightly

Dependencies Used:

log = "0.4.22"
uefi = { version = "0.31.0", features = ["logger", "alloc", "global_allocator", "panic_handler"] }

Source Code:

config.toml

[unstable]
build-std = ["core", "compiler_builtins", "alloc"]
build-std-features = ["compiler-builtins-mem"]

[build]
target = "x86_64-unknown-uefi"

main.rs

#![no_std]
#![no_main]
extern crate alloc;

use alloc::format;
use alloc::string::String;

use log::info;
use uefi::{
    entry,
    Handle,
    Status, table::{Boot, SystemTable},
};
use uefi::prelude::BootServices;
use uefi::proto::console::gop::{GraphicsOutput, PixelFormat};
use uefi::table::boot::{AllocateType, MemoryType};

use crate::framebuffer::{Color, FrameBufferMetadata, RawFrameBuffer};

mod framebuffer;

const MY_MEMORY_TYPE: MemoryType = MemoryType::custom(0x80000001);
#[entry]
fn main(_image_handle: Handle, system_table: SystemTable<Boot>) -> Status {
    uefi::helpers::init().unwrap();

    info!("Hello, uefi world!");

    // some boilerplate to get a working framebuffer using Graphics Output Protocol
    let fb_meta = initialize_framebuffer(system_table.boot_services()).unwrap();
    let fb = RawFrameBuffer::from(fb_meta);

    // color white indicates framebuffer initialization was successful
    fb.fill(Color::white());

    // allocate some memory using the custom memory type
    let x = system_table.boot_services().allocate_pages(AllocateType::AnyPages, MY_MEMORY_TYPE, 5).unwrap();
    // "use" the newly allocated memory
    let _y = unsafe { *(x as *const u8) };

    // color blue indicates allocation was successful
    fb.fill(Color::blue());

    // exit boot services
    let (_, _) = unsafe { system_table.exit_boot_services(MemoryType::LOADER_DATA) };

    // color green indicates exit was successful
    fb.fill(Color::green());

    loop {}
}


/// Initialize framebuffer (GOP)
fn initialize_framebuffer(
    boot_services: &BootServices,
) -> Result<FrameBufferMetadata, String> {
    let gop_handle = boot_services
        .get_handle_for_protocol::<GraphicsOutput>()
        .map_err(|error| format!("Could not get handle for GOP: {error}."))?;

    let mut gop = boot_services
        .open_protocol_exclusive::<GraphicsOutput>(gop_handle)
        .map_err(|error| format!("Could not open GOP: {error}."))?;
    let mut raw_frame_buffer = gop.frame_buffer();
    let base = raw_frame_buffer.as_mut_ptr() as u64;
    let size = raw_frame_buffer.size();
    let info = gop.current_mode_info();

    let is_rgb = match info.pixel_format() {
        PixelFormat::Rgb => Ok(true),
        PixelFormat::Bgr => Ok(false),
        PixelFormat::Bitmask | PixelFormat::BltOnly => {
            Err("Not supported")
        }
    }?;
    let (width, height) = info.resolution();
    let stride = info.stride();

    Ok(FrameBufferMetadata {
        base,
        size,
        width,
        height,
        stride,
        is_rgb,
    })
}

framebuffer.rs

use core::{
    fmt,
    fmt::{Debug, Formatter},
};
use core::ptr::write_volatile;

pub(crate) const BPP: usize = 4;

#[derive(Copy, Clone)]
pub(crate) struct FrameBufferMetadata {
    pub(crate) base: u64,
    pub(crate) size: usize,
    pub(crate) width: usize,
    pub(crate) height: usize,
    pub(crate) stride: usize, // pixels per scanline
    pub(crate) is_rgb: bool,
}

impl Debug for FrameBufferMetadata {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!(
            "FrameBufferMetadata {{\n\tbase: {:#x},\n\tsize: {:#x},\n\twidth: {},\n\theight: {},\n\tstride: {},\n}}",
            self.base, self.size, self.width, self.height, self.stride
        ))
    }
}
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct Color {
    pub(crate) red: u8,
    pub(crate) green: u8,
    pub(crate) blue: u8,
}
macro_rules! color {
    ($color:ident, $red:expr, $green:expr, $blue:expr) => {
        impl Color {
            pub(crate) const fn $color() -> Color {
                Color {
                    red: $red,
                    green: $green,
                    blue: $blue,
                }
            }
        }
    };
}

color!(green, 0x00, 0xFF, 0x00);
color!(blue, 0x00, 0x00, 0xFF);
color!(white, 0xFF, 0xFF, 0xFF);

/// Directly accesses video memory in order to display graphics
#[derive(Clone, Debug)]
pub(crate) struct RawFrameBuffer {
    pub(crate) meta_data: FrameBufferMetadata,
}

impl RawFrameBuffer {
    /// Draws a pixel onto the screen at coordinates x,y and with the specified color. Returns, whether the action succeeds or the coordinates are invalid.
    pub(crate) fn draw_pixel(
        &self,
        x: usize,
        y: usize,
        color: Color,
    ) -> Result<(), (usize, usize)> {
        if !self.in_bounds(x, y) {
            return Err((x, y));
        }

        let pitch = self.meta_data.stride * BPP;

        unsafe {
            let pixel = (self.meta_data.base as *mut u8).add(pitch * y + BPP * x);

            if self.meta_data.is_rgb {
                write_volatile(pixel, color.red);
                write_volatile(pixel.add(1), color.green);
                write_volatile(pixel.add(2), color.blue);
            } else {
                write_volatile(pixel, color.blue);
                write_volatile(pixel.add(1), color.green);
                write_volatile(pixel.add(2), color.red);
            }
        }

        Ok(())
    }
    /// Fills entire display with certain color
    pub(crate) fn fill(&self, color: Color) {
        for x in 0..self.meta_data.width {
            for y in 0..self.meta_data.height {
                self.draw_pixel(x, y, color).unwrap();
            }
        }
    }
}

impl RawFrameBuffer {
    /// Whether a point is within the framebuffer vram
    fn in_bounds(&self, x: usize, y: usize) -> bool {
        x < self.meta_data.width && y < self.meta_data.height
    }
}

impl From<FrameBufferMetadata> for RawFrameBuffer {
    fn from(value: FrameBufferMetadata) -> Self {
        Self { meta_data: value }
    }
}

Note: I'm relatively new to the UEFI and OSDev community so there might be an issue with my code itself. Please feel free to correct me if the mistake is mine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions