document by hand much more of the sprout code

This commit is contained in:
2025-10-19 23:03:28 -07:00
parent 8997e417b3
commit 106064d3e7
9 changed files with 153 additions and 8 deletions

View File

@@ -1,13 +1,18 @@
use anyhow::{Context, Result};
use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput};
/// Represents the EFI framebuffer.
pub struct Framebuffer {
/// The width of the framebuffer in pixels.
width: usize,
/// The height of the framebuffer in pixels.
height: usize,
/// The pixels of the framebuffer.
pixels: Vec<BltPixel>,
}
impl Framebuffer {
/// Creates a new framebuffer of the specified `width` and `height`.
pub fn new(width: usize, height: usize) -> Self {
Framebuffer {
width,
@@ -16,10 +21,12 @@ impl Framebuffer {
}
}
/// Mutably acquires a pixel of the framebuffer at the specified `x` and `y` coordinate.
pub fn pixel(&mut self, x: usize, y: usize) -> Option<&mut BltPixel> {
self.pixels.get_mut(y * self.width + x)
}
/// Blit the framebuffer to the specified `gop` [GraphicsOutput].
pub fn blit(&self, gop: &mut GraphicsOutput) -> Result<()> {
gop.blt(BltOp::BufferToVideo {
buffer: &self.pixels,

View File

@@ -11,9 +11,11 @@ use uefi_raw::{Boolean, Status};
pub mod constants;
/// The media loader protocol.
#[derive(Debug)]
#[repr(C)]
struct MediaLoaderProtocol {
/// This is the standard EFI LoadFile2 protocol.
pub load_file: unsafe extern "efiapi" fn(
this: *mut MediaLoaderProtocol,
file_path: *const DevicePathProtocol,
@@ -21,7 +23,9 @@ struct MediaLoaderProtocol {
buffer_size: *mut usize,
buffer: *mut c_void,
) -> Status,
/// A pointer to a Box<[u8]> containing the data to load.
pub address: *mut c_void,
/// The length of the data to load.
pub length: usize,
}
@@ -29,9 +33,13 @@ struct MediaLoaderProtocol {
/// You MUST call [MediaLoaderHandle::unregister] when ready to unregister.
/// [Drop] is not implemented for this type.
pub struct MediaLoaderHandle {
/// The vendor GUID of the media loader.
guid: Guid,
/// The handle of the media loader in the UEFI stack.
handle: Handle,
/// The protocol interface pointer.
protocol: *mut MediaLoaderProtocol,
/// The device path pointer.
path: *mut DevicePath,
}
@@ -50,11 +58,12 @@ impl MediaLoaderHandle {
buffer_size: *mut usize,
buffer: *mut c_void,
) -> Status {
// Check if the pointers are non-null first.
if this.is_null() || buffer_size.is_null() || file_path.is_null() {
return Status::INVALID_PARAMETER;
}
// Boot policy must not be true, as that's special behavior that is irrelevant
// Boot policy must not be true, and if it is, that is special behavior that is irrelevant
// for the media loader concept.
if boot_policy == Boolean::TRUE {
return Status::UNSUPPORTED;
@@ -63,48 +72,68 @@ impl MediaLoaderHandle {
// SAFETY: Validated as safe because this is checked to be non-null. It is the caller's
// responsibility to ensure that the right pointer is passed for [this].
unsafe {
// Check if the length and address are valid.
if (*this).length == 0 || (*this).address.is_null() {
return Status::NOT_FOUND;
}
// Check if the buffer is large enough.
// If it is not, we need to set the buffer size to the length of the data.
// This is the way that Linux calls this function, to check the size to allocate
// for the buffer that holds the data.
if buffer.is_null() || *buffer_size < (*this).length {
*buffer_size = (*this).length;
return Status::BUFFER_TOO_SMALL;
}
// Copy the data into the buffer.
buffer.copy_from((*this).address, (*this).length);
// Set the buffer size to the length of the data.
*buffer_size = (*this).length;
}
// We've successfully loaded the data.
Status::SUCCESS
}
/// Creates a new device path for the media loader based on a vendor `guid`.
fn device_path(guid: Guid) -> Box<DevicePath> {
// The buffer for the device path.
let mut path = Vec::new();
// Build a device path for the media loader with a vendor-specific guid.
let path = DevicePathBuilder::with_vec(&mut path)
.push(&Vendor {
vendor_guid: guid,
vendor_defined_data: &[],
})
.unwrap()
.unwrap() // We know that the device path is valid, so we can unwrap.
.finalize()
.unwrap();
.unwrap(); // We know that the device path is valid, so we can unwrap.
// Convert the device path to a boxed device path.
// This is safer than dealing with a pooled device path.
path.to_boxed()
}
/// Checks if the media loader is already registered with the UEFI stack.
fn already_registered(guid: Guid) -> Result<bool> {
// Acquire the device path for the media loader.
let path = Self::device_path(guid);
let mut existing_path = path.as_ref();
// Locate the LoadFile2 protocol for the media loader based on the device path.
let result = uefi::boot::locate_device_path::<LoadFile2>(&mut existing_path);
// If the result is okay, the media loader is already registered.
if result.is_ok() {
return Ok(true);
} else if let Err(error) = result
&& error.status() != Status::NOT_FOUND
// If the error is not found, that means it's not registered.
{
bail!("unable to locate media loader device path: {}", error);
}
// The media loader is not registered.
Ok(false)
}
@@ -112,13 +141,20 @@ impl MediaLoaderHandle {
/// This uses a special device path that other EFI programs will look at
/// to load the data from.
pub fn register(guid: Guid, data: Box<[u8]>) -> Result<MediaLoaderHandle> {
// Acquire the vendor device path for the media loader.
let path = Self::device_path(guid);
let path = Box::leak(path);
// Check if the media loader is already registered.
// If it is, we can't register it again safely.
if Self::already_registered(guid)? {
bail!("media loader already registered");
}
// Leak the device path to pass it to the UEFI stack.
let path = Box::leak(path);
// Install a protocol interface for the device path.
// This ensures it can be located by other EFI programs.
let mut handle = unsafe {
uefi::boot::install_protocol_interface(
None,
@@ -128,16 +164,20 @@ impl MediaLoaderHandle {
}
.context("unable to install media loader device path handle")?;
// Leak the data we need to pass to the UEFI stack.
let data = Box::leak(data);
// Allocate a new box for the protocol interface.
let protocol = Box::new(MediaLoaderProtocol {
load_file: Self::load_file,
address: data.as_ptr() as *mut _,
length: data.len(),
});
// Leak the protocol interface to pass it to the UEFI stack.
let protocol = Box::leak(protocol);
// Install a protocol interface for the load file protocol for the media loader protocol.
handle = unsafe {
uefi::boot::install_protocol_interface(
Some(handle),
@@ -147,10 +187,13 @@ impl MediaLoaderHandle {
}
.context("unable to install media loader load file handle")?;
// Check if the media loader is registered.
// If it is not, we can't continue safely because something went wrong.
if !Self::already_registered(guid)? {
bail!("media loader not registered when expected to be registered");
}
// Return a handle to the media loader.
Ok(Self {
guid,
handle,
@@ -162,11 +205,16 @@ impl MediaLoaderHandle {
/// Unregisters a media loader from the UEFI stack.
/// This will free the memory allocated by the passed data.
pub fn unregister(self) -> Result<()> {
// Check if the media loader is registered.
// If it is not, we don't need to do anything.
if !Self::already_registered(self.guid)? {
return Ok(());
}
// SAFETY: We know that the media loader is registered, so we can safely uninstall it.
// We should have allocated the pointers involved, so we can safely free them.
unsafe {
// Uninstall the protocol interface for the device path protocol.
uefi::boot::uninstall_protocol_interface(
self.handle,
&DevicePathProtocol::GUID,
@@ -174,6 +222,7 @@ impl MediaLoaderHandle {
)
.context("unable to uninstall media loader device path handle")?;
// Uninstall the protocol interface for the load file protocol.
uefi::boot::uninstall_protocol_interface(
self.handle,
&LoadFile2Protocol::GUID,
@@ -181,12 +230,16 @@ impl MediaLoaderHandle {
)
.context("unable to uninstall media loader load file handle")?;
// Retrieve a box for the device path and protocols.
let path = Box::from_raw(self.path);
let protocol = Box::from_raw(self.protocol);
// Retrieve a box for the data we passed in.
let slice =
std::ptr::slice_from_raw_parts_mut(protocol.address as *mut u8, protocol.length);
let data = Box::from_raw(slice);
// Drop all the allocations explicitly, as we don't want to leak them.
drop(path);
drop(protocol);
drop(data);

View File

@@ -13,7 +13,8 @@ pub mod xen {
/// The device path GUID for the Xen EFI config.
pub const XEN_EFI_CONFIG_MEDIA_GUID: Guid = guid!("bf61f458-a28e-46cd-93d7-07dac5e8cd66");
/// The device path GUID for the Xen EFI config.
/// The device path GUID for the Xen EFI kernel.
pub const XEN_EFI_KERNEL_MEDIA_GUID: Guid = guid!("4010c8bf-6ced-40f5-a53f-e820aee8f34b");
/// The device path GUID for the Xen EFI ramdisk.
pub const XEN_EFI_RAMDISK_MEDIA_GUID: Guid = guid!("5db1fd01-c3cb-4812-b2ba-8791e52d4a89");
}