implement loading of initramfs via the linux media initrd protocol

This commit is contained in:
2025-10-12 20:22:24 -07:00
parent 9a52fa1d1c
commit 87944067d1
6 changed files with 204 additions and 12 deletions

1
Cargo.lock generated
View File

@@ -259,6 +259,7 @@ dependencies = [
"serde", "serde",
"toml", "toml",
"uefi", "uefi",
"uefi-raw",
] ]
[[package]] [[package]]

View File

@@ -22,6 +22,9 @@ features = ["derive"]
version = "0.35.0" version = "0.35.0"
features = ["alloc", "logger"] features = ["alloc", "logger"]
[dependencies.uefi-raw]
version = "0.11.0"
[profile.release] [profile.release]
lto = "thin" lto = "thin"
strip = "symbols" strip = "symbols"

View File

@@ -1,7 +1,8 @@
version = 1 version = 1
[values] [values]
default-options = "initrd=\\initramfs console=hvc0" default-options = "console=hvc0"
initramfs = "\\initramfs"
[actions.welcome] [actions.welcome]
print.text = "Welcome to Sprout!" print.text = "Welcome to Sprout!"
@@ -13,12 +14,7 @@ splash.time = 1
[actions.chainload-kernel] [actions.chainload-kernel]
chainload.path = "$path" chainload.path = "$path"
chainload.options = ["$default-options"] chainload.options = ["$default-options"]
chainload.linux-initrd = "$initramfs"
[actions.chainload-shell]
chainload.path = "\\EFI\\BOOT\\shell.efi"
[actions.chainload-xen]
chainload.path = "\\EFI\\BOOT\\xen.efi"
[generators.kernels.matrix] [generators.kernels.matrix]
entry.title = "Boot Linux $name" entry.title = "Boot Linux $name"
@@ -26,9 +22,5 @@ entry.values.path = "\\EFI\\BOOT\\$name"
entry.actions = ["chainload-kernel"] entry.actions = ["chainload-kernel"]
values.name = ["kernel.efi"] values.name = ["kernel.efi"]
#[entries.xen]
#title = "Boot Xen"
#actions = ["chainload-xen"]
[[phases.startup]] [[phases.startup]]
actions = ["welcome"] actions = ["welcome"]

View File

@@ -1,5 +1,6 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::utils; use crate::utils;
use crate::utils::linux_media_initrd::{register_linux_initrd, unregister_linux_initrd};
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -13,6 +14,8 @@ pub struct ChainloadConfiguration {
pub path: String, pub path: String,
#[serde(default)] #[serde(default)]
pub options: Vec<String>, pub options: Vec<String>,
#[serde(default, rename = "linux-initrd")]
pub linux_initrd: Option<String>,
} }
pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> { pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
@@ -71,9 +74,24 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
options_holder = Some(options); options_holder = Some(options);
} }
let mut initrd_handle = None;
if let Some(ref linux_initrd) = configuration.linux_initrd {
let initrd_path = context.stamp(linux_initrd);
let content =
utils::read_file_contents(&initrd_path).context("failed to read linux initrd")?;
initrd_handle = Some(
register_linux_initrd(content.into_boxed_slice())
.context("failed to register linux initrd")?,
);
}
let (base, size) = loaded_image_protocol.info(); let (base, size) = loaded_image_protocol.info();
info!("loaded image: base={:#x} size={:#x}", base.addr(), size); info!("loaded image: base={:#x} size={:#x}", base.addr(), size);
uefi::boot::start_image(image).context("failed to start image")?; let result = uefi::boot::start_image(image).context("failed to start image");
if let Some(initrd_handle) = initrd_handle {
unregister_linux_initrd(initrd_handle).context("failed to unregister linux initrd")?;
}
result.context("failed to start image")?;
drop(options_holder); drop(options_holder);
Ok(()) Ok(())
} }

View File

@@ -6,6 +6,7 @@ use uefi::proto::device_path::{DevicePath, PoolDevicePath};
use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::fs::SimpleFileSystem;
pub mod framebuffer; pub mod framebuffer;
pub mod linux_media_initrd;
pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> { pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> {
let path = CString16::try_from(path).context("unable to convert path to CString16")?; let path = CString16::try_from(path).context("unable to convert path to CString16")?;

View File

@@ -0,0 +1,177 @@
use anyhow::{Context, Result, bail};
use log::info;
use std::ffi::c_void;
use uefi::proto::device_path::DevicePath;
use uefi::proto::device_path::build::DevicePathBuilder;
use uefi::proto::device_path::build::media::Vendor;
use uefi::proto::media::load_file::LoadFile2;
use uefi::proto::unsafe_protocol;
use uefi::{Guid, Handle, guid};
use uefi_raw::protocol::device_path::DevicePathProtocol;
use uefi_raw::protocol::media::LoadFile2Protocol;
use uefi_raw::{Boolean, Status};
#[derive(Debug)]
#[repr(C)]
pub struct LinuxMediaInitrdProtocol {
pub load_file: unsafe extern "efiapi" fn(
this: *mut LinuxMediaInitrdProtocol,
file_path: *const DevicePathProtocol,
boot_policy: Boolean,
buffer_size: *mut usize,
buffer: *mut c_void,
) -> Status,
pub address: *mut c_void,
pub length: usize,
}
impl LinuxMediaInitrdProtocol {
pub const GUID: Guid = guid!("5568e427-68fc-4f3d-ac74-ca555231cc68");
pub fn device_path() -> Box<DevicePath> {
let mut path = Vec::new();
let path = DevicePathBuilder::with_vec(&mut path)
.push(&Vendor {
vendor_guid: LinuxMediaInitrdProtocol::GUID,
vendor_defined_data: &[],
})
.unwrap()
.finalize()
.unwrap();
path.to_boxed()
}
}
#[repr(transparent)]
#[unsafe_protocol(LinuxMediaInitrdProtocol::GUID)]
pub struct LinuxMediaInitrd(LinuxMediaInitrdProtocol);
pub struct LinuxMediaInitrdHandle {
pub handle: Handle,
pub protocol: *mut LinuxMediaInitrdProtocol,
pub path: *mut DevicePath,
}
unsafe extern "efiapi" fn load_initrd_file(
this: *mut LinuxMediaInitrdProtocol,
file_path: *const DevicePathProtocol,
boot_policy: Boolean,
buffer_size: *mut usize,
buffer: *mut c_void,
) -> Status {
if this.is_null() || buffer_size.is_null() || file_path.is_null() {
return Status::INVALID_PARAMETER;
}
if boot_policy == Boolean::TRUE {
return Status::UNSUPPORTED;
}
unsafe {
if (*this).length == 0 || (*this).address.is_null() {
return Status::NOT_FOUND;
}
if buffer.is_null() || *buffer_size < (*this).length {
*buffer_size = (*this).length;
return Status::BUFFER_TOO_SMALL;
}
buffer.copy_from((*this).address, (*this).length);
*buffer_size = (*this).length;
}
Status::SUCCESS
}
fn already_registered() -> Result<bool> {
let path = LinuxMediaInitrdProtocol::device_path();
let mut existing_path = path.as_ref();
let result = uefi::boot::locate_device_path::<LoadFile2>(&mut existing_path);
if result.is_ok() {
return Ok(true);
} else if let Err(error) = result
&& error.status() != Status::NOT_FOUND
{
bail!("unable to locate initrd device path: {}", error);
}
Ok(false)
}
pub fn register_linux_initrd(data: Box<[u8]>) -> Result<LinuxMediaInitrdHandle> {
let path = LinuxMediaInitrdProtocol::device_path();
let path = Box::leak(path);
if already_registered()? {
bail!("linux initrd already registered");
}
let mut handle = unsafe {
uefi::boot::install_protocol_interface(
None,
&DevicePathProtocol::GUID,
path.as_ffi_ptr() as *mut c_void,
)
}
.context("unable to install linux initrd device path handle")?;
let data = Box::leak(data);
let protocol = Box::new(LinuxMediaInitrdProtocol {
load_file: load_initrd_file,
address: data.as_ptr() as *mut _,
length: data.len(),
});
let protocol = Box::leak(protocol);
handle = unsafe {
uefi::boot::install_protocol_interface(
Some(handle),
&LoadFile2Protocol::GUID,
protocol as *mut _ as *mut c_void,
)
}
.context("unable to install linux initrd load file handle")?;
if !already_registered()? {
bail!("linux initrd not registered when expected to be registered");
}
info!("linux initrd registered");
Ok(LinuxMediaInitrdHandle {
handle,
protocol,
path,
})
}
pub fn unregister_linux_initrd(handle: LinuxMediaInitrdHandle) -> Result<()> {
if !already_registered()? {
return Ok(());
}
unsafe {
uefi::boot::uninstall_protocol_interface(
handle.handle,
&DevicePathProtocol::GUID,
handle.path as *mut c_void,
)
.context("unable to uninstall linux initrd device path handle")?;
uefi::boot::uninstall_protocol_interface(
handle.handle,
&LoadFile2Protocol::GUID,
handle.protocol as *mut _ as *mut c_void,
)
.context("unable to uninstall linux initrd load file handle")?;
let _path = Box::from_raw(handle.path);
let _protocol = Box::from_raw(handle.protocol);
}
Ok(())
}