mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 15:20:17 +00:00
generify the linux media initrd mechanism to allow reuse for other things
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
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 crate::utils::media_loader::MediaLoaderHandle;
|
||||||
|
use crate::utils::media_loader::constants::LINUX_EFI_INITRD_MEDIA_GUID;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -78,7 +79,7 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
|||||||
let content = utils::read_file_contents(context.root().loaded_image_path()?, &initrd_path)
|
let content = utils::read_file_contents(context.root().loaded_image_path()?, &initrd_path)
|
||||||
.context("unable to read linux initrd")?;
|
.context("unable to read linux initrd")?;
|
||||||
initrd_handle = Some(
|
initrd_handle = Some(
|
||||||
register_linux_initrd(content.into_boxed_slice())
|
MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice())
|
||||||
.context("unable to register linux initrd")?,
|
.context("unable to register linux initrd")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,9 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
|||||||
info!("loaded image: base={:#x} size={:#x}", base.addr(), size);
|
info!("loaded image: base={:#x} size={:#x}", base.addr(), size);
|
||||||
let result = uefi::boot::start_image(image).context("unable to start image");
|
let result = uefi::boot::start_image(image).context("unable to start image");
|
||||||
if let Some(initrd_handle) = initrd_handle {
|
if let Some(initrd_handle) = initrd_handle {
|
||||||
unregister_linux_initrd(initrd_handle).context("unable to unregister linux initrd")?;
|
initrd_handle
|
||||||
|
.unregister()
|
||||||
|
.context("unable to unregister linux initrd")?;
|
||||||
}
|
}
|
||||||
result.context("unable to start image")?;
|
result.context("unable to start image")?;
|
||||||
drop(options_holder);
|
drop(options_holder);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use uefi::proto::media::fs::SimpleFileSystem;
|
|||||||
use uefi::{CString16, Handle};
|
use uefi::{CString16, Handle};
|
||||||
|
|
||||||
pub mod framebuffer;
|
pub mod framebuffer;
|
||||||
pub mod linux_media_initrd;
|
pub mod media_loader;
|
||||||
|
|
||||||
/// Parses the input [path] as a [DevicePath].
|
/// Parses the input [path] as a [DevicePath].
|
||||||
/// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol.
|
/// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol.
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registers the provided [data] with the UEFI stack as a Linux initrd.
|
|
||||||
/// This uses a special device path that Linux EFI stub will look at
|
|
||||||
/// to load the initrd from.
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unregisters a Linux initrd from the UEFI stack.
|
|
||||||
/// This will free the memory allocated by the initrd.
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
176
src/utils/media_loader.rs
Normal file
176
src/utils/media_loader.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
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::{Guid, Handle};
|
||||||
|
use uefi_raw::protocol::device_path::DevicePathProtocol;
|
||||||
|
use uefi_raw::protocol::media::LoadFile2Protocol;
|
||||||
|
use uefi_raw::{Boolean, Status};
|
||||||
|
|
||||||
|
pub mod constants;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct MediaLoaderProtocol {
|
||||||
|
pub load_file: unsafe extern "efiapi" fn(
|
||||||
|
this: *mut MediaLoaderProtocol,
|
||||||
|
file_path: *const DevicePathProtocol,
|
||||||
|
boot_policy: Boolean,
|
||||||
|
buffer_size: *mut usize,
|
||||||
|
buffer: *mut c_void,
|
||||||
|
) -> Status,
|
||||||
|
pub address: *mut c_void,
|
||||||
|
pub length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MediaLoaderHandle {
|
||||||
|
pub guid: Guid,
|
||||||
|
pub handle: Handle,
|
||||||
|
pub protocol: *mut MediaLoaderProtocol,
|
||||||
|
pub path: *mut DevicePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaLoaderHandle {
|
||||||
|
unsafe extern "efiapi" fn load_file(
|
||||||
|
this: *mut MediaLoaderProtocol,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn device_path(guid: Guid) -> Box<DevicePath> {
|
||||||
|
let mut path = Vec::new();
|
||||||
|
let path = DevicePathBuilder::with_vec(&mut path)
|
||||||
|
.push(&Vendor {
|
||||||
|
vendor_guid: guid,
|
||||||
|
vendor_defined_data: &[],
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.finalize()
|
||||||
|
.unwrap();
|
||||||
|
path.to_boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn already_registered(guid: Guid) -> Result<bool> {
|
||||||
|
let path = Self::device_path(guid);
|
||||||
|
|
||||||
|
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 media loader device path: {}", error);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers the provided [data] with the UEFI stack as media loader.
|
||||||
|
/// 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> {
|
||||||
|
let path = Self::device_path(guid);
|
||||||
|
let path = Box::leak(path);
|
||||||
|
|
||||||
|
if Self::already_registered(guid)? {
|
||||||
|
bail!("media loader 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 media loader device path handle")?;
|
||||||
|
|
||||||
|
let data = Box::leak(data);
|
||||||
|
|
||||||
|
let protocol = Box::new(MediaLoaderProtocol {
|
||||||
|
load_file: Self::load_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 media loader load file handle")?;
|
||||||
|
|
||||||
|
if !Self::already_registered(guid)? {
|
||||||
|
bail!("media loader not registered when expected to be registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
guid,
|
||||||
|
handle,
|
||||||
|
protocol,
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregisters a media loader from the UEFI stack.
|
||||||
|
/// This will free the memory allocated by the passed data.
|
||||||
|
pub fn unregister(self) -> Result<()> {
|
||||||
|
if !Self::already_registered(self.guid)? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
uefi::boot::uninstall_protocol_interface(
|
||||||
|
self.handle,
|
||||||
|
&DevicePathProtocol::GUID,
|
||||||
|
self.path as *mut c_void,
|
||||||
|
)
|
||||||
|
.context("unable to uninstall media loader device path handle")?;
|
||||||
|
|
||||||
|
uefi::boot::uninstall_protocol_interface(
|
||||||
|
self.handle,
|
||||||
|
&LoadFile2Protocol::GUID,
|
||||||
|
self.protocol as *mut _ as *mut c_void,
|
||||||
|
)
|
||||||
|
.context("unable to uninstall media loader load file handle")?;
|
||||||
|
|
||||||
|
let _path = Box::from_raw(self.path);
|
||||||
|
let _protocol = Box::from_raw(self.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/utils/media_loader/constants.rs
Normal file
3
src/utils/media_loader/constants.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
use uefi::{Guid, guid};
|
||||||
|
|
||||||
|
pub const LINUX_EFI_INITRD_MEDIA_GUID: Guid = guid!("5568e427-68fc-4f3d-ac74-ca555231cc68");
|
||||||
Reference in New Issue
Block a user