1#!/usr/bin/env python3 2 3# Copyright (c) 2022 Microchip Technology Inc. 4# SPDX-License-Identifier: Apache-2.0 5 6import sys 7import argparse 8import hashlib 9 10verbose_mode = False 11 12# Header parameters 13HDR_SIZE = 0x140 14HDR_VER_MEC172X = 0x03 15HDR_VER_MEC152X = 0x02 16HDR_SPI_CLK_12MHZ = 0x3 17HDR_SPI_CLK_16MHZ = 0x2 18HDR_SPI_CLK_24MHZ = 0x1 19HDR_SPI_CLK_48MHZ = 0 20HDR_SPI_DRV_STR_1X = 0 21HDR_SPI_DRV_STR_2X = 0x4 22HDR_SPI_DRV_STR_4X = 0x8 23HDR_SPI_DRV_STR_6X = 0xc 24HDR_SPI_SLEW_SLOW = 0 25HDR_SPI_SLEW_FAST = 0x10 26HDR_SPI_CPOL_LO = 0 27HDR_SPI_CPOL_HI = 0x20 28HDR_SPI_CHPHA_MOSI_EDGE_2 = 0 29HDR_SPI_CHPHA_MOSI_EDGE_1 = 0x40 30HDR_SPI_CHPHA_MISO_EDGE_1 = 0 31HDR_SPI_CHPHA_MISO_EDGE_2 = 0x80 32 33# User defined constants HDR_SPI_RD_ (0, 1, 2, 3) as per boot rom spec. 34# 1st digit - number of I/O pins used to transmit the opcode 35# 2nd digit - number of I/O pins used to transmit the SPI address 36# 3rd digit - number of pins used to read data from flash 37# 4th digit (if present) - dummy clocks between address and data phase 38HDR_SPI_RD_111 = 0 39HDR_SPI_RD_1118 = 1 40HDR_SPI_RD_1128 = 2 41HDR_SPI_RD_1148 = 3 42 43# Payload parameters 44PLD_LOAD_ADDR = 0xc0000 45PLD_LOAD_ADDR_MEC172X = 0xc0000 46PLD_LOAD_ADDR_MEC152X = 0xe0000 47PLD_ENTRY_ADDR = 0 48PLD_GRANULARITY = 128 49PLD_PAD_SIZE = 128 50PLD_PAD_BYTE = b'\xff' 51 52MCHP_CHAR_P = 0x50 53MCHP_CHAR_H = 0x48 54MCHP_CHAR_C = 0x43 55MCHP_CHAR_M = 0x4D 56 57EC_INFO_BLOCK_SIZE = 128 58ENCR_KEY_HDR_SIZE = 128 59COSIG_SIZE = 96 60TRAILER_SIZE = 160 61TRAILER_PAD_BYTE = b'\xff' 62 63TAG_SPI_LOC = 0 64HDR_SPI_LOC = 0x100 65PLD_SPI_LOC = 0x1000 66 67CRC_TABLE = [0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 68 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d] 69 70CHIP_DICT = { 71 'mec15xx': { 'sram_base': 0xe0000, 'sram_size': 0x40000, 'header_ver': 2 }, 72 'mec172x': { 'sram_base': 0xc0000, 'sram_size': 0x68000, 'header_ver': 3 }, 73} 74 75CHIP_DEFAULT = 'mec172x' 76SPI_READ_MODE_DEFAULT = 'fast' 77SPI_FREQ_MHZ_DEFAULT = 12 78SPI_MODE_DEFAULT = 0 79SPI_MODE_MIN = 0 80SPI_MODE_MAX = 7 81SPI_DRIVE_STRENGTH_MULT_DEFAULT = "1x" 82SPI_SLEW_RATE_DEFAULT = "slow" 83 84def print_bytes(title, b): 85 """Print bytes or bytearray as hex values""" 86 print("{0} = {{ ".format(title), end='') 87 count = 1 88 for v in b: 89 print("0x{0:02x}, ".format(v), end='') 90 if (count % 8) == 0: 91 print("") 92 count = count + 1 93 94 print("}") 95 96def crc8(crc, data): 97 """Update CRC8 value. 98 99 CRC8-ITU calculation 100 """ 101 for v in data: 102 crc = ((crc << 4) & 0xff) ^ (CRC_TABLE[(crc >> 4) ^ (v >> 4)]) 103 crc = ((crc << 4) & 0xff) ^ (CRC_TABLE[(crc >> 4) ^ (v & 0xf)]) 104 return crc ^ 0x55 105 106def build_tag(hdr_spi_loc): 107 """Build MEC172x Boot-ROM TAG 108 109 MEC172x Boot-ROM TAG is 4 bytes 110 bits[23:0] = bits[31:8] of the Header SPI address 111 Header location must be a mutliple of 256 bytes 112 bits[31:24] = CRC8-ITU of bits[23:0] 113 return immutable bytes type 114 """ 115 tag = bytearray([(hdr_spi_loc >> 8) & 0xff, 116 (hdr_spi_loc >> 16) & 0xff, 117 (hdr_spi_loc >> 24) & 0xff]) 118 tag.append(crc8(0, tag)) 119 120 return bytes(tag) 121 122def build_header(chip, spi_config, hdr_spi_loc, pld_spi_loc, pld_entry_addr, pld_len): 123 """Build MEC152x/MEC172x Boot-ROM SPI image header 124 125 Args: 126 chip: mec15xx or mec172x 127 spi_config: spi configuration 128 hdr_spi_loc: Header location in SPI Image 129 pld_spi_loc: Payload(FW binary) location in SPI Image 130 pld_entry_addr: Payload load address in MEC172x SPI SRAM 131 Payload entry point address: index 0 instructs Boot-ROM to assume 132 ARM vector table at beginning of payload and reset handler 133 address is at offset 4 of payload. 134 pld_len: Payload length, must be multiple of PLD_GRANULARITY 135 136 return: immutable bytes type for built header 137 """ 138 hdr = bytearray(HDR_SIZE) 139 140 hdr[0] = MCHP_CHAR_P 141 hdr[1] = MCHP_CHAR_H 142 hdr[2] = MCHP_CHAR_C 143 hdr[3] = MCHP_CHAR_M 144 145 hdr[4] = CHIP_DICT[chip]['header_ver'] & 0xff 146 147 if spi_config['spi_freq_mhz'] == 48: 148 hdr[5] = HDR_SPI_CLK_48MHZ 149 elif spi_config['spi_freq_mhz'] == 24: 150 hdr[5] = HDR_SPI_CLK_24MHZ 151 elif spi_config['spi_freq_mhz'] == 16: 152 hdr[5] = HDR_SPI_CLK_16MHZ 153 else: 154 hdr[5] = HDR_SPI_CLK_12MHZ 155 156 if spi_config['spi_mode'] & 0x01: 157 hdr[5] |= HDR_SPI_CPOL_HI 158 if spi_config['spi_mode'] & 0x02: 159 hdr[5] |= HDR_SPI_CHPHA_MOSI_EDGE_1 160 if spi_config['spi_mode'] & 0x04: 161 hdr[5] |= HDR_SPI_CHPHA_MISO_EDGE_2 162 163 # translate 1x, 2x, 4x, 6x to 0, 1, 2, 3 164 if spi_config['spi_drive_str'] == "6x": 165 hdr[5] |= HDR_SPI_DRV_STR_6X 166 elif spi_config['spi_drive_str'] == "4x": 167 hdr[5] |= HDR_SPI_DRV_STR_4X 168 elif spi_config['spi_drive_str'] == "2x": 169 hdr[5] |= HDR_SPI_DRV_STR_2X 170 else: 171 hdr[5] |= HDR_SPI_DRV_STR_1X 172 173 # translate "slow", "fast" to 0, 1 174 if spi_config['spi_slew_rate'] == "fast": 175 hdr[5] |= HDR_SPI_SLEW_FAST 176 177 # MEC172x b[0]=0 do not allow 96MHz SPI clock 178 hdr[6] = 0 # not using authentication or encryption 179 180 if spi_config['spi_read_mode'] == 'quad': 181 hdr[7] = HDR_SPI_RD_1148 182 elif spi_config['spi_read_mode'] == 'dual': 183 hdr[7] = HDR_SPI_RD_1128 184 elif spi_config['spi_read_mode'] == 'normal': 185 hdr[7] = HDR_SPI_RD_111 186 else: 187 hdr[7] = HDR_SPI_RD_1118 188 189 # payload load address in SRAM 190 pld_load_addr = CHIP_DICT[chip]['sram_base'] 191 hdr[8] = pld_load_addr & 0xff 192 hdr[9] = (pld_load_addr >> 8) & 0xff 193 hdr[0xA] = (pld_load_addr >> 16) & 0xff 194 hdr[0xB] = (pld_load_addr >> 24) & 0xff 195 196 # payload entry point address in SRAM 197 hdr[0xC] = pld_entry_addr & 0xff 198 hdr[0xD] = (pld_entry_addr >> 8) & 0xff 199 hdr[0xE] = (pld_entry_addr >> 16) & 0xff 200 hdr[0xF] = (pld_entry_addr >> 24) & 0xff 201 202 # payload size (16-bit) in granularity units 203 pld_units = pld_len // PLD_GRANULARITY 204 hdr[0x10] = pld_units & 0xff 205 hdr[0x11] = (pld_units >> 8) & 0xff 206 # hdr[0x12:0x13] = 0 reserved 207 208 # Unsigned offset from start of Header to start of FW Binary 209 # FW binary(payload) must always be located after header 210 pld_offset = pld_spi_loc - hdr_spi_loc 211 hdr[0x14] = pld_offset & 0xff 212 hdr[0x15] = (pld_offset >> 8) & 0xff 213 hdr[0x16] = (pld_offset >> 16) & 0xff 214 hdr[0x17] = (pld_offset >> 24) & 0xff 215 216 # hdr[0x18] = 0 not using authentication 217 # hdr[0x19] = 0 not adjusting SPI flash device drive strength 218 # hdr[0x1A through 0x1F] = 0 reserved 219 # hdr[0x20 through 0x27] = 0 not adjust SPI flash device drive strength 220 # hdr[0x28 through 0x47] = 0 reserved 221 # hdr[0x48 through 0x4F] = 0 reserved 222 # hdr[0x50 through 0x7F] = ECDSA P-384 Public key x-component 223 # hdr[0x80 through 0xAF] = ECDSA P-384 Public key y-component 224 # hdr[0xB0 through 0xDF] = SHA-384 digest of hdr[0 through 0xAF] Always required 225 # hdr[0xE0 through 0x10F] = ECDSA signature R-component of hdr[0 through 0xDF] 226 # hdr[0x110 through 0x13F] = ECDSA signature S-component of hdr[0 through 0xDF] 227 228 h = hashlib.sha384() 229 h.update(hdr[0:0xB0]) 230 hdr_digest = h.digest() 231 232 if verbose_mode: 233 print_bytes("hdr_sha384_digest", hdr_digest) 234 235 hdr[0xB0:0xE0] = hdr_digest 236 237 return bytes(hdr) 238 239def parse_args(): 240 parser = argparse.ArgumentParser(allow_abbrev=False) 241 # Use a lambda to handle base 10 or base 16 (hex) input 242 parser.add_argument("-c", 243 type=str, 244 dest="chip", 245 choices = ["mec15xx", "mec172x"], 246 default="mec172x", 247 help="Chip name: mec172x(default) or mec15xx") 248 parser.add_argument("-i", 249 type=str, 250 dest="infilename", 251 default="zephyr.bin", 252 help="Input firmware binary file path/name (default: %(default)s)") 253 parser.add_argument("-o", 254 type=str, 255 dest="outfilename", 256 default="zephyr.mchp.bin", 257 help="Output SPI image file path/name (default: %(default)s)") 258 parser.add_argument("-s", 259 type=int, 260 dest="spi_size_kb", 261 default=256, 262 help="SPI image size in kilobytes (default: %(default)s)") 263 parser.add_argument("-e", 264 type=int, 265 dest="entry_point", 266 default=0, 267 help="FW entry point address Lookup in image (default: %(default)s)") 268 parser.add_argument("-f", 269 type=int, 270 dest="spi_freq_mhz", 271 choices = [12, 16, 24, 48], 272 default=12, 273 help="SPI frequency: 12, 16, 24, or 48 MHz") 274 parser.add_argument("-r", 275 type=str, 276 dest="spi_read_mode", 277 choices = ["normal", "fast", "dual", "quad"], 278 default="fast", 279 help="SPI read mode: normal, fast, dual or quad") 280 parser.add_argument("-m", 281 type=int, 282 dest="spi_mode", 283 choices = [0, 1, 2, 3, 4, 5, 6, 7], 284 default=0, 285 help="SPI signalling mode 3-bit field: 0-7") 286 parser.add_argument("--drvstr", 287 type=str, 288 dest="spi_drive_strength", 289 choices = ["1x", "2x", "4x", "6x"], 290 default="1x", 291 help="SPI pin driver strength multiplier encoded") 292 parser.add_argument("--slewrate", 293 type=str, 294 dest="spi_slew_rate", 295 choices = ["slow", "fast"], 296 default="slow", 297 help="SPI pins slew rate") 298 parser.add_argument("--fill", 299 dest="fill", 300 action='store_true', 301 help="Fill with 0xFF to flash size") 302 parser.add_argument("-v", 303 dest="verbose", 304 action='store_true', 305 help="Enable messages to console") 306 307 ret_args = parser.parse_args() 308 309 return ret_args 310 311def main(): 312 """MEC SPI Gen""" 313 args = parse_args() 314 315 verbose_mode = args.verbose 316 317 if verbose_mode: 318 print("Command line arguments/defaults") 319 print(" chip = {0}".format(args.chip)) 320 print(" infilename = {0}".format(args.infilename)) 321 print(" outfilename = {0}".format(args.outfilename)) 322 print(" SPI size (kilobytes) = {0}".format(args.spi_size_kb)) 323 print(" Entry point address = {0}".format(args.entry_point)) 324 print(" SPI frequency MHz = {0}".format(args.spi_freq_mhz)) 325 print(" SPI Read Mode = {0}".format(args.spi_read_mode)) 326 print(" SPI Signalling Mode = {0}".format(args.spi_mode)) 327 print(" SPI drive strength = {0}".format(args.spi_drive_strength)) 328 print(" SPI slew rate fast = {0}".format(args.spi_slew_rate)) 329 print(" Verbose = {0}".format(args.verbose)) 330 331 if args.infilename is None: 332 print("ERROR: Specify input binary file name with -i") 333 sys.exit(-1) 334 335 if args.outfilename is None: 336 print("ERROR: Specify output binary file name with -o") 337 sys.exit(-1) 338 339 chip = args.chip 340 spi_read_mode = args.spi_read_mode 341 spi_freq_mhz = args.spi_freq_mhz 342 spi_mode = args.spi_mode 343 spi_drive_str_mult = args.spi_drive_strength 344 spi_slew = args.spi_slew_rate 345 346 spi_size = args.spi_size_kb * 1024 347 348 indata = None 349 with open(args.infilename, "rb") as fin: 350 indata = fin.read() 351 352 indata_len = len(indata) 353 if verbose_mode: 354 print("Read input FW binary: length = {0}".format(indata_len)) 355 356 # if necessary pad input data to PLD_GRANULARITY required by Boot-ROM loader 357 pad_len = 0 358 if (indata_len % PLD_GRANULARITY) != 0: 359 pad_len = PLD_GRANULARITY - (indata_len % PLD_GRANULARITY) 360 # NOTE: MCHP Production SPI Image Gen. pads with 0 361 padding = PLD_PAD_BYTE * pad_len 362 indata = indata + padding 363 364 indata_len += pad_len 365 366 if verbose_mode: 367 print("Padded FW binary: length = {0}".format(indata_len)) 368 369 # Do we have enough space for 4KB block containing TAG and Header, padded FW binary, 370 # EC Info Block, Co-Sig Block, and Trailer? 371 mec_add_info_size = PLD_SPI_LOC + EC_INFO_BLOCK_SIZE + COSIG_SIZE + TRAILER_SIZE 372 if indata_len > (spi_size - mec_add_info_size): 373 print("ERROR: FW binary exceeds flash size! indata_len = {0} spi_size = {1}".format(indata_len, spi_size)) 374 sys.exit(-1) 375 376 entry_point = args.entry_point 377 if args.entry_point == 0: 378 # Look up entry point in image 379 # Assumes Cortex-M4 vector table 380 # at beginning of image and second 381 # word in table is address of reset handler 382 entry_point = int.from_bytes(indata[4:8], byteorder="little") 383 384 tag = build_tag(HDR_SPI_LOC) 385 386 if verbose_mode: 387 print_bytes("TAG", tag) 388 print("Build Header at {0}: Load Address = 0x{1:0x} Entry Point Address = 0x{2:0x}".format( 389 HDR_SPI_LOC, PLD_LOAD_ADDR, entry_point)) 390 391 spi_config_info = { 392 "spi_freq_mhz": spi_freq_mhz, 393 "spi_mode": spi_mode, 394 "spi_read_mode": spi_read_mode, 395 "spi_drive_str": spi_drive_str_mult, 396 "spi_slew_rate": spi_slew, 397 } 398 399 header = build_header(chip, spi_config_info, HDR_SPI_LOC, PLD_SPI_LOC, entry_point, indata_len) 400 401 if verbose_mode: 402 print_bytes("HEADER", header) 403 print("") 404 405 # appended to end of padded payload 406 ec_info_block = bytearray(EC_INFO_BLOCK_SIZE) 407 ec_info_loc = PLD_SPI_LOC + len(indata) 408 409 # appended to end of (padded payload + ec_info_block) 410 cosig = bytearray(b'\xff' * COSIG_SIZE) 411 cosig_loc = ec_info_loc + EC_INFO_BLOCK_SIZE 412 413 # appended to end of (padded payload + ec_info_block + cosig) 414 # trailer[0:0x30] = SHA384(indata || ec_info_block || cosig) 415 # trailer[0x30:] = 0xFF 416 trailer = bytearray(b'\xff' * TRAILER_SIZE) 417 trailer_loc = cosig_loc + COSIG_SIZE 418 419 h = hashlib.sha384() 420 h.update(indata) 421 h.update(ec_info_block) 422 h.update(cosig) 423 image_digest = h.digest() 424 trailer[0:len(image_digest)] = image_digest 425 426 if verbose_mode: 427 print("SHA-384 digest (paddedFW || ec_info_block || cosig)") 428 print_bytes("digest", image_digest) 429 430 spi_bufs = [] 431 spi_bufs.append(("TAG", TAG_SPI_LOC, tag)) 432 spi_bufs.append(("HEADER", HDR_SPI_LOC, header)) 433 spi_bufs.append(("PAYLOAD", PLD_SPI_LOC, indata)) 434 spi_bufs.append(("EC_INFO", ec_info_loc, ec_info_block)) 435 spi_bufs.append(("COSIG", cosig_loc, cosig)) 436 spi_bufs.append(("TRAILER", trailer_loc, trailer)) 437 438 spi_bufs.sort(key=lambda x: x[1]) 439 440 if verbose_mode: 441 i = 0 442 for sb in spi_bufs: 443 print("buf[{0}]: {1} location=0x{2:0x} length=0x{3:0x}".format(i, sb[0], sb[1], len(sb[2]))) 444 print("") 445 446 fill = bytes(b'\xff' * 256) 447 if verbose_mode: 448 print("len(fill) = {0}".format(len(fill))) 449 450 loc = 0 451 with open(args.outfilename, "wb") as fout: 452 for sb in spi_bufs: 453 if verbose_mode: 454 print("sb: {0} location=0x{1:0x} len=0x{2:0x}".format(sb[0], sb[1], len(sb[2]))) 455 if loc < sb[1]: 456 fill_len = sb[1] - loc 457 if verbose_mode: 458 print("loc = 0x{0:0x}: Fill with 0xFF len=0x{1:0x}".format(loc, fill_len)) 459 nfill = fill_len // 256 460 rem = fill_len % 256 461 for _ in range(nfill): 462 fout.write(fill) 463 if rem > 0: 464 fout.write(fill[0:rem]) 465 loc = loc + fill_len 466 if verbose_mode: 467 print("loc = 0x{0:0x}: write {1} len=0x{2:0x}".format(loc, sb[0], len(sb[2]))) 468 fout.write(sb[2]) 469 loc = loc + len(sb[2]) 470 if args.fill and (loc < spi_size): 471 fill_len = spi_size - loc 472 nfill = fill_len // 256 473 rem = fill_len % 256 474 for _ in range(nfill): 475 fout.write(fill) 476 if rem > 0: 477 fout.write(fill[0:rem]) 478 loc = loc + fill_len 479 if verbose_mode: 480 print("Final loc = 0x{0:0x}".format(loc)) 481 482 if verbose_mode: 483 print("MEC SPI Gen done") 484 485if __name__ == '__main__': 486 main() 487