Skip to content

librador: score iso meta packets once their frames have been assembled (iso6 false alarms) - #467

Merged
EspoTek merged 1 commit into
unified-appfrom
librador-iso6-meta-scoring
Oct 19, 2026
Merged

EspoTek merged 1 commit into
unified-appfrom
librador-iso6-meta-scoring

Conversation

@EspoTek

@EspoTek EspoTek commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes the false frames_bad_checksum / frames_unvalidated counts on the iso6 transport that @AronRubin reported in #462 (about 0.6% bad and 12% unvalidated on a healthy stream, with no actual sample corruption). Host-side only; no firmware change.

Root cause

ingest_thread_function scored each meta packet the moment it was processed, but assembled iso6 frames only after the whole batch (drain_iso6_queues() at the end of the loop). A block's six data URBs and its meta URB are submitted together, so whenever the meta URB landed in the same ingest batch as the data URBs, all 33 metas of that block were matched against a checksum ring that was a full block behind: one ok, two bad, thirty unvalidated per such block. That 1:15 bad-to-unvalidated ratio is what Aron measured. iso1 never showed it because handle_data_iso_packet dispatches and notes the checksum inline.

Fix

  • Metas wait in pending_meta until the candidate frames around their slot (lag -3..+1, nominal -1) have been noted, or until the ring is about to lap them, and are scored then. This is order-independent, so it also covers libusb delivering a meta URB ahead of its data URBs.
  • iso6 frames are assembled as soon as their six slices are queued rather than at batch end, so they reach the buffers and the checksum ring without a batch-sized lag.
  • note_seq no longer adds 65535 to frames_dropped when the device repeats a header (negative delta).

Validation

A host-only simulation drives the real functions from usbcallhandler.cpp with the firmware's lag-1 meta model (meta polled in frame X+1 carries SOF-X's checksums). Shipped code with the meta URB sharing the batch one block in eight: 0.76% bad / 12.06% unvalidated. With this change, every batch-collision and meta-before-data scenario scores 0 / 0 with identical frame assembly. A genuinely lost data URB (which permanently skews the six-queue lockstep) now reads as ~93% bad instead of mostly unvalidated.

Hardware A/B (macOS 15.7.5 host with a scratch build that enables iso on macOS, firmware 0x000C variant 3, tools/librador_bulk_test: three phases of about 6 s each, modes 0 and 2, quiet and with the generator looped back). Counts are per phase.

Build Transport bad unvalidated pass rate
shipped iso6 0 / 3 / 29 844 / 780 / 1156 (14-19%) 100 / 99.94 / 99.41%
shipped iso6, LABRADOR_INLINE_DISPATCH=1 0 / 0 / 0 60 / 120 / 120 (1-2%) 100%
shipped iso1 0 0 100%
this PR iso6 0 0 100%
this PR iso1 0 0 100%

The inline-dispatch row (drain after every packet, scoring still immediate) removes most of it, which pins the batch-end drain as the main mechanism; the residual 1-2% is the meta URB occasionally completing ahead of its data URBs, which only the deferred scoring covers. Zero dropped frames throughout.

Follow-up, not addressed here

iso6 has no cross-endpoint frame tag: a data URB that completes with an error is dropped whole, leaving that endpoint's queue 33 packets behind for the rest of the session, after which every frame mixes two USB frames. The counters then show a high bad rate but nothing resyncs. Worth a separate change (queue zero-length placeholders for a failed URB so it becomes 33 invalid frames instead of a permanent skew).

On iso6 the ingest thread scored each meta packet the moment it was
processed, but assembled iso6 frames only after the whole batch
(drain_iso6_queues at the end of ingest_thread_function).  Whenever a
block's meta URB landed in the same batch as its six data URBs, all 33
metas were matched against a checksum ring that was a full block behind:
one ok, two bad, thirty unvalidated per such block.  That is the ~0.6%
bad / ~12% unvalidated reported on healthy iso6 streams (#462), and the
reason iso1 (which dispatches inline) never showed it.

Metas now wait in pending_meta until the candidate frames around their
slot (lag -3..+1) have been noted, or until the ring is about to lap
them, and are scored then.  iso6 frames are also drained as soon as
their six slices are queued rather than at batch end, so they reach the
buffers without a batch-sized lag.  note_seq no longer adds 65535 to
frames_dropped when the device repeats a header (negative delta).

Validated with a host-only simulation that drives these functions with
the firmware's lag-1 meta model: every batch-collision and
meta-before-data scenario drops from 0.76% bad / 12% unvalidated to
0 / 0 with no change to frame assembly.  A lost data URB (six-queue
lockstep skew) now reads as ~93% bad instead of mostly unvalidated.
Not yet verified on iso6 hardware: this machine is macOS, where only
the bulk transport may be opened.
EspoTek added a commit to AronRubin/Labrador that referenced this pull request Oct 13, 2026
…auto

- DeviceSession treated librador_connect() == 1 (board open but on a
  firmware librador will not stream from) as success, so capture armed
  and waited forever on an empty buffer.  It now fails with the board's
  version against the expected one.  librador.h documents the
  0 / 1 / negative contract of librador_connect().
- New `labrador-cli update-firmware [--hex FILE] [--force]`: flashes the
  bundled labrafirm hex (assets/firmware next to the binary, as the app
  lays it out; cli/CMakeLists copies it for dev builds) when the board's
  firmware differs, launches and if needed reflashes a board stuck in
  bootloader mode, and exits 0 when the board ends up on the expected
  firmware.  A Gobindar-state board is reported, not handled.
- --transport defaults to auto (librador's per-platform choice: iso6 on
  Windows and Linux, bulk on macOS); the iso6 false-alarm fix that makes
  that default trustworthy is espotek-org#467.  `bulk` is selectable.
- Command list column sized to the longest command name.
@EspoTek
EspoTek merged commit ab429e7 into unified-app Oct 19, 2026
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