mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 18:42:21 +00:00
Add a provides field to drivers, either any or filesystems. When every driver only provides filesystems, only disk and partition controllers are reconnected.
122 lines
4.5 KiB
Rust
122 lines
4.5 KiB
Rust
use crate::context::SproutContext;
|
|
use alloc::collections::BTreeMap;
|
|
use alloc::format;
|
|
use alloc::rc::Rc;
|
|
use alloc::string::String;
|
|
use alloc::vec::Vec;
|
|
use anyhow::{Context, Result};
|
|
use edera_sprout_config::drivers::{DriverDeclaration, DriverProvides};
|
|
use eficore::loader::source::ImageSource;
|
|
use eficore::loader::{ImageLoadRequest, ImageLoader};
|
|
use log::info;
|
|
use uefi::boot::SearchType;
|
|
use uefi::proto::media::block::BlockIO;
|
|
use uefi::proto::media::disk::{DiskIo, DiskIo2};
|
|
use uefi::{Handle, Identify};
|
|
|
|
/// Loads the driver specified by the `driver` declaration.
|
|
fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result<()> {
|
|
// Acquire the handle and device path of the loaded image.
|
|
let sprout_image = uefi::boot::image_handle();
|
|
|
|
// Resolve the path to the driver image.
|
|
let resolved = eficore::path::resolve_path(
|
|
Some(context.root().loaded_image_path()?),
|
|
context.stamp(&driver.path),
|
|
)
|
|
.context("unable to resolve path to driver")?;
|
|
|
|
// Create an image load request with the current image and the resolved path.
|
|
let request = ImageLoadRequest::new(sprout_image, ImageSource::ResolvedPath(&resolved));
|
|
|
|
// Load the driver image using the image loader support module.
|
|
// It will determine if the image needs to be loaded via the shim or can be loaded directly.
|
|
let image = ImageLoader::load(request)?;
|
|
|
|
// Start the driver image, this is expected to return control to sprout.
|
|
// There is no guarantee that the driver will actually return control as it is
|
|
// just a standard EFI image.
|
|
uefi::boot::start_image(*image.handle()).context("unable to start driver image")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Locates the handles that are disks or partitions.
|
|
/// These are the only controllers that filesystem drivers can bind to.
|
|
fn locate_storage_handles() -> Result<Vec<Handle>> {
|
|
let mut storage = Vec::new();
|
|
for guid in [&BlockIO::GUID, &DiskIo::GUID, &DiskIo2::GUID] {
|
|
// Not finding any handles for a protocol is not an error.
|
|
let Ok(handles) = uefi::boot::locate_handle_buffer(SearchType::ByProtocol(guid)) else {
|
|
continue;
|
|
};
|
|
for handle in handles.iter() {
|
|
if !storage.contains(handle) {
|
|
storage.push(*handle);
|
|
}
|
|
}
|
|
}
|
|
Ok(storage)
|
|
}
|
|
|
|
/// Reconnects handles to their controllers.
|
|
/// This is effectively a UEFI stack reload in a sense.
|
|
/// After we load all the drivers, we need to reconnect their handles
|
|
/// so that filesystems are recognized again.
|
|
/// When `provides` is [`DriverProvides::Filesystems`], only disk and partition handles are reconnected.
|
|
fn reconnect(provides: DriverProvides) -> Result<()> {
|
|
// Locate the handles to reconnect in the UEFI stack.
|
|
let handles = match provides {
|
|
DriverProvides::Any => uefi::boot::locate_handle_buffer(SearchType::AllHandles)
|
|
.context("unable to locate handles buffer")?
|
|
.iter()
|
|
.copied()
|
|
.collect(),
|
|
DriverProvides::Filesystems => locate_storage_handles()?,
|
|
};
|
|
|
|
for handle in handles {
|
|
// Ignore the result as there is nothing we can do if reconnecting a controller fails.
|
|
// This is also likely to fail in some cases but should fail safely.
|
|
let _ = uefi::boot::connect_controller(handle, &[], None, true);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Load all the drivers specified in `drivers`.
|
|
/// There is no driver order currently. This will reconnect the controllers
|
|
/// if at least one driver was loaded. Every controller is reconnected unless all
|
|
/// the drivers only provide filesystems, in which case only storage is reconnected.
|
|
pub fn load(
|
|
context: Rc<SproutContext>,
|
|
drivers: &BTreeMap<String, DriverDeclaration>,
|
|
) -> Result<()> {
|
|
// If there are no drivers, we don't need to do anything.
|
|
if drivers.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
info!("loading drivers");
|
|
|
|
// Load all the drivers in no particular order.
|
|
for (name, driver) in drivers {
|
|
load_driver(context.clone(), driver).context(format!("unable to load driver: {}", name))?;
|
|
}
|
|
|
|
// Reconnect everything if any driver can provide anything, otherwise only storage.
|
|
let provides = if drivers
|
|
.values()
|
|
.all(|driver| driver.provides == DriverProvides::Filesystems)
|
|
{
|
|
DriverProvides::Filesystems
|
|
} else {
|
|
DriverProvides::Any
|
|
};
|
|
reconnect(provides).context("unable to reconnect drivers")?;
|
|
info!("loaded drivers");
|
|
|
|
// We've now loaded all the drivers, so we can return.
|
|
Ok(())
|
|
}
|