1515//!
1616extern crate alloc;
1717
18- use alloc:: vec:: Vec ;
18+ use alloc:: { boxed :: Box , vec:: Vec } ;
1919
2020use 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} ;
2426use r_efi:: efi;
2527
2628use 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.
4245fn 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
7478pub struct SimpleBootManager {
7579 config : BootConfig ,
80+ connect_strategy : Box < dyn ConnectController > ,
7681}
7782
7883impl 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
0 commit comments