diff --git a/crates/boot/src/drivers.rs b/crates/boot/src/drivers.rs index 9ec151a..d75a49c 100644 --- a/crates/boot/src/drivers.rs +++ b/crates/boot/src/drivers.rs @@ -3,12 +3,16 @@ 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; +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, driver: &DriverDeclaration) -> Result<()> { @@ -37,27 +41,53 @@ fn load_driver(context: Rc, driver: &DriverDeclaration) -> Result Ok(()) } -/// Reconnects all handles to their controllers. +/// Locates the handles that are disks or partitions. +/// These are the only controllers that filesystem drivers can bind to. +fn locate_storage_handles() -> Result> { + 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 all of their handles +/// After we load all the drivers, we need to reconnect their handles /// so that filesystems are recognized again. -fn reconnect() -> Result<()> { - // Locate all of the handles in the UEFI stack. - let handles = uefi::boot::locate_handle_buffer(SearchType::AllHandles) - .context("unable to locate handles buffer")?; +/// 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.iter() { + 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); + 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 all the controllers -/// to all handles if at least one driver was loaded. +/// 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, drivers: &BTreeMap, @@ -74,8 +104,16 @@ pub fn load( load_driver(context.clone(), driver).context(format!("unable to load driver: {}", name))?; } - // Reconnect all the controllers to all handles. - reconnect().context("unable to reconnect drivers")?; + // 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. diff --git a/crates/config/src/drivers.rs b/crates/config/src/drivers.rs index 48320be..880879f 100644 --- a/crates/config/src/drivers.rs +++ b/crates/config/src/drivers.rs @@ -10,4 +10,21 @@ pub struct DriverDeclaration { /// The filesystem path to the driver. /// This file should be an EFI executable that can be located and executed. pub path: String, + /// What the driver provides. + /// This decides which controllers are reconnected after the drivers are loaded. + #[serde(default)] + pub provides: DriverProvides, +} + +/// What a driver provides to the firmware. +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum DriverProvides { + /// The driver can provide anything, so every controller is reconnected. + #[default] + #[serde(rename = "any")] + Any, + /// The driver only provides filesystem or disk support, + /// so only the controllers that are disks or partitions are reconnected. + #[serde(rename = "filesystems")] + Filesystems, } diff --git a/docs/config.md b/docs/config.md index d9fa75c..a4de8c2 100644 --- a/docs/config.md +++ b/docs/config.md @@ -161,11 +161,17 @@ controller so the new filesystems appear. Drivers have no defined load order. ```toml [drivers.ext4] path = "\\sprout\\drivers\\ext4.efi" +provides = "filesystems" ``` -| Key | Meaning | -|--------|--------------------------------------------------------| -| `path` | Path to the driver image. Required. Can use values. | +| Key | Default | Meaning | +|------------|---------|-------------------------------------------------------------------------| +| `path` | | Path to the driver image. Required. Can use values. | +| `provides` | `any` | What the driver provides: `any` or `filesystems`. | + +With `filesystems`, the driver only adds filesystem or disk support. When every driver says so, Sprout +reconnects only the disk and partition controllers instead of all of them. A single `any` driver makes +Sprout reconnect every controller. The name (`ext4` above) is only for error messages and doesn't have to match the file.