4"""Wrap a raw application binary into a dfu_bootloader slot image.
6Produces the body at the slot base plus the 32-byte image header in the slot's
7LAST page (slot_base + 0x6FFE0), emitted as an Intel HEX that J-Link `loadfile`
8can flash. That stages a slot WITHOUT a USB host -- the bench path for
9validating the bootloader's copy-to-run behaviour.
11This is NOT the operator path. Operators use dfu-util, and the bootloader
12writes the header itself on DFU commit, so that path needs only the body
13binary and never this tool.
15Copy-to-run is why one image works in either slot: the bootloader copies the
16slot body to the fixed SRAM run base (RUN_BASE) and launches it there, so the
17IDENTICAL app.bin can be staged to slot a or slot b. The consequence is a hard
18requirement on the input -- the application MUST be linked at the SRAM run base
19(ORIGIN = RUN_BASE), NOT at a slot base. An image linked at a slot base stages
20and flashes cleanly and then faults on launch. See this app's README, "One
21image, either slot (copy-to-run)".
23The header layout and CRC must stay in lock-step with libs/ra8_dfu
26 magic(u32 = 0x52413844) seq(u32) img_len(u32, multiple of 32)
27 img_crc32(u32, CRC32 over [slot_base, slot_base + img_len))
28 entry(u32 = RUN_BASE) reserved(12 bytes, zero)
30CRC32 is the standard reflected zlib polynomial.
33 python3 examples/ek_ra8d2/hw_validated/hil/dfu_bootloader/scripts/stage_slot_image.py \
34 --payload app.bin --slot a [--seq 1] --out slotA.hex
41from pathlib
import Path
44SLOT_BASE = {
"a": 0x02020000,
"b": 0x02090000}
46HDR_OFFSET = 0x0006FFE0
52def ihex_records(data: bytes, base: int) -> list:
53 """Emit Intel-HEX records (ELA + data) placing data at absolute base."""
55 hi = (base >> 16) & 0xFFFF
56 rec = bytes([2, 0, 0, 4, (hi >> 8) & 0xFF, hi & 0xFF])
57 out.append(
":" + rec.hex().upper() + f
"{((-sum(rec)) & 0xFF):02X}")
59 for i
in range(0, len(data), 16):
60 chunk = data[i : i + 16]
62 rec = bytes([len(chunk), (addr >> 8) & 0xFF, addr & 0xFF, 0]) + chunk
63 out.append(
":" + rec.hex().upper() + f
"{((-sum(rec)) & 0xFF):02X}")
67def _build_parser() -> argparse.ArgumentParser:
68 """Build the command-line parser for the slot stager."""
69 ap = argparse.ArgumentParser(description=
"Stage a dfu_bootloader slot image.")
71 "--payload", required=
True, help=
"raw app binary, linked at the SRAM run base 0x22020000"
73 ap.add_argument(
"--slot", required=
True, choices=(
"a",
"b"), help=
"target slot")
74 ap.add_argument(
"--seq", type=int, default=1, help=
"monotonic sequence number")
78 help=
"payload is a rot_sign.py-signed [body][ra8_rot_trailer_t] "
79 "image; the header img_len + CRC cover the body only and "
80 "the trailer is staged contiguously for the RoT launch gate",
86 help=
"ra8_rot_trailer_t size in bytes (only with --signed)",
88 ap.add_argument(
"--out", required=
True, help=
"output Intel HEX path")
92def _build_slot_body(args: argparse.Namespace) -> tuple[int, int, bytes] |
None:
93 """Return ``(img_len, crc, slot_bytes)``, or None after reporting a fault.
95 The two layouts differ in what the header covers. A plain payload is
96 FF-padded to a page multiple and the CRC spans all of it. A signed slot is
97 ``[body][ra8_rot_trailer_t]``: the header's img_len and CRC cover the BODY
98 only, and the trailer is staged contiguously right after it so the RoT
99 launch gate finds it at ``slot_base + img_len`` (see ra8_rot_trailer_after).
100 The bootloader copies only img_len body bytes to the run base.
102 raw = bytearray(Path(args.payload).read_bytes())
104 if len(raw) % PAGE != 0:
105 raw += b
"\xff" * (PAGE - (len(raw) % PAGE))
106 return len(raw), zlib.crc32(bytes(raw)) & 0xFFFFFFFF, bytes(raw)
108 if len(raw) <= args.trailer_size:
109 sys.stderr.write(f
"error: signed image {len(raw)}B <= trailer {args.trailer_size}B\n")
111 img_len = len(raw) - args.trailer_size
112 if img_len % PAGE != 0:
114 f
"error: signed body {img_len}B is not a multiple of {PAGE} "
115 f
"-- sign a page-aligned body\n"
118 return img_len, zlib.crc32(bytes(raw[:img_len])) & 0xFFFFFFFF, bytes(raw)
122 """Build the slot image from the command line and write the Intel HEX.
124 Pads the payload to a PAGE multiple, computes the CRC over the padded body,
125 and places the header in the slot's last page. Body and header land at
126 absolute addresses derived from `--slot`, so the output HEX is
127 slot-specific even though the payload is not.
129 `--seq` selects which slot the bootloader prefers at boot: the higher
130 sequence number wins. Staging an image with a sequence at or below the other
131 slot's produces a valid image the bootloader will simply not choose --
132 which looks exactly like a flash that did not take.
135 0 on success, non-zero on a usage or validation failure.
137 args = _build_parser().parse_args()
139 base = SLOT_BASE[args.slot]
140 built = _build_slot_body(args)
143 img_len, crc, slot = built
145 if len(slot) > HDR_OFFSET:
146 sys.stderr.write(f
"error: slot content {len(slot)} bytes exceeds capacity {HDR_OFFSET}\n")
151 hdr = struct.pack(
"<5I", HDR_MAGIC, args.seq, img_len, crc, RUN_BASE) + b
"\x00" * 12
153 records = ihex_records(slot, base)
154 records += ihex_records(hdr, base + HDR_OFFSET)
155 records.append(
":00000001FF")
156 with Path(args.out).open(
"w", encoding=
"ascii")
as f:
157 f.write(
"\n".join(records) +
"\n")
159 kind =
"signed" if args.signed
else "plain"
161 f
"slot {args.slot.upper()} @0x{base:08X} ({kind}): img_len={img_len}B "
162 f
"slot={len(slot)}B crc=0x{crc:08X} hdr@0x{base + HDR_OFFSET:08X} "
163 f
"seq={args.seq} -> {args.out}"
168if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.