Reserve Full host KISS codec RAM without reducing existing budgets
This commit is contained in:
1 parent
1aef6de288
commit
ec4aba8a1c
6 files changed
+119
-9
No files matched your search
@@ -77,6 +77,16 @@ Headless and OLED devices use their own smaller totals. The JSON lists each
|
||||
component and checks the largest available region against the largest planned
|
||||
single allocation.
|
||||
|
||||
Enabled Full host KISS images reserve a separate 2,048-byte
|
||||
`host_kiss_codec` allocation on ESP32 and nRF52, including while KISS is saved
|
||||
off. Turning the service on allocates its two host-TX and two encoded-output
|
||||
frames plus parser state. The source assertion bounds the complete Codec and
|
||||
allocator headroom to this allowance. WiFi/TCP retains its existing network
|
||||
allowance; the independent transient margin and all normal-role capacities
|
||||
remain additive. Explicit feature-off and ESP-NOW-only images omit this
|
||||
allocation. A passing report proves linked capacity, not physical session or
|
||||
maximum network-load qualification.
|
||||
|
||||
### MQTT startup minimum and runtime qualification
|
||||
|
||||
The `mqtt_startup_allowance` component keeps a 24,576-byte internal startup
|
||||
|
||||
@@ -314,6 +314,16 @@ def requirements(platform, defines, target):
|
||||
parts["neighbor_history"] = count * 9 + (16 if count else 0)
|
||||
else:
|
||||
parts["core_filesystems_sensors"] = 8192
|
||||
if (platform in {"ESP32_PLATFORM", "NRF52_PLATFORM"}
|
||||
and not integer(defines, "MESH_PRIMARY_ESPNOW", 0)
|
||||
and integer(defines, "MESH_ENABLE_HOST_KISS",
|
||||
integer(defines, "COMPANION_RADIO_FULL", 0))):
|
||||
# Full raw KISS is lazy but can coexist with every normal-role owner.
|
||||
# Its two RX/output frames, two host TX frames and framing state live
|
||||
# in one Codec allocation; its source assertion retains allocator
|
||||
# headroom within 2 KiB. Socket/WiFi objects retain their existing
|
||||
# network allowance. This never replaces the transient margin.
|
||||
parts["host_kiss_codec"] = 2048
|
||||
# Packet bytes plus all three queue tables and allocation overhead.
|
||||
parts["radio_packet_pool"] = 5120 if companion else 10240
|
||||
# These tables moved out of .bss, so linker heap bounds now include their
|
||||
@@ -448,7 +458,7 @@ def requirements(platform, defines, target):
|
||||
"solarxiao_voltage_history",
|
||||
"ota_context", "screen_objects_and_history", "ota_encoder_workspace",
|
||||
"ota_generic_source_buffers", "ethernet_or_external_ota_workspaces", "ota_target_name_lookup",
|
||||
"ethernet_companion_worker"):
|
||||
"ethernet_companion_worker", "host_kiss_codec"):
|
||||
largest = max(largest, parts.get(name, 0))
|
||||
if "room_browser_mailbox" in parts:
|
||||
largest = max(largest, 8192, parts["room_browser_mailbox"])
|
||||
|
||||
@@ -146,4 +146,9 @@ class Codec {
|
||||
uint32_t droppedFrames() const { return dropped_; }
|
||||
};
|
||||
|
||||
// Lazy Codec allocation is included in check_firmware_ram.py on every
|
||||
// enabled Full image. Retain allocator metadata room if queues grow later.
|
||||
static_assert(sizeof(Codec) + 32 <= 2048,
|
||||
"Update the host KISS Codec runtime RAM allowance");
|
||||
|
||||
}} // namespace mesh::hostkiss
|
||||
@@ -1161,12 +1161,15 @@ int main() {
|
||||
"BLE_PIN_CODE": 123456, "UI_SMALL_MESSAGE_FONT": 0}
|
||||
with tempfile.TemporaryDirectory() as temp, contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
||||
path = Path(temp) / "firmware.elf"
|
||||
for available, accepted in ((54724, False), (73727, False), (73728, True), (74060, True)):
|
||||
with self.subTest(available=available):
|
||||
elf32(path, {"__HeapBase": (0x2003F800 - available, 0), "__HeapLimit": (0x2003F800, 0)})
|
||||
result = ram.check_firmware(path, "NRF52_PLATFORM", "nrf52840", flags,
|
||||
"Heltec_t096_companion_radio_full_femon")
|
||||
self.assertEqual(result == 0, accepted)
|
||||
for host_kiss, boundary in ((0, 73728), (1, 74530)):
|
||||
selected = {**flags, "MESH_ENABLE_HOST_KISS": host_kiss}
|
||||
for available, accepted in ((54724, False), (boundary - 1, False),
|
||||
(boundary, True), (boundary + 332, True)):
|
||||
with self.subTest(host_kiss=host_kiss, available=available):
|
||||
elf32(path, {"__HeapBase": (0x2003F800 - available, 0), "__HeapLimit": (0x2003F800, 0)})
|
||||
result = ram.check_firmware(path, "NRF52_PLATFORM", "nrf52840", selected,
|
||||
"Heltec_t096_companion_radio_full_femon")
|
||||
self.assertEqual(result == 0, accepted)
|
||||
|
||||
def test_arm_heap_excludes_stack_softdevice_and_mota_arena(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
|
||||
@@ -205,10 +205,13 @@ apply_policy() {
|
||||
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
|
||||
for enabled, cooperative, lose_owner in ((0, 0, False), (0, 1, False),
|
||||
(1, 0, False), (1, 1, False),
|
||||
(1, 1, True)):
|
||||
(1, 1, True), (1, 1, "late")):
|
||||
with self.subTest(enabled=enabled, cooperative=cooperative, lose_owner=lose_owner):
|
||||
candidate = pump
|
||||
if lose_owner:
|
||||
if lose_owner == "late":
|
||||
candidate = candidate.replace(" if (mesh::hostkiss::usbInputReserved(console.peek())) break;\n", "")
|
||||
self.assertNotEqual(candidate, pump)
|
||||
elif lose_owner:
|
||||
candidate = candidate.replace("if (mesh::hostkiss::serviceUsb())",
|
||||
"if (false && mesh::hostkiss::serviceUsb())")
|
||||
self.assertNotEqual(candidate, pump)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bind the optional KISS heap allowance to the real codec and ELF gate."""
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0,str(ROOT/"scripts"))
|
||||
import check_firmware_ram as ram
|
||||
from test_firmware_ram import elf32,esp_fixture
|
||||
|
||||
|
||||
class HostKissRamTest(unittest.TestCase):
|
||||
def test_enabled_codec_adds_two_kib_without_lowering_other_reserves(self):
|
||||
for platform in ("ESP32_PLATFORM","NRF52_PLATFORM"):
|
||||
for target in ("board_companion_radio_full","board_repeater","board_room_server","board_sensor"):
|
||||
with self.subTest(platform=platform,target=target):
|
||||
flags={"BLE_PIN_CODE":123456}
|
||||
off=ram.requirements(platform,{**flags,"MESH_ENABLE_HOST_KISS":0},target)
|
||||
on=ram.requirements(platform,{**flags,"MESH_ENABLE_HOST_KISS":1},target)
|
||||
self.assertEqual(on["components"]["host_kiss_codec"],2048)
|
||||
self.assertEqual(on["required_heap_bytes"],off["required_heap_bytes"]+2048)
|
||||
self.assertEqual({key:value for key,value in on["components"].items()
|
||||
if key!="host_kiss_codec"},off["components"])
|
||||
self.assertEqual(on["required_contiguous_bytes"],off["required_contiguous_bytes"])
|
||||
default=ram.requirements(platform,{**flags,"COMPANION_RADIO_FULL":1},target)
|
||||
self.assertEqual(default["components"],on["components"])
|
||||
for extra in ({"COMPANION_RADIO_FULL":0},
|
||||
{"COMPANION_RADIO_FULL":1,"MESH_ENABLE_HOST_KISS":0},
|
||||
{"MESH_ENABLE_HOST_KISS":1,"MESH_PRIMARY_ESPNOW":1}):
|
||||
self.assertEqual(ram.requirements(platform,{**flags,**extra},target)["components"],off["components"])
|
||||
for platform in ("STM32_PLATFORM","RP2040_PLATFORM"):
|
||||
self.assertEqual(ram.requirements(platform,{"MESH_ENABLE_HOST_KISS":1},"board_companion_radio_ble"),
|
||||
ram.requirements(platform,{},"board_companion_radio_ble"))
|
||||
|
||||
def test_real_elf_rejects_unbudgeted_codec_and_honors_exact_new_boundary(self):
|
||||
for platform in ("ESP32_PLATFORM","NRF52_PLATFORM"):
|
||||
with self.subTest(platform=platform),tempfile.TemporaryDirectory() as folder:
|
||||
folder=Path(folder);path=folder/"firmware.elf";report=folder/"memory.json"
|
||||
target="board_companion_radio_full"
|
||||
flags={"COMPANION_RADIO_FULL":1,"MESH_ENABLE_HOST_KISS":1,"BLE_PIN_CODE":123456}
|
||||
required=ram.requirements(platform,flags,target)["required_heap_bytes"]
|
||||
old=ram.requirements(platform,{**flags,"MESH_ENABLE_HOST_KISS":0},target)["required_heap_bytes"]
|
||||
self.assertEqual(required,old+2048)
|
||||
for available,accepted in ((old,False),(required-1,False),(required,True)):
|
||||
with self.subTest(available=available),contextlib.redirect_stdout(io.StringIO()),contextlib.redirect_stderr(io.StringIO()):
|
||||
if platform=="ESP32_PLATFORM":
|
||||
esp_fixture(path,internal_length=available-0x10000+0x4000)
|
||||
else:
|
||||
elf32(path,{"__HeapBase":(0x20010000,0),"__HeapLimit":(0x20010000+available,0)})
|
||||
status=ram.check_firmware(path,platform,"esp32" if platform=="ESP32_PLATFORM" else "nrf52840",flags,target,report)
|
||||
actual=json.loads(report.read_text())
|
||||
self.assertEqual(status==0,accepted)
|
||||
self.assertEqual(actual["required_heap_bytes"],required)
|
||||
self.assertEqual(actual["components"]["host_kiss_codec"],2048)
|
||||
|
||||
def test_actual_codec_size_is_source_bounded_and_queue_growth_fails(self):
|
||||
source=(ROOT/"src/helpers/HostKissCodec.h").read_text()
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
folder=Path(folder);header=folder/"Codec.h";main=folder/"main.cpp";binary=folder/"test"
|
||||
main.write_text('#include "Codec.h"\n#include <stdio.h>\nint main(){printf("%zu\\n",sizeof(mesh::hostkiss::Codec));}\n')
|
||||
for changed,accepted in ((source,True),(source.replace("QueueDepth = 2","QueueDepth = 3"),False)):
|
||||
header.write_text(changed)
|
||||
result=subprocess.run(["g++","-std=c++17",str(main),"-o",str(binary)],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode==0,accepted,result.stdout+result.stderr)
|
||||
if accepted:
|
||||
size=int(subprocess.check_output([str(binary)]))
|
||||
self.assertEqual(size,1828)
|
||||
self.assertLessEqual(size+32,2048)
|
||||
else:
|
||||
self.assertIn("runtime RAM allowance",result.stderr)
|
||||
|
||||
|
||||
if __name__=="__main__":unittest.main()
|
||||
Reference in new issue
Block a user