pureboot: the info block is what proves a knock landed

A prompt byte alone does not: one left over from a previous session can
still be in the pipeline while the port opening resets the device into a
fresh window, where the bare command that follows is discarded. Each
attempt is now the whole handshake, retried until the block comes back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:49:37 +02:00
parent c763ca856a
commit 11707e0b06
2 changed files with 85 additions and 12 deletions

View File

@@ -364,26 +364,37 @@ class Loader:
self.info = None
def connect(self, wait):
"""Knock until the window answers, then read the info block. Also
converges into a live session: the knock bytes are ignored there and
the drain absorbs whatever they produced."""
self.port.flush_input()
"""Knock until the info block comes back. The block is what proves the
loader is listening — a prompt byte alone does not, since one left over
from a previous session can still be in the pipeline while the port
opening resets the device into a fresh activation window, where a
command without its knock is discarded. Each attempt is therefore the
whole handshake, retried until it produces the block or the window
closes. Also converges into a live session: the knock bytes are ignored
there and the drain absorbs whatever they produced."""
deadline = time.monotonic() + wait
knocks = 0
while True:
self.port.flush_input()
self.port.write(b"pb")
knocks += 1
if PROMPT in self.port.read_available(0.4):
break
while self.port.read_available(0.3):
pass
self.port.write(b"b")
try:
block = self.port.read_exact(12, 2.0)
except Error:
block = b""
# A version the tool cannot speak is the loader's own answer,
# not a failed knock: Info reports it rather than retrying.
if block[0:2] == b"PB":
self.info = Info(block)
self._expect_prompt()
verbose(f"loader answered knock {knocks}; info block read")
return self.info
if time.monotonic() > deadline:
raise Error("no answer — reset the device within its activation window")
while self.port.read_available(0.3):
pass
self.port.write(b"b")
self.info = Info(self.port.read_exact(12, 2.0))
self._expect_prompt()
verbose(f"loader answered knock {knocks}; info block read")
return self.info
def _expect_prompt(self, timeout=2.0):
byte = self.port.read_exact(1, timeout)