1use crate::fuchsia_backends::{
14 FuchsiaClock, FuchsiaGpio, FuchsiaI2c, FuchsiaReset, FuchsiaSerial, FuchsiaSpi, LiveBackend,
15};
16use crate::platform_provider::MappedMmio;
17use crate::server::{self, ProxyState, SharedState, SharedStateData, ZxClock};
18use crate::trace::AuditTracer;
19use fidl_fuchsia_driver_lab as flab;
20use fidl_fuchsia_hardware_clock as fclock;
21use fidl_fuchsia_hardware_gpio as fgpio;
22use fidl_fuchsia_hardware_i2c as fi2c;
23use fidl_fuchsia_hardware_reset as freset;
24use fidl_fuchsia_hardware_serial as fserial;
25use fidl_fuchsia_hardware_spi as fspi;
26use fuchsia_async as fasync;
27#[cfg(feature = "fdf_driver")]
28use fuchsia_component::server::{ServiceFs, ServiceObj, ServiceObjLocal};
29use lab_proxy_core::access_policy::{
30 ProtocolCeiling, ResourceCeiling, ResourceId, ResourceKind, WritableRegister,
31};
32use lab_proxy_core::audit_ring::{AuditRecord, AuditRing, Decision, OpStatus};
33use lab_proxy_core::config::{AccessLimitEnforcer, ProxyConfig};
34use lab_proxy_core::executor::{ExecLimits, Executor};
35use lab_proxy_core::interrupt::{InterruptEvent, InterruptManager};
36use lab_proxy_core::provider::{ProvidedResources, ProviderError};
37use lab_proxy_core::session::{ProxyIdentity, QuiesceHook, SessionManager};
38pub use lab_proxy_core::state_bank::{StateBank, StateEntry, StateSlotHandle};
39use lab_proxy_core::target_policy::TargetPolicyManifest;
40use std::collections::BTreeMap;
41use std::ops::Range;
42use std::sync::atomic::{AtomicBool, Ordering};
43use std::sync::{Arc, Mutex};
44
45fn now_ns() -> i64 {
46 zx::MonotonicInstant::get().into_nanos()
47}
48
49pub struct DriverLabBuilder {
52 bundle: ProvidedResources<LiveBackend>,
53 next_id: ResourceId,
54 allow_mutating_sessions: bool,
55 config: ProxyConfig,
56 quiesce_hook: Option<QuiesceHook>,
57}
58
59impl DriverLabBuilder {
60 pub fn new(node_identity: impl Into<String>) -> Self {
62 let mut config = ProxyConfig::default();
63 config.enabled = cfg!(any(feature = "driver_lab_enabled", test));
64 Self {
65 bundle: ProvidedResources::embedded(&node_identity.into()),
66 next_id: 0,
67 allow_mutating_sessions: true,
68 config,
69 quiesce_hook: None,
70 }
71 }
72
73 #[cfg(feature = "fdf_driver")]
75 pub fn from_context(context: &fdf_component::DriverContext) -> Self {
76 let identity = context.node_name().unwrap_or("driver-lab.embedded");
77 Self::new(identity)
78 }
79
80 pub fn with_enabled(mut self, enabled: bool) -> Self {
84 self.config.enabled = enabled;
85 self
86 }
87
88 pub fn with_allow_mutating_sessions(mut self, allow: bool) -> Self {
90 self.allow_mutating_sessions = allow;
91 self
92 }
93
94 pub fn with_config(mut self, config: ProxyConfig) -> Self {
96 if let Ok(narrowed) = ProxyConfig::default().narrow_with(&config) {
97 self.config = narrowed;
98 }
99 self
100 }
101
102 pub fn with_quiesce_hook<F>(&mut self, hook: F) -> &mut Self
107 where
108 F: Fn(bool) + Send + Sync + 'static,
109 {
110 self.quiesce_hook = Some(Arc::new(hook));
111 self
112 }
113
114 pub fn with_mmio(
116 &mut self,
117 name: impl Into<String>,
118 vmo: &zx::Vmo,
119 offset: usize,
120 size: usize,
121 ) -> Result<ResourceId, zx::Status> {
122 self.add_mmio_vmo(name, vmo, offset, size as u64)
123 }
124
125 pub fn with_writable_registers(&mut self, id: ResourceId, offsets: Vec<u64>) -> &mut Self {
127 if let Some(ceiling) = self.bundle.ceiling.get_mut(&id) {
128 ceiling.writable_registers = offsets
129 .into_iter()
130 .map(|offset| lab_proxy_core::access_policy::WritableRegister {
131 offset,
132 width: 4,
133 allow_mask: 0xFFFF_FFFF,
134 allow_rmw: true,
135 require_precondition: false,
136 precondition_mask: 0,
137 readback: true,
138 })
139 .collect();
140 }
141 self
142 }
143
144 pub fn with_hard_denied_ranges(
146 &mut self,
147 id: ResourceId,
148 ranges: Vec<(u64, u64)>,
149 ) -> &mut Self {
150 if let Some(ceiling) = self.bundle.ceiling.get_mut(&id) {
151 ceiling.hard_denied = ranges.into_iter().map(|(start, end)| start..end).collect();
152 }
153 self
154 }
155
156 pub fn with_interrupt(&mut self, name: impl Into<String>) -> ResourceId {
158 self.add_interrupt(name)
159 }
160
161 pub fn add_mmio_vmo(
167 &mut self,
168 name: impl Into<String>,
169 vmo: &zx::Vmo,
170 offset: usize,
171 logical_size: u64,
172 ) -> Result<ResourceId, zx::Status> {
173 let ceiling = ResourceCeiling {
174 hard_denied: vec![],
175 allow_unknown_reads: true,
176 allow_poll: true,
177 writable_registers: vec![],
178 protocol: None,
179 allow_interrupt: false,
180 };
181 self.add_mmio_vmo_with_ceiling(name, vmo, offset, logical_size, ceiling)
182 }
183
184 pub fn add_mmio_vmo_with_ceiling(
188 &mut self,
189 name: impl Into<String>,
190 vmo: &zx::Vmo,
191 offset: usize,
192 logical_size: u64,
193 ceiling: ResourceCeiling,
194 ) -> Result<ResourceId, zx::Status> {
195 let map_len = usize::try_from(logical_size).map_err(|_| zx::Status::OUT_OF_RANGE)?;
196 let mmio = MappedMmio::from_vmo(vmo, offset, map_len)?;
197 Ok(self.add_mapped_mmio(name, mmio, logical_size, ceiling))
198 }
199
200 pub fn add_mapped_mmio(
202 &mut self,
203 name: impl Into<String>,
204 mmio: MappedMmio,
205 logical_size: u64,
206 ceiling: ResourceCeiling,
207 ) -> ResourceId {
208 let id = self.next_id;
209 self.next_id += 1;
210 let mapped_size = mmio.len() as u64;
211 self.bundle.add_mmio(id, name, logical_size, mapped_size, ceiling, LiveBackend::Mmio(mmio));
212 id
213 }
214
215 pub fn with_state_bank(&mut self, name: impl Into<String>, bank: StateBank) -> ResourceId {
219 self.add_state_bank(name, bank)
220 }
221
222 pub fn add_state_bank(&mut self, name: impl Into<String>, bank: StateBank) -> ResourceId {
225 let ceiling = bank.ceiling();
226 self.add_state_bank_with_ceiling(name, bank, ceiling)
227 }
228
229 pub fn add_state_bank_with_ceiling(
231 &mut self,
232 name: impl Into<String>,
233 bank: StateBank,
234 ceiling: ResourceCeiling,
235 ) -> ResourceId {
236 let id = self.next_id;
237 self.next_id += 1;
238 let logical_size = bank.logical_size();
239 self.bundle.add_mmio(
240 id,
241 name,
242 logical_size,
243 logical_size,
244 ceiling,
245 LiveBackend::State(bank),
246 );
247 id
248 }
249
250 pub fn set_writable_registers(
253 &mut self,
254 resource_id: ResourceId,
255 writable_registers: Vec<WritableRegister>,
256 ) {
257 if let Some(ceiling) = self.bundle.ceiling.get_mut(&resource_id) {
258 ceiling.writable_registers = writable_registers;
259 }
260 }
261
262 pub fn set_hard_denied_ranges(&mut self, resource_id: ResourceId, ranges: Vec<Range<u64>>) {
265 if let Some(ceiling) = self.bundle.ceiling.get_mut(&resource_id) {
266 ceiling.hard_denied = ranges;
267 }
268 }
269
270 pub fn add_gpio(
272 &mut self,
273 name: impl Into<String>,
274 proxy: fgpio::GpioSynchronousProxy,
275 ) -> ResourceId {
276 let id = self.next_id;
277 self.next_id += 1;
278 self.bundle.add_protocol(
279 id,
280 name,
281 ResourceKind::Gpio,
282 ProtocolCeiling::default_for(ResourceKind::Gpio),
283 LiveBackend::Gpio(FuchsiaGpio::new(proxy)),
284 );
285 id
286 }
287
288 pub fn add_i2c(
290 &mut self,
291 name: impl Into<String>,
292 proxy: fi2c::DeviceSynchronousProxy,
293 ) -> ResourceId {
294 let id = self.next_id;
295 self.next_id += 1;
296 self.bundle.add_protocol(
297 id,
298 name,
299 ResourceKind::I2c,
300 ProtocolCeiling::default_for(ResourceKind::I2c),
301 LiveBackend::I2c(FuchsiaI2c::new(proxy)),
302 );
303 id
304 }
305
306 pub fn add_spi(
308 &mut self,
309 name: impl Into<String>,
310 proxy: fspi::DeviceSynchronousProxy,
311 ) -> ResourceId {
312 let id = self.next_id;
313 self.next_id += 1;
314 self.bundle.add_protocol(
315 id,
316 name,
317 ResourceKind::Spi,
318 ProtocolCeiling::default_for(ResourceKind::Spi),
319 LiveBackend::Spi(FuchsiaSpi::new(proxy)),
320 );
321 id
322 }
323
324 pub fn add_clock(
326 &mut self,
327 name: impl Into<String>,
328 proxy: fclock::ClockSynchronousProxy,
329 ) -> ResourceId {
330 let id = self.next_id;
331 self.next_id += 1;
332 self.bundle.add_protocol(
333 id,
334 name,
335 ResourceKind::Clock,
336 ProtocolCeiling::default_for(ResourceKind::Clock),
337 LiveBackend::Clock(FuchsiaClock::new(proxy)),
338 );
339 id
340 }
341
342 pub fn add_reset(
344 &mut self,
345 name: impl Into<String>,
346 proxy: freset::ResetSynchronousProxy,
347 ) -> ResourceId {
348 let id = self.next_id;
349 self.next_id += 1;
350 self.bundle.add_protocol(
351 id,
352 name,
353 ResourceKind::Reset,
354 ProtocolCeiling::default_for(ResourceKind::Reset),
355 LiveBackend::Reset(FuchsiaReset::new(proxy)),
356 );
357 id
358 }
359
360 pub fn add_serial(
362 &mut self,
363 name: impl Into<String>,
364 proxy: fserial::DeviceSynchronousProxy,
365 ) -> ResourceId {
366 let id = self.next_id;
367 self.next_id += 1;
368 self.bundle.add_protocol(
369 id,
370 name,
371 ResourceKind::Serial,
372 ProtocolCeiling::default_for(ResourceKind::Serial),
373 LiveBackend::Serial(FuchsiaSerial::new(proxy)),
374 );
375 id
376 }
377
378 pub fn add_interrupt(&mut self, name: impl Into<String>) -> ResourceId {
380 let id = self.next_id;
381 self.next_id += 1;
382 self.bundle.add_interrupt(id, name, LiveBackend::Interrupt);
383 id
384 }
385
386 pub fn build(self) -> Result<EmbeddedLabServer, ProviderError> {
388 self.bundle.validate()?;
389
390 let mut boot_id_bytes = [0u8; 16];
391 zx::cprng_draw(&mut boot_id_bytes);
392 let boot_id: String = boot_id_bytes.iter().map(|byte| format!("{byte:02x}")).collect();
393 let proxy_generation = zx::BootInstant::get().into_nanos() as u64;
394
395 let manifest = TargetPolicyManifest::from_bundle_ceiling(
396 &self.bundle.resources,
397 &self.bundle.ceiling,
398 self.allow_mutating_sessions,
399 );
400 let policy_digest = manifest.policy_digest().to_string();
401 let digests = self.bundle.digests();
402 let identity = ProxyIdentity {
403 boot_id,
404 proxy_generation,
405 resource_digest: self.bundle.combined_digest().to_string(),
406 policy_digest,
407 };
408 let resource_digests: BTreeMap<u32, String> =
409 digests.iter().map(|(id, digest)| (*id, digest.to_string())).collect();
410
411 let mut interrupts = InterruptManager::new();
412 for (id, res) in &self.bundle.resources {
413 if res.kind == ResourceKind::Interrupt {
414 interrupts.register(*id);
415 }
416 }
417
418 let tracer = self.config.enabled.then(|| {
421 Arc::new(AuditTracer::new(&self.bundle.node_identity, &self.bundle.resources))
422 });
423
424 let limit_enforcer =
425 AccessLimitEnforcer::new(self.config.max_ops_per_second, self.config.max_deadline_ns);
426 let mut sessions = SessionManager::with_limits(
427 identity,
428 self.bundle.resources,
429 self.bundle.ceiling,
430 self.allow_mutating_sessions,
431 limit_enforcer,
432 );
433 if let Some(hook) = self.quiesce_hook {
434 sessions.set_quiesce_hook(hook);
435 }
436 let quiesce_flag = sessions.quiesce_flag();
437 if !self.config.enabled {
438 sessions.reject_new_sessions();
439 }
440
441 let executor = Executor::new(
442 self.bundle.backends,
443 ZxClock,
444 ExecLimits {
445 max_snapshot_items: self.config.max_snapshot_items as usize,
446 max_sequence_items: self.config.max_sequence_items as usize,
447 max_delay_ns: self.config.max_delay_ns as i64,
448 max_sequence_duration_ns: self.config.max_sequence_duration_ns as i64,
449 },
450 );
451 let mut audit = AuditRing::new(self.config.audit_capacity as usize);
452 if let Some(tracer) = &tracer {
453 audit.set_observer(tracer.observer());
454 }
455 audit.append(AuditRecord::lifecycle("embedded_driver_start", now_ns()));
456
457 let state: SharedState = Arc::new(SharedStateData {
458 inner: Mutex::new(ProxyState {
459 sessions,
460 executor,
461 audit,
462 resource_digests,
463 interrupts,
464 config: self.config,
465 }),
466 abort_token: AtomicBool::new(false),
467 });
468
469 Ok(EmbeddedLabServer { state, quiesce_flag, tracer })
470 }
471}
472
473#[derive(Clone)]
476pub struct EmbeddedLabServer {
477 state: SharedState,
478 quiesce_flag: Arc<AtomicBool>,
479 tracer: Option<Arc<AuditTracer>>,
480}
481
482impl EmbeddedLabServer {
483 pub fn is_enabled(&self) -> bool {
485 self.state.inner.lock().unwrap().config.enabled
486 }
487
488 #[cfg(feature = "fdf_driver")]
495 pub fn publish(
496 &self,
497 outgoing: &mut ServiceFs<ServiceObj<'static, ()>>,
498 scope_handle: fasync::ScopeHandle,
499 ) {
500 if !self.is_enabled() {
501 return;
502 }
503 let state = self.state.clone();
504 outgoing.dir("svc").add_fidl_service_instance(
505 "default",
506 move |request: flab::ServiceRequest| {
507 let flab::ServiceRequest::Proxy(stream) = request;
508 scope_handle.spawn(server::serve_proxy(
509 state.clone(),
510 scope_handle.clone(),
511 stream,
512 ));
513 },
514 );
515 }
516
517 #[cfg(feature = "fdf_driver")]
524 pub fn publish_local(
525 &self,
526 outgoing: &mut ServiceFs<ServiceObjLocal<'static, ()>>,
527 scope_handle: fasync::ScopeHandle,
528 ) {
529 if !self.is_enabled() {
530 return;
531 }
532 let state = self.state.clone();
533 outgoing.dir("svc").add_fidl_service_instance(
534 "default",
535 move |request: flab::ServiceRequest| {
536 let flab::ServiceRequest::Proxy(stream) = request;
537 scope_handle.spawn(server::serve_proxy(
538 state.clone(),
539 scope_handle.clone(),
540 stream,
541 ));
542 },
543 );
544 }
545
546 pub fn serve_proxy_stream(
548 &self,
549 scope_handle: fasync::ScopeHandle,
550 stream: flab::Proxy_RequestStream,
551 ) {
552 let state = self.state.clone();
553 scope_handle.spawn(server::serve_proxy(state, scope_handle.clone(), stream));
554 }
555
556 pub fn serve_proxy_channel(&self, scope_handle: fasync::ScopeHandle, channel: zx::Channel) {
558 let stream = fidl::endpoints::ServerEnd::<flab::Proxy_Marker>::new(channel).into_stream();
559 self.serve_proxy_stream(scope_handle, stream);
560 }
561
562 pub fn state(&self) -> &SharedState {
564 &self.state
565 }
566
567 pub fn is_quiesced(&self) -> bool {
570 self.quiesce_flag.load(Ordering::SeqCst)
571 }
572
573 pub fn quiesce_flag(&self) -> Arc<AtomicBool> {
576 self.quiesce_flag.clone()
577 }
578
579 pub fn trace_counter(&self, resource_id: ResourceId, offset: u64, value: u32) {
587 if let Some(tracer) = &self.tracer {
588 tracer.counter(resource_id, offset, value);
589 }
590 }
591
592 pub fn notify_interrupt(&self, resource_id: ResourceId) -> Option<InterruptEvent> {
599 self.notify_interrupt_with_timestamp(resource_id, now_ns())
600 }
601
602 pub fn notify_interrupt_with_timestamp(
604 &self,
605 resource_id: ResourceId,
606 timestamp_ns: i64,
607 ) -> Option<InterruptEvent> {
608 let mut guard = self.state.inner.lock().unwrap();
609 let event = guard.interrupts.on_interrupt(resource_id, timestamp_ns, None).ok();
610 if let Some(ref ev) = event {
611 guard.audit.append(AuditRecord {
612 session: None,
613 resource: Some(resource_id),
614 operation: "interrupt_tap",
615 offset: None,
616 decision: Decision::Allowed,
617 status: OpStatus::Ok,
618 value: Some(ev.sequence as u32),
619 timestamp_ns,
620 run_id: None,
621 item_index: None,
622 });
623 }
624 event
625 }
626
627 pub fn stop(&self) {
631 self.state.abort_token.store(true, Ordering::SeqCst);
632 let mut state = self.state.inner.lock().unwrap();
633 state.sessions.reject_new_sessions();
634 state.interrupts.cancel_waiters();
635 state.audit.append(AuditRecord::lifecycle("embedded_driver_stop", now_ns()));
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use lab_proxy_core::access_policy::WritableRegister;
643 use mmio::Mmio as _;
644 use mmio::vmo::VmoMapping;
645 use zx::CachePolicy;
646
647 #[fuchsia::test]
648 async fn embedded_server_describes_and_reads_live_mmio_over_fidl() {
649 const SIZE: usize = 4096;
650 let vmo = zx::Vmo::create(SIZE as u64).unwrap();
651 let driver_vmo = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
652
653 let mut driver_mmio =
654 VmoMapping::map_with_cache_policy(0, SIZE, driver_vmo, CachePolicy::Cached).unwrap();
655 driver_mmio.try_store32(0x00, 0xD1A6_0001).unwrap();
660 driver_mmio.try_store32(0x04, 0x0000_0007).unwrap();
661 driver_mmio.try_store32(0x40, 0xDEAD_BEEF).unwrap();
662
663 let mut builder = DriverLabBuilder::new("sample-driver");
664 let mmio_id = builder
665 .add_mmio_vmo_with_ceiling(
666 "regs",
667 &vmo,
668 0,
669 0x100,
670 ResourceCeiling {
671 hard_denied: vec![0x40..0x44],
672 allow_unknown_reads: true,
673 allow_poll: true,
674 writable_registers: vec![WritableRegister {
675 offset: 0x08,
676 width: 4,
677 allow_mask: 0xFFFF_FFFF,
678 allow_rmw: true,
679 require_precondition: false,
680 precondition_mask: 0,
681 readback: true,
682 }],
683 protocol: None,
684 allow_interrupt: false,
685 },
686 )
687 .unwrap();
688 assert_eq!(mmio_id, 0);
689
690 let server = builder.build().unwrap();
691 let scope = fasync::Scope::new_with_name("embedded-test");
692 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<flab::Proxy_Marker>();
693 server.serve_proxy_stream(scope.to_handle(), stream);
694
695 let desc = proxy.describe().await.unwrap();
697 assert_eq!(desc.protocol_major, Some(1));
698 let resources = desc.resources.as_ref().unwrap();
699 assert_eq!(resources.len(), 1);
700 assert_eq!(resources[0].name.as_deref(), Some("regs"));
701 assert_eq!(resources[0].logical_size, Some(0x100));
702
703 let (session_proxy, session_server) =
705 fidl::endpoints::create_proxy::<flab::SessionMarker>();
706 let open_res = proxy
707 .open_session(
708 &flab::RunContext {
709 run_id: Some("run-cs25".to_string()),
710 case_id: Some("case-cs25".to_string()),
711 plan_digest: Some("sha256:test".to_string()),
712 host_tool_version: Some("0.1.0".to_string()),
713 ..Default::default()
714 },
715 flab::SessionMode::ReadOnly,
716 &flab::Expectations {
717 boot_id: desc.boot_id.clone(),
718 proxy_generation: desc.proxy_generation,
719 resource_digest: desc.resource_digest.clone(),
720 policy_digest: desc.policy_digest.clone(),
721 ..Default::default()
722 },
723 &[
724 flab::AccessRule {
725 resource: 0,
726 offset: 0x00,
727 width: 4,
728 class: flab::AccessClass::ReadOnce,
729 },
730 flab::AccessRule {
731 resource: 0,
732 offset: 0x04,
733 width: 4,
734 class: flab::AccessClass::Snapshot,
735 },
736 ],
737 session_server,
738 )
739 .await
740 .unwrap();
741 assert!(open_res.is_ok());
742
743 let (read_val, _, _) = session_proxy.read32(0, 0x00).await.unwrap().unwrap();
745 assert_eq!(read_val, 0xD1A6_0001);
746
747 let (snap_results, snap_complete) = session_proxy
749 .snapshot(&[flab::SnapshotItem { resource: 0, offset: 0x04 }])
750 .await
751 .unwrap()
752 .unwrap();
753 assert!(snap_complete);
754 assert_eq!(snap_results.len(), 1);
755 assert_eq!(snap_results[0].value, 0x0000_0007);
756
757 let (_bad_session_proxy, bad_session_server) =
759 fidl::endpoints::create_proxy::<flab::SessionMarker>();
760 let bad_open = proxy
761 .open_session(
762 &flab::RunContext::default(),
763 flab::SessionMode::ReadOnly,
764 &flab::Expectations {
765 boot_id: desc.boot_id.clone(),
766 proxy_generation: desc.proxy_generation,
767 resource_digest: desc.resource_digest.clone(),
768 policy_digest: desc.policy_digest.clone(),
769 ..Default::default()
770 },
771 &[flab::AccessRule {
772 resource: 0,
773 offset: 0x40,
774 width: 4,
775 class: flab::AccessClass::ReadOnce,
776 }],
777 bad_session_server,
778 )
779 .await
780 .unwrap();
781 assert_eq!(bad_open, Err(flab::OpenSessionError::RejectedAllowlist));
782
783 server.stop();
785 }
786
787 #[fuchsia::test]
788 async fn embedded_mutating_session_engages_quiesce_and_disconnect_releases_lease() {
789 const SIZE: usize = 4096;
790 let vmo = zx::Vmo::create(SIZE as u64).unwrap();
791 let driver_vmo = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
792 let mut driver_mmio =
793 VmoMapping::map_with_cache_policy(0, SIZE, driver_vmo, CachePolicy::Cached).unwrap();
794 driver_mmio.try_store32(0x08, 0x0000_0001).unwrap();
795
796 let hook_log = Arc::new(Mutex::new(Vec::new()));
797 let hook_log_clone = hook_log.clone();
798
799 let mut builder = DriverLabBuilder::new("sample-driver");
800 builder.with_quiesce_hook(move |paused| {
801 hook_log_clone.lock().unwrap().push(paused);
802 });
803 builder
804 .add_mmio_vmo_with_ceiling(
805 "regs",
806 &vmo,
807 0,
808 0x100,
809 ResourceCeiling {
810 hard_denied: vec![],
811 allow_unknown_reads: true,
812 allow_poll: true,
813 writable_registers: vec![WritableRegister {
814 offset: 0x08,
815 width: 4,
816 allow_mask: 0xFFFF_FFFF,
817 allow_rmw: true,
818 require_precondition: false,
819 precondition_mask: 0,
820 readback: true,
821 }],
822 protocol: None,
823 allow_interrupt: false,
824 },
825 )
826 .unwrap();
827 let irq_id = builder.add_interrupt("irq0");
828 assert_eq!(irq_id, 1);
829
830 let server = builder.build().unwrap();
831 let scope = fasync::Scope::new_with_name("embedded-quiesce-test");
832 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<flab::Proxy_Marker>();
833 server.serve_proxy_stream(scope.to_handle(), stream);
834
835 let desc = proxy.describe().await.unwrap();
836 assert!(!server.is_quiesced());
837
838 let (session_proxy, session_server) =
840 fidl::endpoints::create_proxy::<flab::SessionMarker>();
841 let open_res = proxy
842 .open_session(
843 &flab::RunContext { run_id: Some("run-cs26".to_string()), ..Default::default() },
844 flab::SessionMode::Mutating,
845 &flab::Expectations {
846 boot_id: desc.boot_id.clone(),
847 proxy_generation: desc.proxy_generation,
848 resource_digest: desc.resource_digest.clone(),
849 policy_digest: desc.policy_digest.clone(),
850 ..Default::default()
851 },
852 &[
853 flab::AccessRule {
854 resource: 0,
855 offset: 0x08,
856 width: 4,
857 class: flab::AccessClass::Write,
858 },
859 flab::AccessRule {
860 resource: 1,
861 offset: 0,
862 width: 0,
863 class: flab::AccessClass::Interrupt,
864 },
865 ],
866 session_server,
867 )
868 .await
869 .unwrap();
870 assert!(open_res.is_ok());
871
872 assert!(server.is_quiesced());
874 assert_eq!(*hook_log.lock().unwrap(), vec![true]);
875
876 let (readback_val, _, _) = session_proxy
878 .write32(0, 0x08, 0xABCD_EF01, 0xFFFF_FFFF, None, true)
879 .await
880 .unwrap()
881 .unwrap();
882 assert_eq!(readback_val, 0xABCD_EF01);
883 assert_eq!(driver_mmio.try_load32(0x08), Ok(0xABCD_EF01));
884
885 let server_for_irq = server.clone();
887 scope.spawn(async move {
888 server_for_irq.notify_interrupt(1);
889 });
890 let (irq_res, irq_seq, _, _, _) =
891 session_proxy.wait_for_interrupt(1, 0, 500_000_000).await.unwrap().unwrap();
892 assert_eq!(irq_res, 1);
893 assert_eq!(irq_seq, 1);
894
895 drop(session_proxy);
897
898 for _ in 0..50 {
900 if !server.is_quiesced() {
901 break;
902 }
903 fasync::Timer::new(zx::MonotonicInstant::after(zx::Duration::from_millis(5))).await;
904 }
905 assert!(!server.is_quiesced());
906 assert_eq!(*hook_log.lock().unwrap(), vec![true, false]);
907
908 let audit_page = server.state().inner.lock().unwrap().audit.read(0, 100);
909 assert!(audit_page.entries.iter().any(|e| e.record.operation == "quiesce_engaged"));
910 assert!(audit_page.entries.iter().any(|e| e.record.operation == "quiesce_released"));
911 assert!(audit_page.entries.iter().any(|e| e.record.operation == "interrupt_tap"));
912 }
913
914 #[fuchsia::test]
915 fn embedded_server_mirrors_audit_into_tracing_only_when_enabled() {
916 let enabled = DriverLabBuilder::new("sample-driver").build().unwrap();
917 assert!(enabled.state().inner.lock().unwrap().audit.has_observer());
918 enabled.trace_counter(0, 0x1c, 1);
920
921 let disabled = DriverLabBuilder::new("sample-driver").with_enabled(false).build().unwrap();
922 assert!(!disabled.state().inner.lock().unwrap().audit.has_observer());
923 disabled.trace_counter(0, 0x1c, 1);
924 }
925
926 #[fuchsia::test]
927 async fn embedded_state_bank_supports_slots_knobs_and_triggers_over_fidl() {
928 let mut bank = StateBank::new(0x20);
929 let violations_slot = bank.define_state_slot(0x00, 0);
930 let delay_knob = bank.define_knob(0x04, 0);
931 let fix_knob = bank.define_knob(0x08, 0);
932
933 let violations_for_trigger = violations_slot.clone();
934 let delay_for_trigger = delay_knob.clone();
935 let fix_for_trigger = fix_knob.clone();
936 bank.define_trigger(0x0C, move |_arg| {
937 if delay_for_trigger.load() > 0 && fix_for_trigger.load() == 0 {
938 violations_for_trigger.fetch_add(1);
939 Ok(1)
940 } else {
941 Ok(0)
942 }
943 });
944
945 let mut builder = DriverLabBuilder::new("sample-driver");
946 let state_id = builder.with_state_bank("state0", bank);
947 assert_eq!(state_id, 0);
948
949 let server = builder.build().unwrap();
950 let scope = fasync::Scope::new_with_name("embedded-state-bank-test");
951 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<flab::Proxy_Marker>();
952 server.serve_proxy_stream(scope.to_handle(), stream);
953
954 let desc = proxy.describe().await.unwrap();
955 let resources = desc.resources.as_ref().unwrap();
956 assert_eq!(resources.len(), 1);
957 assert_eq!(resources[0].name.as_deref(), Some("state0"));
958 assert_eq!(resources[0].logical_size, Some(0x20));
959
960 let (_bad_session, bad_server) = fidl::endpoints::create_proxy::<flab::SessionMarker>();
962 let bad_open = proxy
963 .open_session(
964 &flab::RunContext::default(),
965 flab::SessionMode::Mutating,
966 &flab::Expectations {
967 boot_id: desc.boot_id.clone(),
968 proxy_generation: desc.proxy_generation,
969 resource_digest: desc.resource_digest.clone(),
970 policy_digest: desc.policy_digest.clone(),
971 ..Default::default()
972 },
973 &[flab::AccessRule {
974 resource: 0,
975 offset: 0x00,
976 width: 4,
977 class: flab::AccessClass::Write,
978 }],
979 bad_server,
980 )
981 .await
982 .unwrap();
983 assert_eq!(bad_open, Err(flab::OpenSessionError::RejectedAllowlist));
984
985 let (session_proxy, session_server) =
987 fidl::endpoints::create_proxy::<flab::SessionMarker>();
988 let open_res = proxy
989 .open_session(
990 &flab::RunContext { run_id: Some("run-state".to_string()), ..Default::default() },
991 flab::SessionMode::Mutating,
992 &flab::Expectations {
993 boot_id: desc.boot_id.clone(),
994 proxy_generation: desc.proxy_generation,
995 resource_digest: desc.resource_digest.clone(),
996 policy_digest: desc.policy_digest.clone(),
997 ..Default::default()
998 },
999 &[
1000 flab::AccessRule {
1001 resource: 0,
1002 offset: 0x00,
1003 width: 4,
1004 class: flab::AccessClass::ReadOnce,
1005 },
1006 flab::AccessRule {
1007 resource: 0,
1008 offset: 0x04,
1009 width: 4,
1010 class: flab::AccessClass::Write,
1011 },
1012 flab::AccessRule {
1013 resource: 0,
1014 offset: 0x08,
1015 width: 4,
1016 class: flab::AccessClass::Write,
1017 },
1018 flab::AccessRule {
1019 resource: 0,
1020 offset: 0x0C,
1021 width: 4,
1022 class: flab::AccessClass::Write,
1023 },
1024 ],
1025 session_server,
1026 )
1027 .await
1028 .unwrap();
1029 assert!(open_res.is_ok());
1030
1031 let (delay_rb, _, _) =
1033 session_proxy.write32(0, 0x04, 50, 0xFFFF_FFFF, None, true).await.unwrap().unwrap();
1034 assert_eq!(delay_rb, 50);
1035 let (trig_rb, _, _) =
1036 session_proxy.write32(0, 0x0C, 1, 0xFFFF_FFFF, None, true).await.unwrap().unwrap();
1037 assert_eq!(trig_rb, 1);
1038 let (violations, _, _) = session_proxy.read32(0, 0x00).await.unwrap().unwrap();
1039 assert_eq!(violations, 1);
1040
1041 let (fix_rb, _, _) =
1043 session_proxy.write32(0, 0x08, 1, 0xFFFF_FFFF, None, true).await.unwrap().unwrap();
1044 assert_eq!(fix_rb, 1);
1045 let (trig_fixed_rb, _, _) =
1046 session_proxy.write32(0, 0x0C, 1, 0xFFFF_FFFF, None, true).await.unwrap().unwrap();
1047 assert_eq!(trig_fixed_rb, 0);
1048 let (violations_after, _, _) = session_proxy.read32(0, 0x00).await.unwrap().unwrap();
1049 assert_eq!(violations_after, 1);
1050 }
1051}