Skip to content

ringof/rx888-firmware

Repository files navigation

RX888mk2 Firmware

Latest release Build

Documentation: ringof.github.io/rx888-firmware — USB API reference, GPIO map, host-application compatibility, and architecture notes.

Open-source Cypress FX3 firmware for the RX888 mk2 (also written RX888mk2) direct-sampling SDR receiver. If you're about to write FX3 firmware for an SDR — USB vendor-command protocol, GPIF II state machine, DMA pipeline, Si5351 clock control, ka9q-radio compatibility, recovery cascade with streaming watchdog and FX3 hardware-watchdog backstop — start here, not from scratch. HF reception 0–32 MHz via the on-board LTC2208 ADC; the R828D VHF tuner is detected but not driven by the firmware (see Limitations).

Compatible host applications

  • ka9q-radio — multichannel software-defined-radio receiver with native RX888 support. Validated end-to-end via the bundled Docker test container; see docker/ka9q-radio/ for setup.
  • rx888_tools — streamer, DSP utilities, and udev rules maintained alongside this firmware. The rx888_stream binary from this project is the firmware uploader and USB capture tool used by the test harness in tests/.
  • ExtIO-based Windows hosts (HDSDR, SDR Console) — historically supported via the upstream ExtIO_sddc project. This fork is firmware-only and does not include the ExtIO DLL.

Limitations

This firmware operates the HF direct-sampling path only. The R828D VHF tuner is detected during hardware identification but is not controlled by the firmware — the GPL-licensed R82xx driver has been removed to resolve a license conflict with the proprietary Cypress SDK. VHF reception via the R828D requires a host-side tuner driver communicating over the I2C passthrough commands (I2CWFX3/I2CRFX3).

Building

Requirements

  • Debian or Ubuntu-based Linux distribution
  • arm-none-eabi-gcc toolchain (tested with 13.2.1)
  • gcc (host compiler, for the elf2img utility)
  • make

Install the cross-compiler on Debian/Ubuntu:

sudo apt install gcc-arm-none-eabi

The Cypress FX3 SDK (v1.3.4) is included in the SDK/ directory.

Build

cd SDDC_FX3
make clean && make all

This produces SDDC_FX3.img, which can be flashed to the device.

Testing

Hardware tests require an RX888mk2 connected via USB and libusb-1.0-0-dev:

sudo apt install libusb-1.0-0-dev
git submodule update --init
cd tests && make
./fw_test.sh --firmware ../SDDC_FX3/SDDC_FX3.img

Full validation in one command

tests/validate.sh runs the whole firmware-validation sequence against an attached RX888 — three sequential stages with one overall PASS/FAIL:

tests/validate.sh                 # all stages (300 s soak)
tests/validate.sh --skip-soak     # quick: correctness + ka9q-radio only
  1. fw_test.sh — vendor-command and data-flow correctness.
  2. soak_test.sh — stability (default 300 s; pass --soak-secs N / run the soak standalone for hours for a real run).
  3. ka9q-radio — confirms the firmware runs under the real host stack (radiod + rx888.so) and produces real output: a live power spectrum (sane noise floor, natural variance, and the fs/2 Nyquist alias — not the flat line a dead/frozen ADC would give), plus a kill-and-return check that radiod restarts and re-streams cleanly (issue #131). Needs the ka9q-radio docker image (see below).

See tests/README.md for the per-stage pass/fail criteria and the ka9q_smoke.sh / ka9q_test.sh details.

USB device permissions

The FX3 USB device must be accessible to the user running the tests. Install the udev rules from the rx888_tools submodule and reload:

sudo cp tests/rx888_tools/udev/99-rx888.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

This grants read/write access to both the bootloader (PID 00f3) and programmed (PID 00f1) device endpoints. Without these rules, rx888_stream and fx3_cmd will fail with LIBUSB_ERROR_ACCESS unless run as root.

USB buffer memory

The streaming test submits many large USB transfers concurrently. Linux limits per-device USB buffer memory to 16 MB by default, which is not enough and will cause LIBUSB_ERROR_NO_MEM failures. Raise (or disable) the limit before running the tests:

sudo sh -c 'echo 0 > /sys/module/usbcore/parameters/usbfs_memory_mb'

To make this persistent across reboots, create /etc/modprobe.d/usbcore.conf:

options usbcore usbfs_memory_mb=0

This builds fx3_cmd (vendor command exerciser) and rx888_stream (from the rx888_tools submodule), uploads the firmware, and runs an automated test suite.

Firmware Robustness

Recovery is owned by a single module — SDDC_FX3/health.c — that collects liveness signals from across the firmware and applies a documented cascade of remedies when the device is unhealthy. This section describes the architecture and the implementation status of each cascade level. See docs/archive/PLAN_RECOVERY.md for the original design rationale and PR sequencing.

Health interface

Three functions form the contract. Every recovery contribution goes through them — no parallel watchdog mechanisms, no inline cleanup in handlers.

Function Role
health_record_event(event) Callers observe a liveness event (EP0 callback exit, DMA progress, …) and report it. Cheap; safe from any thread.
health_evaluate() Pure function called periodically from the main loop. Examines accumulated state and returns a structured health_status_t. Does not take action.
health_recover(status) Called when evaluation reports unhealthy. Picks and applies a remedy from the cascade based on the failure reason.

Plus a fourth, narrowly-scoped function used by the FX3 hardware watchdog (Level 5):

Function Role
health_main_heartbeat() Bumped once per main-loop iteration. The HWDT clear-timer callback (KB223337 pattern, set up in health_init()) only pets the watchdog when the heartbeat counter has advanced since its last check — so main-thread death is what fires Level 5, not just any failure in the timer subsystem.

Adding a new failure-mode detection means adding a new event type and a new evaluation branch — not writing a new watchdog.

Recovery cascade

Level Remedy Appropriate for Latency Status
1 Soft-stop FW_TRG + DmaMultiChannelReset + GpifSMStart Streaming wedge ~300 ms Implemented in health_recover(HEALTH_WEDGED_STREAMING) (migrated from RunApplication.c into the health module — #115).
2 EP0 stall+unstall + FlushEp on EP1 EP0 stuck state ~10 ms Not implemented yet
3 StopApplication + StartApplication Application-level state corruption ~100 ms Not implemented yet
4 CyU3PDeviceReset(CyFalse) Vendor-handler hang (EP0 thread deadlocked in an SDK call) full re-enumeration (~1-2 s) Implemented in health_recover(HEALTH_WEDGED_EP0) (#104, #105). Validated end-to-end by test_health_recovery (HANGFX3).
5 FX3 hardware watchdog timer (CyU3PSysWatchDogConfigure, KB223337 pattern) Main-thread death or ThreadX scheduler/timer death — anything that prevents the heartbeat from advancing ~5 s Implemented in health_init() (5 s WD period) + a ThreadX timer that fires every 3 s. The timer callback pets the WD only when glMainHeartbeat has advanced; a frozen heartbeat causes the WD to fire and hard-reset the device. Validated end-to-end by test_main_recovery (HANGMAIN).

What's covered today

  • Streaming wedges — DMA producer stuck waiting for the host to drain, GPIF state machine in TH0_WAIT/TH1_WAIT/TH0_RD for

    300 ms. The watchdog in SDDC_FX3/health.c (health_evaluate / health_recover) detects and recovers; observed reliable across wedge_recovery scenarios in fw_test.sh and the soak rotation.

  • Cold-start streaming wedges — the GPIF SM left IDLE (a stream was commanded) but the first DMA buffer never arrives (glDMACount frozen at 0 for ~500 ms). health_evaluate() detects it; light recovery is tried, and if it can't clear it the cap exhausts and health_recover() escalates to CyU3PDeviceReset — gated on a healthy ADC clock, once per streaming session, and disable-able via the WDG_RESET_ESCALATE arg. Post-stream backpressure / abandoned streams are unaffected (they cap-and-wait). Validated by the test_coldstart_recovery host scenario (firmware-side HANGCOLDSTART) and the health_eval_test host unit test (#137, #119).
  • EP0 vendor-handler hangs — a vendor request callback wedged inside an SDK call for >2 s. health_evaluate() detects via the EP0 enter/exit timestamp; health_recover(HEALTH_WEDGED_EP0) fires CyU3PDeviceReset(CyFalse) (Level 4). Validated by the test_health_recovery host scenario, which uses the firmware-side HANGFX3 test command to trigger the failure deterministically.
  • Catastrophic main-thread death — when the main thread stops bumping glMainHeartbeat (deadlock, infinite loop, stack overflow, ThreadX scheduler failure), the WD-clear timer callback in health.c notices the stale counter and stops petting the FX3 HWDT. The watchdog fires within ~5 s and hard-resets the device (firmware re-uploaded by the host on next attach). Validated by the test_main_recovery host scenario, which uses the firmware-side HANGMAIN test command to trigger the failure deterministically.

Test commands for the recovery layers

Command Layer What it does
HANGFX3 (0xCE) Level 4 Test-only vendor command; sleeps wValue ms in the EP0 handler context. Used by test_health_recovery to deterministically trip the EP0 watchdog.
HANGMAIN (0xCF) Level 5 Test-only vendor command; sets a flag so the main thread enters an infinite spin on its next iteration. Used by test_main_recovery to deterministically trip the FX3 HWDT.

Both are safe in production firmware — invoking either eventually self-recovers via the respective watchdog layer.

What's not covered yet — known gaps

  • gpif_soft_stop 1 ms timing window (issue #106). An intermittent (originally observed ~10% per cycle) where the firmware's 1 ms wait after FW_TRG deassertion is insufficient for the SM to reach IDLE, causing a force-stop fallback. Independent of the recovery architecture; tracked separately. Now in the soak rotation, so future runs will provide empirical evidence on whether PR #112's scheduling changes affected the rate.

  • boot_count (GETSTATS byte [20..23]) — increments once per firmware health_init(). Use to detect mid-test device resets: snapshot before, compare after. Mismatch means the device reset (cause indistinguishable without further state, but presence of reset is unambiguous).

Soak status

Latest 2-hour soak on the PR #112 firmware (commit 0fcfae8, seed 26): 3368 cycles, 1 transient failure (test_health_recovery, #113 — unrelated to the recovery cascade itself; the deterministic version passes 100%), health checks 3368/3368 passed. Recovery cascade exercised:

Level Soak scenario Runs Pass Fail
1 wedge_recovery 211 211 0
4 test_health_recovery 55 54 1
5 test_main_recovery 58 58 0

Result: 0.03% per-cycle failure rate, no unrecoverable wedges.

Docker — ka9q-radio Compatibility Testing

The docker/ka9q-radio/ directory provides a containerized ka9q-radio environment for end-to-end firmware validation against a real-world SDR application.

ka9q-radio is a widely used SDR receiver that drives the RX888mk2 directly via libusb — it programs the Si5351 clock via I2C passthrough, controls GPIO and attenuator settings, and streams IQ data through the GPIF pipeline. Testing against it exercises firmware code paths (firmware upload, STARTFX3/STOPFX3 cycling, I2CWFX3, GPIOFX3, SETARGFX3) that the low-level test harness in tests/ does not cover.

The Docker container builds only the radiod core, the rx888.so plugin, and control utilities — no other SDR hardware drivers are included. It runs with --privileged (required because the FX3 changes USB PID during firmware upload) and --network host (for ka9q-radio's multicast output).

docker build -t ka9q-radio docker/ka9q-radio/
docker run --rm -it --privileged \
  -v /dev/bus/usb:/dev/bus/usb \
  -v /run/udev:/run/udev:ro \
  -v $(pwd)/SDDC_FX3:/firmware \
  -v $(pwd)/wisdom:/var/lib/ka9q-radio \
  --network host \
  ka9q-radio

See docker/ka9q-radio/README.md for detailed usage and known compatibility notes.

Repository Layout

SDDC_FX3/           Firmware source code
  driver/           IC drivers (Si5351 clock generator)
  radio/            Radio hardware support (rx888r2)
SDK/                Cypress FX3 SDK 1.3.4 (headers, libraries, build system)
tests/              Hardware test tools
  rx888_tools/      Submodule: USB streamer and firmware uploader
docker/             Docker-based integration testing
  ka9q-radio/       ka9q-radio radiod + rx888 plugin container
docs/               Project documentation and analysis
  architecture.md   Firmware architecture overview
  gpif-and-recovery.md  GPIF state machine design and recovery layers
  diagnostics_side_channel.md  Debug and diagnostics interface
  wedge_detection.md  DMA stall detection and watchdog design
  LICENSE_ANALYSIS.md  License audit of SDK and dependencies

License

MIT License — see LICENSE.txt.

The Cypress FX3 SDK in SDK/ is covered by the Cypress Software License Agreement (included in SDK/license/license.txt).

Acknowledgments

This project is derived from ExtIO_sddc by Oscar Steila (IK1XPV). Thanks to all who contributed to the original project:

  • Oscar Steila (IK1XPV) — original author
  • Howard Su
  • Franco Venturi
  • Hayati Ayguen
  • Ruslan Migirov
  • Vladisslav2011
  • Phil Ashby (phlash)
  • Alberto di Bene (I2PHD)
  • Mario Taeubel