Skip to content

feat(gust): generic driver model — gust:hal seam + verified thin-seam UART driver (gale#65)#113

Merged
avrabe merged 10 commits into
mainfrom
feat/gust-driver-model
Jun 25, 2026
Merged

feat(gust): generic driver model — gust:hal seam + verified thin-seam UART driver (gale#65)#113
avrabe merged 10 commits into
mainfrom
feat/gust-driver-model

Conversation

@avrabe

@avrabe avrabe commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What

A generic, repeatable way to extend gust with drivers — and the first one working end-to-end. Realises gale#65 ("only registers/MMIO native, everything above is wasm") on the STM32F100 px4io class.

Run through the PulseEngine feature loop: rivet traceability → gust:hal WIT seam → verified driver → Kani proof → Renode content gate.

The seam

gust:hal WIT: mmio (read32/write32), irq (poll, split-phase), uart-byte (mid), uart-block (fat). A driver imports capabilities; the ~10-line TCB bridge implements them. Granularity (thin/mid/fat) trades TCB size vs verified-wasm fraction.

Thin-seam UART driver — working & verified

The whole STM32 USART protocol in wasm, importing only mmio + irq:

  • 254 B flash, 0 SRAM, TCB = 3 import relocations.
  • Kani-proven RX decision (usart_rx_decide): errors take priority over data-ready, so the driver provably never reads DR mid-error (all 2³² SR values — VERIFICATION SUCCESSFUL).
  • End-to-end on Renode (Cortex-M3 + real UART.STM32_UART): emits gust-uart-thin — asserted by a 4th renode_test CI target (gust-uart-renode, Wait For Line On Uart, content not just no-fault).

Design notes

  • Driver = protocol primitives; app = payload (uart_init/uart_tx_byte/uart_rx). Keeps the driver data-segment-free (0 linmem) and fixed the native-pointer-abi placement issue.
  • embassy HAL-only, kiln stays executor; sync + split-phase capability shapes.
  • synth 0.15.0 perf test: levers ON==OFF (0%) on this I/O-bound driver vs gust_mix −31% — finding: driver-code's optimization opportunity is meld-dispatch import overhead, not the ARM peephole levers (a meld/synth recommendation).

Follow-ups (tracked)

  • Promote the RX decision into the gale verified crate for the Verus + Rocq tracks (Kani done); buffered-RX variant reusing the proven gale::msgq ring (where SRAM + the CCSDS-stream verification attach).
  • Wire the driver into COMPARE.md as a tracked perf-bench module; mid/fat seam measurements.
  • Manifest+generator deferred until driver Verus 37/39, rivet artifacts updated, Mathlib integration #2 (Jess's ask) — extract from real duplication.

rivet: PASS. Spec: docs/superpowers/specs/2026-06-25-gust-driver-model-design.md.

🤖 Generated with Claude Code

avrabe and others added 10 commits June 25, 2026 05:53
…T spike)

Right-sized first bite for a generic driver-extension model: define the
gust:hal WIT capability seam (dissolved driver imports, TCB bridge implements)
and prove it with ONE UART driver end-to-end, protocol in verified wasm, built
at three seam granularities (thin MMIO-prim / mid byte-caps / fat embassy) to
MEASURE the TCB bytes each costs — the empirical answer to "how much of a driver
can move into verified wasm."

Pinned to the STM32VLDISCOVERY (STM32F100) Renode target (real UART.STM32_UART
model, content-based Wait-For-Line correctness gate, 4th renode_test target) —
the constrained device we need anyway; the fat/embassy seam is expected to NOT
fit 8 KB SRAM, which is itself the proof the tiny node requires the thin seam.

embassy HAL-only (kiln stays the one executor); manifest+generator deferred
until Jess's ask makes driver #2 (extract from real duplication, not guess).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e composition

Incorporate review feedback:
- Target is the STM32F100 (Jess's device class) THROUGHOUT — no larger-device
  arm. Fat/embassy is the on-F100 overflow check (quantify why it can't fit 8 KB),
  not a path on a bigger part.
- SRAM (.bss/.data) vs the 8 KB budget is the HEADLINE metric (the binding
  constraint), alongside TCB bytes. Flash is cheap (dissolved .text runs from
  128 KB flash); SRAM = wasm linear memory / buffers / shadow stack is what binds.
- Per-node bespoke composition is the core extension model: each node's image is
  one wasm composed with exactly the specific drivers it needs, fused + dissolved
  — the composition (not a fixed firmware) is the product; minimal SRAM, maximal
  verified surface.
- XIP recorded as a follow-on lever that addresses flash/code (a library of
  per-node images in external flash), NOT the SRAM/buffer pressure — scoped out.
- Risk: embassy-stm32 may not support F100 → fat-seam footprint taken on nearest
  supported F1 (F103) as proxy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Traceability leads the feature loop. 15 artifacts under SYSREQ-BYOOS-001:
- REQ-DRV-{SEAM,WASM,SRAM,COMPOSE,ASYNC}-001 — the seam, the verified-wasm/TCB
  split, the F100 8 KiB SRAM budget, per-node bespoke composition, sync+split-phase.
- DD-DRV-{EMBASSY,GRAN,XIP}-001 — embassy-HAL-only/kiln-executor, granularity-by-
  measurement, XIP-as-flash-lever-not-SRAM.
- SAC-DRV-{HAL,UART} — the gust:hal WIT seam + the UART driver (3-seam bridges).
- VER-DRV-{UART-RENODE,SRAM} — the Renode content-based TX/RX gate + the SRAM
  budget oracle.
- FIND-DRV-MEASURE-001 (the per-seam SRAM/TCB spike, results pending; feeds the
  perf bench) + FIND-DRV-SPAR-001 (gust:hal hand-authored, spar->WIT not wired
  for the wasm-component layer — friction logged).

rivet validate: PASS. Design spec: docs/superpowers/specs/2026-06-25-...

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ale#65)

The thin-seam maximal-wasm extreme, realising gale#65's "only registers/MMIO are
native, everything above is wasm" on the STM32F100 px4io failsafe class.

- benches/gust/drivers/wit/gust-hal.wit — the capability seam: mmio (read32/
  write32/read8/write8), irq (poll, split-phase), uart-byte (mid), uart-block
  (fat); driver-thin/mid/fat worlds. Hand-authored (FIND-DRV-SPAR-001); Jess
  co-designs per gale#65.
- benches/gust/drivers/uart-thin — the ENTIRE STM32 USART protocol (init, baud,
  TXE/RXNE poll, RX drain) in verified wasm, importing only mmio + irq.

Dissolved (loom 1.1.16 + synth 0.15.0, --native-pointer-abi): **326 B flash
.text, 0 B .data, 0 B .bss — ZERO SRAM** (poll-drain form), TCB = 3 import
relocations (mmio_read32/write32, irq_poll), exports driver_step. The whole
driver is verified; the trusted surface is the ~10-line generic bridge.

Honest caveat: a buffered CCSDS-stream RX (the gale#65 real target — USART @
1.5 Mbaud carrying relay-sec-wrapped CCSDS, which can itself dissolve to wasm)
puts its ring buffer in SRAM; the protocol logic stays SRAM-free. mid/fat seam
measurements + the buffered variant + the Renode F100 USART gate are next.

rivet: PASS (FIND-DRV-MEASURE-001 records the thin result). Spec updated with the
gale#65 requirements source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed wasm")

Correcting an overclaim: the thin driver was "in wasm", not verified. gale's bar
is Verus + Rocq + Kani (CLAUDE.md), and a DAL-A px4io driver (gale#65) must meet
it. So the driver's pure decision now carries a real proof:

- usart_rx_decide (gale _decide style): errors take priority over data-ready, so
  the driver provably never reads DR on an overrun/framing error (stream desync).
  Wired into rx_poll. **Kani: VERIFICATION SUCCESSFUL** over all 2^32 SR values
  (cargo kani --harness rx_decide_error_priority, 0/14 checks failed).
- Re-dissolved (synth 0.15.0): 382 B flash, still 0 SRAM (was 326 B; +56 B for
  the verified RX gate).

Design corrected accordingly:
- REQ-DRV-VERIFY-001 + VER-DRV-KANI in rivet — driver logic carries Verus+Rocq+
  Kani like the kernel primitives; "verified" is earned by proofs.
- Verus + Rocq tracks attach on promoting the decision into the gale verified
  crate (src/ -> verus-strip -> plain/ -> wasm; proofs/*.v).
- Buffering will REUSE the already-proven gale::msgq ring (Verus+Rocq+Kani) rather
  than an unverified buffer — maximal-wasm AND maximal-verified.
- Only the irreducible MMIO poke stays unverified I/O (the whole TCB).

rivet: PASS. Spec gains a Verification section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….0 perf test

The driver driven bare-metal on the M3, and the perf test the loop asked for.

- gust_uart bin: the whole TRUSTED side — a ~10-line gust:hal thin bridge
  (mmio_read32/write32 + irq_poll) + the r11=0 trampoline (driver_step_body) +
  boot. Links the dissolved thin driver. build.rs wires it.
- renode-test: usart1 = UART.STM32_UART @ 0x40013800 (-> nvic@37) added to
  stm32f100.repl + m3_64k.repl — the real STM32 USART register model.

synth 0.15.0 perf test (new version), levers ON vs OFF on the UART driver:
**0% delta** (382 B both, cm3==cm4) — the driver is I/O-bound (register
loads/stores + poll via meld-dispatched imports), not the arithmetic the levers
target (which gave gust_mix -31%). Finding for the perf loop: the optimisation
opportunity for driver code is the MELD-DISPATCH import-call overhead, not the
ARM peephole levers → a meld/synth recommendation.

Honest status: gust_uart RUNS on the M3 (762 instr, no fault; USART SR TXE set,
no TX spin). The content-based TX gate is PENDING a data-placement detail —
native-pointer-abi places the TX string in the 1 MB wasm linmem at a VMA the
gust linker doesn't map (control_step avoided this via its reserved-linmem .bss;
this 0-bss driver's string doesn't). Resolution = the synth#383/native-pointer-abi
linmem placement (or drop the string constant). Driver LOGIC is Kani-proven and
dissolves clean; only the demo's string placement is pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thin"

Resolves the data-placement blocker by the cleaner design: a driver provides
PROTOCOL PRIMITIVES (uart_init/uart_tx_byte/uart_rx/uart_rx_fired) and the APP
owns the payload. The driver now carries NO data segment (the earlier failure was
an embedded TX string landing in the wasm 1MB linmem at a VMA the linker didn't
map under native-pointer-abi). 254 B flash, 0 SRAM, no r11 trampoline needed.

End-to-end on a hermetic Renode Cortex-M3 with a REAL STM32 USART model: the
dissolved driver TXes over MMIO and the USART emits — captured "gust-uart-thin"
(614 instr, no fault). Bonus: a real USART file-backend IS capturable headless on
the macOS portable (unlike SemihostingUart), so the content gate works locally + CI.

Verification intact: usart_rx_decide still Kani-proven (error-priority over all
2^32 SR values). The driver logic is verified; the app's string is plain flash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…real USART

4th renode_test target (gust-uart-renode): boots the gust_uart demonstrator on
the hermetic Cortex-M3 + real STM32 USART model and asserts the dissolved
driver's emitted bytes via `Wait For Line On Uart  gust-uart-thin` — content on
the wire, not just no-fault. Uses the terminal-tester mechanism the stm32f4 sem
robots already prove in CI; a real USART captures headless (unlike the
SemihostingUart, so this is a true content gate). Verified locally: the USART
emits "gust-uart-thin".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add //:gust-uart-renode to the gust-renode workflow so the UART driver's
content-based TX gate (Wait For Line on a real STM32 USART) runs in CI alongside
the three M3 device-class targets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit ba4604c into main Jun 25, 2026
101 of 102 checks passed
@avrabe avrabe deleted the feat/gust-driver-model branch June 25, 2026 06:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant