sata: support concurrent GQRX capture and replay#147
Merged
Conversation
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
force-pushed
the
feature/sata-gqrx-shared-streaming
branch
from
July 13, 2026 16:34
5b81b55 to
3b31e70
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
capture-current/serve.serveon 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.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-identifybranch 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 intermittentDrive: not presentreports.🤖 Generated with Claude Code