1#!/usr/bin/env python3
2#
3# Copyright (c) 2023 Antmicro
4# Copyright (c) 2024 Silicon Laboratories Inc.
5#
6# SPDX-License-Identifier: Apache-2.0
7
8import argparse
9import struct
10import sys
11
12import intelhex
13
14
15# For reference:
16#     width=32 poly=0xd95eaae5 init=0 refin=true refout=true xorout=0
17def create_table() -> list[int]:
18    crc_table = [0] * 256
19    for b in range(256):
20        register = b
21        for _ in range(8):
22            lsb = register & 1
23            register >>= 1
24            if lsb:
25                # Reflected polynomial: 0xd95eaae5
26                register ^= 0xA7557A9B
27        crc_table[b] = register
28    return crc_table
29
30
31def calc_crc32(data: bytes) -> int:
32    crc_table = create_table()
33    register = 0
34    for b in data:
35        register = crc_table[(b ^ register) & 0xFF] ^ (register >> 8)
36    return register
37
38
39def calc_checksum(data: bytes | bytearray, size: int, prev_sum: int) -> int:
40    # Truncate
41    data = data[:size]
42    # Zero-pad data to mul of 4 bytes
43    nzeros = ((len(data) + 3) // 4 * 4) - len(data)
44    data += b"\0" * nzeros
45    # Reinterpret data as LE u32
46    ints = list(x[0] for x in struct.iter_unpack("<I", data))
47    # Sum
48    chk = prev_sum + sum(ints)
49    # Convert to u32 and account each overflow as 1"s complement addition
50    chk = (chk & 0xFFFFFFFF) + (chk >> 32)
51    chk = (~chk) & 0xFFFFFFFF
52    return chk
53
54
55def set_bits(x: int, off: int, size: int, field: int) -> int:
56    field = int(field)
57    mask = ((1 << size) - 1) << off
58    x &= ~mask
59    x |= (field << off) & mask
60    return x
61
62
63def get_bootload_entry(
64    ctrl_len: int = 0,
65    ctrl_reserved: int = 0,
66    ctrl_spi_32bitmode: bool = False,
67    ctrl_release_ta_softreset: bool = False,
68    ctrl_start_from_rom_pc: bool = False,
69    ctrl_spi_8bitmode: bool = False,
70    ctrl_last_entry: bool = True,
71    dest_addr: int = 0,
72) -> bytes:
73    # Format bootload_entry struct
74    ctrl = 0
75    ctrl = set_bits(ctrl, 0, 24, ctrl_len)
76    ctrl = set_bits(ctrl, 24, 3, ctrl_reserved)
77    ctrl = set_bits(ctrl, 27, 1, ctrl_spi_32bitmode)
78    ctrl = set_bits(ctrl, 28, 1, ctrl_release_ta_softreset)
79    ctrl = set_bits(ctrl, 29, 1, ctrl_start_from_rom_pc)
80    ctrl = set_bits(ctrl, 30, 1, ctrl_spi_8bitmode)
81    ctrl = set_bits(ctrl, 31, 1, ctrl_last_entry)
82    return struct.pack("<II", ctrl, dest_addr)
83
84
85def get_bootload_ds(offset: int, ivt_offset: int, fixed_pattern: int = 0x5AA5) -> bytes:
86    ret = b""
87    ret += int(fixed_pattern).to_bytes(2, "little")
88    ret += int(offset).to_bytes(2, "little")
89    ret += int(ivt_offset).to_bytes(4, "little")
90    for i in range(7):
91        ret += get_bootload_entry(ctrl_last_entry=i == 0)
92    return ret
93
94
95def get_fwupreq(flash_location: int, image_size: int) -> bytes:
96    # Field values
97    cflags = 1
98    sha_type = 0
99    magic_no = 0x900D900D
100    fw_version = 0
101    # Initially CRC value is set to 0, then the CRC is calculated on the
102    # whole image (including fwupreq header), and injected here
103    crc = 0
104    mic = [0, 0, 0, 0]
105    counter = 0
106    rsvd = [0, 0, 0, 0, magic_no]
107    # Format
108    ret = b""
109    ret += cflags.to_bytes(2, "little")
110    ret += sha_type.to_bytes(2, "little")
111    ret += magic_no.to_bytes(4, "little")
112    ret += image_size.to_bytes(4, "little")
113    ret += fw_version.to_bytes(4, "little")
114    ret += flash_location.to_bytes(4, "little")
115    ret += crc.to_bytes(4, "little")
116    for x in mic:
117        ret += x.to_bytes(4, "little")
118    ret += counter.to_bytes(4, "little")
119    for x in rsvd:
120        ret += x.to_bytes(4, "little")
121    return ret
122
123
124def main():
125    parser = argparse.ArgumentParser(
126        description="Converts raw binary output from Zephyr into an ISP binary for Silabs SiWx91x",
127        allow_abbrev=False,
128    )
129    parser.add_argument(
130        "ifile",
131        metavar="INPUT.BIN",
132        help="Raw binary file to read",
133        type=argparse.FileType("rb"),
134    )
135    parser.add_argument(
136        "ofile",
137        metavar="OUTPUT.BIN",
138        help="ISP binary file to write",
139        type=argparse.FileType("wb"),
140    )
141    parser.add_argument(
142        "--load-addr",
143        metavar="ADDRESS",
144        help="Address at which the raw binary image begins in the memory",
145        type=lambda x: int(x, 0),
146        required=True,
147    )
148    parser.add_argument(
149        "--out-hex",
150        metavar="FILE.HEX",
151        help="Generate Intel HEX output in addition to binary one",
152        type=argparse.FileType("w", encoding="ascii"),
153    )
154    args = parser.parse_args()
155
156    img = bytearray(args.ifile.read())
157
158    # Calculate and inject checksum
159    chk = calc_checksum(img, 236, 1)
160    print(f"ROM checksum: 0x{chk:08x}", file=sys.stderr)
161    img[236:240] = chk.to_bytes(4, "little")
162
163    # Get bootloader header, pad to 4032 and glue it to the image payload
164    bl = bytearray(get_bootload_ds(4032, args.load_addr))
165    padding = bytearray(4032 - len(bl))
166    img = bl + padding + img
167
168    # Get fwupreq header and glue it to the bootloader payload
169    fwupreq = bytearray(get_fwupreq(args.load_addr - 0x8001000, len(img)))
170    img = fwupreq + img
171
172    # Calculate and inject CRC
173    crc = calc_crc32(img)
174    print(f"Image CRC: 0x{crc:08x}", file=sys.stderr)
175    img[20:24] = crc.to_bytes(4, "little")
176
177    args.ofile.write(img)
178
179    # If you want to compare this file with the .hex file generated by Zephyr,
180    # You have to reformat the Zephyr output:
181    #   import intelhex
182    #   hx = intelhex.IntelHex()
183    #   hx.fromfile("zephyr.hex", "hex")
184    #   hx.write_hex_file("zephyr.out.hex", byte_count=32)
185    if args.out_hex:
186        hx = intelhex.IntelHex()
187        # len(bl) + len(padding) + len(fwupreq) == 4096
188        hx.frombytes(img, args.load_addr - 4096)
189        hx.write_hex_file(args.out_hex, byte_count=32)
190
191
192if __name__ == "__main__":
193    main()
194