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