diff --git a/tools/pbhw.py b/tools/pbhw.py index a96eca2..40d5156 100755 --- a/tools/pbhw.py +++ b/tools/pbhw.py @@ -161,30 +161,63 @@ class Suite: self.check("application flash reads back", rc == 0 and len(got) == info.base, f"{len(got)} B of {info.base}") + def _witness(self, info, slot_length: int): + """Read back the erased region and the loader slot: (erased, slot, how). + + Prefers ISP, because an independent reader is the only one that can + testify about a loader just asked to erase around itself. Where no + programmer is attached the link answers instead — which is weaker for + exactly the reason it is worth having, a destroyed loader being unable + to report anything at all. The two are never printed under one word: + an absent probe is a fact about the bench, a wrong byte is a verdict on + the loader, and a check that conflates them stops being read. + """ + limit = info.base - 2 if info.patch_vector else info.base + whole = self.work / "whole.bin" + if self.rig.read_memory("flash", whole, "r"): + image = whole.read_bytes() + image += b"\xff" * (info.flash_size - len(image)) + return image[0:limit], image[info.base:info.base + slot_length], "ISP" + + module = pbrig.load_pureboot(self.rig.d.pureboot) + port = self.rig.open_port() + try: + loader = module.Loader(port) + if self.rig.d.autobaud: + loader.connect_autobaud(self.rig.d.wait) + else: + loader.connect(self.rig.d.wait) + return (loader.read_flash(0, limit), + loader.read_flash(info.base, slot_length), + "the link, no probe attached — the loader's own account") + except Exception as error: # noqa: BLE001 — a dead link is a result + print(f" skip slot checks: no programmer, and the link did not " + f"answer either ({str(error)[:60]})") + return None, None, "" + finally: + try: + port.close() + except Exception: # noqa: BLE001 + pass + def erase_and_slot(self, info, loader_image: pathlib.Path | None) -> None: rc, out = self.rig.pureboot("--erase-flash") self.check("application region erases", rc == 0, self._brief(out)) - # The slot must be untouched by an application erase, which only an - # independent read can show — so this one goes over ISP, not the link. - whole = self.work / "whole.bin" - if not self.rig.read_memory("flash", whole, "r"): - self.check("loader slot survives the erase", False, "ISP read failed") + want = loader_image.read_bytes() if loader_image and loader_image.exists() else b"" + erased, slot, how = self._witness(info, len(want)) + if erased is None: return - image = whole.read_bytes() - image += b"\xff" * (info.flash_size - len(image)) # Erased application flash, up to the trampoline word the host composes # on a patched-vector part. limit = info.base - 2 if info.patch_vector else info.base self.check("erased application region is 0xff", - set(image[0:limit]) <= {0xFF}, f"0x0000..{limit:#06x}") + set(erased) <= {0xFF}, f"0x0000..{limit:#06x} via {how}") - if loader_image and loader_image.exists(): - want = loader_image.read_bytes() - got = image[info.base:info.base + len(want)] - self.check("loader slot survives the erase", got == want, - f"{len(want)} B at {info.base:#06x}") + if want: + self.check("loader slot survives the erase", slot == want, + f"{len(want)} B at {info.base:#06x} via {how}") else: print(" skip loader slot comparison (pass --loader )")