feat(boot): reconnect only storage for filesystem drivers (#130)

Add a provides field to drivers, either any or filesystems. When every
driver only provides filesystems, only disk and partition controllers
are reconnected.
This commit is contained in:
alex authored and GitHub committed 2026-10-16 12:33:35 -07:00
1 parent fe068c5f00
commit 86b7a6e5a5
3 files changed
+79 -18

No files matched your search

+53 -15
View File
@@ -3,12 +3,16 @@ use alloc::collections::BTreeMap;
use alloc::format; use alloc::format;
use alloc::rc::Rc; use alloc::rc::Rc;
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec;
use anyhow::{Context, Result}; 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::source::ImageSource;
use eficore::loader::{ImageLoadRequest, ImageLoader}; use eficore::loader::{ImageLoadRequest, ImageLoader};
use log::info; use log::info;
use uefi::boot::SearchType; 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. /// Loads the driver specified by the `driver` declaration.
fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result<()> { fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result<()> {
@@ -37,27 +41,53 @@ fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result
Ok(()) Ok(())
} }
/// Reconnects all handles to their controllers. /// Locates the handles that are disks or partitions.
/// This is effectively a UEFI stack reload in a sense. /// These are the only controllers that filesystem drivers can bind to.
/// After we load all the drivers, we need to reconnect all of their handles fn locate_storage_handles() -> Result<Vec<Handle>> {
/// so that filesystems are recognized again. let mut storage = Vec::new();
fn reconnect() -> Result<()> { for guid in [&BlockIO::GUID, &DiskIo::GUID, &DiskIo2::GUID] {
// Locate all of the handles in the UEFI stack. // Not finding any handles for a protocol is not an error.
let handles = uefi::boot::locate_handle_buffer(SearchType::AllHandles) let Ok(handles) = uefi::boot::locate_handle_buffer(SearchType::ByProtocol(guid)) else {
.context("unable to locate handles buffer")?; continue;
};
for handle in handles.iter() {
if !storage.contains(handle) {
storage.push(*handle);
}
}
}
Ok(storage)
}
for handle in handles.iter() { /// 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. // 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. // 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(()) Ok(())
} }
/// Load all the drivers specified in `drivers`. /// Load all the drivers specified in `drivers`.
/// There is no driver order currently. This will reconnect all the controllers /// There is no driver order currently. This will reconnect the controllers
/// to all handles if at least one driver was loaded. /// 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( pub fn load(
context: Rc<SproutContext>, context: Rc<SproutContext>,
drivers: &BTreeMap<String, DriverDeclaration>, drivers: &BTreeMap<String, DriverDeclaration>,
@@ -74,8 +104,16 @@ pub fn load(
load_driver(context.clone(), driver).context(format!("unable to load driver: {}", name))?; load_driver(context.clone(), driver).context(format!("unable to load driver: {}", name))?;
} }
// Reconnect all the controllers to all handles. // Reconnect everything if any driver can provide anything, otherwise only storage.
reconnect().context("unable to reconnect drivers")?; 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"); info!("loaded drivers");
// We've now loaded all the drivers, so we can return. // We've now loaded all the drivers, so we can return.
+17
View File
@@ -10,4 +10,21 @@ pub struct DriverDeclaration {
/// The filesystem path to the driver. /// The filesystem path to the driver.
/// This file should be an EFI executable that can be located and executed. /// This file should be an EFI executable that can be located and executed.
pub path: String, 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,
} }
+9 -3
View File
@@ -161,11 +161,17 @@ controller so the new filesystems appear. Drivers have no defined load order.
```toml ```toml
[drivers.ext4] [drivers.ext4]
path = "\\sprout\\drivers\\ext4.efi" path = "\\sprout\\drivers\\ext4.efi"
provides = "filesystems"
``` ```
| Key | Meaning | | Key | Default | Meaning |
|--------|--------------------------------------------------------| |------------|---------|-------------------------------------------------------------------------|
| `path` | Path to the driver image. Required. Can use values. | | `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. The name (`ext4` above) is only for error messages and doesn't have to match the file.