document ( by hand :( ) all of the code of sprout

This commit is contained in:
2025-10-20 00:06:46 -07:00
parent 106064d3e7
commit 3453826e9d
8 changed files with 261 additions and 8 deletions

View File

@@ -19,53 +19,88 @@ use crate::{
},
};
/// The configuration of the edera action which boots the Edera hypervisor.
/// Edera is based on Xen but modified significantly with a Rust stack.
/// Sprout is a component of the Edera stack and provides the boot functionality of Xen.
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct EderaConfiguration {
/// The path to the Xen hypervisor EFI image.
pub xen: String,
/// The path to the kernel to boot for dom0.
pub kernel: String,
/// The path to the initrd to load for dom0.
#[serde(default)]
pub initrd: Option<String>,
/// The options to pass to the kernel.
#[serde(default, rename = "kernel-options")]
pub kernel_options: Vec<String>,
/// The options to pass to the Xen hypervisor.
#[serde(default, rename = "xen-options")]
pub xen_options: Vec<String>,
}
/// Builds a configuration string for the Xen EFI stub using the specified `configuration`.
fn build_xen_config(configuration: &EderaConfiguration) -> String {
// xen config file format is ini-like
[
// global section
"[global]".to_string(),
// default configuration section
"default=sprout".to_string(),
// configuration section for sprout
"[sprout]".to_string(),
// xen options
format!("options={}", configuration.xen_options.join(" ")),
// kernel options, stub replaces the kernel path
// the kernel is provided via media loader
format!("kernel=stub {}", configuration.kernel_options.join(" ")),
"".to_string(), // required or else the last line will be ignored
// required or else the last line will be ignored
"".to_string(),
]
.join("\n")
}
/// Register a media loader for some `text` with the vendor `guid`.
/// `what` should indicate some identifying value for error messages
/// like `config` or `kernel`.
/// Provides a [MediaLoaderHandle] that can be used to unregister the media loader.
fn register_media_loader_text(guid: Guid, what: &str, text: String) -> Result<MediaLoaderHandle> {
MediaLoaderHandle::register(guid, text.as_bytes().to_vec().into_boxed_slice())
.context(format!("unable to register {} media loader", what)) /* */
}
/// Register a media loader for the file `path` with the vendor `guid`.
/// `what` should indicate some identifying value for error messages
/// like `config` or `kernel`.
/// Provides a [MediaLoaderHandle] that can be used to unregister the media loader.
fn register_media_loader_file(
context: &Rc<SproutContext>,
guid: Guid,
what: &str,
path: &str,
) -> Result<MediaLoaderHandle> {
// Stamp the path to the file.
let path = context.stamp(path);
// Read the file contents.
let content = utils::read_file_contents(context.root().loaded_image_path()?, &path)
.context(format!("unable to read {} file", what))?;
// Register the media loader.
let handle = MediaLoaderHandle::register(guid, content.into_boxed_slice())
.context(format!("unable to register {} media loader", what))?;
Ok(handle)
}
/// Executes the edera action which will boot the Edera hypervisor with the specified
/// `configuration` and `context`. This action uses Edera-specific Xen EFI stub functionality.
pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> Result<()> {
// Build the Xen config file content for this configuration.
let config = build_xen_config(configuration);
// Register the media loader for the config.
let config = register_media_loader_text(XEN_EFI_CONFIG_MEDIA_GUID, "config", config)
.context("unable to register config media loader")?;
// Register the media loaders for the kernel.
let kernel = register_media_loader_file(
&context,
XEN_EFI_KERNEL_MEDIA_GUID,
@@ -74,8 +109,10 @@ pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) ->
)
.context("unable to register kernel media loader")?;
// Create a vector of media loaders to unregister on error.
let mut media_loaders = vec![config, kernel];
// Register the initrd if it is provided.
if let Some(ref initrd) = configuration.initrd {
let initrd =
register_media_loader_file(&context, XEN_EFI_RAMDISK_MEDIA_GUID, "initrd", initrd)
@@ -83,6 +120,7 @@ pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) ->
media_loaders.push(initrd);
}
// Chainload to the Xen EFI stub.
let result = actions::chainload::chainload(
context.clone(),
&ChainloadConfiguration {
@@ -93,6 +131,7 @@ pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) ->
)
.context("unable to chainload to xen");
// Unregister the media loaders on error.
for media_loader in media_loaders {
if let Err(error) = media_loader.unregister() {
error!("unable to unregister media loader: {}", error);

View File

@@ -12,25 +12,39 @@ use std::time::Duration;
use uefi::boot::ScopedProtocol;
use uefi::proto::console::gop::GraphicsOutput;
const DEFAULT_SPLASH_TIME: u32 = 0;
/// The configuration of the splash action.
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct SplashConfiguration {
/// The path to the image to display.
/// Currently, only PNG images are supported.
pub image: String,
/// The time to display the splash image without interruption, in seconds.
/// The default value is `0` which will display the image and let everything
/// continue.
#[serde(default = "default_splash_time")]
pub time: u32,
}
pub fn default_splash_time() -> u32 {
0
fn default_splash_time() -> u32 {
DEFAULT_SPLASH_TIME
}
/// Acquire the [GraphicsOutput]. We will find the first graphics output only.
fn setup_graphics() -> Result<ScopedProtocol<GraphicsOutput>> {
// Grab the handle for the graphics output protocol.
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
.context("unable to get graphics output")?;
// Open the graphics output protocol exclusively.
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle)
.context("unable to open graphics output")
}
/// Produces a [Rect] that fits the `image` inside the specified `frame`.
/// The output [Rect] should be used to resize the image.
fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
// Convert the image dimensions to a [Rect].
let input = Rect {
x: 0,
y: 0,
@@ -38,9 +52,13 @@ fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
height: image.height(),
};
// Calculate the ratio of the image dimensions.
let input_ratio = input.width as f32 / input.height as f32;
// Calculate the ratio of the frame dimensions.
let frame_ratio = frame.width as f32 / frame.height as f32;
// Create [Rect] to store the output dimensions.
let mut output = Rect {
x: 0,
y: 0,
@@ -63,24 +81,38 @@ fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
output
}
/// Resize the input `image` to fit the `frame`.
fn resize_to_fit(image: &DynamicImage, frame: Rect) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
let image = image.to_rgba8();
resize(&image, frame.width, frame.height, FilterType::Lanczos3)
}
/// Draw the `image` on the screen using [GraphicsOutput].
fn draw(image: DynamicImage) -> Result<()> {
// Acquire the [GraphicsOutput] protocol.
let mut gop = setup_graphics()?;
// Acquire the current screen size.
let (width, height) = gop.current_mode_info().resolution();
// Create a display frame.
let display_frame = Rect {
x: 0,
y: 0,
width: width as _,
height: height as _,
};
// Fit the image to the display frame.
let fit = fit_to_frame(&image, display_frame);
// Resize the image to fit the display frame.
let image = resize_to_fit(&image, fit);
// Create a framebuffer to draw the image on.
let mut framebuffer = Framebuffer::new(width, height);
// Iterate over the pixels in the image and put them on the framebuffer.
for (x, y, pixel) in image.enumerate_pixels() {
let Some(fb) = framebuffer.pixel((x + fit.x) as usize, (fit.y + y) as usize) else {
continue;
@@ -90,17 +122,27 @@ fn draw(image: DynamicImage) -> Result<()> {
fb.blue = pixel[2];
}
// Blit the framebuffer to the screen.
framebuffer.blit(&mut gop)?;
Ok(())
}
/// Runs the splash action with the specified `configuration` inside the provided `context`.
pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -> Result<()> {
// Stamp the image path value.
let image = context.stamp(&configuration.image);
// Read the image contents.
let image = read_file_contents(context.root().loaded_image_path()?, &image)?;
// Decode the image as a PNG.
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
.decode()
.context("unable to decode splash image")?;
// Draw the image on the screen.
draw(image)?;
// Sleep for the specified time.
std::thread::sleep(Duration::from_secs(configuration.time as u64));
// Return control to sprout.
Ok(())
}