This is the normative specification for the engineering-only driver-lab proxy driver (lab_proxy) at //src/devices/driver-lab. The companion host tooling specification lives at //tools/driver-lab/SPEC.md.
The specification is delivered in three phases. Phase 1 -- unclaimed-node binding through existing Driver Framework mechanisms, with no Driver Manager changes -- is delivered first. Phase 2 -- in-situ cooperative debugging via an embedded driver-lab library -- extends the architecture to existing, active drivers without managed takeover or Driver Manager modifications, allowing live register inspection while preserving device state. Phase 3 -- software state banks, runtime knobs, and diagnostic triggers -- extends in-situ debugging beyond physical hardware registers so host plans can observe internal driver state, inject bounded race-window delays, toggle candidate fixes at runtime, and trigger diagnostic actions in a single build.
Changeset numbers (CS) are global across this specification and //tools/driver-lab/SPEC.md, whose changesets interleave with these; gaps in the numbering below are driver-lab changesets. Every changeset marks itself complete in this list when it lands, so at any commit this list shows which changes precede and which follow that commit.
[x] CS1 [driver-lab] Proxy driver specification -- this document.
[x] CS3 [driver-lab] Phase 1 proxy driver core -- the takeover-ready fuchsia.driver.lab wire contract (sections 7.4, 11) and the host-testable policy core: ceiling and exact allowlist enforcement (9, 10), per-boot identity and description digests (8.3, 10.2), bounded reads and snapshots (12.1, 12.5), session lifecycle and leases (16), and the audit ring (18). Bind rules are false and no resources are offered.
[x] CS9 [driver-lab] Platform resource provider, bind rules, realm tests -- resource acquisition and local MMIO mapping (8.4, 15), the PROXY_TARGET bind library and property-gated binding (7.2, 7.3), driver realm tests (23.3), and the lab_root test fixture serving a fake platform device (23.3, and the live-activation path documented by CS10).
[x] CS10 [driver-lab] Add live-target conformance runbook (in //tools/driver-lab/SPEC.md).
[x] CS11 [driver-lab] Host node discovery via fuchsia.driver.development (in //tools/driver-lab/SPEC.md).
[x] CS12 [driver-lab] Direct mode client and published-protocol workflow (in //tools/driver-lab/SPEC.md).
[x] CS13 [driver-lab] Target ceiling policy manifests and verification (9.2, 9.6, 22, 23.1; milestone P1 remainder).
Remaining phase 1 work:
[driver-lab] Bounded sequences and mutation in proxy driver (11.4, 11.5, 12.2-12.4, 13; milestone P3).[driver-lab] Host tooling support for mutation and sequences (in //tools/driver-lab/SPEC.md).[driver-lab] Driver-shaped public Python API (in //tools/driver-lab/SPEC.md).[driver-lab] Stop-path hardening and cancellation (19; milestone P2).[driver-lab] Host CLI subtool expansion and verified teardown (in //tools/driver-lab/SPEC.md).[driver-lab] Protocol resources, typed endpoints, and heterogeneous sequence (8.1, 8.4, 9.5, 11.5, 11.6, 12, 14.2; milestone P4).[driver-lab] Interrupt observation and local acknowledgement (11.7, 17; milestone P5).[driver-lab] Structured configuration and per-class rate limits (12.1, 22).[driver-lab] Serial capture and independent recovery integration (in //tools/driver-lab/SPEC.md; milestone H5).Phase: 1 -- new-driver development. The proxy binds to unclaimed nodes through existing Driver Framework mechanisms. Existing-driver debugging via an embedded library is specified in the Phase 2 section of this document.
Scope: standalone proxy driver, target policy, resource execution, audit, and lifecycle.
Companion specification: host tooling at //tools/driver-lab/SPEC.md
The proxy driver provides exclusive, resource-scoped hardware access on a Fuchsia target for interactive and agent-assisted driver development. It runs locally on the target, maps only resources offered to its device node, executes bounded operations, enforces a target ceiling and session allowlist, and records a target-side audit trail.
The driver-lab capability supports two deployment situations:
The core policy, execution, audit, and wire contracts serve both situations. Phase 1 specifies the standalone driver; Phase 2 specifies the embedded library.
The terms must, must not, should, and may describe requirements, recommendations, and optional behavior respectively.
The following terms have specific meanings:
The proxy must:
The proxy must not:
Host tooling | | ffx target transport v Driver Manager control plane discovery: fuchsia.driver.development | | bind proxy (phase 1) / connect embedded endpoint (phase 2) v Target Component (standalone lab_proxy in phase 1, active driver in phase 2) | +-- policy and session server +-- MMIO executor +-- protocol-resource adapters +-- interrupt tracker / event tap +-- audit ring | v Hardware resources mapped to the target driver
Driver Manager owns discovery (fuchsia.driver.development). The session server owns the experimental session and bounded hardware execution after connection.
The proxy can access only resources offered to its bound node. It must use the parent's normal platform, PCI, or bus protocol. It must not obtain a global resource and reconstruct mappings from host input.
In phase 1 the standalone proxy binds only to nodes with no bound driver. In phase 2 the embedded library runs inside the active driver and enforces safety through cooperative locking, quiesce hooks for mutating sequences, and ceiling denials on destructive reads.
The host may provide an exact read consent list and an approved plan, but it cannot:
MMIO mappings and interrupt objects remain on the target. FIDL carries scalar operations, protocol transactions, results, descriptions, and audit entries.
The target wire contract should preserve the meaning of normal driver operations:
A generic sequence protocol may coordinate heterogeneous operations locally, but it must identify the original resource operation precisely. It is an execution mechanism, not a replacement programming model.
Every request has target-enforced bounds. No host field can select an infinite timeout, unconstrained vector, or blocking dispatcher sleep.
Writes generally cannot be undone. Execution reports every operation that began, stops according to declared error rules, and never labels the sequence atomic.
The proxy audit establishes what it attempted under a run/session context. It does not prove that the host approval was issued by a particular human.
The proxy package should be present by default in the engineering configurations that support driver-lab workflows. Presence alone grants no hardware access.
Production images must not offer any of the following, and the build must verify this:
The verification mechanism (package absence, a Driver Manager binary variant, or config gating with capability-route verification) is owned by platform assembly. This specification requires only that the chosen mechanism be verifiable at build time.
The proxy must not opportunistically claim normal device nodes. In phase 1 it becomes eligible only through explicit engineering-only node metadata or a developer-created test node; it may be loaded through engineering assembly inclusion or ephemeral registration (fuchsia.driver.registrar). Plans cannot supply a driver URL.
In phase 2 a Driver Manager operation may additionally select the fixed proxy identity for an exclusive managed takeover. That node-scoped force-bind is new Driver Manager work (the existing DisableDriver is global by URL and unsuitable) and is specified in the Phase 2 section of this document.
For hardware without a normal driver, an engineering product or explicit developer action selects the proxy at initial bind. This is the phase 1 activation path. It uses the same policy, FIDL, evidence, and session implementation that the embedded library (phase 2) reuses.
In-situ debugging -- embedding the driver-lab server into an active driver, sharing mapped MMIO via VMO duplication, coordinating concurrency via quiesce hooks, and tapping interrupts -- is specified in the Phase 2 section of this document. The phase 1 wire contract serves both standalone and embedded deployments with no major version change.
The extensible resource model begins with:
Additional kinds require explicit capability descriptions, bounds, tests, and threat-model review.
Each resource description contains:
Descriptions never expose physical addresses or raw target handles.
The digest is computed from canonical descriptions including:
Dynamic handle values, mapping addresses, boot IDs, and service-instance names are excluded.
The digest allows a persistent host read grant to stop matching when resource layout or policy changes. Scoping the ceiling input per resource ensures that a policy change on one resource does not invalidate grants on unrelated resources.
Use an injected ResourceProvider interface with implementations appropriate to the target node:
ResourceProvider DescribeResources() OpenMmio(id) OpenInterrupt(id) ConnectProtocol(id, kind) PlatformResourceProvider PciResourceProvider Other reviewed bus-specific providers FakeResourceProvider
Do not place bus-specific address translation in the FIDL server or policy engine.
If a parent exposes a larger region than the proxy's logical resource, checked translation must verify both the logical resource and actual backing mapping:
backing_offset = logical_base + request_offset
All arithmetic is checked before access.
Resource providers communicate with the parent over zircon channel-transport FIDL. The isolated driver-host requirement (goal 1) already forces this: driver-transport (fdf) protocols require colocation with the parent. Parents that offer resources only over driver transport are ineligible for proxy access in V1; whether a colocated mode is ever introduced is a phase 2 open decision.
The proxy evaluates:
actual resource bounds ∩ immutable target ceiling ∩ session allowlist ∩ session mode ∩ per-operation structural validation
Every layer must permit the operation.
The ceiling comes from engineering assembly, board/bus metadata, or immutable package content. Runtime configuration may narrow it but cannot widen it.
It specifies:
For a resource whose ceiling permits operator-authorized exploration, an otherwise unclassified aligned read may be allowed only when:
This is an engineering risk decision. The proxy does not claim that an allowed unknown read is side-effect free.
A session request to sweep a range is expanded into exact offsets before the session opens. The proxy does not accept a wildcard “all offsets in this region” allowlist rule.
Writes are denied unless the immutable target ceiling contains an exact writable-register entry. A host/session grant cannot invent a write entry.
Each entry specifies:
Write-one-to-clear, self-clearing, FIFO-like, or destructive-read registers require a distinct operation semantic. Do not overload ordinary masked writes.
For GPIO, SPI, I2C, serial, clock, reset, and future protocols, policy uses method-specific bounds rather than pretending they are MMIO:
When policy is expressed in JSON5 or another checked-in format, generate:
Describe.Generation rejects duplicate IDs/names, overflow, ambiguous overlap, invalid masks, unreachable cleanup, nondeterministic ordering, and limits above FIDL maxima.
Hash the canonical manifest bytes. Evidence records the digest.
The session allowlist is derived from the canonical host plan after permission resolution. It prevents a stale or buggy client from accessing anything beyond the frozen run, even though it is not cryptographic proof of approval.
Opening a session includes:
The proxy rejects a stale identity before access.
Before opening the session, the proxy validates the entire allowlist against the target ceiling. A rejected allowlist creates no session and performs no hardware operation.
A read-only session can never issue a write even if a write appears in target policy. A mutating session additionally requires an exclusive mutation lease.
The final FIDL syntax must match the selected Fuchsia revision. The semantic contract below is normative. The V1 contract is implemented as the fuchsia.driver.lab library with the Proxy (Describe, OpenSession) and Session (Read32, Snapshot, ReadAudit) protocols.
The protocol declares compile-time bounds for:
Runtime target limits may be smaller.
Describe returns:
OpenSession accepts run context, session mode, identity expectations, and the exact allowlist. It returns a session channel and session ID.
Read-only sessions may coexist when safe. Exactly one mutating session exists per proxy instance. Target-local sequences are serialized initially, including read-only sequences whose interleaving would make evidence ambiguous.
The allowlist is fixed for the session's lifetime in V1. Expanding consent requires closing the session and reopening it with the expanded allowlist; a narrowing-only ExtendAllowlist operation is a deferred open decision (section 26).
The initial scalar MMIO methods are:
Read32(resource_id, offset);Write32(resource_id, offset, value, write_mask, precondition, readback);Snapshot(items); andPoll32(resource_id, offset, expected, mask, interval, timeout).Additional widths require protocol and policy revision. Offsets are bytes from the start of a logical resource.
ExecuteSequence accepts a bounded ordered list containing:
Every entry identifies resource kind, resource ID, and operation-specific fields. Unknown operations produce ZX_ERR_NOT_SUPPORTED.
The sequence is not atomic. Whole-sequence structural/policy validation occurs before the first hardware operation. Hardware preconditions are evaluated at execution time.
Where practical, a session should open a typed endpoint for a standard protocol-backed resource. The endpoint:
If an exact production protocol cannot represent necessary debug policy or audit behavior, define a narrowly shaped adapter and document the difference.
WaitForInterrupt(resource_id, after_sequence, timeout) is a bounded hanging request. The proxy retains and acknowledges the interrupt locally.
The report includes:
The initial protocol observes interrupts only. It does not let the host change polarity, routing, or masking.
ReadAudit(cursor, limit) returns a bounded page and reports the oldest available sequence so the host can detect gaps.
Immediately before every read:
offset + width for overflow;Rejected accesses are audited without touching hardware.
For an ordinary 32-bit masked write:
policy_mask = immutable target mask requested = request.write_mask require requested != 0 require (requested & ~policy_mask) == 0 if requested covers the full register width and no precondition is configured or requested: volatile_write32(request.value) else: require read-modify-write valid for this policy entry before = volatile_read32() require precondition if configured after = (before & ~requested) | (request.value & requested) volatile_write32(after) apply required barrier read back configured stable bits if required
The before-read in the read-modify-write path is itself a hardware read with potential side effects; it is audited, and it is permitted only where the policy entry declares read-modify-write valid. Write-only registers must be declared full-mask, non-read-modify-write, and without preconditions.
Reject rather than silently narrow a requested mask.
Polls use asynchronous timers on the driver dispatcher. They never busy-wait and never block the dispatcher with sleep.
Dispatcher timers provide millisecond-class scheduling precision. V1 makes no sub-millisecond guarantee for delays, polls, or inter-operation gaps; whether ceiling-bounded short spin-delays are permitted for microsecond-class sequencing is an open decision (section 26). Declared timing precision is part of capability reporting.
The proxy enforces:
Use the architecture/platform MMIO primitives from the selected Fuchsia tree. A barrier orders accesses; it does not prove device-internal completion.
Validate every item before the first read. The snapshot is an ordered series of reads, not an atomic hardware snapshot. Each value includes an individual timestamp, status, and audit sequence.
Start with one synchronized dispatcher for:
Use an asynchronous sequence state machine:
ValidateWholeSequence | v BeginOperation +-- immediate operation -> Record -> next +-- delay -> arm timer -> resume -> Record -> next +-- poll -> read -> matched/timeout/arm interval +-- protocol wait -> async completion -> Record -> next
A second target-local sequence receives ZX_ERR_SHOULD_WAIT until safe concurrency semantics are designed.
On failure:
stop_on_error is true;stop_on_error = true for every mutating sequence.Use narrow testable modules:
ProxyDriver lifecycle, outgoing service, instance identity ResourceProvider node/parent-specific acquisition HardwareBackend MMIO, barriers, clock, and injected protocol operations AccessPolicy actual bounds + target ceiling + session allowlist Executor scalar operations and asynchronous sequences SessionServer run context, allowlist, leases, cancellation ProtocolResourceAdapter GPIO/SPI/I2C/serial/clock/reset endpoints InterruptTracker async waits, counters, acknowledgment AuditRing bounded ordered target evidence
FIDL conversion stays at service boundaries. Policy and executor tests run without a component realm: the pure-logic modules form the lab_proxy_core crate, which builds for both the target and the build host so their tests run without an emulator.
Start must:
Start performs no experimental write. Avoid reads with possible side effects. An identity read during start is allowed only when explicitly declared safe and audited.
Start fails if a required resource is absent, undersized, ambiguous, or incompatible with policy.
A session owns:
On session channel close:
Cleanup is fallible and audited. A resource with unknown safe cleanup has no automatic cleanup and relies on normal driver rebind, target reset, or out-of-band recovery.
For every interrupt callback:
Use virtual interrupts in tests. An interrupt storm must not allocate unbounded memory or starve stop/restoration.
The fixed-capacity ring assigns monotonically increasing 64-bit sequence numbers. Wrap overwrites old entries but does not reset sequence.
The ring is in-memory: a driver-host crash loses undrained entries. Host workflows must therefore drain audit incrementally during mutating plans rather than only at run end.
Each entry contains:
Never audit pointer values, physical addresses, channel handles, or unbounded caller strings.
The driver layer mirrors each appended entry into system tracing through an append observer, keeping the core crate free of Fuchsia dependencies. Entries are written to the driver_lab trace category, or driver_lab:irq for interrupt taps, and successful writes also sample a per-resource counter. Mirroring must be a no-op unless a trace session enables the category, must not block, and must be disabled for embedded servers in builds without enable_driver_lab. Trace events are best-effort; the audit ring remains the authoritative record.
PrepareStop must:
Every pending call terminates with a result, epitaph, cancellation, or bounded host timeout.
Use typed application errors and Zircon status consistently:
Machine-parsed information must not exist only in a human detail string.
Execution reports contain:
Exact paths may follow repository conventions:
src/devices/driver-lab/ BUILD.bazel BUILD.gn meta/ fidl/ BUILD.bazel BUILD.gn driver_lab.fidl fuchsia.driver.lab wire contract; moves to an internal-category SDK library once the contract stabilizes src/ lib.rs driver lifecycle and service publication driver_lab_lib.rs embedded Rust library crate root embedded.rs EmbeddedLabServer and DriverLabBuilder c_api.rs C FFI staticlib shim (driver_lab_c) server.rs FIDL session server trace.rs audit-to-trace mirroring (driver_lab categories) core.rs host-testable core crate root access_policy.rs audit_ring.rs digest.rs executor.rs hardware_backend.rs session.rs resource_provider.rs planned: parent adapters (P0/P1) protocol_resource_adapter.rs planned (P4) interrupt_tracker.rs planned (P5) cpp/ include/lib/driver_lab/ driver_lab.h idiomatic C++20 wrapper and StateVmoBank driver_lab_c.h pure C ABI declarations and global state helpers driver_lab.cc C++20 wrapper and StateVmoBank implementation tests/ driver_lab_cpp_test.cc C++ embedded server and StateVmoBank unit tests policy/ planned: schema and generation bind/ src/devices/bin/driver_manager/ ... engineering takeover integration (phase 2) ...
The proxy is implemented in Rust on the DFv2 Rust driver bindings. Where the Rust driver-framework, MMIO, or interrupt bindings lag their C++ equivalents in the selected revision, the gap is a feasibility-spike finding to resolve upstream, not a reason to switch languages. The wire contract and host programming model remain language-neutral.
Create separate targets for:
Structured configuration may reduce:
It may not widen resource or write policy.
Cover:
Use fake MMIO, fake clocks, fake timers, and fake protocol resources. Cover:
Provide fake parent resources and test:
With a synthetic unclaimed node:
Inject failure at every transition. Managed-takeover integration tests are specified in phase 2.
Verify count/sequence, retained events, future waits, timeout, coalescing, multiple observers, close cancellation, stop cancellation, acknowledgment, storm bounding, and stop/teardown responsiveness.
Verify rejection of:
Cover:
| Fault | Required result |
|---|---|
| Peer closes before operation | no access; transport failure |
| Peer closes after write | partial/unknown result remains visible |
| Stop during delay/poll | bounded cancellation |
| Driver host crashes | proxy generation changes; session becomes stale |
| Target reboot | boot identity changes; no replay |
| Audit wrap | explicit gap reported |
| Interrupt storm | bounded memory and responsive stop |
| Protocol backend hangs | target/host deadline and recovery path |
| Cleanup fails | audited failure; no success claim |
Describe;Read32 and snapshot;Exit: only an exact allowed read inside an offered resource reaches fake MMIO.
Exit: synthetic activation and teardown succeed and every injected transition failure is visible. (Managed-takeover integration is the corresponding phase 2 milestone.)
Exit: no out-of-policy bit can be written and no failed sequence is called atomic.
Exit: host Python can perform equivalent resource operations through direct and proxy modes without flattening their semantics.
Exit: hardware runs preserve evidence and return the target to a known operational state.
The phase 1 proxy is ready when:
//tools/driver-lab/SPEC.md); andBefore implementation, resolve:
ExtendAllowlist session operation replaces V1's reopen-per-grant behavior.Takeover-related decisions (force-bind and restore APIs, node-class persistence, restoration-state persistence, driver-host restart during takeover, subtree teardown) live in the Phase 2 section of this document.
Changesets continue the global numbering from Phase 1:
[driver-lab] Core library extraction & pre-mapped MMIO adapter[driver-lab] Embedded Rust driver library with ServiceFs integration[driver-lab] Cooperative locking, quiesce hooks, and interrupt tap support[driver-lab] Assembly gating, CML debug shard, and production-absence verification[driver-lab] Reference integration: synthetic platform driver in testing/[driver-lab] Host tooling discovery of embedded debug endpoints and in-situ session workflow (in //tools/driver-lab/SPEC.md).[driver-lab] In-situ live-target conformance test suite (in //tools/driver-lab/SPEC.md).[driver-lab] Expose FIDL and debug shard to Bazel[driver-lab] Native Bazel build for driver_lab_rust[driver-lab] Add C FFI staticlib shim (driver_lab_c)[driver-lab] Add C/C++ wrapper and StateVmoBank[driver-lab] Add C++ embedded server unit testsPhase: 2 -- existing-driver in-situ inspection and live debugging via embedded library. Extends: Phase 1 of this document. The wire contract (fuchsia.driver.lab), policy models, audit ring, and bounded executor apply unchanged.
Companion specification: host tooling at //tools/driver-lab/SPEC.md.
Phase 2 enables empirical hardware exploration and live debugging of existing, bound drivers without stopping them or tearing down their child subtrees.
A driver incorporates the driver_lab library during compilation (in engineering and debug builds), shares its mapped hardware resources with the embedded server, and serves fuchsia.driver.lab.Service from its outgoing directory. Host tooling connects directly to the driver's moniker, enabling register inspection, bounded probing, and timing-sensitive sequences while the driver remains alive and active.
The embedded library does not request root capabilities or acquire new resources from the parent node:
zx::Rights::SAME_RIGHTS).MappedMmio) within the same driver host process address space.Because both the driver‘s normal dispatch loops and the host’s probe sequences have access to hardware registers, unsynchronized mutations must be prevented:
hard_denied in the target ceiling manifest unless explicitly authorized by an exact allowlist.quiesce_hook(bool paused) callback, allowing the driver's state machine or worker threads to pause background register polling while host mutations and sequences execute. If complex driver dispatchers require asynchronous coordination in the future, this interface may be extended to support an async stream/channel notification.In Zircon, a zx::interrupt object can only be bound to a single dispatcher or thread. The library therefore does not attempt to steal or bind the raw interrupt handle.
lab.notify_interrupt(resource_id).WaitForInterrupt hanging gets.driver_lab_rust)Prioritized for immediate support across Fuchsia's DFv2 Rust driver stack in both GN and Bazel builds.
lab_proxy_core.fdf_component::DriverContext, ServiceFs, and fuchsia_async::Scope (with GN-only driver crates feature-gated under fdf_driver and a pure-zx root VMAR mapping fallback for native Bazel compilation).testing/ (CS28).driver_lab_c and driver_lab_cpp)Supports DFv2 C++ and hybrid C/C++ drivers across both GN and Bazel builds by wrapping driver_lab_rust in a thin extern "C" FFI static library (driver_lab_c, declared in cpp/include/lib/driver_lab/driver_lab_c.h) so all SHA-256 resource/policy digest calculation, session allowlist enforcement, audit ring buffering, and sequence execution remain single-sourced in Rust:
driver_lab_c exposes opaque DriverLabBuilderHandle and DriverLabServerHandle pointers over a pure C ABI, runs a dedicated background fuchsia_async::LocalExecutor thread to serve incoming fuchsia.driver.lab.Proxy channels without blocking the C++ fdf::Dispatcher, and provides process-wide global state/knob helpers (driver_lab_global_set_state_u32 and driver_lab_global_get_knob_u32) for legacy .c files.driver_lab_cpp (cpp/include/lib/driver_lab/driver_lab.h and cpp/driver_lab.cc) provides RAII C++20 wrappers in namespace driver_lab: StateVmoBank (shared-memory VMO bank for telemetry/state and host-tunable fault-injection knobs), Builder, and EmbeddedServer (integrating with fdf::OutgoingDirectory and component::OutgoingDirectory).driver_lab_cpp_tests in cpp/tests/driver_lab_cpp_test.cc) verify Describe, mutating OpenSession, Read32, Write32 on StateVmoBank knobs, global C state/knob visibility, interrupt tapping, quiesce hooks, ReadAudit, and hard-denied range rejection over FIDL.The embedded debug library must never be active in production (user) builds:
enable_driver_lab = is_debug). In release builds, library types compile to zero-sized no-ops and outgoing service handlers are omitted.//src/devices/driver-lab/meta/debug.shard.cml (exported to both GN and Bazel targets as //src/devices/driver-lab:meta/debug.shard.cml) that exposes fuchsia.driver.lab.Service.fuchsia.driver.lab.Service.Changesets continue the global numbering from Phase 2:
[driver-lab] Software state banks, runtime knobs, and diagnostic triggers[driver-lab] Host plan aliases and StateHandle API for state and knob experiments (in //tools/driver-lab/SPEC.md).[driver-lab] Update driver-lab and autoda-fix skills for Mode A/B/C verification (in //tools/driver-lab/SPEC.md).Phase: 3 -- single-build in-situ software state observation, runtime knobs, and diagnostic triggers. Extends: Phases 1 and 2 of this document. StateBank implements MmioBackend and ResourceBackend, requiring no breaking changes to fuchsia.driver.lab.
Companion specification: host tooling at //tools/driver-lab/SPEC.md.
Phase 3 extends embedded in-situ debugging (driver_lab_rust::embedded) beyond physical MMIO registers so developers and agents can reproduce, diagnose, prototype fixes for, and verify software state-machine and concurrency bugs without repeated edit -> build -> flash/OTA -> reboot cycles.
StateBank architecture and primitiveslab_proxy_core::state_bank::StateBank exposes a 32-bit word-addressed virtual resource backed by 4-byte aligned offsets within a declared logical_size:
define_state_slot(offset, initial) -> StateSlotHandle):Arc<AtomicU32> (StateEntry::AtomicSlot).offset is omitted from writable_offsets).StateSlotHandle (load, store, fetch_add, fetch_or, fetch_and).define_knob(offset, initial) -> StateSlotHandle):Arc<AtomicU32> (StateEntry::AtomicSlot) with offset added to writable_offsets.knob = 0 and resolution with knob = 1 in the same boot).define_read_probe(offset, probe)):Arc<dyn Fn() -> Result<u32, BackendError> + Send + Sync> (StateEntry::ReadProbe), evaluated synchronously when read32 is called.define_trigger(offset, trigger)):StateEntry::Trigger { last_result: Arc<AtomicU32>, callback } with offset added to writable_offsets.callback(value) on write32(offset, value) under a mutating session lease, stores the returned u32 status into last_result for immediate readback or subsequent read32/poll32, and returns Err(BackendError::Fault) if the callback fails.StateBank implements MmioBackend (read32, write32, and barrier via std::sync::atomic::fence(Ordering::SeqCst)) and ResourceBackend.LiveBackend::State(StateBank) integrates with DriverLabBuilder::with_state_bank(name, bank), which registers the bank in ProvidedResources with a ResourceCeiling populated from bank.writable_offsets().writable_registers, hard_denied, session allowlist narrowing, mutating session lease, quiesce_hook, and target-audit.jsonl recording) apply automatically without changing driver_lab.fidl.lab_sample_driver (//src/devices/driver-lab/testing/src/sample_driver.rs) registers a "state0" StateBank alongside "mmio0" and "irq0", exposing STATE_BUSY_FLAG (0x00), STATE_INVARIANT_VIOLATIONS (0x04), KNOB_RACE_DELAY_US (0x08), KNOB_FIX_ENABLED (0x0C), and TRIGGER_CONCURRENT_OP (0x10), verified in sample_driver_realm_test.