Keep Full C3 Companion queues within the DRAM budget
Place 32 existing retained frames in RTC FAST without changing the 224 normal or 128 active-OTA queue capacities. Keep the default backing layout unchanged for other boards and cover ownership, wraparound and OTA transfers.
This commit is contained in:
1 parent
d1a2fd74ba
commit
cfcfc832b1
8 files changed
+213
-32
No files matched your search
@@ -4587,6 +4587,11 @@ apply_companion_radio_full_profile() {
|
||||
# Reuse idle queue storage during explicit OTA; retain all 224 normal slots.
|
||||
if [[ "${env_name,,}" != generic_espnow_companion_radio_full ]]; then
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DOTA_SHARED_COMPANION_QUEUE=1 -DONE_KEY_DM_COMPACT_HELD=1"
|
||||
if [[ "${env_name,,}" == xiao_c3_companion_radio_full ]]; then
|
||||
# Move 32 of the existing 128 retained slots into byte-accessible
|
||||
# RTC FAST. Both the 224 normal and 128 active-OTA limits stay intact.
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_RTC_FAST_OFFLINE_FRAMES=32"
|
||||
fi
|
||||
else
|
||||
# Keep all 224 frames during OTA too; RTC FAST holds 32 existing
|
||||
# slots without borrowing the queue or shrinking held-DM storage.
|
||||
|
||||
@@ -1003,18 +1003,7 @@ private:
|
||||
int offline_queue_head;
|
||||
// Local onboarding only: RAM-backed like the receive queue, not RF traffic.
|
||||
uint8_t pending_emergency_notices[(MAX_GROUP_CHANNELS + 7) / 8] = {};
|
||||
#if defined(OTA_SHARED_COMPANION_QUEUE)
|
||||
static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES = 128;
|
||||
static_assert(OFFLINE_QUEUE_SIZE > OTA_RETAINED_OFFLINE_FRAMES,
|
||||
"Shared mOTA queue must retain 128 frames and provide workspace storage");
|
||||
// The buffer also asserts that the complete context fits in the remaining
|
||||
// frames. Constrained ESP32 recipes keep 224 normal slots; nRF52 and Paper keep 256.
|
||||
mesh::BorrowableFrameBuffer<Frame, OFFLINE_QUEUE_SIZE,
|
||||
OTA_RETAINED_OFFLINE_FRAMES, mesh::ota::OtaContext> offline_queue;
|
||||
static mesh::ota::OtaContext* acquireOfflineQueueForOta(void* owner);
|
||||
static uint16_t drainOfflineQueueForOta(void* owner);
|
||||
static void releaseOfflineQueueFromOta(void* owner);
|
||||
#elif COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES
|
||||
#if COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES
|
||||
// Concrete definitions in MyMesh.cpp keep GCC from moving template-local
|
||||
// statics back into ordinary .bss despite the requested RTC attribute.
|
||||
#if COMPANION_RTC_FAST_OFFLINE_FRAMES
|
||||
@@ -1037,6 +1026,23 @@ private:
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if defined(OTA_SHARED_COMPANION_QUEUE)
|
||||
static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES = 128;
|
||||
static_assert(OFFLINE_QUEUE_SIZE > OTA_RETAINED_OFFLINE_FRAMES,
|
||||
"Shared mOTA queue must retain 128 frames and provide workspace storage");
|
||||
// The buffer also asserts that the complete context fits in the remaining
|
||||
// frames. Constrained ESP32 recipes keep 224 normal slots; nRF52 and Paper keep 256.
|
||||
mesh::BorrowableFrameBuffer<Frame, OFFLINE_QUEUE_SIZE,
|
||||
OTA_RETAINED_OFFLINE_FRAMES, mesh::ota::OtaContext
|
||||
#if COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES
|
||||
, mesh::CompanionRtcQueue<Frame, OTA_RETAINED_OFFLINE_FRAMES>
|
||||
#endif
|
||||
> offline_queue;
|
||||
static mesh::ota::OtaContext* acquireOfflineQueueForOta(void* owner);
|
||||
static uint16_t drainOfflineQueueForOta(void* owner);
|
||||
static void releaseOfflineQueueFromOta(void* owner);
|
||||
#elif COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES
|
||||
mesh::CompanionRtcQueue<Frame, OFFLINE_QUEUE_SIZE> offline_queue;
|
||||
#elif defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
||||
enum {
|
||||
|
||||
@@ -6,16 +6,28 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
template<class Frame, size_t Count> class InlineRetainedFrames {
|
||||
Frame frames[Count];
|
||||
public:
|
||||
static constexpr size_t frame_count = Count;
|
||||
Frame& at(size_t index) { return frames[index]; }
|
||||
};
|
||||
|
||||
// One owner at a time: an offline FIFO borrows the workspace's storage while
|
||||
// idle. Switching owners compacts the ring in place, without allocating RAM or
|
||||
// dropping unread frames. The caller owns the FIFO's length and head index.
|
||||
template<class Frame, size_t Capacity, size_t Reserved, class Workspace>
|
||||
// Qualified boards can supply a physically separate retained-frame bank. It
|
||||
// replaces only existing retained slots, never the extra frames or workspace.
|
||||
template<class Frame, size_t Capacity, size_t Reserved, class Workspace,
|
||||
class RetainedFrames = InlineRetainedFrames<Frame, Reserved>>
|
||||
class BorrowableFrameBuffer {
|
||||
static_assert(Reserved > 0 && Reserved < Capacity, "Invalid shared queue capacities");
|
||||
static_assert(RetainedFrames::frame_count == Reserved,
|
||||
"The retained backing must preserve every active-OTA slot");
|
||||
struct ExtraFrames { Frame frames[Capacity - Reserved]; };
|
||||
static_assert(sizeof(Workspace) <= sizeof(ExtraFrames), "mOTA exceeds borrowed queue storage");
|
||||
|
||||
Frame _retained[Reserved];
|
||||
RetainedFrames _retained;
|
||||
union Extra {
|
||||
ExtraFrames queue;
|
||||
Workspace workspace;
|
||||
@@ -45,6 +57,7 @@ class BorrowableFrameBuffer {
|
||||
|
||||
public:
|
||||
BorrowableFrameBuffer() = default;
|
||||
BorrowableFrameBuffer(Frame* fast, Frame* slow) : _retained(fast, slow) {}
|
||||
BorrowableFrameBuffer(const BorrowableFrameBuffer&) = delete;
|
||||
BorrowableFrameBuffer& operator=(const BorrowableFrameBuffer&) = delete;
|
||||
~BorrowableFrameBuffer() {
|
||||
@@ -53,7 +66,7 @@ public:
|
||||
|
||||
size_t capacity() const { return _borrowed ? Reserved : Capacity; }
|
||||
Frame& at(size_t physical_index) {
|
||||
return physical_index < Reserved ? _retained[physical_index]
|
||||
return physical_index < Reserved ? _retained.at(physical_index)
|
||||
: _extra.queue.frames[physical_index - Reserved];
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
#if !defined(ESP32_PLATFORM) || !defined(COMPANION_RADIO_FULL)
|
||||
#error "RTC offline frames require a qualified ESP32 Full Companion recipe"
|
||||
#endif
|
||||
#if defined(BOARD_HAS_PSRAM) || defined(OTA_SHARED_COMPANION_QUEUE) \
|
||||
|| ONE_KEY_DM_SHARED_OFFLINE_QUEUE
|
||||
#if defined(BOARD_HAS_PSRAM) || ONE_KEY_DM_SHARED_OFFLINE_QUEUE
|
||||
#error "RTC offline frames require the independent fixed-capacity queue"
|
||||
#endif
|
||||
#include <sdkconfig.h>
|
||||
@@ -36,6 +35,10 @@
|
||||
#if COMPANION_RTC_SLOW_OFFLINE_FRAMES && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#error "The separate RTC SLOW offline-frame bank requires ESP32-S3"
|
||||
#endif
|
||||
#if defined(OTA_SHARED_COMPANION_QUEUE) \
|
||||
&& (!defined(CONFIG_IDF_TARGET_ESP32C3) || COMPANION_RTC_SLOW_OFFLINE_FRAMES)
|
||||
#error "RTC retained shared-OTA frames are qualified only on ESP32-C3 FAST memory"
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
namespace companion_rtc {
|
||||
@@ -75,6 +78,7 @@ template<typename Frame, size_t Count> class CompanionRtcQueue {
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr size_t frame_count = Count;
|
||||
CompanionRtcQueue(Frame* fast, Frame* slow) : fast_frames(fast), slow_frames(slow) {
|
||||
if ((FastCount && (!fast || reinterpret_cast<uintptr_t>(fast) % 4))
|
||||
|| (SlowCount && (!slow || reinterpret_cast<uintptr_t>(slow) % 4))) abort();
|
||||
|
||||
@@ -196,7 +196,10 @@ class CompanionRtcQueueTests(unittest.TestCase):
|
||||
source = (ROOT / 'examples/companion_radio/MyMesh.cpp').read_text()
|
||||
start = header.index('#elif COMPANION_RTC_FAST_OFFLINE_FRAMES || COMPANION_RTC_SLOW_OFFLINE_FRAMES', header.index('static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES'))
|
||||
end = header.index('#elif defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)', start)
|
||||
storage = header[start:].split('\n', 1)[1][:end - start - len(header[start:].split('\n', 1)[0]) - 1]
|
||||
common_start = header.index(' // Concrete definitions in MyMesh.cpp')
|
||||
common_end = header.index('\n#endif\n#if defined(OTA_SHARED_COMPANION_QUEUE)', common_start)
|
||||
storage = (header[common_start:common_end] + '\n'
|
||||
+ header[start:].split('\n', 1)[1][:end - start - len(header[start:].split('\n', 1)[0]) - 1])
|
||||
banks_start = source.index('#if COMPANION_RTC_FAST_OFFLINE_FRAMES\n')
|
||||
banks_end = source.index('bool MyMesh::Frame::isChannelMsg()', banks_start)
|
||||
methods = '\n'.join(extract_braced(source, name) for name in (
|
||||
@@ -287,7 +290,8 @@ class CompanionRtcQueueTests(unittest.TestCase):
|
||||
base = ['ESP32_PLATFORM=1', 'COMPANION_RADIO_FULL=1', 'CONFIG_IDF_TARGET_ESP32C3=1',
|
||||
'CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=1', 'COMPANION_RTC_FAST_OFFLINE_FRAMES=32']
|
||||
cases = ([value for value in base if not value.startswith('CONFIG_IDF_TARGET_')],
|
||||
base + ['BOARD_HAS_PSRAM=1'], base + ['OTA_SHARED_COMPANION_QUEUE=1'],
|
||||
base + ['BOARD_HAS_PSRAM=1'], [value for value in base if not value.startswith('CONFIG_IDF_TARGET_')]
|
||||
+ ['OTA_SHARED_COMPANION_QUEUE=1', 'CONFIG_IDF_TARGET_ESP32S3=1'],
|
||||
base + ['ONE_KEY_DM_SHARED_OFFLINE_QUEUE=1'],
|
||||
base + ['COMPANION_RTC_SLOW_OFFLINE_FRAMES=32'],
|
||||
[value for value in base if not value.startswith('COMPANION_RADIO_FULL')],
|
||||
@@ -299,7 +303,7 @@ class CompanionRtcQueueTests(unittest.TestCase):
|
||||
capture_output=True, text=True, timeout=15)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
def test_only_five_named_full_policies_select_banks_without_capacity_reductions(self):
|
||||
def test_only_named_full_policies_select_banks_without_capacity_reductions(self):
|
||||
source = (ROOT / 'build_legacy.sh').read_text()
|
||||
start = source.index(' # BLE + WiFi exhaust internal DRAM')
|
||||
start = source.index(' case "${env_name,,}" in', start)
|
||||
@@ -310,6 +314,8 @@ class CompanionRtcQueueTests(unittest.TestCase):
|
||||
self.assertNotIn('COMPANION_RTC_SLOW_OFFLINE_FRAMES', prefix)
|
||||
for target, fast, slow, contacts, queue in (
|
||||
('Generic_ESPNOW_companion_radio_full', 32, 0, 100, 224),
|
||||
('Xiao_C3_companion_radio_full', 32, 0, 100, 224),
|
||||
('Heltec_CT62_companion_radio_full', 0, 0, 100, 224),
|
||||
('Heltec_Wireless_Tracker_companion_radio_full', 32, 32, 150, 256),
|
||||
('MKE_s3_companion_radio_full', 32, 32, 350, 256),
|
||||
('ThinkNode_M2_companion_radio_full', 32, 32, 350, 256),
|
||||
@@ -328,7 +334,11 @@ class CompanionRtcQueueTests(unittest.TestCase):
|
||||
self.assertEqual(int(flags.get('COMPANION_RTC_SLOW_OFFLINE_FRAMES', 0)), slow)
|
||||
self.assertEqual(int(flags.get('MAX_CONTACTS', 350)), contacts)
|
||||
self.assertEqual(int(flags.get('OFFLINE_QUEUE_SIZE', 256)), queue)
|
||||
self.assertNotIn('OTA_SHARED_COMPANION_QUEUE', flags)
|
||||
if target in ('Xiao_C3_companion_radio_full', 'Heltec_CT62_companion_radio_full'):
|
||||
self.assertEqual(flags.get('OTA_SHARED_COMPANION_QUEUE'), '1')
|
||||
self.assertEqual(flags.get('ONE_KEY_DM_COMPACT_HELD'), '1')
|
||||
else:
|
||||
self.assertNotIn('OTA_SHARED_COMPANION_QUEUE', flags)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -209,7 +209,10 @@ int main() {
|
||||
self.assertEqual(flags.get('COMPANION_RTC_TERMINAL_INPUT')=='1', selected)
|
||||
self.assertEqual(int(flags['MAX_CONTACTS']), contacts)
|
||||
self.assertEqual(int(flags.get('OFFLINE_QUEUE_SIZE', 256)), queue)
|
||||
self.assertNotIn('COMPANION_RTC_FAST_OFFLINE_FRAMES', flags)
|
||||
if target == 'Xiao_C3_companion_radio_full':
|
||||
self.assertEqual(flags.get('COMPANION_RTC_FAST_OFFLINE_FRAMES'), '32')
|
||||
else:
|
||||
self.assertNotIn('COMPANION_RTC_FAST_OFFLINE_FRAMES', flags)
|
||||
self.assertNotIn('COMPANION_RTC_SLOW_OFFLINE_FRAMES', flags)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import resource
|
||||
import signal
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -22,24 +25,41 @@ def method(text, signature):
|
||||
|
||||
|
||||
class SharedCompanionOtaCapacityTest(unittest.TestCase):
|
||||
def compile(self, text, *, run=False):
|
||||
def compile(self, text, *, run=False, rtc=False, args=(), expected_returncode=0):
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-shared-c3-") as temp:
|
||||
binary = Path(temp) / "queue"
|
||||
(Path(temp) / "sdkconfig.h").write_text("#pragma once\n")
|
||||
(Path(temp) / "esp_attr.h").write_text(
|
||||
'#pragma once\n#define RTC_FAST_ATTR __attribute__((section(".rtc.force_fast.test")))\n')
|
||||
command = ["c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", str(ROOT / "src"), "-x", "c++", "-",
|
||||
"-I", str(ROOT / "src"), "-I", temp, "-x", "c++", "-",
|
||||
"-o", str(binary)]
|
||||
if rtc:
|
||||
command[1:1] = ["-DNDEBUG", "-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1",
|
||||
"-DOTA_SHARED_COMPANION_QUEUE=1", "-DOFFLINE_QUEUE_SIZE=224", "-DCONFIG_IDF_TARGET_ESP32C3=1",
|
||||
"-DCONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=1",
|
||||
"-DCOMPANION_RTC_FAST_OFFLINE_FRAMES=32"]
|
||||
if run:
|
||||
command[1:1] = ["-fsanitize=address,undefined", "-g"]
|
||||
result = subprocess.run(command, input=text, text=True,
|
||||
capture_output=True)
|
||||
if run:
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
result = subprocess.run([str(binary)], text=True,
|
||||
capture_output=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
if rtc:
|
||||
objdump = shutil.which('objdump')
|
||||
self.assertIsNotNone(objdump, 'objdump is required for the physical RTC bank check')
|
||||
sections = subprocess.check_output([objdump, '-h', str(binary)], text=True)
|
||||
match = re.search(r'\.rtc\.force_fast\.test\s+([0-9a-fA-F]+)', sections)
|
||||
self.assertIsNotNone(match, sections)
|
||||
self.assertEqual(int(match[1], 16), 32 * 177, sections)
|
||||
def no_core_dump():
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
||||
result = subprocess.run([str(binary), *args], text=True,
|
||||
capture_output=True, preexec_fn=no_core_dump)
|
||||
self.assertEqual(result.returncode, expected_returncode, result.stderr)
|
||||
return result
|
||||
|
||||
def test_224_normal_slots_and_128_retained_slots_preserve_fifo_and_ownership(self):
|
||||
def queue_fixture(self, rtc=False):
|
||||
source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text()
|
||||
declaration = (ROOT / "examples/companion_radio/MyMesh.h").read_text()
|
||||
retained = re.search(
|
||||
@@ -52,10 +72,14 @@ class SharedCompanionOtaCapacityTest(unittest.TestCase):
|
||||
))
|
||||
harness = r'''
|
||||
#include <helpers/BorrowableFrameBuffer.h>
|
||||
#include <helpers/CompanionRtcQueue.h>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#undef assert
|
||||
#define assert(value) do { if (!(value)) std::abort(); } while (0)
|
||||
constexpr unsigned MAX_FRAME_SIZE = 176;
|
||||
namespace mesh { namespace ota {
|
||||
struct OtaContext {
|
||||
@@ -76,8 +100,7 @@ unsigned OtaContext::live = 0, OtaContext::constructions = 0,
|
||||
struct MyMesh {
|
||||
struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; };
|
||||
static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES = @RETAINED@;
|
||||
mesh::BorrowableFrameBuffer<Frame, 224, OTA_RETAINED_OFFLINE_FRAMES,
|
||||
mesh::ota::OtaContext> offline_queue;
|
||||
@STORAGE@
|
||||
int offline_queue_len = 0, offline_queue_head = 0;
|
||||
unsigned next = 0, notifications = 0;
|
||||
std::deque<unsigned> expected;
|
||||
@@ -112,6 +135,7 @@ struct MyMesh {
|
||||
}
|
||||
void pop() { uint8_t bytes[MAX_FRAME_SIZE]; assert(getFromOfflineQueue(bytes)); }
|
||||
};
|
||||
@BANKS@
|
||||
@METHODS@
|
||||
int main() {
|
||||
using mesh::ota::OtaContext;
|
||||
@@ -148,7 +172,96 @@ int main() {
|
||||
}
|
||||
}
|
||||
'''.replace("@RETAINED@", retained).replace("@METHODS@", methods)
|
||||
self.compile(harness, run=True)
|
||||
if rtc:
|
||||
start = declaration.index(' // Concrete definitions in MyMesh.cpp')
|
||||
end = declaration.index('\n#endif\n#if defined(OTA_SHARED_COMPANION_QUEUE)', start)
|
||||
shared_start = declaration.index(' static constexpr unsigned OTA_RETAINED_OFFLINE_FRAMES')
|
||||
queue_start = declaration.index(' mesh::BorrowableFrameBuffer<Frame', shared_start)
|
||||
queue_end = declaration.index(' static mesh::ota::OtaContext* acquireOfflineQueueForOta', queue_start)
|
||||
storage = (declaration[start:end] + '\n' + declaration[queue_start:queue_end]
|
||||
+ '\n MyMesh() : offline_queue(rtcFastQueueBank(), rtcSlowQueueBank()) {}\n')
|
||||
bank_start = source.index('#if COMPANION_RTC_FAST_OFFLINE_FRAMES\n')
|
||||
bank_end = source.index('bool MyMesh::Frame::isChannelMsg()', bank_start)
|
||||
banks = source[bank_start:bank_end]
|
||||
else:
|
||||
storage = ('mesh::BorrowableFrameBuffer<Frame, 224, OTA_RETAINED_OFFLINE_FRAMES, '
|
||||
'mesh::ota::OtaContext> offline_queue;')
|
||||
banks = ''
|
||||
return harness.replace('@STORAGE@', storage).replace('@BANKS@', banks)
|
||||
|
||||
def test_224_normal_slots_and_128_retained_slots_preserve_fifo_and_ownership(self):
|
||||
self.compile(self.queue_fixture(), run=True)
|
||||
|
||||
def test_rtc_backed_224_and_128_fifo_all_heads_use_actual_mesh_storage_and_callbacks(self):
|
||||
self.compile(self.queue_fixture(rtc=True), run=True, rtc=True)
|
||||
|
||||
def test_rtc_owner_reset_geometry_and_release_build_safeguards(self):
|
||||
harness = self.queue_fixture(rtc=True).split('int main() {', 1)[0] + r'''
|
||||
using Frame = MyMesh::Frame;
|
||||
using Workspace = mesh::ota::OtaContext;
|
||||
using Inline = mesh::BorrowableFrameBuffer<Frame, 224, 128, Workspace>;
|
||||
using Retained = mesh::CompanionRtcQueue<Frame, 128>;
|
||||
using Split = mesh::BorrowableFrameBuffer<Frame, 224, 128, Workspace, Retained>;
|
||||
struct Legacy {
|
||||
Frame retained[128];
|
||||
union Extra { Frame frames[96]; Workspace workspace; Extra() {} ~Extra() {} } extra;
|
||||
bool borrowed;
|
||||
};
|
||||
static_assert(sizeof(Inline) == sizeof(Legacy), "Ordinary targets keep the exact BSS layout");
|
||||
static_assert(alignof(Inline) == alignof(Legacy), "Ordinary targets keep their alignment");
|
||||
static_assert(sizeof(Inline) - sizeof(Split) >= 32 * sizeof(Frame) - 2 * sizeof(void*) - alignof(Split),
|
||||
"The complete 32-slot bank must leave internal DRAM");
|
||||
static_assert(Retained::frame_count == 128, "All active-OTA slots remain independent of workspace");
|
||||
static_assert(!std::is_copy_constructible<Split>::value && !std::is_move_constructible<Split>::value,
|
||||
"A bank lease must never be duplicated");
|
||||
int main(int argc, char** argv) {
|
||||
alignas(4) Frame bank[32];
|
||||
memset(bank, 0xA5, sizeof bank);
|
||||
if (argc > 1 && !strcmp(argv[1], "null")) { Split bad(nullptr, nullptr); return 1; }
|
||||
if (argc > 1 && !strcmp(argv[1], "misaligned")) {
|
||||
Split bad(reinterpret_cast<Frame*>(reinterpret_cast<uint8_t*>(bank) + 1), nullptr); return 1;
|
||||
}
|
||||
if (argc > 1 && !strcmp(argv[1], "range")) {
|
||||
Retained owner(bank, nullptr); owner.at(128); return 1;
|
||||
}
|
||||
{
|
||||
Split owner(bank, nullptr);
|
||||
for (const auto& frame : bank) {
|
||||
assert(frame.len == 0);
|
||||
for (uint8_t byte : frame.buf) assert(byte == 0);
|
||||
}
|
||||
for (unsigned i = 0; i < 128; ++i) {
|
||||
auto& frame = owner.at(i); assert(frame.len == 0);
|
||||
for (uint8_t byte : frame.buf) assert(byte == 0);
|
||||
}
|
||||
if (argc > 1 && !strcmp(argv[1], "second")) { Split second(bank, nullptr); return 1; }
|
||||
if (argc > 1 && !strcmp(argv[1], "other-count")) {
|
||||
mesh::CompanionRtcQueue<Frame, 224> second(bank, nullptr); return 1;
|
||||
}
|
||||
if (argc > 1 && !strcmp(argv[1], "foreign")) {
|
||||
// A stale or foreign lease must be rejected, even with NDEBUG.
|
||||
mesh::companion_rtc::Lease<Frame>::owner() = nullptr;
|
||||
owner.at(0); return 1;
|
||||
}
|
||||
for (unsigned i = 0; i < 224; ++i) memset(&owner.at(i), 0xBB, sizeof(Frame));
|
||||
int head = 217;
|
||||
assert(owner.acquire(128, head) && head == 0 && owner.capacity() == 128);
|
||||
// Destruction must end an active workspace exactly once before releasing its bank.
|
||||
}
|
||||
assert(Workspace::live == 0 && Workspace::constructions == Workspace::destructions);
|
||||
Split next(bank, nullptr);
|
||||
for (unsigned i = 0; i < 224; ++i) {
|
||||
const auto& frame = next.at(i); assert(frame.len == 0);
|
||||
for (uint8_t byte : frame.buf) assert(byte == 0);
|
||||
}
|
||||
}
|
||||
'''
|
||||
harness = harness.replace('#include <deque>', '#include <deque>\n#include <type_traits>')
|
||||
self.compile(harness, run=True, rtc=True)
|
||||
for argument in ('null', 'misaligned', 'second', 'other-count', 'foreign', 'range'):
|
||||
with self.subTest(argument=argument):
|
||||
self.compile(harness, run=True, rtc=True, args=(argument,),
|
||||
expected_returncode=-signal.SIGABRT)
|
||||
|
||||
def test_insufficient_workspace_or_no_retained_region_cannot_compile(self):
|
||||
harness = r'''
|
||||
|
||||
@@ -17,7 +17,10 @@ class SharedMotaQueueTest(unittest.TestCase):
|
||||
def test_wireless_paper_wifi_listener_and_transfers_share_only_when_needed(self):
|
||||
self.run_transfer_test("ESP32_PLATFORM")
|
||||
|
||||
def run_transfer_test(self, platform):
|
||||
def test_c3_rtc_retained_queue_preserves_real_ota_and_wifi_transfers(self):
|
||||
self.run_transfer_test("ESP32_PLATFORM", rtc=True)
|
||||
|
||||
def run_transfer_test(self, platform, rtc=False):
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-mota-queue-") as temp:
|
||||
binary = Path(temp) / "transfer"
|
||||
tinf = Path(temp) / "tinf.o"
|
||||
@@ -44,6 +47,30 @@ class SharedMotaQueueTest(unittest.TestCase):
|
||||
sources += ["src/helpers/esp32/WiFiOtaSeeder.cpp",
|
||||
"src/helpers/ota/MotaSourceSerial.cpp",
|
||||
"src/helpers/ota/FolderMotaStore.cpp"]
|
||||
if rtc:
|
||||
fixture = ROOT / "test/fixtures/shared_mota_queue"
|
||||
source = (fixture / "test_shared_mota_queue.cpp").read_text()
|
||||
source = source[:source.index(" Queue full;")] + "}\n"
|
||||
source = source.replace("256", "224")
|
||||
source = source.replace("struct Queue {", (
|
||||
"#include <helpers/CompanionRtcQueue.h>\n"
|
||||
"alignas(4) RTC_FAST_ATTR Frame rtc_frames[32];\nstruct Queue {"))
|
||||
source = source.replace(
|
||||
"mesh::BorrowableFrameBuffer<Frame, 224, 128, OtaContext> buffer;",
|
||||
"mesh::BorrowableFrameBuffer<Frame, 224, 128, OtaContext, "
|
||||
"mesh::CompanionRtcQueue<Frame, 128>> buffer;\n"
|
||||
" Queue() : buffer(rtc_frames, nullptr) {}")
|
||||
(Path(temp) / "test_shared_rtc.cpp").write_text(source)
|
||||
(Path(temp) / "test_wifi_shared_queue.h").write_text(
|
||||
(fixture / "test_wifi_shared_queue.h").read_text().replace("256", "224"))
|
||||
(Path(temp) / "sdkconfig.h").write_text("#pragma once\n")
|
||||
(Path(temp) / "esp_attr.h").write_text(
|
||||
'#pragma once\n#define RTC_FAST_ATTR __attribute__((section(".rtc.force_fast.test")))\n')
|
||||
sources[0] = str(Path(temp) / "test_shared_rtc.cpp")
|
||||
flags += ["-DCOMPANION_RTC_FAST_OFFLINE_FRAMES=32",
|
||||
"-DCONFIG_IDF_TARGET_ESP32C3=1",
|
||||
"-DCONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=1",
|
||||
"-I", str(fixture)]
|
||||
built = subprocess.run([
|
||||
"c++", "-std=c++17", "-fsanitize=address,undefined", "-g",
|
||||
*flags, "-DOTA_SEEDER_ONLY=1",
|
||||
|
||||
Reference in new issue
Block a user