tighten transport failure semantics
This commit is contained in:
@@ -89,11 +89,13 @@ new samples rather than overwriting older ones; sequence gaps and the cumulative
|
||||
lost-sample counter expose that permanent loss.
|
||||
|
||||
The shared transport state machine distinguishes three nonfatal states. `RETRY`
|
||||
means zero bytes were accepted and the complete frame may be submitted again.
|
||||
`PENDING` means the backend owns an in-flight frame, so firmware may only poll
|
||||
that transfer. `COMPLETE` permits the output task to reuse its packet buffer and
|
||||
consume more samples. This prevents a timeout after partial progress from
|
||||
causing an ambiguous whole-frame duplicate.
|
||||
is valid only from initial submission: it means zero bytes were accepted and the
|
||||
complete frame may be submitted again. `PENDING` means the backend owns an
|
||||
in-flight frame, so firmware may only poll that transfer. A `RETRY` returned by
|
||||
polling fails closed as `FATAL`, because generic code cannot prove whole-frame
|
||||
resubmission is duplicate-safe. `COMPLETE` permits the output task to reuse its
|
||||
packet buffer and consume more samples. This prevents a timeout after partial
|
||||
progress from causing an ambiguous whole-frame duplicate.
|
||||
|
||||
The direct USB Serial/JTAG backend atomically copies a complete frame into its TX
|
||||
ring, then polls a bounded transmit-drain wait. A timeout remains `PENDING`; it
|
||||
@@ -104,7 +106,9 @@ USB drain is not end-to-end application delivery confirmation. A host process
|
||||
may attach after earlier frames have already left the endpoint, or fail after
|
||||
the endpoint accepts them. CRC and sequence checks make resulting loss visible,
|
||||
but an application acknowledgement and replay window are still required to
|
||||
guarantee receipt.
|
||||
guarantee receipt. Accordingly, USB `COMPLETE` means endpoint drain, while the
|
||||
planned reliable BLE backend will reserve `COMPLETE` for an application ACK of
|
||||
the exact frame.
|
||||
|
||||
Receivers report bytes left in an incomplete trailing frame when capture ends.
|
||||
Those bytes cannot pass CRC validation and are not silently admitted as samples.
|
||||
|
||||
@@ -6,23 +6,38 @@ ESP-IDF's interrupt-driven USB Serial/JTAG driver.
|
||||
|
||||
## Transaction contract
|
||||
|
||||
- `RETRY`: the backend accepted zero bytes, so whole-frame resubmission is safe.
|
||||
- `RETRY`: valid only from initial submission; the backend accepted zero bytes,
|
||||
so whole-frame resubmission is safe.
|
||||
- `PENDING`: the backend owns an in-flight frame. Only completion polling is
|
||||
allowed; the caller must retain and not modify the packet buffer.
|
||||
- `COMPLETE`: the backend's completion criterion is satisfied and the caller may
|
||||
reuse the packet buffer.
|
||||
- `COMPLETE`: the backend's documented completion criterion is satisfied and the
|
||||
caller may reuse the packet buffer. USB means endpoint drain; reliable BLE
|
||||
will mean application acknowledgement of the exact frame.
|
||||
- `FATAL`: a programming or backend invariant failed. The output task stops
|
||||
consuming the sample queue rather than silently discarding its in-flight data.
|
||||
`RETRY` from a pending poll is promoted to `FATAL` because generic code cannot
|
||||
prove resubmission is duplicate-safe.
|
||||
|
||||
For USB, submission uses a 512-byte TX ring and a bounded 50 ms write. ESP-IDF's
|
||||
ring-buffer send is all-or-nothing for each `TRK1` frame. Once accepted, bounded
|
||||
50 ms `usb_serial_jtag_wait_tx_done()` calls continue returning `PENDING` until
|
||||
the host drains the endpoint. A timeout never resubmits the frame.
|
||||
the host drains the endpoint. The first completion poll happens immediately
|
||||
after acceptance; repeated pending polls are scheduler-paced. A timeout never
|
||||
resubmits the frame.
|
||||
|
||||
The VFS is switched to driver mode after readable startup output so any
|
||||
unexpected diagnostic output cannot race the driver's ISR by accessing the
|
||||
hardware FIFO directly. Firmware logs are disabled before binary telemetry
|
||||
tasks start, as before.
|
||||
tasks start, as before. A terminal transport invariant re-enables error logging,
|
||||
emits one final diagnostic, and suspends the output task. Because no later binary
|
||||
frame can follow, that diagnostic cannot corrupt a recoverable stream.
|
||||
|
||||
Queue-allocation and task-creation failure paths switch the VFS back to its
|
||||
non-driver mode and uninstall the USB driver before emitting their error, so the
|
||||
only startup diagnostic is not stranded in a TX ring that is immediately freed.
|
||||
The USB backend's zero-write-to-`RETRY` mapping depends explicitly on its checked
|
||||
initialized/sole-owner lifecycle; a torn-down backend fails the guard as
|
||||
`FATAL` instead of masquerading as backpressure.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -33,12 +48,24 @@ The host C transport fixture compiles the production state machine with
|
||||
- accepted transfers become pending;
|
||||
- repeated pending polls never call submission again;
|
||||
- completion returns the sender to idle;
|
||||
- backend-confirmed safe retry returns to idle;
|
||||
- retry returned after acceptance fails closed without resubmission;
|
||||
- invalid arguments and unknown backend states fail closed.
|
||||
|
||||
The assembled ESP32-C3 prototype produced a normal 1,728-sample direct-driver
|
||||
capture with no packet gaps, sample gaps, resets, CRC failures, reported drops,
|
||||
loop overruns, trailing partial bytes, or timestamp-saturation frames.
|
||||
The assembled ESP32-C3 prototype first produced a normal 1,728-sample
|
||||
pre-commit direct-driver capture with no packet gaps, sample gaps, resets, CRC
|
||||
failures, reported drops, loop overruns, trailing partial bytes, or
|
||||
timestamp-saturation frames.
|
||||
|
||||
After commit `3c95f3d` was built and flashed exactly, a second capture contained
|
||||
1,680 contiguous samples, sequences 0 through 1,679, with the same zero-loss
|
||||
integrity result. Its optional raw wire file contains all 563 startup bytes and
|
||||
re-decodes to CSV byte-for-byte identical to the live-rendered CSV. Both sides
|
||||
of that exact capture are tracked as fixtures:
|
||||
|
||||
- `tests/fixtures/direct_usb_3c95f3d.trk`, SHA-256
|
||||
`f495486f094a758bb785e145026e3934d60b52dd5083aef7f5d896195d967869`
|
||||
- `tests/fixtures/direct_usb_3c95f3d.wire`, SHA-256
|
||||
`3bdaeadff7962c6eac48c4ebeda285c8eb359e439d5e1728104add2009122c03`
|
||||
|
||||
For the connected-stall case, the USB endpoint was left enumerated without a
|
||||
serial reader long enough to overflow the 512-sample acquisition queue. When the
|
||||
|
||||
Reference in New Issue
Block a user