Skip to content

Commit 4f3c7ec

Browse files
committed
[feature/patina-boot] patina_boot: Add ConnectController service trait
Add a pluggable ConnectController trait for controller connection strategies during device enumeration, replacing the hardcoded connect_all() call in SimpleBootManager. - ConnectController trait with single connect() method, generic over B: BootServices for mockable unit testing - ConnectAllStrategy default implementation preserves existing behavior - SimpleBootManager::with_connect_strategy() constructor for custom strategies; SimpleBootManager::new() is unchanged - interleave_connect_and_dispatch() made generic over the connection function Enables platforms to implement selective device enumeration (e.g., PCI-only, skip USB for headless) without writing a full custom BootOrchestrator. Ref: OpenDevicePartnership/modern-payload#205
1 parent f808a3e commit 4f3c7ec

6 files changed

Lines changed: 365 additions & 26 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//! Connect Controller service interface.
2+
//!
3+
//! Defines the [`ConnectController`] trait for pluggable controller connection
4+
//! strategies during device enumeration.
5+
//!
6+
//! ## License
7+
//!
8+
//! Copyright (c) Microsoft Corporation.
9+
//!
10+
//! SPDX-License-Identifier: Apache-2.0
11+
//!
12+
13+
use patina::{
14+
boot_services::{BootServices, StandardBootServices},
15+
error::Result,
16+
};
17+
18+
/// Pluggable controller connection strategy for device enumeration.
19+
///
20+
/// The default [`ConnectAllStrategy`](crate::ConnectAllStrategy) connects all
21+
/// controllers recursively. Platforms can implement this trait on a struct or
22+
/// pass a closure directly (blanket impl for `Fn(&B) -> Result<()> + Send + Sync + 'static`):
23+
///
24+
/// ```rust,ignore
25+
/// SimpleBootManager::with_connect_strategy(config, |bs: &StandardBootServices| {
26+
/// connect_pci(bs)?;
27+
/// connect_usb(bs)
28+
/// });
29+
/// ```
30+
pub trait ConnectController<B: BootServices = StandardBootServices>: Send + Sync + 'static {
31+
/// Perform one connection pass. The caller handles looping and
32+
/// interleaving with DXE dispatch.
33+
fn connect(&self, boot_services: &B) -> Result<()>;
34+
}
35+
36+
impl<B, F> ConnectController<B> for F
37+
where
38+
B: BootServices,
39+
F: Fn(&B) -> Result<()> + Send + Sync + 'static,
40+
{
41+
fn connect(&self, boot_services: &B) -> Result<()> {
42+
self(boot_services)
43+
}
44+
}

components/patina_boot/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@
3333
pub mod boot_dispatcher;
3434
pub mod boot_orchestrator;
3535
pub mod config;
36+
pub mod connect_controller;
3637
pub mod helpers;
3738
pub mod orchestrators;
39+
pub mod strategies;
3840

3941
pub use boot_dispatcher::BootDispatcher;
4042
pub use boot_orchestrator::BootOrchestrator;
43+
pub use connect_controller::ConnectController;
4144
pub use orchestrators::SimpleBootManager;
45+
pub use strategies::{ConnectAllStrategy, ConnectByProtocolStrategy};

components/patina_boot/src/orchestrators/simple_boot_manager.rs

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,50 @@
1515
//!
1616
extern crate alloc;
1717

18-
use alloc::vec::Vec;
18+
use alloc::{boxed::Box, vec::Vec};
1919

2020
use patina::{
21-
boot_services::StandardBootServices, device_path::paths::DevicePathBuf, error::EfiError,
21+
boot_services::{BootServices, StandardBootServices},
22+
device_path::paths::DevicePathBuf,
23+
error::EfiError,
2224
runtime_services::StandardRuntimeServices,
2325
};
2426
use r_efi::efi;
2527

2628
use patina::component::service::dxe_dispatch::DxeDispatch;
2729

28-
use patina::boot_services::BootServices;
29-
30-
use crate::{boot_orchestrator::BootOrchestrator, config::BootConfig, helpers};
30+
use crate::{
31+
boot_orchestrator::BootOrchestrator, config::BootConfig, connect_controller::ConnectController, helpers,
32+
strategies::ConnectAllStrategy,
33+
};
3134

3235
/// Interleave controller connection with DXE driver dispatch.
3336
///
34-
/// Alternates between connecting all controllers and dispatching newly loaded
35-
/// drivers (e.g., PCI option ROMs) until both the device topology stabilizes
36-
/// and no new drivers are dispatched.
37+
/// Alternates between the given connect function and dispatching newly loaded
38+
/// drivers (e.g., PCI option ROM drivers) until both the device topology
39+
/// stabilizes and no new drivers are dispatched.
3740
///
3841
/// This ensures that drivers loaded from firmware volumes during device
3942
/// enumeration (such as PCI option ROM drivers) are dispatched before
4043
/// continuing enumeration, allowing those drivers to bind to newly
4144
/// discovered controllers.
4245
fn interleave_connect_and_dispatch<B: BootServices, D: DxeDispatch + ?Sized>(
46+
connect_fn: impl Fn(&B) -> patina::error::Result<()>,
4347
boot_services: &B,
4448
dxe_services: &D,
4549
) -> patina::error::Result<()> {
4650
const MAX_ROUNDS: usize = 10;
4751

4852
for _round in 0..MAX_ROUNDS {
49-
helpers::connect_all(boot_services)?;
53+
connect_fn(boot_services)?;
5054
if !dxe_services.dispatch()? {
5155
return Ok(());
5256
}
5357
}
5458

5559
debug_assert!(false, "connect-dispatch interleaving did not converge after {MAX_ROUNDS} rounds");
5660

57-
Ok(())
61+
Err(EfiError::Timeout)
5862
}
5963

6064
/// Simple boot manager implementing [`BootOrchestrator`].
@@ -73,11 +77,16 @@ fn interleave_connect_and_dispatch<B: BootServices, D: DxeDispatch + ?Sized>(
7377
/// 7. Call failure handler if all options exhausted
7478
pub struct SimpleBootManager {
7579
config: BootConfig,
80+
connect_strategy: Box<dyn ConnectController>,
7681
}
7782

7883
impl SimpleBootManager {
7984
/// Create a `SimpleBootManager` from a boot configuration.
8085
///
86+
/// Uses [`ConnectAllStrategy`] by default. To customize which controllers
87+
/// are connected during device enumeration, use
88+
/// [`with_connect_strategy()`](Self::with_connect_strategy).
89+
///
8190
/// ## Example
8291
///
8392
/// ```rust,ignore
@@ -92,7 +101,23 @@ impl SimpleBootManager {
92101
/// );
93102
/// ```
94103
pub fn new(config: BootConfig) -> Self {
95-
Self { config }
104+
Self { config, connect_strategy: Box::new(ConnectAllStrategy) }
105+
}
106+
107+
/// Create a `SimpleBootManager` with a custom connection strategy.
108+
///
109+
/// ## Example
110+
///
111+
/// ```rust,ignore
112+
/// use patina_boot::{SimpleBootManager, ConnectByProtocolStrategy, config::BootConfig};
113+
///
114+
/// let manager = SimpleBootManager::with_connect_strategy(
115+
/// BootConfig::new(nvme_esp_path()),
116+
/// ConnectByProtocolStrategy::new(&[&PCI_ROOT_BRIDGE_IO_PROTOCOL_GUID]),
117+
/// );
118+
/// ```
119+
pub fn with_connect_strategy(config: BootConfig, strategy: impl ConnectController) -> Self {
120+
Self { config, connect_strategy: Box::new(strategy) }
96121
}
97122
}
98123

@@ -113,7 +138,9 @@ impl BootOrchestrator for SimpleBootManager {
113138
dxe_dispatch: &dyn DxeDispatch,
114139
image_handle: efi::Handle,
115140
) -> Result<!, EfiError> {
116-
if let Err(e) = interleave_connect_and_dispatch(boot_services, dxe_dispatch) {
141+
if let Err(e) =
142+
interleave_connect_and_dispatch(|bs| self.connect_strategy.connect(bs), boot_services, dxe_dispatch)
143+
{
117144
log::error!("interleave_connect_and_dispatch failed: {:?}", e);
118145
}
119146

@@ -213,7 +240,7 @@ mod tests {
213240
}
214241

215242
#[test]
216-
fn test_interleave_single_round_no_drivers_dispatched() {
243+
fn test_simple_boot_manager_interleave_single_round_no_drivers_dispatched() {
217244
let box_mock = leaked_boot_services_for_box();
218245
let mut boot_mock = MockBootServices::new();
219246

@@ -222,12 +249,12 @@ mod tests {
222249

223250
let dxe_mock = MockDxeDispatcher::new(&[Ok(false)]);
224251

225-
let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock);
252+
let result = interleave_connect_and_dispatch(helpers::connect_all, &boot_mock, &dxe_mock);
226253
assert!(result.is_ok());
227254
}
228255

229256
#[test]
230-
fn test_interleave_multiple_rounds() {
257+
fn test_simple_boot_manager_interleave_multiple_rounds() {
231258
let box_mock = leaked_boot_services_for_box();
232259
let mut boot_mock = MockBootServices::new();
233260

@@ -236,24 +263,24 @@ mod tests {
236263

237264
let dxe_mock = MockDxeDispatcher::new(&[Ok(true), Ok(false)]);
238265

239-
let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock);
266+
let result = interleave_connect_and_dispatch(helpers::connect_all, &boot_mock, &dxe_mock);
240267
assert!(result.is_ok());
241268
}
242269

243270
#[test]
244-
fn test_interleave_connect_failure_propagates() {
271+
fn test_simple_boot_manager_interleave_connect_failure_propagates() {
245272
let mut boot_mock = MockBootServices::new();
246273

247274
boot_mock.expect_locate_handle_buffer().returning(|_| Err(efi::Status::NOT_FOUND));
248275

249276
let dxe_mock = MockDxeDispatcher::new(&[]);
250277

251-
let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock);
278+
let result = interleave_connect_and_dispatch(helpers::connect_all, &boot_mock, &dxe_mock);
252279
assert!(result.is_err());
253280
}
254281

255282
#[test]
256-
fn test_interleave_dispatch_failure_propagates() {
283+
fn test_simple_boot_manager_interleave_dispatch_failure_propagates() {
257284
let box_mock = leaked_boot_services_for_box();
258285
let mut boot_mock = MockBootServices::new();
259286

@@ -262,12 +289,12 @@ mod tests {
262289

263290
let dxe_mock = MockDxeDispatcher::new(&[Err(EfiError::DeviceError)]);
264291

265-
let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock);
292+
let result = interleave_connect_and_dispatch(helpers::connect_all, &boot_mock, &dxe_mock);
266293
assert!(result.is_err());
267294
}
268295

269296
#[test]
270-
fn test_interleave_stops_at_max_rounds() {
297+
fn test_simple_boot_manager_interleave_stops_at_max_rounds() {
271298
let box_mock = leaked_boot_services_for_box();
272299
let mut boot_mock = MockBootServices::new();
273300

@@ -276,12 +303,48 @@ mod tests {
276303

277304
let dxe_mock = MockDxeDispatcher::new(&[Ok(true); 10]);
278305

279-
let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock);
306+
let result = interleave_connect_and_dispatch(helpers::connect_all, &boot_mock, &dxe_mock);
307+
assert!(result.is_err(), "non-converging interleave must return an error, not silently succeed");
308+
assert_eq!(result.unwrap_err(), EfiError::Timeout);
309+
}
310+
311+
#[test]
312+
fn test_simple_boot_manager_interleave_custom_connect_fn_failure() {
313+
let boot_mock = MockBootServices::new();
314+
let dxe_mock = MockDxeDispatcher::new(&[]);
315+
316+
let result =
317+
interleave_connect_and_dispatch(|_bs: &MockBootServices| Err(EfiError::DeviceError), &boot_mock, &dxe_mock);
318+
assert!(result.is_err());
319+
}
320+
321+
#[test]
322+
fn test_simple_boot_manager_interleave_custom_connect_fn_success() {
323+
let boot_mock = MockBootServices::new();
324+
let dxe_mock = MockDxeDispatcher::new(&[Ok(false)]);
325+
326+
let result = interleave_connect_and_dispatch(|_bs: &MockBootServices| Ok(()), &boot_mock, &dxe_mock);
280327
assert!(result.is_ok());
281328
}
282329

330+
// Tests for ConnectController and with_connect_strategy
331+
332+
#[test]
333+
fn test_simple_boot_manager_with_connect_strategy() {
334+
let config = BootConfig::new(test_device_path());
335+
let manager = SimpleBootManager::with_connect_strategy(config, ConnectAllStrategy);
336+
assert_eq!(manager.config().devices().count(), 1);
337+
}
338+
339+
#[test]
340+
fn test_simple_boot_manager_with_closure_connect_strategy() {
341+
let config = BootConfig::new(test_device_path()).with_hotkey(0x16);
342+
let manager = SimpleBootManager::with_connect_strategy(config, |_bs: &StandardBootServices| Ok(()));
343+
assert_eq!(manager.config().hotkey(), Some(0x16));
344+
}
345+
283346
#[test]
284-
fn test_new() {
347+
fn test_simple_boot_manager_new() {
285348
let config = BootConfig::new(test_device_path()).with_hotkey(0x16).with_hotkey_device(test_device_path());
286349
let manager = SimpleBootManager::new(config);
287350
assert_eq!(manager.config().hotkey(), Some(0x16));
@@ -290,7 +353,7 @@ mod tests {
290353
}
291354

292355
#[test]
293-
fn test_with_hotkey() {
356+
fn test_simple_boot_manager_with_hotkey() {
294357
let config = BootConfig::new(test_device_path())
295358
.with_device(test_device_path())
296359
.with_hotkey(0x16)
@@ -302,7 +365,7 @@ mod tests {
302365
}
303366

304367
#[test]
305-
fn test_without_hotkey() {
368+
fn test_simple_boot_manager_without_hotkey() {
306369
let config = BootConfig::new(test_device_path()).with_device(test_device_path());
307370
let manager = SimpleBootManager::new(config);
308371
assert!(manager.config().hotkey().is_none());
@@ -311,7 +374,7 @@ mod tests {
311374
}
312375

313376
#[test]
314-
fn test_with_failure_handler() {
377+
fn test_simple_boot_manager_with_failure_handler() {
315378
let called = Arc::new(AtomicBool::new(false));
316379
let called_clone = called.clone();
317380

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Built-in [`ConnectController`](crate::connect_controller::ConnectController) strategies.
2+
//!
3+
//! ## License
4+
//!
5+
//! Copyright (c) Microsoft Corporation.
6+
//!
7+
//! SPDX-License-Identifier: Apache-2.0
8+
//!
9+
10+
mod connect_all;
11+
mod connect_by_protocol;
12+
13+
pub use connect_all::ConnectAllStrategy;
14+
pub use connect_by_protocol::ConnectByProtocolStrategy;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Connect-all strategy: connect all controllers.
2+
//!
3+
//! ## License
4+
//!
5+
//! Copyright (c) Microsoft Corporation.
6+
//!
7+
//! SPDX-License-Identifier: Apache-2.0
8+
//!
9+
10+
use patina::{boot_services::BootServices, error::Result};
11+
12+
use crate::{connect_controller::ConnectController, helpers};
13+
14+
/// Connects all controllers recursively via
15+
/// [`helpers::connect_all()`](crate::helpers::connect_all).
16+
/// Used as the default strategy by [`SimpleBootManager::new()`](crate::SimpleBootManager::new).
17+
pub struct ConnectAllStrategy;
18+
19+
impl<B: BootServices> ConnectController<B> for ConnectAllStrategy {
20+
fn connect(&self, boot_services: &B) -> Result<()> {
21+
helpers::connect_all(boot_services)
22+
}
23+
}

0 commit comments

Comments
 (0)