From b77499659bbdb3d7ad0c02118fa25e7b0a2e463f Mon Sep 17 00:00:00 2001 From: R0ck Date: Sat, 24 Oct 2026 23:47:14 +0100 Subject: [PATCH 1/3] renode: lay the chip structs out to ABI 1.7, and refuse a newer one VsxState and VsxCounters were ABI 1.3 shapes while the version check let any later minor in, so a 1.4+ library wrote past both. Both now follow the 1.7 header; their sizes are checked against C's on load, and a minor above 1.7 is refused until they are brought up to it. The stats record goes to 47 bytes: ConsoleBaud (zero) and, from a chip at 1.4 or later, irq_suppressed. An older chip's record stops at 43 so the engine reads the count as absent, not zero. Closes #747. --- CHANGELOG.md | 6 ++ .../renode/peripherals/VirtualSX1262Engine.cs | 25 +++++- tools/renode/peripherals/VirtualSX1262Lib.cs | 89 +++++++++++++++++-- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a38e02b4..dff76313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,12 @@ had changed in them - which is the gap this file exists to close. ### Fixed +- **Renode's chip binding is safe with a newer chip library.** Its + `VsxState` and `VsxCounters` were laid out for virtual-sx1262 ABI 1.3 while + the version check accepted any later minor, so a 1.4+ library wrote past + the end of both. They now follow the 1.7 header, a library newer than that + is refused, and the sizes are checked against C's on load. Renode nodes also + report `irq_suppressed` from a chip that counts it. - **Adding a scheduled send no longer repeats the ones already said.** Any `schedule.add` made every one-shot send whose moment had passed go out again. diff --git a/tools/renode/peripherals/VirtualSX1262Engine.cs b/tools/renode/peripherals/VirtualSX1262Engine.cs index 669cbe7d..59490817 100644 --- a/tools/renode/peripherals/VirtualSX1262Engine.cs +++ b/tools/renode/peripherals/VirtualSX1262Engine.cs @@ -249,8 +249,25 @@ private void SendStats() // carrier, the pin was still high when RxDone arrived, and a driver // that attaches on the rising edge never learned the packet existed. Put16(sb, 37, state.Dio1Mask); + // The console's line rate. Zero, which the engine reads as "not + // on a UART": the chip cannot see the console, and the board's + // UART is not this peripheral's to ask. + Put32(sb, 39, 0); + // What IrqMask kept out, only from a chip new enough to count it. + // An older one leaves the field zero, and zero there would read as + // "nothing suppressed" rather than "not counted", so the record + // stops short of it instead. + var length = StatsLength; + if(lib.Minor >= VirtualSX1262Lib.AbiIrqSuppressed) + { + Put32(sb, 43, counters.IrqSuppressed); + } + else + { + length = StatsLength - 4; + } - Send(RadioStats, sb, sb.Length); + Send(RadioStats, sb, length); } private static bool ReadAll(NetworkStream wire, byte[] buffer, int count) @@ -327,8 +344,10 @@ public void Dispose() private const byte ChannelBusy = 0x08; private const byte RadioStats = 0x09; - // The stats record, whose layout the engine reads on length. - private const int StatsLength = 39; + // The stats record, whose layout the engine reads on length: up to + // irq_suppressed, which ends at byte 47. Byte 47 itself, the + // capabilities, is left off - this host sends no failed frames. + private const int StatsLength = 47; private readonly VirtualSX1262Lib lib; private readonly IntPtr chip; diff --git a/tools/renode/peripherals/VirtualSX1262Lib.cs b/tools/renode/peripherals/VirtualSX1262Lib.cs index 017514a0..1b346090 100644 --- a/tools/renode/peripherals/VirtualSX1262Lib.cs +++ b/tools/renode/peripherals/VirtualSX1262Lib.cs @@ -18,10 +18,17 @@ // // The ABI is a contract, and the other side of it is // MeshBench/virtual-sx1262's include/virtual_sx1262.h. It is append-only: -// never reorder a struct, never change what an entry point means. This -// declares ABI 1.3, the version that added the byte-at-a-time SPI path, which -// is the only one an emulator can use - Renode's ISPIPeripheral.Transmit is -// called once per clocked byte and must answer that byte before the next. +// never reorder a struct, never change what an entry point means. The floor is +// ABI 1.3, the version that added the byte-at-a-time SPI path, which is the +// only one an emulator can use - Renode's ISPIPeripheral.Transmit is called +// once per clocked byte and must answer that byte before the next. +// +// The two structs below are laid out to the newest ABI this file knows, 1.7. +// Append-only cuts one way: an older library writes a prefix of them and the +// rest stays zero, which is safe, but a newer one may have appended fields +// and would write past the end, which corrupts whatever is beyond rather than +// failing. So a minor above the newest known is refused, and declaring it +// means bringing both structs up to its header first (#747). // using System; using System.IO; @@ -29,9 +36,10 @@ namespace Antmicro.Renode.Peripherals.Radio { - // Laid out to match vsx_state exactly. Sequential, and no field may be - // reordered or resized: a mismatch here does not fail, it reads one - // setting as another. + // Laid out to match vsx_state exactly, as of ABI 1.7. Sequential, and no + // field may be reordered or resized: a mismatch here does not fail, it + // reads one setting as another. The comment on each group is the minor + // that appended it; a library older than that leaves it zero. [StructLayout(LayoutKind.Sequential)] public struct VsxState { @@ -47,8 +55,35 @@ public struct VsxState public byte RxGainReg; public byte FemAtTx; // 0 never transmitted, 1 module out, 2 module in public ushort Dio1Mask; + // 1.4 + public byte CadSymbolNum; + public byte CadDetPeak; + public byte CadDetMin; + public byte CadExitMode; + // 1.5 + public ushort SyncWord; + public byte LowDataRateOptimize; + // 1.6 + public byte PacketType; + public byte FallbackMode; + public byte RegulatorMode; + public byte Dio2AsRfSwitch; + public byte Dio3AsTcxo; + public byte PaDutyCycle; + public byte PaHpMax; + public byte PaDeviceSel; + public byte SymbNumTimeout; + public byte StopTimerOnPreamble; + public byte TxContinuousWave; + public byte TxInfinitePreamble; + public byte HeaderImplicit; + public byte CrcOn; + public byte InvertIq; + public uint RxDutyRxPeriod; + public uint RxDutySleepPeriod; } + // vsx_counters, as of ABI 1.7. [StructLayout(LayoutKind.Sequential)] public struct VsxCounters { @@ -58,6 +93,17 @@ public struct VsxCounters public uint SpuriousRaises; public uint PreambleRaises; public uint FramesDropped; + // 1.4 + public uint IrqSuppressed; + public uint CadRuns; + public uint CadDetections; + // 1.5 + public uint SyncMismatches; + public uint ParamsRejected; + // 1.6 + public ushort StatRxPackets; + public ushort StatCrcErrors; + public ushort StatHeaderErrors; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -67,6 +113,15 @@ public class VirtualSX1262Lib { public const int AbiMajor = 1; public const int AbiMinor = 3; + // The newest minor the structs above are laid out for. + public const int AbiNewestMinor = 7; + // The minor that appended irq_suppressed to vsx_counters. + public const int AbiIrqSuppressed = 4; + // sizeof(vsx_state) and sizeof(vsx_counters) at AbiNewestMinor, as a C + // compiler lays them out. Checked on load, so a field mistyped here + // fails loudly rather than shifting every field after it. + public const int StateSize = 52; + public const int CountersSize = 52; public static VirtualSX1262Lib Open(string path) { @@ -90,11 +145,31 @@ public static VirtualSX1262Lib Open(string path) "{0} is ABI {1}.{2}, and this peripheral needs {3}.{4} or a later minor", path, major, minor, AbiMajor, AbiMinor)); } + // A later minor may have appended to vsx_state or vsx_counters, + // and the library would write the longer struct into the shorter + // one declared here. + if(minor > AbiNewestMinor) + { + throw new InvalidOperationException(string.Format( + "{0} is ABI {1}.{2}, newer than the {1}.{3} this peripheral's structs are laid out for: " + + "bring VsxState and VsxCounters up to its header first", + path, major, minor, AbiNewestMinor)); + } + if(Marshal.SizeOf(typeof(VsxState)) != StateSize || Marshal.SizeOf(typeof(VsxCounters)) != CountersSize) + { + throw new InvalidOperationException(string.Format( + "VsxState is {0} bytes and VsxCounters {1}, where the C header lays them out at {2} and {3}", + Marshal.SizeOf(typeof(VsxState)), Marshal.SizeOf(typeof(VsxCounters)), StateSize, CountersSize)); + } + lib.Minor = minor; return lib; } public string Path { get; private set; } + // The library's ABI minor, which says how much of each struct it fills. + public int Minor { get; private set; } + public AbiVersionFn AbiVersion; public CreateFn Create; public DestroyFn Destroy; From 3520f05a18daeeba36516418396c8fa88b9df442 Mon Sep 17 00:00:00 2001 From: R0ck Date: Fri, 30 Oct 2026 17:05:57 +0000 Subject: [PATCH 2/3] renode: accept chip ABI 1.8, which appended nothing to the structs --- tools/renode/peripherals/VirtualSX1262Lib.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/renode/peripherals/VirtualSX1262Lib.cs b/tools/renode/peripherals/VirtualSX1262Lib.cs index 1b346090..667371c7 100644 --- a/tools/renode/peripherals/VirtualSX1262Lib.cs +++ b/tools/renode/peripherals/VirtualSX1262Lib.cs @@ -114,7 +114,9 @@ public class VirtualSX1262Lib public const int AbiMajor = 1; public const int AbiMinor = 3; // The newest minor the structs above are laid out for. - public const int AbiNewestMinor = 7; + // 1.8 added receive failures 4 to 6 and header registers, and nothing + // to either struct, so its layouts are 1.7's. + public const int AbiNewestMinor = 8; // The minor that appended irq_suppressed to vsx_counters. public const int AbiIrqSuppressed = 4; // sizeof(vsx_state) and sizeof(vsx_counters) at AbiNewestMinor, as a C From e2b3f69c325cfa5ba8fb4c4fc430af353b1c036a Mon Sep 17 00:00:00 2001 From: R0ck Date: Fri, 30 Oct 2026 17:05:57 +0000 Subject: [PATCH 3/3] Pin virtual-sx1262 v1.5.0 for emulated boards --- CHANGELOG.md | 5 +++++ internal/app/resource/toolchaincatalogue.go | 16 ++++++++-------- packaging/emulator-pins.env | 6 +++++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dff76313..7b0f2983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,11 @@ had changed in them - which is the gap this file exists to close. ### Fixed +- **Native nodes see CRC errors and a busy channel.** The chip model, + virtual-sx1262 v1.5.0, answers the four-byte status reads RadioLib sends in + full. Native MeshCore builds made on it no longer repeat a frame that failed + its CRC as a good one, and listen-before-talk sees preamble and header + detection. Emulated boards pin the same version. - **Renode's chip binding is safe with a newer chip library.** Its `VsxState` and `VsxCounters` were laid out for virtual-sx1262 ABI 1.3 while the version check accepted any later minor, so a 1.4+ library wrote past diff --git a/internal/app/resource/toolchaincatalogue.go b/internal/app/resource/toolchaincatalogue.go index 03ffe610..f9d90dd2 100644 --- a/internal/app/resource/toolchaincatalogue.go +++ b/internal/app/resource/toolchaincatalogue.go @@ -23,7 +23,7 @@ package resource const ( qemuBase = "https://github.com/MeshBench/qemu/releases/download/v9.2.2-meshbench-sx1262-15/" renodeBase = "https://github.com/MeshBench/renode/releases/download/meshbench-20260904-e7196ef/" - chipBase = "https://github.com/MeshBench/virtual-sx1262/releases/download/v1.3.1/" + chipBase = "https://github.com/MeshBench/virtual-sx1262/releases/download/v1.5.0/" ) // qemuArm64LinuxIsUntried is the one platform the fork builds and this does not @@ -52,7 +52,7 @@ const qemuArm64LinuxIsUntried = "the fork publishes an aarch64 Linux build and " // before it clocks the next. var toolReleases = []toolRelease{{ Name: "virtual-sx1262", - Version: "v1.3.1", + Version: "v1.5.0", MCU: "", Why: "the SX1262 itself, which both emulators load and a native node links; " + "every emulated node needs it, ESP32 or nRF52", @@ -60,22 +60,22 @@ var toolReleases = []toolRelease{{ Assets: map[string]toolAsset{ "linux/amd64": { URL: chipBase + "virtual-sx1262-linux-amd64.tar.gz", - SHA256: "7e04041afd4f0b2e063566042c136b6255e7adf6da27e19c02a8ac6737ee0fa0", - Bytes: 26335, Kind: tarGzip, Magic: elfAMD64, + SHA256: "bfa992792c080d992a49396e221a6038ee085d23a386289c293b87c7943f34ad", + Bytes: 44958, Kind: tarGzip, Magic: elfAMD64, Root: "virtual-sx1262-linux-amd64", Binary: "virtual-sx1262-linux-amd64/lib/libvirtualsx1262.so", }, "darwin/arm64": { URL: chipBase + "virtual-sx1262-macos-arm64.tar.gz", - SHA256: "167e471ba64416cace0296e5b2c40dabbe1ed80f523e200cc96890f5f96c0fbc", - Bytes: 19119, Kind: tarGzip, Magic: machARM64, + SHA256: "9503d1cb1dd28069ccdecbb2a1b6ac59d7e0d86f89ae2dd646d348dfe8d8afc1", + Bytes: 29339, Kind: tarGzip, Magic: machARM64, Root: "virtual-sx1262-macos-arm64", Binary: "virtual-sx1262-macos-arm64/lib/libvirtualsx1262.dylib", }, "windows/amd64": { URL: chipBase + "virtual-sx1262-windows-amd64.tar.gz", - SHA256: "90621299f0e2e85720900a7b75ebcdfd8fd7fe2db89432cdb9ff4505ff9ac67d", - Bytes: 146549, Kind: tarGzip, Magic: peAMD64, + SHA256: "ea5296f8f2607f76a82502b1f85f21fb3501038b2ed930033aafe18135ed83b4", + Bytes: 188504, Kind: tarGzip, Magic: peAMD64, Root: "virtual-sx1262-windows-amd64", Binary: "virtual-sx1262-windows-amd64/lib/libvirtualsx1262.dll", }, diff --git a/packaging/emulator-pins.env b/packaging/emulator-pins.env index 238467f5..1cfc865c 100644 --- a/packaging/emulator-pins.env +++ b/packaging/emulator-pins.env @@ -79,7 +79,11 @@ RENODE_ASSET_WINDOWS_AMD64=meshbench-renode-1.16.1.windows-portable.zip # any ordinary Windows machine and every emulated board there died as a radio # that never answered. It is statically linked now and imports only kernel32 # and msvcrt. MeshBench#632, fixed by MeshBench/virtual-sx1262#1. -CHIPMODEL_RELEASE=v1.3.1 +# +# v1.5.0 is ABI 1.8: four-byte status reads answered in full, the received +# header's CRC flag, and receive failures 4 to 6. Nothing was appended to the +# structs a host reads, so a 1.3 host still works with it. +CHIPMODEL_RELEASE=v1.5.0 CHIPMODEL_ASSET_LINUX_AMD64=virtual-sx1262-linux-amd64.tar.gz CHIPMODEL_ASSET_DARWIN_ARM64=virtual-sx1262-macos-arm64.tar.gz CHIPMODEL_ASSET_WINDOWS_AMD64=virtual-sx1262-windows-amd64.tar.gz