1use anyhow::{Context as _, Result};
6use cm_rust::FidlIntoNative;
7use fidl_fuchsia_component_decl as fdecl;
8use fidl_fuchsia_component_test as ftest;
9use fidl_fuchsia_driver_development as fdd;
10use fidl_fuchsia_driver_test as fdt;
11use fidl_fuchsia_io as fio;
12use flyweights::FlyStr;
13use fuchsia_async as fasync;
14use fuchsia_component::server::ServiceFs;
15use fuchsia_component_test::{
16 Capability, ChildOptions, ChildRef, CollectionRef, LocalComponentHandles, RealmBuilder,
17 RealmInstance, Ref, Route,
18};
19use futures::{StreamExt, TryStreamExt};
20use std::sync::Arc;
21use zx::AsHandleRef;
22
23fn clone(
24 dir: &fio::DirectoryProxy,
25) -> Result<fidl::endpoints::ClientEnd<fio::DirectoryMarker>, fidl::Error> {
26 let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
27 dir.clone(fidl::endpoints::ServerEnd::new(server_end.into_channel()))?;
28 Ok(client_end)
29}
30
31async fn internal_serve(
32 stream: fidl_fuchsia_driver_test::InternalRequestStream,
33 test_pkg_dir: Arc<fio::DirectoryProxy>,
34 test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
35 boot_dir: Arc<Option<fio::DirectoryProxy>>,
36 boot_driver_components: Arc<Option<Vec<String>>>,
37) {
38 stream
39 .try_for_each_concurrent(None, |request| {
40 let test_pkg_dir = test_pkg_dir.clone();
41 let test_resolution_context = test_resolution_context.clone();
42 let boot_dir = boot_dir.clone();
43 let boot_driver_components = boot_driver_components.clone();
44 async move {
45 match request {
46 fidl_fuchsia_driver_test::InternalRequest::GetTestPackage { responder } => {
47 let cloned = clone(test_pkg_dir.as_ref())?;
48 responder.send(Ok(Some(cloned)))?;
49 }
50 fidl_fuchsia_driver_test::InternalRequest::GetTestResolutionContext {
51 responder,
52 } => responder.send(Ok(test_resolution_context.as_ref().as_ref()))?,
53 fidl_fuchsia_driver_test::InternalRequest::GetBootDirectory { responder } => {
54 match boot_dir.as_ref() {
55 Some(boot_dir) => {
56 let cloned = clone(boot_dir)?;
57 responder.send(Ok(Some(cloned)))?;
58 }
59 None => responder.send(Ok(None))?,
60 }
61 }
62 fidl_fuchsia_driver_test::InternalRequest::GetBootDriverOverrides {
63 responder,
64 } => responder.send(Ok(boot_driver_components
65 .as_ref()
66 .clone()
67 .unwrap_or(vec![])
68 .as_slice()))?,
69 };
70 Ok(())
71 }
72 })
73 .await
74 .expect("fuchsia.driver.test.Internal failed.");
75}
76
77async fn resource_provider_serve(
78 stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream,
79 devicetree: Arc<Option<zx::Vmo>>,
80) {
81 stream
82 .try_for_each_concurrent(None, |request| {
83 let devicetree = devicetree.clone();
84 async move {
85 match request {
86 fidl_fuchsia_driver_test::ResourceProviderRequest::GetDeviceTree {
87 responder,
88 } => {
89 if let Some(vmo) = devicetree.as_ref().as_ref().map(|d| {
90 d.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate")
91 }) {
92 responder.send(Ok(vmo))?;
93 } else {
94 responder.send(Err(zx::Status::NOT_FOUND.into_raw()))?;
95 }
96 }
97 };
98 Ok(())
99 }
100 })
101 .await
102 .expect("fuchsia.driver.test.Internal failed.");
103}
104
105async fn run_internal_server(
106 handles: LocalComponentHandles,
107 test_pkg_dir: Arc<fio::DirectoryProxy>,
108 test_resolution_context: Arc<Option<fidl_fuchsia_component_resolution::Context>>,
109 boot_dir: Arc<Option<fio::DirectoryProxy>>,
110 boot_driver_components: Arc<Option<Vec<String>>>,
111 devicetree: Arc<Option<zx::Vmo>>,
112) -> Result<()> {
113 let mut fs = ServiceFs::new();
114
115 fs.dir("svc").add_fidl_service(
116 move |stream: fidl_fuchsia_driver_test::InternalRequestStream| {
117 fasync::Task::spawn(internal_serve(
118 stream,
119 test_pkg_dir.clone(),
120 test_resolution_context.clone(),
121 boot_dir.clone(),
122 boot_driver_components.clone(),
123 ))
124 .detach();
125 },
126 );
127 fs.dir("svc").add_fidl_service(
128 move |stream: fidl_fuchsia_driver_test::ResourceProviderRequestStream| {
129 fasync::Task::spawn(resource_provider_serve(stream, devicetree.clone())).detach();
130 },
131 );
132 fs.serve_connection(handles.outgoing_dir)?;
133 fs.collect::<()>().await;
134 Ok(())
135}
136
137fn capabilities_eq_name(a: &ftest::Capability, b: &ftest::Capability) -> bool {
139 match (a, b) {
140 (ftest::Capability::Protocol(a), ftest::Capability::Protocol(b)) => a.name == b.name,
141 (ftest::Capability::Directory(a), ftest::Capability::Directory(b)) => a.name == b.name,
142 (ftest::Capability::Storage(a), ftest::Capability::Storage(b)) => a.name == b.name,
143 (ftest::Capability::Service(a), ftest::Capability::Service(b)) => a.name == b.name,
144 (ftest::Capability::EventStream(a), ftest::Capability::EventStream(b)) => a.name == b.name,
145 (ftest::Capability::Config(a), ftest::Capability::Config(b)) => a.name == b.name,
146 (ftest::Capability::Dictionary(a), ftest::Capability::Dictionary(b)) => a.name == b.name,
147 (ftest::Capability::Resolver(a), ftest::Capability::Resolver(b)) => a.name == b.name,
148 (ftest::Capability::Runner(a), ftest::Capability::Runner(b)) => a.name == b.name,
149 _ => false,
150 }
151}
152
153#[derive(Debug, Clone, Default)]
154pub struct Options {
155 using_subpackage: Option<bool>,
156 driver_offers: Option<(Ref, Vec<ftest::Capability>)>,
157 driver_exposes: Option<Vec<ftest::Capability>>,
158 extra_realm_capabilities: Vec<(ftest::Capability, Ref)>,
159}
160
161impl Options {
162 pub fn new() -> Self {
163 Self::default()
164 }
165
166 pub fn using_subpackage(mut self, using_subpackage: bool) -> Self {
167 self.using_subpackage = Some(using_subpackage);
168 self
169 }
170
171 pub fn driver_offers(mut self, provider: Ref, offers: Vec<ftest::Capability>) -> Self {
172 self.driver_offers = Some((provider, offers));
173 self
174 }
175
176 pub fn driver_exposes(mut self, exposes: Vec<ftest::Capability>) -> Self {
177 self.driver_exposes = Some(exposes);
178 self
179 }
180
181 pub fn add_extra_realm_capability(
182 mut self,
183 capability: ftest::Capability,
184 from: impl Into<Ref>,
185 ) -> Self {
186 self.extra_realm_capabilities.push((capability, from.into()));
187 self
188 }
189}
190
191#[async_trait::async_trait]
192pub trait DriverTestRealmBuilder {
193 async fn driver_test_realm_setup(
197 &self,
198 options: Options,
199 args: fdt::RealmArgs,
200 ) -> Result<&Self>;
201}
202
203#[async_trait::async_trait]
204impl DriverTestRealmBuilder for RealmBuilder {
205 async fn driver_test_realm_setup(
206 &self,
207 options: Options,
208 args: fdt::RealmArgs,
209 ) -> Result<&Self> {
210 let manifest_provider =
211 fuchsia_component::client::connect_to_protocol::<fdt::ManifestProviderMarker>()?;
212 let stream = manifest_provider
213 .get_manifest(&fdt::GetManifestRequest {
214 using_subpackage: options.using_subpackage,
215 ..Default::default()
216 })
217 .await?
218 .expect("manifest stream");
219
220 let mut manifest: Vec<u8> = vec![];
221 loop {
222 let mut read = stream.read_to_vec(
223 zx::StreamReadOptions::empty(),
224 fidl_fuchsia_io::MAX_TRANSFER_SIZE as usize,
225 )?;
226 if read.is_empty() {
227 break;
228 }
229 manifest.append(&mut read);
230 }
231
232 let component = fidl::unpersist::<fdecl::Component>(manifest.as_slice())
233 .context("unpersisting the manifest vector")?;
234
235 let realm = self
238 .add_child_realm_from_decl(
239 "driver_test_realm",
240 component.fidl_into_native(),
241 ChildOptions::new(),
242 )
243 .await?;
244
245 self.add_route(
247 Route::new()
248 .capability(Capability::protocol_by_name("fuchsia.diagnostics.ArchiveAccessor"))
249 .from(Ref::parent())
250 .to(&realm),
251 )
252 .await?;
253
254 let dtr_support: ChildRef = "dtr_support".into();
255 let fake_resolver: ChildRef = "fake_resolver".into();
256 let driver_manager: ChildRef = "driver_manager".into();
257 let driver_index: ChildRef = "driver_index".into();
258
259 let boot_drivers: CollectionRef = "boot-drivers".into();
260 let base_drivers: CollectionRef = "base-drivers".into();
261 let full_drivers: CollectionRef = "full-drivers".into();
262
263 let devicetree = Arc::new(args.devicetree);
264
265 let test_component = if let Some(test_component) = args.test_component {
267 test_component
268 } else {
269 let realm = fuchsia_component::client::connect_to_protocol::<
270 fidl_fuchsia_component::RealmMarker,
271 >()?;
272
273 realm.get_resolved_info().await.unwrap().unwrap()
274 };
275
276 let test_resolution_context = Arc::new(test_component.resolution_context);
277 let test_pkg_dir = Arc::new(
278 test_component.package.expect("a pkg").directory.expect("a directory").into_proxy(),
279 );
280
281 let boot_dir = Arc::new(match args.boot {
282 Some(boot_dir) => {
283 if !boot_dir.as_handle_ref().is_invalid() {
284 Some(boot_dir.into_proxy())
285 } else {
286 None
287 }
288 }
289 None => None,
290 });
291 let boot_driver_components = Arc::new(args.boot_driver_components);
292
293 self.add_route(
295 Route::new()
296 .capability(
297 Capability::protocol_by_name("fuchsia.component.resolution.Resolver-hermetic")
298 .optional(),
299 )
300 .capability(
301 Capability::protocol_by_name("fuchsia.pkg.PackageResolver-hermetic").optional(),
302 )
303 .from(Ref::parent())
304 .to(&realm),
305 )
306 .await?;
307
308 let driver_test_internal = realm
310 .add_local_child(
311 "driver_test_internal",
312 move |handles| {
313 let test_pkg_dir = test_pkg_dir.clone();
314 let test_resolution_context = test_resolution_context.clone();
315 let boot_dir = boot_dir.clone();
316 let boot_driver_components = boot_driver_components.clone();
317 let devicetree = devicetree.clone();
318
319 Box::pin(run_internal_server(
320 handles,
321 test_pkg_dir,
322 test_resolution_context,
323 boot_dir,
324 boot_driver_components,
325 devicetree,
326 ))
327 },
328 ChildOptions::new(),
329 )
330 .await?;
331
332 let mut tunnel_boot_items = false;
335 let mut voided_offers: Vec<ftest::Capability> = vec![
336 Capability::protocol_by_name("fuchsia.tracing.provider.Registry").optional().into(),
337 Capability::protocol_by_name("fuchsia.boot.WriteOnlyLog").optional().into(),
338 Capability::protocol_by_name("fuchsia.scheduler.RoleManager").optional().into(),
339 Capability::protocol_by_name("fuchsia.boot.Items").optional().into(),
340 Capability::protocol_by_name("fuchsia.kernel.IommuResource").optional().into(),
341 Capability::protocol_by_name("fuchsia.diagnostics.LogFlusher").optional().into(),
342 Capability::protocol_by_name("fuchsia.kernel.MexecResource").optional().into(),
343 Capability::protocol_by_name("fuchsia.kernel.PowerResource").optional().into(),
344 ];
345 for (capability, from) in options.extra_realm_capabilities {
346 voided_offers.retain(|voided| !capabilities_eq_name(voided, &capability));
348
349 if from != Ref::void()
350 && capabilities_eq_name(
351 &Capability::protocol_by_name("fuchsia.boot.Items").into(),
352 &capability,
353 )
354 {
355 tunnel_boot_items = true;
356 }
357
358 self.add_route(Route::new().capability(capability).from(from).to(&realm)).await?;
359 }
360
361 for voided in voided_offers {
363 self.add_route(Route::new().capability(voided).from(Ref::void()).to(&realm)).await?;
364 }
365
366 if args.dtr_offers.is_some() {
369 panic!("Please use |Options::driver_offers| instead of dtr_offers.")
370 }
371 if let Some((provider, offers)) = options.driver_offers {
372 for offer in offers {
373 self.add_route(
374 Route::new().capability(offer.clone()).from(provider.clone()).to(&realm),
375 )
376 .await?;
377 realm
378 .add_route(
379 Route::new()
380 .capability(offer)
381 .from(Ref::parent())
382 .to(&boot_drivers)
383 .to(&base_drivers)
384 .to(&full_drivers),
385 )
386 .await?;
387 }
388 }
389
390 if args.dtr_exposes.is_some() {
392 panic!("Please use |Options::driver_exposes| instead of dtr_exposes.")
393 }
394 if let Some(exposes) = options.driver_exposes {
395 for expose in exposes {
396 realm
397 .add_route(
398 Route::new()
399 .capability(expose.clone())
400 .from(&boot_drivers)
401 .to(Ref::parent()),
402 )
403 .await?;
404 realm
405 .add_route(
406 Route::new()
407 .capability(expose.clone())
408 .from(&base_drivers)
409 .to(Ref::parent()),
410 )
411 .await?;
412 realm
413 .add_route(
414 Route::new()
415 .capability(expose.clone())
416 .from(&full_drivers)
417 .to(Ref::parent()),
418 )
419 .await?;
420
421 self.add_route(Route::new().capability(expose).from(&realm).to(Ref::parent()))
422 .await?;
423 }
424 }
425
426 realm
428 .add_route(
429 Route::new()
430 .capability(Capability::protocol_by_name(
431 "fuchsia.driver.test.ResourceProvider",
432 ))
433 .from(&driver_test_internal)
434 .to(&dtr_support),
435 )
436 .await?;
437
438 realm
440 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
441 name: "fuchsia.driver.testrealm.TunnelBootItems".parse()?,
442 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(
443 tunnel_boot_items,
444 )),
445 }))
446 .await?;
447
448 realm
449 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
450 name: "fuchsia.driver.testrealm.BoardName".parse()?,
451 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
452 args.board_name.unwrap_or_default().into(),
453 )),
454 }))
455 .await?;
456
457 realm
458 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
459 name: "fuchsia.driver.testrealm.PlatformVid".parse()?,
460 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
461 args.platform_vid.map(|v| v.to_string()).unwrap_or_default().into(),
462 )),
463 }))
464 .await?;
465
466 realm
467 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
468 name: "fuchsia.driver.testrealm.PlatformPid".parse()?,
469 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
470 args.platform_pid.map(|v| v.to_string()).unwrap_or_default().into(),
471 )),
472 }))
473 .await?;
474
475 let bind_eager = args.driver_bind_eager.unwrap_or_default();
476 realm
477 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
478 name: "fuchsia.driver.BindEager".parse()?,
479 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
480 bind_eager.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
481 )),
482 }))
483 .await?;
484
485 let driver_disable = args.driver_disable.unwrap_or_default();
486 realm
487 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
488 name: "fuchsia.driver.DisabledDrivers".parse()?,
489 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
490 driver_disable.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
491 )),
492 }))
493 .await?;
494
495 let driver_index_stop_timeout_millis = args.driver_index_stop_timeout_millis.unwrap_or(-1);
496 realm
497 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
498 name: "fuchsia.driver.index.StopOnIdleTimeoutMillis".parse()?,
499 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Int64(
500 driver_index_stop_timeout_millis,
501 )),
502 }))
503 .await?;
504 realm
505 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
506 name: "fuchsia.power.WaitForSuspendingToken".parse()?,
507 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
508 }))
509 .await?;
510
511 let root_driver = match args.root_driver {
512 Some(val) => val,
513 None => "fuchsia-boot:///dtr#meta/test-parent-sys.cm".to_string(),
514 };
515 realm
516 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
517 name: "fuchsia.driver.manager.RootDriver".parse()?,
518 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
519 root_driver.into(),
520 )),
521 }))
522 .await?;
523
524 let software_devs_src = match args.software_devices {
526 Some(devs) => {
527 let names = devs.iter().map(|dev| dev.device_name.clone()).collect::<Vec<_>>();
528 let ids = devs.iter().map(|dev| dev.device_id).collect::<Vec<_>>();
529 realm
530 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
531 name: "fuchsia.platform.bus.SoftwareDeviceNames".parse()?,
532 value: cm_rust::ConfigValue::Vector(
533 cm_rust::ConfigVectorValue::StringVector(
534 names.into_iter().map(FlyStr::new).collect::<Box<[_]>>(),
535 ),
536 ),
537 }))
538 .await?;
539 realm
540 .add_capability(cm_rust::CapabilityDecl::Config(cm_rust::ConfigurationDecl {
541 name: "fuchsia.platform.bus.SoftwareDeviceIds".parse()?,
542 value: cm_rust::ConfigValue::Vector(
543 cm_rust::ConfigVectorValue::Uint32Vector(ids.into()),
544 ),
545 }))
546 .await?;
547 Ref::self_()
548 }
549 None => Ref::void(),
550 };
551
552 realm
554 .add_route(
555 Route::new()
556 .capability(Capability::configuration("fuchsia.driver.BindEager"))
557 .capability(Capability::configuration("fuchsia.driver.DisabledDrivers"))
558 .capability(Capability::configuration(
559 "fuchsia.driver.index.StopOnIdleTimeoutMillis",
560 ))
561 .from(Ref::self_())
562 .to(&driver_index),
563 )
564 .await?;
565
566 realm
567 .add_route(
568 Route::new()
569 .capability(Capability::configuration("fuchsia.driver.manager.RootDriver"))
570 .from(Ref::self_())
571 .to(&driver_manager),
572 )
573 .await?;
574
575 realm
576 .add_route(
577 Route::new()
578 .capability(
579 Capability::configuration(
580 "fuchsia.driver.manager.SetRootDriverHostCritical",
581 )
582 .optional(),
583 )
584 .capability(
585 Capability::configuration("fuchsia.driver.manager.SuspendTimeoutFallback")
586 .optional(),
587 )
588 .from(Ref::void())
589 .to(&driver_manager),
590 )
591 .await?;
592
593 realm
594 .add_route(
595 Route::new()
596 .capability(Capability::configuration(
597 "fuchsia.driver.testrealm.TunnelBootItems",
598 ))
599 .capability(Capability::configuration("fuchsia.driver.testrealm.BoardName"))
600 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformVid"))
601 .capability(Capability::configuration("fuchsia.driver.testrealm.PlatformPid"))
602 .from(Ref::self_())
603 .to(&dtr_support),
604 )
605 .await?;
606
607 realm
608 .add_route(
609 Route::new()
610 .capability(Capability::configuration("fuchsia.power.WaitForSuspendingToken"))
611 .from(Ref::self_())
612 .to(&driver_manager),
613 )
614 .await?;
615
616 realm
617 .add_route(
618 Route::new()
619 .capability(
620 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceNames")
621 .optional(),
622 )
623 .capability(
624 Capability::configuration("fuchsia.platform.bus.SoftwareDeviceIds")
625 .optional(),
626 )
627 .from(software_devs_src)
628 .to(&boot_drivers),
629 )
630 .await?;
631
632 realm
634 .add_route(
635 Route::new()
636 .capability(Capability::protocol_by_name("fuchsia.driver.test.Internal"))
637 .from(&driver_test_internal)
638 .to(&fake_resolver),
639 )
640 .await?;
641
642 self.add_route(
644 Route::new()
645 .capability(Capability::directory("dev-class"))
646 .capability(Capability::directory("dev-topological"))
647 .capability(Capability::protocol_by_name(
648 "fuchsia.driver.registrar.DriverRegistrar",
649 ))
650 .capability(Capability::protocol_by_name("fuchsia.driver.development.Manager"))
651 .capability(Capability::protocol_by_name(
652 "fuchsia.driver.framework.CompositeNodeManager",
653 ))
654 .capability(Capability::protocol_by_name("fuchsia.system.state.Administrator"))
655 .from(&realm)
656 .to(Ref::parent()),
657 )
658 .await?;
659 Ok(&self)
661 }
662}
663
664#[async_trait::async_trait]
665pub trait DriverTestRealmInstance {
666 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy>;
668
669 async fn wait_for_bootup(&self) -> Result<()>;
673
674 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo>;
676}
677
678#[async_trait::async_trait]
679impl DriverTestRealmInstance for RealmInstance {
680 fn driver_test_realm_connect_to_dev(&self) -> Result<fio::DirectoryProxy> {
681 fuchsia_fs::directory::open_directory_async(
682 self.root.get_exposed_dir(),
683 "dev-topological",
684 fio::Flags::empty(),
685 )
686 .map_err(Into::into)
687 }
688
689 async fn wait_for_bootup(&self) -> Result<()> {
690 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
691 manager.wait_for_bootup().await?;
692 Ok(())
693 }
694
695 async fn wait_for_node(&self, moniker: &str) -> Result<fdd::NodeInfo> {
696 let manager: fdd::ManagerProxy = self.root.connect_to_protocol_at_exposed_dir()?;
697 loop {
698 let (iterator, iterator_server) =
699 fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
700 manager.get_node_info(&[moniker.to_string()], iterator_server, true)?;
701 let next = iterator.get_next().await;
702 if let Ok(nodes) = next
703 && !nodes.is_empty()
704 && nodes[0].moniker == Some(moniker.to_string())
705 {
706 return Ok(nodes[0].clone());
707 }
708 }
709 }
710}