Skip to content

radiolib: bring LinuxSX1262 to parity with CustomSX1262 - #3

Open
mmmorks wants to merge 1 commit into
pr/02-config-validationfrom
pr/03-sx1262-parity
Open

radiolib: bring LinuxSX1262 to parity with CustomSX1262#3
mmmorks wants to merge 1 commit into
pr/02-config-validationfrom
pr/03-sx1262-parity

Conversation

@mmmorks

@mmmorks mmmorks commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Staged on the fork for review. Final destination: l5yth/meshcore-linux linux. Base here is pr/02-config-validation so the diff shows only this change.

Summary

LinuxSX1262 carried a hand-copied subset of CustomSX1262 (the RX watchdog, the millis setters, the boosted-gain readback) and had drifted from it. This derives it from CustomSX1262 instead, keeps only the genuinely Linux-specific part (a std_init() that reads its settings from meshcored.ini at runtime), and closes the two gaps that drift had opened: two SX126x build-flag knobs with no Linux equivalent, and an AGC reset that was a bare sleep() on Linux.

What changed

  • src/helpers/radiolib/LinuxSX1262.h: class LinuxSX1262 : public CustomSX1262. Upstream's RX-IRQ-timeout state machine (bounding a latched PREAMBLE_DETECTED that never becomes a packet) now applies on Linux too; before this, isReceiving() here could report busy forever after a preamble that went nowhere, deferring every transmit until getCADFailMaxDuration(). std_init() shadows the non-virtual base and takes LinuxConfig by reference.
  • Two new meshcored.ini keys, mirroring the compile-time flags the MCU variants use, defaulting to the compile-time defaults (DC-DC, no patch) so existing installs are unaffected:
    • use_regulator_ldo (SX126X_USE_REGULATOR_LDO, begin()'s useRegulatorLDO)
    • rx_register_patch (SX126X_REGISTER_PATCH, bit 0 of register 0x8B5)
  • LinuxSX1262Wrapper:
    • doResetAGC() override running the full SX126x reset (warm sleep, Calibrate(0x7F), calibrateImage() for the configured band), like every other SX126x board. Without it, set agc_int fell through to RadioLibWrapper's bare sleep() fallback.
    • powerOff() for parity with CustomSX1262Wrapper (cold sleep). Nothing calls it on Linux today.
    • The seventeen ((LinuxSX1262 *)_radio)-> casts become one r() accessor.
  • src/helpers/radiolib/SX126xReset.h: recalibration drops DIO2-as-RF-switch, RX boosted gain and the 0x8B5 patch, and the existing sx126xResetAGC(radio, bool) restores them from SX126X_* build flags. A runtime-configured target has none of those, so this adds a second overload taking an SX126xRxSettings struct, factors the recalibration and the settings re-application into sx126xRecalibrate() / sx126xApplyRxSettings(), and replaces the three verbatim copies of the register poke with sx126xApplyRegisterPatch(). The MCU overload keeps its #ifdef path and behaviour.
  • CustomSX1262.h: uses sx126xApplyRegisterPatch() (no behaviour change).
  • README: settings-table rows for the two keys; the "Upstream-sync fragility" Known Gap is removed, since the wrapper no longer re-implements the interface by hand.

Why

Everything in the copied code is shared SX126x logic that upstream keeps fixing (the RX timeout handling, which IRQ flag a timeout clears, the setters themselves all changed this year). Keeping a fork of it meant re-applying each fix by hand and silently missing the ones nobody noticed; the missed RX-timeout port above is the concrete example.

How it was tested

  • Clean linux_repeater build for arm64 in the container (build-docker.sh).
  • Native test suite unchanged and passing.
  • Not exercised on hardware: neither new key (needs a module that wants LDO or the patch) nor the reset path itself.

Dependencies

Stacked on pr/02-config-validation (uses its parse_bool() for the two new keys). Review the last commit only until that merges.

Shared code touched

  • src/helpers/radiolib/SX126xReset.h (additive: struct, two helpers, a second sx126xResetAGC overload; the existing overload's behaviour is unchanged)
  • src/helpers/radiolib/CustomSX1262.h (register poke replaced by the helper; no behaviour change)
  • src/helpers/radiolib/LinuxSX1262.h, LinuxSX1262Wrapper.h (Linux-only)

Noticed in passing, not changed here: CustomLLCC68Wrapper.h calls sx126xResetAGC((SX126x *)_radio) with one argument, which does not match either overload's signature. That is pre-existing in this tree.

LinuxSX1262 carried its own copy of the SX126x RX watchdog -- the
preamble/header deadline state, startReceive(), isReceiving(), the millis
setters and getRxBoostedGainMode() -- and that copy had drifted: upstream's
RX-IRQ-timeout work (bounding a latched PREAMBLE_DETECTED that never becomes
a packet) landed in every Custom*Wrapper but not here, so the Linux repeater
kept an unbounded isReceiving() that reported busy forever after a preamble
that went nowhere, deferring every transmit until getCADFailMaxDuration().
None of that logic is Linux-specific, and upstream keeps fixing it.

Derive from CustomSX1262 instead. std_init() stays, shadowing the
non-virtual base, because that part genuinely differs: the MCU variants pick
regulator, RF switch and gain per board at compile time, while one binary
here serves every HAT and reads all of it from meshcored.ini. Two of those
compile-time knobs had no Linux equivalent at all, so they become keys:

  use_regulator_ldo   (SX126X_USE_REGULATOR_LDO, begin()'s useRegulatorLDO)
  rx_register_patch   (SX126X_REGISTER_PATCH, bit 0 of register 0x8B5)

Both default to the compile-time defaults (DC-DC, no patch), so existing
installs are unaffected.

The wrapper gains the doResetAGC() override every other SX126x board has.
Without it Linux fell through to RadioLibWrapper's fallback -- a bare
sleep() -- so `set agc_int` was quietly a weaker knob here than anywhere
else. The full reset (warm sleep, Calibrate(0x7F), calibrateImage() for the
band) drops DIO2-as-RF-switch, RX boosted gain and the 0x8B5 patch, and the
shared sx126xResetAGC() restores them from SX126X_* build flags that this
variant does not define. So SX126xReset.h gains a second entry point that
takes the settings as a struct, with the recalibration and the settings
re-application each factored into one helper; the MCU overload keeps its
#ifdef path and its behaviour. sx126xApplyRegisterPatch() replaces the
register-poke that had grown to three verbatim copies. powerOff() comes
along for parity (cold sleep rather than the base class's warm one); nothing
on Linux calls it today.

Also names the downcast once: _radio is held as the base mesh::Radio, and
the wrapper repeated ((LinuxSX1262 *)_radio)-> seventeen times.

Verified: clean linux_repeater build in the arm64 container. Neither new key
is exercised on hardware -- that needs a module that wants them -- and the
reset path is untested on hardware.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXSCjgNEbJfHwLjD2WSHW4
@mmmorks
mmmorks force-pushed the pr/02-config-validation branch from b61c48c to 037b93f Compare September 8, 2026 04:11
@mmmorks
mmmorks force-pushed the pr/03-sx1262-parity branch from d1082d7 to 294f8c5 Compare September 8, 2026 04:11
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