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