Skip to content

MSIM-560 the emulators hold the chip, and radioserver is gone - #567

Merged
A13xB0 merged 4 commits into
mainfrom
emulators-hold-the-chip
Sep 4, 2026
Merged

A13xB0 merged 4 commits into
mainfrom
emulators-hold-the-chip

Conversation

@A13xB0

@A13xB0 A13xB0 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

An emulated node ran in two processes: the emulator, and a radioserver that
owned the SX1262 and forwarded SPI to it over a socket. The chip moved into
MeshBench/virtual-sx1262, and
both emulators now load it directly. That leaves one process per node and
one socket, to the RF engine, which is genuinely elsewhere because the channel
is shared with every node in the scenario.

before after
processes per emulated node 2 1
SPI a socket round trip per clocked byte a function call
DIO1 polled at 1 kHz pushed by the chip
protocol implementations 4 2

What the socket cost, beyond the round trips

  • It framed SPI a byte at a time, a path less exercised than the buffered
    one. That is how GetRssiInst came to answer correctly for a native node and
    zero for an emulated one for months, which is where the firmware takes its
    entropy: every emulated board derived its identity from a constant.
  • DIO1 could only be polled, because the protocol was request-response. A
    millisecond is a long time to hold a receive interrupt, and a packet delivered
    and acknowledged inside one sampling gap raised the pin never.
  • It put three clocks in three processes in front of anybody asking what
    happened when.

Renode's peripheral

Rewritten around the library it loads, in three files because one was 548 lines
and the limit is 500: the loader and the ABI, the line to the engine, and the
pins and SPI the firmware can see.

It resolves the library by path from MESHBENCH_RADIO_LIB and binds each
entry point through dlopen/LoadLibrary plus
Marshal.GetDelegateForFunctionPointer, rather than by DllImport name. Renode
may be running on Mono or on .NET and NativeLibrary's resolver is not on both;
a name would also need a library search path set three different ways for a file
whose location we already know.

Two things found on the way

  • A release bundle never carried Renode's platform descriptions or our own
    peripherals
    , so an nRF52 board could not start from one at all. Fixed here,
    because it sits inside making bundles work: SupportDir() looks beside the
    binary first, every bundle copies them there, and verify-bundle.sh refuses a
    bundle without them.
  • heltec_t096 carries a front-end module and no .repl wires its pin, so
    its FEM reports as never switched in. Unchanged by this and said out loud at
    the call site rather than half-fixed; wiring it means a pin per board profile,
    read from each variant.

Measured

One board at a time on an idle machine, with no radioserver process in
either run. Every row of the board probe passes:

board emulator flood
Generic_E22_sx1262 QEMU, chip via GModule passed, 2 of 2
RAK_4631 Renode, chip via dlopen passed, 2 of 2

Merge order matters

This must not merge before a QEMU release carrying the new device exists and
QEMU_RELEASE names it.
The pinned v9.2.2-meshbench-sx1262-10 still has the
socket device and does not know radio-bridge, so anyone who fetches QEMU
rather than building it would get a machine that refuses to start.

  1. merge MeshBench/qemu#18
  2. cut the QEMU release, move QEMU_RELEASE and its digests here
  3. merge this

MeshBench/meshcore-native#18
retires radioserver at the source and is independent of the order above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6

random-thought and others added 4 commits September 4, 2026 00:38
An emulated node ran in two processes: the emulator, and a radioserver that
owned the SX1262 and forwarded SPI to it over a socket. The chip moved into its
own repository, MeshBench/virtual-sx1262, and both emulators now load it
directly. That leaves one process per node and one socket, to the RF engine,
which is genuinely elsewhere because the channel is shared with every node in
the scenario.

What the socket cost, beyond the round trips:

  It framed SPI a byte at a time, a path less exercised than the buffered one.
  That is how GetRssiInst came to answer correctly for a native node and zero
  for an emulated one for months, which is where the firmware takes its
  entropy: every emulated board derived its identity from a constant.

  DIO1 could only be polled, because the protocol was request-response. A
  millisecond is a long time to hold a receive interrupt, and a packet
  delivered and acknowledged inside one sampling gap raised the pin never.

  And it put three clocks in three processes in front of anybody asking what
  happened when.

Protocol implementations go down rather than up: four, counting SPI in QEMU,
Renode and radioserver plus the engine's, become two.

Renode's peripheral is rewritten around the library it loads, in three files
because one was 548 lines and the limit is 500: the loader and the ABI, the
line to the engine, and the pins and SPI the firmware can see. It resolves the
library by path from MESHBENCH_RADIO_LIB rather than by DllImport name,
because Renode may be running on Mono or .NET and NativeLibrary is not on both.

Two things found on the way:

  A release bundle never carried Renode's platform descriptions or our own
  peripherals, so an nRF52 board could not start from one at all. SupportDir
  looks beside the binary first, every bundle now copies them there, and
  verify-bundle refuses a bundle without them.

  heltec_t096 carries a front-end module and no .repl wires its pin, so its FEM
  reports as never switched in. That is unchanged here and said out loud at the
  call site; wiring it means a pin per board profile, read from each variant.

Measured, one board at a time on an idle machine, with no radioserver process
in either run: Generic_E22_sx1262 under QEMU and RAK_4631 under Renode both
pass every row of the board probe, flood included, at 2 of 2 attempts.

This must not merge before a QEMU release carrying the new device exists and
QEMU_RELEASE names it. The pinned v9.2.2-meshbench-sx1262-10 still has the
socket device and does not know radio-bridge, so a fetched QEMU would refuse to
start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6
The package comment named a Renode file that has never existed under that name
and described a socket the SX1262 was on the far end of. The chip is inside the
emulator now, on both backends; this socket carries the air.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6
Every emulated board used to come up with the same keypair, so a mesh of them
was one node repeated (#556). The mechanism is fixed - per-node receiver noise,
and the random-number registers generated rather than stored - but nothing
asserted the thing the issue is actually about. noiseSeedFor is unit tested for
distinct seeds per name and the chip is tested for distinct noise per seed; the
link from noise to the key real firmware derives can only be seen by booting
two of them.

    emu-a 9261FD8CAEED53DF07A62903D0D9D45A7F393E61BFCCE3D7F95E7A377B2D5A3A
    emu-b A858425E10AD9FA03A84444DCF769CAEE58B2931D9AB59A1B9D29B3A543B07C8

Determinism was checked by hand across two runs and is byte-identical, and the
test says plainly that it does not assert that itself: pinning expected keys
would break on a MeshCore version rather than on a fault of ours.

The README's measurement note also said the matrix was measured against
virtual-sx1262 v1.3.0, which an earlier edit of mine made untrue - those rows
were measured through the radio server. Nine of them have now been re-measured
against the chip loaded inside the emulator, one board at a time on an idle
machine, and every one reproduced. LilyGo_TDeck is named as the exception.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6
The device in -10 forwards SPI to a radioserver process and does not know
radio-bridge, so a fetched QEMU would refuse to start against this branch even
though a locally built one works. That is the whole reason this pin moves in the
same change.

-11 also carries a portability fix worth naming, because nothing local could
have caught it: upstream resolves gmodule only when modules or plugins are
enabled, and the sx1262 device calls g_module_open. The release configures with
neither, so it linked every target and then failed on qemu-system-xtensa alone,
on macOS, with an undefined _g_module_error. This machine had plugins on.

Digests taken from the published assets and checked by fetching them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6
@A13xB0
A13xB0 force-pushed the emulators-hold-the-chip branch from 510e6c1 to c088dc3 Compare September 4, 2026 00:06
@A13xB0
A13xB0 merged commit 21b3e35 into main Sep 4, 2026
11 checks passed
@A13xB0
A13xB0 deleted the emulators-hold-the-chip branch September 4, 2026 00:37
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.

2 participants