diff --git a/Cargo.lock b/Cargo.lock index dbbf330..74c28a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,7 @@ dependencies = [ "serde", "toml", "uefi", + "uefi-raw", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f15dd1b..0ded186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ features = ["derive"] version = "0.35.0" features = ["alloc", "logger"] +[dependencies.uefi-raw] +version = "0.11.0" + [profile.release] lto = "thin" strip = "symbols" diff --git a/hack/dev/configs/kernel.sprout.toml b/hack/dev/configs/kernel.sprout.toml index 353efe3..d8cde37 100644 --- a/hack/dev/configs/kernel.sprout.toml +++ b/hack/dev/configs/kernel.sprout.toml @@ -1,7 +1,8 @@ version = 1 [values] -default-options = "initrd=\\initramfs console=hvc0" +default-options = "console=hvc0" +initramfs = "\\initramfs" [actions.welcome] print.text = "Welcome to Sprout!" @@ -13,12 +14,7 @@ splash.time = 1 [actions.chainload-kernel] chainload.path = "$path" chainload.options = ["$default-options"] - -[actions.chainload-shell] -chainload.path = "\\EFI\\BOOT\\shell.efi" - -[actions.chainload-xen] -chainload.path = "\\EFI\\BOOT\\xen.efi" +chainload.linux-initrd = "$initramfs" [generators.kernels.matrix] entry.title = "Boot Linux $name" @@ -26,9 +22,5 @@ entry.values.path = "\\EFI\\BOOT\\$name" entry.actions = ["chainload-kernel"] values.name = ["kernel.efi"] -#[entries.xen] -#title = "Boot Xen" -#actions = ["chainload-xen"] - [[phases.startup]] actions = ["welcome"] diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 3962a97..764a978 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -1,5 +1,6 @@ use crate::context::SproutContext; use crate::utils; +use crate::utils::linux_media_initrd::{register_linux_initrd, unregister_linux_initrd}; use anyhow::{Context, Result, bail}; use log::info; use serde::{Deserialize, Serialize}; @@ -13,6 +14,8 @@ pub struct ChainloadConfiguration { pub path: String, #[serde(default)] pub options: Vec, + #[serde(default, rename = "linux-initrd")] + pub linux_initrd: Option, } pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) -> Result<()> { @@ -71,9 +74,24 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat 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(); 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); Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index bfda015..05f3557 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use uefi::proto::device_path::{DevicePath, PoolDevicePath}; use uefi::proto::media::fs::SimpleFileSystem; pub mod framebuffer; +pub mod linux_media_initrd; pub fn text_to_device_path(path: &str) -> Result { let path = CString16::try_from(path).context("unable to convert path to CString16")?; diff --git a/src/utils/linux_media_initrd.rs b/src/utils/linux_media_initrd.rs new file mode 100644 index 0000000..02a32fa --- /dev/null +++ b/src/utils/linux_media_initrd.rs @@ -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 { + 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 { + let path = LinuxMediaInitrdProtocol::device_path(); + + let mut existing_path = path.as_ref(); + let result = uefi::boot::locate_device_path::(&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 { + 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(()) +}