Let motatool size authenticated nRF52 delta workspaces
Forward --inplace-memory only when explicitly supplied, preserving authenticated layout bounds and the conservative legacy fallback. Validation: MOTATOOL_TEST_BIN=/home/mesh/git/motatool/target/release/motatool python3 -m unittest test_lora_ota (318 passed). Both new auto-sizing regressions fail against the prior runner.
This commit is contained in:
1 parent
784d2f4964
commit
e22d5d7d0a
2 files changed
+76
-9
No files matched your search
@@ -1560,14 +1560,11 @@ def prepare_package(
|
||||
"--patch-type",
|
||||
"in-place",
|
||||
])
|
||||
# External staging leaves the application region available as the
|
||||
# detools workspace. 0xC6000 is safe for both S140 v7 (app starts
|
||||
# at 0x27000) and older v6 layouts (0x26000). Internal staging
|
||||
# retains its deliberately smaller legacy workspace.
|
||||
inplace_memory = args.inplace_memory or (
|
||||
"0xC6000" if target.nrf_external else "0x98000"
|
||||
)
|
||||
command.extend(["--inplace-memory", inplace_memory])
|
||||
# Let motatool size the workspace from both authenticated layout
|
||||
# records and validate the application/staging bounds. Images
|
||||
# without those records retain motatool's conservative fallback.
|
||||
if args.inplace_memory is not None:
|
||||
command.extend(["--inplace-memory", args.inplace_memory])
|
||||
if args.sign_key:
|
||||
command.extend(["--sign", str(args.sign_key.resolve())])
|
||||
result = run_checked(
|
||||
@@ -7949,7 +7946,10 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--public-key", type=Path, help="require this signer when verifying")
|
||||
parser.add_argument(
|
||||
"--inplace-memory",
|
||||
help="nRF52 OTAFIX workspace (auto: 0x98000 internal, 0xC6000 external SD/QSPI)",
|
||||
help=(
|
||||
"explicit nRF52 OTAFIX workspace override; default: motatool "
|
||||
"authenticated-layout sizing, or its conservative legacy fallback"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform", choices=("esp32", "nrf52"),
|
||||
|
||||
@@ -8065,6 +8065,73 @@ class MotatoolIntegrationTests(unittest.TestCase):
|
||||
)
|
||||
subprocess.run([cls.motatool, "--version"], check=True, capture_output=True)
|
||||
|
||||
def prepare_layout_delta(
|
||||
self, *, external: bool = False, memory: str | None = None,
|
||||
authenticated: bool = True,
|
||||
) -> tuple[ota.MotaInfo, list[mock._Call]]:
|
||||
# The real tool must size an image larger than its legacy 0x98000
|
||||
# workspace from the EndF-authenticated geometry, including two spare
|
||||
# detools segments. Only a small content change needs transferring.
|
||||
layout = struct.pack(
|
||||
"<8sBBHIII", b"mOTALay1", 1, 1 if external else 0, 24,
|
||||
0x27000, 0xED000, 0xED000,
|
||||
) if authenticated else b""
|
||||
body = b"A" * (0x99000 - 56 - len(layout)) + layout
|
||||
base_image = firmware(body, VERSION_OLD)
|
||||
new_image = firmware(b"B" * 32 + body[32:], VERSION_NEW)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
base_path = root / "running.bin"
|
||||
base_path.write_bytes(base_image)
|
||||
archive_path = root / "release.zip"
|
||||
with zipfile.ZipFile(archive_path, "w") as archive:
|
||||
archive.writestr("firmware.bin", new_image)
|
||||
args = prepare_args(archive_path, self.motatool, base_path)
|
||||
args.inplace_memory = memory
|
||||
work = root / "work"
|
||||
work.mkdir()
|
||||
with mock.patch.object(ota, "run_checked", wraps=ota.run_checked) as run:
|
||||
_path, package, expected = ota.prepare_package(
|
||||
args, target(
|
||||
platform="nrf52", nrf_sd=external,
|
||||
base_hash=ota.parse_endf(base_image).body_hash,
|
||||
boot_codecs=1 << ota.MOTA_CODEC_IN_PLACE,
|
||||
), work,
|
||||
)
|
||||
self.assertFalse(package.is_full)
|
||||
self.assertEqual(package.codec_id, ota.MOTA_CODEC_IN_PLACE)
|
||||
self.assertEqual(package.image_size, len(new_image))
|
||||
self.assertEqual(package.base_hash, ota.parse_endf(base_image).body_hash)
|
||||
self.assertEqual(expected, ota.parse_endf(new_image).body_hash)
|
||||
return package, run.call_args_list
|
||||
|
||||
def test_authenticated_internal_layout_selects_large_workspace(self) -> None:
|
||||
with contextlib.redirect_stdout(io.StringIO()) as output:
|
||||
_package, calls = self.prepare_layout_delta()
|
||||
build = next(call.args[0] for call in calls if call.kwargs["label"] == "build mOTA")
|
||||
self.assertNotIn("--inplace-memory", build)
|
||||
self.assertIn("in-place memory=0x9B000", output.getvalue())
|
||||
|
||||
def test_authenticated_external_layout_uses_minimum_workspace(self) -> None:
|
||||
with contextlib.redirect_stdout(io.StringIO()) as output:
|
||||
_package, calls = self.prepare_layout_delta(external=True)
|
||||
build = next(call.args[0] for call in calls if call.kwargs["label"] == "build mOTA")
|
||||
self.assertNotIn("--inplace-memory", build)
|
||||
self.assertIn("in-place memory=0x9B000", output.getvalue())
|
||||
|
||||
def test_explicit_workspace_is_preserved_and_layout_checked(self) -> None:
|
||||
with contextlib.redirect_stdout(io.StringIO()) as output:
|
||||
_package, calls = self.prepare_layout_delta(memory="0xA0000")
|
||||
build = next(call.args[0] for call in calls if call.kwargs["label"] == "build mOTA")
|
||||
self.assertEqual(build[build.index("--inplace-memory") + 1], "0xA0000")
|
||||
self.assertIn("in-place memory=0xA0000", output.getvalue())
|
||||
with self.assertRaisesRegex(ota.OtaError, "exceeds authenticated nRF52 workspace"):
|
||||
self.prepare_layout_delta(memory="0xD0000")
|
||||
|
||||
def test_legacy_image_retains_conservative_workspace_limit(self) -> None:
|
||||
with self.assertRaisesRegex(ota.OtaError, "legacy default memory 0x98000 is too small"):
|
||||
self.prepare_layout_delta(authenticated=False)
|
||||
|
||||
def test_raw_esp32_zip_becomes_full_mota(self) -> None:
|
||||
image = firmware(b"ESP32-new" * 900, VERSION_NEW)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
|
||||
Reference in new issue
Block a user