Skip to content

sata: support concurrent GQRX capture and replay#147

Merged
enjoy-digital merged 6 commits into
mainfrom
feature/sata-gqrx-shared-streaming
Jul 13, 2026
Merged

sata: support concurrent GQRX capture and replay#147
enjoy-digital merged 6 commits into
mainfrom
feature/sata-gqrx-shared-streaming

Conversation

@enjoy-digital

Copy link
Copy Markdown
Owner

Adds shared SATA/host streaming so a SATA capture or replay can run while a live SoapySDR/GQRX session keeps using the board, plus the robustness work that came out of validating it on hardware.

What's included

  • sata: support concurrent GQRX capture and replay — lossless RX tap so recording shares the live host stream, Etherbone replies routed to each client's UDP source port for concurrent control users, long multi-sector buffered writes, multi-FIS reads, paced replay from capture metadata, and progress reporting for capture-current/serve.
  • libm2sdr: make SATA identify robust against slow multi-FIS transfers — drives can split the IDENTIFY block into several DRQ chunks and stall it during garbage collection (seen on a Samsung 850 EVO at ~60% failure rate); the host now sequences and drains identify correctly. Pairs with the LiteSATA multi-FIS identify fix (see below).
  • sata: program the Ethernet replay destination before starting a replayserve on a freshly loaded bitstream used to stream UDP at 0.0.0.0, stalling LiteEth's shared TX path on ARP and taking Etherbone/ICMP down until an FPGA reload.
  • sata: pipeline Etherbone catalog reads when the RX path is idle — exports back to 26–30 MiB/s while staying cooperative (single-burst window) during a live GQRX session.
  • sata: stop interrupted streamer transfers at the current command boundary — new stop CSRs in both streamers; Ctrl-C on a capture/serve now exits in well under a second instead of abandoning an accepted ATA command (which wedged the SATA core until reload) or draining gigabytes.
  • doc: record the SATA bandwidth investigation and 850 EVO validation — the sustained capture rate is a drive property: a degraded DRAM-less WD Green sustained 10–25 MiB/s while a Samsung 850 EVO captures 30.72 MS/s SC16 2T2R (233 MiB/s) in real time.

Validation

Every commit builds and passes the simulation suites (170 Python tests, libm2sdr/CLI/hostio C tests). Validated on Acorn Baseboard Mini Ethernet+SATA hardware with a Samsung 850 EVO: real-time captures up to 30.72 MS/s SC16 2T2R past the drive's TurboWrite cache, concurrent GQRX capture and paced replay, serve from a freshly loaded bitstream and from the disabled-streamer state a closed GQRX session leaves behind, sub-second interrupt aborts with the board healthy afterwards, and 60 consecutive clean identify runs. Timing closes cleanly.

Dependency

The identify robustness relies on the LiteSATA multi-FIS IDENTIFY fix (fix-multi-fis-identify branch in litesata: count the 128-dword block in the command layer and identify frontend, accept intermediate PIO Setup FISes). Without it, drives that segment identify data regress to intermittent Drive: not present reports.

🤖 Generated with Claude Code

Add a lossless RX tap so SATA recording can share the live host stream, and route Etherbone replies to each client's UDP source port for concurrent control users.

Buffer long multi-sector writes, handle multi-FIS reads, pace replay from capture metadata, publish the active RF configuration, and reset stale streamer state before transfers. Extend capture-current and serve with progress reporting and document sustained-drive limits and timeout recovery.

Validated on baseboard Ethernet+SATA hardware at 4 MS/s SC16 for simultaneous GQRX capture and paced replay; focused gateware simulations pass and timing closes cleanly.
A drive can split the 512-byte IDENTIFY block into several DRQ chunks
and stall it for hundreds of milliseconds while busy with garbage
collection (observed on a Samsung 850 EVO). The host fired a new
identify start into a still-busy engine, which the gateware FSM
silently ignores, drained only half the block, and decoded whatever
partial data it got: back-to-back info calls failed more than half the
time, misreported the drive as absent, or decoded a bogus LBA28-sized
capacity while the strings looked fine.

Wait for the identify engine to go idle before starting, drain the full
256-word block so no residue is left for the next identify, retry the
drain while a slow transfer is still arriving, reject partial blocks
instead of decoding them, and give the info command a budget that
covers a garbage-collection stall (the PHY-ready wait stays bounded to
1 s: link training completes within milliseconds or not at all).
Requires the LiteSATA multi-FIS identify fix on the gateware side.

Validated on baseboard Ethernet+SATA hardware against the 850 EVO:
60 consecutive info calls run clean where identify previously failed
at about 60%.
The LiteEth RX streamer resets with ip_address 0.0.0.0 and only a
SoapySDR/GQRX session programs it; a stream deactivation additionally
leaves the streamer disabled. Starting serve or diag replay toward the
Ethernet destination on a freshly loaded bitstream therefore pushed a
UDP stream at an unresolvable address - the packetizer stalled on ARP
holding LiteEth's shared TX path, and Etherbone and ICMP died with it
until the FPGA was reloaded over JTAG - while a replay toward a
disabled streamer backpressured the SATA command mid-transfer and sat
at zero progress until its timeout.

Before routing the RX path to the Ethernet streamer, keep the
destination of a live client (programmed address with the streamer
enabled), otherwise program this host as the destination through
libm2sdr's m2sdr_liteeth_rx_stream_prepare(), enable the streamer, and
fall back to a clear error when no resolvable destination can be
determined, so a replay can no longer take the whole Ethernet endpoint
down.

Reproduced and verified on baseboard Ethernet+SATA hardware: serve on a
freshly loaded bitstream previously wedged the board within one command
and now completes with the endpoint healthy, including from the
disabled-streamer state a closed GQRX session leaves behind.
Holding bulk reads to one outstanding burst made every catalog read,
export and diag read pay the full request round-trip: about 6.4 MiB/s
against the 26-34 MiB/s the pipelined reader used to reach. The
cooperative behavior is only needed while a client actually shares the
endpoint, so select the window per session: keep the single-burst window
when the RX path is routed to Ethernet with the streamer enabled and a
programmed destination, and use the 8-deep pipeline otherwise. The
enable bit alone is not a client indicator since it resets to 1.

Measured on baseboard Ethernet+SATA hardware with a Samsung 850 EVO:
64 MiB diag read improves from 6.4 to 26-30 MiB/s with the readback
matching the written data. A 16-deep window measured slower (19 MiB/s):
in-flight records overrun the gateware's 160-word Etherbone record
buffering, so 8 is the sweet spot.
…dary

An interrupt used to reset the streamer frontend while the SATA core
still had an accepted ATA command in flight. LiteSATA cannot abort an
accepted command, so the core kept its crossbar grant and every later
command hung until the FPGA bitstream was reloaded over JTAG.

Add a stop CSR to both streamers that is honored at the next ATA
command boundary, where no command is in flight: the recorder stops
after the acknowledged write or while its reservoir fills, and the
player stops after the acknowledged read, without waiting for the paced
output to drain. On Ctrl-C the utility requests the stop and exits as
soon as the in-flight command completes; a stopped transfer is reported
as interrupted so the frontend reset flushes FIFO leftovers and a
partial capture is not registered. The stop command applies the same
boundary stop to a busy streamer before resetting it, a disk-to-disk
copy finishes its current single-sector command pair and stops at that
boundary, and gateware without the stop CSRs falls back to finishing
the whole programmed transfer (a replay is unpaced so it drains as fast
as the destination accepts).

Validated on baseboard Ethernet+SATA hardware with a Samsung 850 EVO:
interrupting a 30 s replay exits in 0.25 s and a 30 s capture in 0.58 s
(previously 25 s and the full capture duration), the board stays
healthy and a follow-up serve completes normally; streamer simulations
cover both stop paths.
The 18-20 MiB/s sustained capture rate that backpressured the shared
GQRX RX path above 4 MS/s SC16 was a drive limit: a degraded, DRAM-less
WDC WDS120G1G0A sustained only 10-25 MiB/s on long LiteSATA writes while
bursting at 55 MiB/s into its SLC cache. The same gateware with a
Samsung 850 EVO 250GB captures 30.72 MS/s SC16 2T2R (233 MiB/s) in real
time past the drive's TurboWrite cache, so the RFIC rate is now the
capture limit. Record the investigation, the measured numbers on both
drives, the benchmarking recipe, and the remaining follow-ups (TRIM and
SMART support, LiteEth ARP-stall robustness, deeper Etherbone record
buffering, SATA port counters), and update the workflow guidance to
present the sustained rate as a drive property and describe the
command-boundary stop behavior on interrupt.
@enjoy-digital
enjoy-digital force-pushed the feature/sata-gqrx-shared-streaming branch from 5b81b55 to 3b31e70 Compare July 13, 2026 16:34
@enjoy-digital
enjoy-digital merged commit 8c363ec into main Jul 13, 2026
6 checks passed
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