Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Hqnnqh opened this issue Aug 27, 2024 · 1 comment
Open

Comments

@Hqnnqh
Copy link

Hqnnqh commented Aug 27, 2024

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.

@phip1611
Copy link
Contributor

Do you have the option to get the serial output from the real hardware? This would help to get a specific panic message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants