host: one fact per line, progress bars, --verbose
--info prints the decoded info block field by field and --fuses each byte on its own line plus the BOOTSZ/BOOTRST meaning on boot-sectioned megas. Transfers that take wire time draw a transient progress bar on stderr when it is a tty — logs, pipes and the tests see only the summary lines. -v/--verbose narrates decisions: knock counts, the programming plan, update state handling and per-phase page counts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,11 +38,57 @@ PROMPT = b"+"
|
|||||||
PROTOCOL_VERSION = 1
|
PROTOCOL_VERSION = 1
|
||||||
SLOT = 512 # the loader slot on byte-addressed chips; word-addressed ones (>64 KiB) use 1 KiB — their own smallest boot sector
|
SLOT = 512 # the loader slot on byte-addressed chips; word-addressed ones (>64 KiB) use 1 KiB — their own smallest boot sector
|
||||||
|
|
||||||
|
VERBOSE = False
|
||||||
|
|
||||||
|
|
||||||
|
def verbose(message):
|
||||||
|
"""Detail printed only under --verbose: decisions and derived facts, not
|
||||||
|
per-byte chatter — the progress bar carries the bulk transfers."""
|
||||||
|
if VERBOSE:
|
||||||
|
print(f" {message}")
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Progress:
|
||||||
|
"""A transient in-place bar on stderr for the operations that take wire
|
||||||
|
time. Drawn only when stderr is a tty — logs, pipes and the test harness
|
||||||
|
see nothing — and erased once done; the summary line each operation
|
||||||
|
prints afterwards is the persistent record. A zero total (or no label)
|
||||||
|
disables it, so callers can pass one through unconditionally."""
|
||||||
|
|
||||||
|
def __init__(self, label, total, unit="pages"):
|
||||||
|
self.label, self.total, self.unit = label, total, unit
|
||||||
|
self.done = 0
|
||||||
|
self.width = 0
|
||||||
|
self.live = bool(label) and total > 0 and sys.stderr.isatty()
|
||||||
|
self._draw()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *exc):
|
||||||
|
if self.live:
|
||||||
|
sys.stderr.write("\r" + " " * self.width + "\r")
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def step(self, n=1):
|
||||||
|
self.done += n
|
||||||
|
self._draw()
|
||||||
|
|
||||||
|
def _draw(self):
|
||||||
|
if not self.live:
|
||||||
|
return
|
||||||
|
bar = 24 * self.done // self.total
|
||||||
|
line = (f"{self.label:<16} [{'#' * bar}{'-' * (24 - bar)}] "
|
||||||
|
f"{100 * self.done // self.total:3d}% {self.done}/{self.total} {self.unit}")
|
||||||
|
self.width = max(self.width, len(line))
|
||||||
|
sys.stderr.write("\r" + line)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- serial ---
|
# ---------------------------------------------------------------- serial ---
|
||||||
|
|
||||||
|
|
||||||
@@ -295,6 +341,23 @@ class Info:
|
|||||||
f"EEPROM {self.eeprom_size} B, {vector}"
|
f"EEPROM {self.eeprom_size} B, {vector}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def lines(self):
|
||||||
|
"""The info block as one fact per line — what --info prints."""
|
||||||
|
if self.patch_vector:
|
||||||
|
hand_over = f"host-patched reset vector, trampoline at {self.base - 2:#06x}"
|
||||||
|
else:
|
||||||
|
hand_over = "hardware boot section, jump to word 0"
|
||||||
|
return (
|
||||||
|
f"signature {' '.join(f'{b:02x}' for b in self.signature)}",
|
||||||
|
f"flash {self.flash_size} B, {self.page} B pages"
|
||||||
|
+ (", word-addressed wire" if self.word_flash else ""),
|
||||||
|
f"application 0x0000..{self.base - 1:#06x} ({self.base} B)",
|
||||||
|
f"loader {self.base:#06x} ({self.slot} B slot)",
|
||||||
|
f"staging {self.stage:#06x}",
|
||||||
|
f"EEPROM {self.eeprom_size} B",
|
||||||
|
f"hand-over {hand_over}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Loader:
|
class Loader:
|
||||||
"""A pureboot session. Between commands the loader has prompted `+` and
|
"""A pureboot session. Between commands the loader has prompted `+` and
|
||||||
@@ -312,8 +375,10 @@ class Loader:
|
|||||||
absorbs whatever they produced."""
|
absorbs whatever they produced."""
|
||||||
self.port.flush_input()
|
self.port.flush_input()
|
||||||
deadline = time.monotonic() + wait
|
deadline = time.monotonic() + wait
|
||||||
|
knocks = 0
|
||||||
while True:
|
while True:
|
||||||
self.port.write(b"pb")
|
self.port.write(b"pb")
|
||||||
|
knocks += 1
|
||||||
if PROMPT in self.port.read_available(0.4):
|
if PROMPT in self.port.read_available(0.4):
|
||||||
break
|
break
|
||||||
if time.monotonic() > deadline:
|
if time.monotonic() > deadline:
|
||||||
@@ -323,6 +388,7 @@ class Loader:
|
|||||||
self.port.write(b"b")
|
self.port.write(b"b")
|
||||||
self.info = Info(self.port.read_exact(12, 2.0))
|
self.info = Info(self.port.read_exact(12, 2.0))
|
||||||
self._expect_prompt()
|
self._expect_prompt()
|
||||||
|
verbose(f"loader answered knock {knocks}; info block read")
|
||||||
return self.info
|
return self.info
|
||||||
|
|
||||||
def _expect_prompt(self, timeout=2.0):
|
def _expect_prompt(self, timeout=2.0):
|
||||||
@@ -373,7 +439,7 @@ class Loader:
|
|||||||
head = bytes((ord("W"), wire & 0xFF, wire >> 8))
|
head = bytes((ord("W"), wire & 0xFF, wire >> 8))
|
||||||
self._command(head + data, 0, 2.0)
|
self._command(head + data, 0, 2.0)
|
||||||
|
|
||||||
def write_eeprom(self, address, data):
|
def write_eeprom(self, address, data, progress=None):
|
||||||
offset = 0
|
offset = 0
|
||||||
while offset < len(data):
|
while offset < len(data):
|
||||||
chunk = data[offset : offset + 256]
|
chunk = data[offset : offset + 256]
|
||||||
@@ -382,6 +448,8 @@ class Loader:
|
|||||||
for byte in chunk:
|
for byte in chunk:
|
||||||
self.port.write(bytes((byte,)))
|
self.port.write(bytes((byte,)))
|
||||||
self._expect_prompt() # per-byte ack: the write has begun
|
self._expect_prompt() # per-byte ack: the write has begun
|
||||||
|
if progress:
|
||||||
|
progress.step()
|
||||||
self._expect_prompt() # the next command prompt
|
self._expect_prompt() # the next command prompt
|
||||||
address += len(chunk)
|
address += len(chunk)
|
||||||
offset += len(chunk)
|
offset += len(chunk)
|
||||||
@@ -487,6 +555,8 @@ def plan_flash(image, info):
|
|||||||
final += bytearray([0xFF] * (trampoline_page + page - len(final)))
|
final += bytearray([0xFF] * (trampoline_page + page - len(final)))
|
||||||
jump = rjmp_to(trampoline_word, entry, flash_words)
|
jump = rjmp_to(trampoline_word, entry, flash_words)
|
||||||
final[info.base - 2], final[info.base - 1] = jump & 0xFF, jump >> 8
|
final[info.base - 2], final[info.base - 1] = jump & 0xFF, jump >> 8
|
||||||
|
verbose(f"vector surgery: word 0 -> loader {info.base:#06x}, "
|
||||||
|
f"trampoline {info.base - 2:#06x} -> entry word {entry:#06x}")
|
||||||
|
|
||||||
pages = {a: bytes(final[a : a + page]) for a in range(0, len(final), page)}
|
pages = {a: bytes(final[a : a + page]) for a in range(0, len(final), page)}
|
||||||
return pages
|
return pages
|
||||||
@@ -681,17 +751,22 @@ class UpdateState:
|
|||||||
os.unlink(self.path)
|
os.unlink(self.path)
|
||||||
|
|
||||||
|
|
||||||
def write_differing(loader, base, content, order=None):
|
def write_differing(loader, base, content, order=None, label=None):
|
||||||
"""Program the pages of `content` at `base` that differ from flash —
|
"""Program the pages of `content` at `base` that differ from flash —
|
||||||
idempotent, so a resumed phase redoes only what an interruption left."""
|
idempotent, so a resumed phase redoes only what an interruption left.
|
||||||
|
A label puts the compare-and-program loop on the progress bar."""
|
||||||
page = loader.info.page
|
page = loader.info.page
|
||||||
offsets = order if order is not None else range(0, len(content), page)
|
offsets = list(order) if order is not None else list(range(0, len(content), page))
|
||||||
written = 0
|
written = 0
|
||||||
for offset in offsets:
|
with Progress(label, len(offsets)) as bar:
|
||||||
want = content[offset : offset + page]
|
for offset in offsets:
|
||||||
if loader.read_flash(base + offset, page) != want:
|
want = content[offset : offset + page]
|
||||||
loader.write_page(base + offset, want)
|
if loader.read_flash(base + offset, page) != want:
|
||||||
written += 1
|
loader.write_page(base + offset, want)
|
||||||
|
written += 1
|
||||||
|
bar.step()
|
||||||
|
if label:
|
||||||
|
verbose(f"{label}: {written} of {len(offsets)} pages differed")
|
||||||
for at in range(0, len(content), 256):
|
for at in range(0, len(content), 256):
|
||||||
if loader.read_flash(base + at, min(256, len(content) - at)) != content[at : at + 256]:
|
if loader.read_flash(base + at, min(256, len(content) - at)) != content[at : at + 256]:
|
||||||
raise Error(f"verify failed at {base + at:#06x} after programming")
|
raise Error(f"verify failed at {base + at:#06x} after programming")
|
||||||
@@ -723,6 +798,10 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
|
|||||||
page = info.page
|
page = info.page
|
||||||
|
|
||||||
state = UpdateState(state_path)
|
state = UpdateState(state_path)
|
||||||
|
if os.path.exists(state_path):
|
||||||
|
verbose(f"resuming the update recorded in {state_path}")
|
||||||
|
else:
|
||||||
|
verbose(f"saving the staging slot to {state_path}")
|
||||||
state.load_or_save(loader)
|
state.load_or_save(loader)
|
||||||
|
|
||||||
# Install the staging copy — unless a loader already sits whole in the
|
# Install the staging copy — unless a loader already sits whole in the
|
||||||
@@ -741,7 +820,7 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
|
|||||||
current = loader.read_flash(info.stage, info.slot)
|
current = loader.read_flash(info.stage, info.slot)
|
||||||
staged_loader = image_info(current[:268])
|
staged_loader = image_info(current[:268])
|
||||||
if staged_loader is not None and staged_loader.raw == info.raw and current == state.staging:
|
if staged_loader is not None and staged_loader.raw == info.raw and current == state.staging:
|
||||||
print(f"staging slot already holds a loader — left in place")
|
print("staging slot already holds a loader — left in place")
|
||||||
else:
|
else:
|
||||||
# On a chip whose staging slot starts at address 0 (the 1 KB
|
# On a chip whose staging slot starts at address 0 (the 1 KB
|
||||||
# tiny13s), its first page carries the reset vector: written last,
|
# tiny13s), its first page carries the reset vector: written last,
|
||||||
@@ -750,30 +829,34 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
|
|||||||
order = list(range(0, info.slot, page))
|
order = list(range(0, info.slot, page))
|
||||||
if info.stage == 0:
|
if info.stage == 0:
|
||||||
order = order[1:] + [0]
|
order = order[1:] + [0]
|
||||||
if write_differing(loader, info.stage, staged, order):
|
if write_differing(loader, info.stage, staged, order, label="staging copy"):
|
||||||
print(f"staging copy installed at {info.stage:#06x}")
|
print(f"staging copy installed at {info.stage:#06x}")
|
||||||
|
|
||||||
# Enter it and let it rewrite the resident slot. Where a patched reset
|
# Enter it and let it rewrite the resident slot. Where a patched reset
|
||||||
# vector routes through the resident (a tiny with the staging slot away
|
# vector routes through the resident (a tiny with the staging slot away
|
||||||
# from page 0), word 0 is re-aimed at the staging copy around the
|
# from page 0), word 0 is re-aimed at the staging copy around the
|
||||||
# rewrite, so a power failure mid-rewrite still resets into a loader.
|
# rewrite, so a power failure mid-rewrite still resets into a loader.
|
||||||
|
verbose(f"entering the staging copy at {info.stage:#06x}")
|
||||||
loader.enter_copy(info.stage, wait)
|
loader.enter_copy(info.stage, wait)
|
||||||
redirect = info.patch_vector and info.stage != 0
|
redirect = info.patch_vector and info.stage != 0
|
||||||
if redirect:
|
if redirect:
|
||||||
|
verbose("word 0 re-aimed at the staging copy for the rewrite")
|
||||||
patch_word0(loader, state.page0, info.stage)
|
patch_word0(loader, state.page0, info.stage)
|
||||||
if write_differing(loader, info.base, resident):
|
if write_differing(loader, info.base, resident, label="resident"):
|
||||||
print(f"resident loader rewritten at {info.base:#06x}")
|
print(f"resident loader rewritten at {info.base:#06x}")
|
||||||
|
|
||||||
# Enter the new resident and put the staging region back: page 0 first
|
# Enter the new resident and put the staging region back: page 0 first
|
||||||
# where it lives in that region (word 0 then points at the new resident
|
# where it lives in that region (word 0 then points at the new resident
|
||||||
# for the rest of the restore), the saved trampoline with the rest.
|
# for the rest of the restore), the saved trampoline with the rest.
|
||||||
|
verbose(f"entering the new resident at {info.base:#06x}")
|
||||||
loader.enter_copy(info.base, wait)
|
loader.enter_copy(info.base, wait)
|
||||||
if redirect:
|
if redirect:
|
||||||
|
verbose("word 0 restored")
|
||||||
write_differing(loader, 0, state.page0)
|
write_differing(loader, 0, state.page0)
|
||||||
order = list(range(0, info.slot, page))
|
order = list(range(0, info.slot, page))
|
||||||
if info.stage == 0:
|
if info.stage == 0:
|
||||||
order = [0] + order[1:]
|
order = [0] + order[1:]
|
||||||
write_differing(loader, info.stage, state.staging, order)
|
write_differing(loader, info.stage, state.staging, order, label="staging restore")
|
||||||
|
|
||||||
state.discard()
|
state.discard()
|
||||||
print(f"loader updated: {len(image)} B at {info.base:#06x}, staging region restored")
|
print(f"loader updated: {len(image)} B at {info.base:#06x}, staging region restored")
|
||||||
@@ -808,39 +891,49 @@ def op_erase_flash(loader):
|
|||||||
is erased and the reset walk reaches the loader anyway."""
|
is erased and the reset walk reaches the loader anyway."""
|
||||||
blank = bytes([0xFF] * loader.info.page)
|
blank = bytes([0xFF] * loader.info.page)
|
||||||
addresses = range(0, loader.info.base, loader.info.page)
|
addresses = range(0, loader.info.base, loader.info.page)
|
||||||
for address in reversed(addresses) if loader.info.patch_vector else addresses:
|
with Progress("erase", len(addresses)) as bar:
|
||||||
loader.write_page(address, blank)
|
for address in reversed(addresses) if loader.info.patch_vector else addresses:
|
||||||
|
loader.write_page(address, blank)
|
||||||
|
bar.step()
|
||||||
print(f"erase: {loader.info.base // loader.info.page} pages")
|
print(f"erase: {loader.info.base // loader.info.page} pages")
|
||||||
|
|
||||||
|
|
||||||
def op_erase_eeprom(loader):
|
def op_erase_eeprom(loader):
|
||||||
loader.write_eeprom(0, bytes([0xFF] * loader.info.eeprom_size))
|
with Progress("erase EEPROM", loader.info.eeprom_size, "B") as bar:
|
||||||
|
loader.write_eeprom(0, bytes([0xFF] * loader.info.eeprom_size), progress=bar)
|
||||||
print(f"erase: {loader.info.eeprom_size} B of EEPROM")
|
print(f"erase: {loader.info.eeprom_size} B of EEPROM")
|
||||||
|
|
||||||
|
|
||||||
def op_flash(loader, path, erase, verify, fuse_bytes=None, force=False):
|
def op_flash(loader, path, erase, verify, fuse_bytes=None, force=False):
|
||||||
image = load_image(path)
|
image = load_image(path)
|
||||||
|
verbose(f"{path}: {len(image)} B image")
|
||||||
pages = plan_flash(image, loader.info)
|
pages = plan_flash(image, loader.info)
|
||||||
check_walk_region(pages, loader.info, fuse_bytes, force)
|
check_walk_region(pages, loader.info, fuse_bytes, force)
|
||||||
if erase:
|
if erase:
|
||||||
op_erase_flash(loader)
|
op_erase_flash(loader)
|
||||||
order = covered(pages, loader.info, skip_blank=erase)
|
order = covered(pages, loader.info, skip_blank=erase)
|
||||||
for address in order:
|
if len(order) != len(pages):
|
||||||
loader.write_page(address, pages[address])
|
verbose(f"{len(pages) - len(order)} blank pages skipped (erased flash underneath)")
|
||||||
|
with Progress("flash", len(order)) as bar:
|
||||||
|
for address in order:
|
||||||
|
loader.write_page(address, pages[address])
|
||||||
|
bar.step()
|
||||||
print(f"flash: {path}: {len(order)} pages")
|
print(f"flash: {path}: {len(order)} pages")
|
||||||
if verify:
|
if verify:
|
||||||
verify_pages(loader, pages)
|
verify_pages(loader, pages)
|
||||||
|
|
||||||
|
|
||||||
def verify_pages(loader, pages):
|
def verify_pages(loader, pages):
|
||||||
for address in sorted(pages):
|
with Progress("verify", len(pages)) as bar:
|
||||||
got = loader.read_flash(address, loader.info.page)
|
for address in sorted(pages):
|
||||||
if got != pages[address]:
|
got = loader.read_flash(address, loader.info.page)
|
||||||
first = next(i for i in range(len(got)) if got[i] != pages[address][i])
|
if got != pages[address]:
|
||||||
raise Error(
|
first = next(i for i in range(len(got)) if got[i] != pages[address][i])
|
||||||
f"verify failed at {address + first:#06x}: "
|
raise Error(
|
||||||
f"wrote {pages[address][first]:02x}, read {got[first]:02x}"
|
f"verify failed at {address + first:#06x}: "
|
||||||
)
|
f"wrote {pages[address][first]:02x}, read {got[first]:02x}"
|
||||||
|
)
|
||||||
|
bar.step()
|
||||||
print(f"verify: {len(pages)} pages ok")
|
print(f"verify: {len(pages)} pages ok")
|
||||||
|
|
||||||
|
|
||||||
@@ -848,8 +941,19 @@ def op_verify_flash(loader, path):
|
|||||||
verify_pages(loader, plan_flash(load_image(path), loader.info))
|
verify_pages(loader, plan_flash(load_image(path), loader.info))
|
||||||
|
|
||||||
|
|
||||||
|
def read_progress(reader, total, label):
|
||||||
|
"""A bulk read in 256-byte wire chunks under a progress bar."""
|
||||||
|
data = b""
|
||||||
|
with Progress(label, total, "B") as bar:
|
||||||
|
while len(data) < total:
|
||||||
|
chunk = min(256, total - len(data))
|
||||||
|
data += reader(len(data), chunk)
|
||||||
|
bar.step(chunk)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def op_read_flash(loader, path):
|
def op_read_flash(loader, path):
|
||||||
data = loader.read_flash(0, loader.info.base)
|
data = read_progress(loader.read_flash, loader.info.base, "read flash")
|
||||||
open(path, "wb").write(data)
|
open(path, "wb").write(data)
|
||||||
print(f"read flash: {len(data)} B -> {path}")
|
print(f"read flash: {len(data)} B -> {path}")
|
||||||
|
|
||||||
@@ -860,10 +964,11 @@ def op_eeprom(loader, path, erase, verify):
|
|||||||
raise Error(f"EEPROM image is {len(image)} B, device has {loader.info.eeprom_size}")
|
raise Error(f"EEPROM image is {len(image)} B, device has {loader.info.eeprom_size}")
|
||||||
if erase:
|
if erase:
|
||||||
op_erase_eeprom(loader)
|
op_erase_eeprom(loader)
|
||||||
loader.write_eeprom(0, image)
|
with Progress("eeprom", len(image), "B") as bar:
|
||||||
|
loader.write_eeprom(0, image, progress=bar)
|
||||||
print(f"eeprom: {path}: {len(image)} B")
|
print(f"eeprom: {path}: {len(image)} B")
|
||||||
if verify:
|
if verify:
|
||||||
got = loader.read_eeprom(0, len(image))
|
got = read_progress(loader.read_eeprom, len(image), "verify EEPROM")
|
||||||
if got != image:
|
if got != image:
|
||||||
first = next(i for i in range(len(got)) if got[i] != image[i])
|
first = next(i for i in range(len(got)) if got[i] != image[i])
|
||||||
raise Error(f"verify failed at EEPROM {first:#06x}: wrote {image[first]:02x}, read {got[first]:02x}")
|
raise Error(f"verify failed at EEPROM {first:#06x}: wrote {image[first]:02x}, read {got[first]:02x}")
|
||||||
@@ -872,7 +977,7 @@ def op_eeprom(loader, path, erase, verify):
|
|||||||
|
|
||||||
def op_verify_eeprom(loader, path):
|
def op_verify_eeprom(loader, path):
|
||||||
image = load_image(path)
|
image = load_image(path)
|
||||||
got = loader.read_eeprom(0, len(image))
|
got = read_progress(loader.read_eeprom, len(image), "verify EEPROM")
|
||||||
if got != image:
|
if got != image:
|
||||||
first = next(i for i in range(len(got)) if got[i] != image[i])
|
first = next(i for i in range(len(got)) if got[i] != image[i])
|
||||||
raise Error(f"verify failed at EEPROM {first:#06x}: expected {image[first]:02x}, read {got[first]:02x}")
|
raise Error(f"verify failed at EEPROM {first:#06x}: expected {image[first]:02x}, read {got[first]:02x}")
|
||||||
@@ -880,15 +985,30 @@ def op_verify_eeprom(loader, path):
|
|||||||
|
|
||||||
|
|
||||||
def op_read_eeprom(loader, path):
|
def op_read_eeprom(loader, path):
|
||||||
data = loader.read_eeprom(0, loader.info.eeprom_size)
|
data = read_progress(loader.read_eeprom, loader.info.eeprom_size, "read EEPROM")
|
||||||
open(path, "wb").write(data)
|
open(path, "wb").write(data)
|
||||||
print(f"read EEPROM: {len(data)} B -> {path}")
|
print(f"read EEPROM: {len(data)} B -> {path}")
|
||||||
|
|
||||||
|
|
||||||
def op_fuses(loader):
|
def op_fuses(loader):
|
||||||
low, lock, extended, high = loader.read_fuses()
|
low, lock, extended, high = loader.read_fuses()
|
||||||
print(f"fuses: low {low:02x} high {high:02x} extended {extended:02x} lock {lock:02x}")
|
print("fuses:")
|
||||||
return bytes((low, lock, extended, high))
|
print(f" low 0x{low:02x}")
|
||||||
|
print(f" high 0x{high:02x}")
|
||||||
|
print(f" extended 0x{extended:02x}")
|
||||||
|
print(f" lock 0x{lock:02x}")
|
||||||
|
fuse_bytes = bytes((low, lock, extended, high))
|
||||||
|
# On a boot-sectioned mega the BOOTSZ/BOOTRST decode is the fuse fact the
|
||||||
|
# loader's whole deployment hangs on — say it in words.
|
||||||
|
if not loader.info.patch_vector:
|
||||||
|
try:
|
||||||
|
bootrst, bls_start = mega_boot(loader.info, fuse_bytes)
|
||||||
|
reset = "reset enters it" if bootrst else "reset boots the application"
|
||||||
|
print(f" boot section at {bls_start:#06x} ({loader.info.flash_size - bls_start} B), "
|
||||||
|
f"BOOTRST {'programmed' if bootrst else 'unprogrammed'} — {reset}")
|
||||||
|
except Error:
|
||||||
|
pass # unknown signature: the raw bytes above still stand
|
||||||
|
return fuse_bytes
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------- cli ---
|
# -------------------------------------------------------------------- cli ---
|
||||||
@@ -918,7 +1038,11 @@ def main():
|
|||||||
parser.add_argument("--verify-eeprom", metavar="FILE", help="compare EEPROM against an image")
|
parser.add_argument("--verify-eeprom", metavar="FILE", help="compare EEPROM against an image")
|
||||||
parser.add_argument("--force", action="store_true", help="override refusable safety checks")
|
parser.add_argument("--force", action="store_true", help="override refusable safety checks")
|
||||||
parser.add_argument("--stay", action="store_true", help="leave the loader in its session")
|
parser.add_argument("--stay", action="store_true", help="leave the loader in its session")
|
||||||
|
parser.add_argument("-v", "--verbose", action="store_true",
|
||||||
|
help="print decisions and derived facts as operations run")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
global VERBOSE
|
||||||
|
VERBOSE = args.verbose
|
||||||
|
|
||||||
if args.update_loader and (args.flash or args.erase_flash):
|
if args.update_loader and (args.flash or args.erase_flash):
|
||||||
parser.error("--update-loader does not combine with application flash operations")
|
parser.error("--update-loader does not combine with application flash operations")
|
||||||
@@ -931,11 +1055,14 @@ def main():
|
|||||||
parser.error("--assume-fuses takes 8 hex digits: low,lock,extended,high")
|
parser.error("--assume-fuses takes 8 hex digits: low,lock,extended,high")
|
||||||
|
|
||||||
port = Port(args.port, args.baud)
|
port = Port(args.port, args.baud)
|
||||||
|
verbose(f"{args.port}: {args.baud} Bd 8N1, DTR/RTS asserted")
|
||||||
try:
|
try:
|
||||||
loader = Loader(port)
|
loader = Loader(port)
|
||||||
info = loader.connect(args.wait)
|
info = loader.connect(args.wait)
|
||||||
if args.info:
|
if args.info:
|
||||||
print(f"device: {info.describe()}")
|
print("device:")
|
||||||
|
for line in info.lines():
|
||||||
|
print(f" {line}")
|
||||||
fuse_bytes = fuse_override
|
fuse_bytes = fuse_override
|
||||||
if args.fuses or (args.update_loader and not info.patch_vector and fuse_bytes is None):
|
if args.fuses or (args.update_loader and not info.patch_vector and fuse_bytes is None):
|
||||||
read = op_fuses(loader)
|
read = op_fuses(loader)
|
||||||
|
|||||||
Reference in New Issue
Block a user