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