#!/usr/bin/env python3 # Copyright 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # # Ignore indention messages, since legacy scripts use 2 spaces instead of 4. # pylint: disable=bad-indentation,docstring-section-indent # pylint: disable=docstring-trailing-quotes # A script to pack EC binary into SPI flash image for MEC17xx # Based on MEC170x_ROM_Description.pdf DS00002225C (07-28-17). import argparse import hashlib import os import struct import subprocess import tempfile import zlib # CRC32 # MEC1701 has 256KB SRAM from 0xE0000 - 0x120000 # SRAM is divided into contiguous CODE & DATA # CODE at [0xE0000, 0x117FFF] DATA at [0x118000, 0x11FFFF] # SPI flash size for board is 512KB # Boot-ROM TAG is located at SPI offset 0 (two 4-byte tags) # LFW_SIZE = 0x1000 LOAD_ADDR = 0x0E0000 LOAD_ADDR_RW = 0xE1000 HEADER_SIZE = 0x40 SPI_CLOCK_LIST = [48, 24, 16, 12] SPI_READ_CMD_LIST = [0x3, 0xb, 0x3b, 0x6b] CRC_TABLE = [0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d] def mock_print(*args, **kwargs): pass debug_print = mock_print def Crc8(crc, data): """Update CRC8 value.""" for v in data: crc = ((crc << 4) & 0xff) ^ (CRC_TABLE[(crc >> 4) ^ (v >> 4)]); crc = ((crc << 4) & 0xff) ^ (CRC_TABLE[(crc >> 4) ^ (v & 0xf)]); return crc ^ 0x55 def GetEntryPoint(payload_file): """Read entry point from payload EC image.""" with open(payload_file, 'rb') as f: f.seek(4) s = f.read(4) return struct.unpack('> 6) & 0xff) header.append((payload_len >> 14) & 0xff) PadZeroTo(header, 0x14) # bytes 0x14 - 0x17 header.extend(struct.pack('> 6) & 0xff) header.append((payload_len >> 14) & 0xff) PadZeroTo(header, 0x14) # bytes 0x14 - 0x17 header.extend(struct.pack('> 8) & 0xff, (args.header_loc >> 16) & 0xff, (args.header_loc >> 24) & 0xff]) tag.append(Crc8(0, tag)) return tag def BuildTagFromHdrAddr(header_loc): tag = bytearray([(header_loc >> 8) & 0xff, (header_loc >> 16) & 0xff, (header_loc >> 24) & 0xff]) tag.append(Crc8(0, tag)) return tag # # Creates temporary file for read/write # Reads binary file containing LFW image_size (loader_file) # Writes LFW image to temporary file # Reads RO image at beginning of rorw_file up to image_size # (assumes RO/RW images have been padded with 0xFF # Returns temporary file name # def PacklfwRoImage(rorw_file, loader_file, image_size): """Create a temp file with the first image_size bytes from the loader file and append bytes from the rorw file. return the filename""" fo=tempfile.NamedTemporaryFile(delete=False) # Need to keep file around with open(loader_file,'rb') as fin1: # read 4KB loader file pro = fin1.read() fo.write(pro) # write 4KB loader data to temp file with open(rorw_file, 'rb') as fin: ro = fin.read(image_size) fo.write(ro) fo.close() return fo.name # # Generate a test EC_RW image of same size # as original. # Preserve image_data structure and fill all # other bytes with 0xA5. # useful for testing SPI read and EC build # process hash generation. # def gen_test_ecrw(pldrw): debug_print("gen_test_ecrw: pldrw type =", type(pldrw)) debug_print("len pldrw =", len(pldrw), " = ", hex(len(pldrw))) cookie1_pos = pldrw.find(b'\x99\x88\x77\xce') cookie2_pos = pldrw.find(b'\xdd\xbb\xaa\xce', cookie1_pos+4) t = struct.unpack(" 0 and cookie2_pos > cookie1_pos: for i in range(0, cookie1_pos): pldrw[i] = 0xA5 for i in range(cookie2_pos+4, len(pldrw)): pldrw[i] = 0xA5 with open("ec_RW_test.bin", "wb") as fecrw: fecrw.write(pldrw[:size]) def parseargs(): rpath = os.path.dirname(os.path.relpath(__file__)) parser = argparse.ArgumentParser() parser.add_argument("-i", "--input", help="EC binary to pack, usually ec.bin or ec.RO.flat.", metavar="EC_BIN", default="ec.bin") parser.add_argument("-o", "--output", help="Output flash binary file", metavar="EC_SPI_FLASH", default="ec.packed.bin") parser.add_argument("--loader_file", help="EC loader binary", default="ecloader.bin") parser.add_argument("-s", "--spi_size", type=int, help="Size of the SPI flash in KB", default=512) parser.add_argument("-l", "--header_loc", type=int, help="Location of header in SPI flash", default=0x1000) parser.add_argument("-p", "--payload_offset", type=int, help="The offset of payload from the start of header", default=0x80) parser.add_argument("-r", "--rw_loc", type=int, help="Start offset of EC_RW. Default is -1 meaning 1/2 flash size", default=-1) parser.add_argument("--spi_clock", type=int, help="SPI clock speed. 8, 12, 24, or 48 MHz.", default=24) parser.add_argument("--spi_read_cmd", type=int, help="SPI read command. 0x3, 0xB, or 0x3B.", default=0xb) parser.add_argument("--image_size", type=int, help="Size of a single image. Default 220KB", default=(220 * 1024)) parser.add_argument("--test_spi", action='store_true', help="Test SPI data integrity by adding CRC32 in last 4-bytes of RO/RW binaries", default=False) parser.add_argument("--test_ecrw", action='store_true', help="Use fixed pattern for EC_RW but preserve image_data", default=False) parser.add_argument("--verbose", action='store_true', help="Enable verbose output", default=False) return parser.parse_args() # Debug helper routine def dumpsects(spi_list): debug_print("spi_list has {0} entries".format(len(spi_list))) for s in spi_list: debug_print("0x{0:x} 0x{1:x} {2:s}".format(s[0],len(s[1]),s[2])) def printByteArrayAsHex(ba, title): debug_print(title,"= ") count = 0 for b in ba: count = count + 1 debug_print("0x{0:02x}, ".format(b),end="") if (count % 8) == 0: debug_print("") debug_print("\n") def print_args(args): debug_print("parsed arguments:") debug_print(".input = ", args.input) debug_print(".output = ", args.output) debug_print(".loader_file = ", args.loader_file) debug_print(".spi_size (KB) = ", hex(args.spi_size)) debug_print(".image_size = ", hex(args.image_size)) debug_print(".header_loc = ", hex(args.header_loc)) debug_print(".payload_offset = ", hex(args.payload_offset)) if args.rw_loc < 0: debug_print(".rw_loc = ", args.rw_loc) else: debug_print(".rw_loc = ", hex(args.rw_loc)) debug_print(".spi_clock = ", args.spi_clock) debug_print(".spi_read_cmd = ", args.spi_read_cmd) debug_print(".test_spi = ", args.test_spi) debug_print(".verbose = ", args.verbose) # # Handle quiet mode build from Makefile # Quiet mode when V is unset or V=0 # Verbose mode when V=1 # def main(): global debug_print args = parseargs() if args.verbose: debug_print = print debug_print("Begin MEC17xx pack_ec.py script") # MEC17xx maximum 192KB each for RO & RW # mec1701 chip Makefile sets args.spi_size = 512 # Tags at offset 0 # print_args(args) spi_size = args.spi_size * 1024 debug_print("SPI Flash image size in bytes =", hex(spi_size)) # !!! IMPORTANT !!! # These values MUST match chip/mec1701/config_flash_layout.h # defines. # MEC17xx Boot-ROM TAGs are at offset 0 and 4. # lfw + EC_RO starts at beginning of second 4KB sector # EC_RW starts at offset 0x40000 (256KB) spi_list = [] debug_print("args.input = ",args.input) debug_print("args.loader_file = ",args.loader_file) debug_print("args.image_size = ",hex(args.image_size)) rorofile=PacklfwRoImage(args.input, args.loader_file, args.image_size) payload = GetPayload(rorofile) payload_len = len(payload) # debug debug_print("EC_LFW + EC_RO length = ",hex(payload_len)) # SPI image integrity test # compute CRC32 of EC_RO except for last 4 bytes # skip over 4KB LFW # Store CRC32 in last 4 bytes if args.test_spi == True: crc = zlib.crc32(bytes(payload[LFW_SIZE:(payload_len - 4)])) crc_ofs = payload_len - 4 debug_print("EC_RO CRC32 = 0x{0:08x} @ 0x{1:08x}".format(crc, crc_ofs)) for i in range(4): payload[crc_ofs + i] = crc & 0xff crc = crc >> 8 # Chromebooks are not using MEC BootROM ECDSA. # We implemented the ECDSA disabled case where # the 64-byte signature contains a SHA-256 of the binary plus # 32 zeros bytes. payload_signature = SignByteArray(payload) # debug printByteArrayAsHex(payload_signature, "LFW + EC_RO payload_signature") # MEC17xx Header is 0x80 bytes with an 64 byte signature # (32 byte SHA256 + 32 zero bytes) header = BuildHeader(args, payload_len, LOAD_ADDR, rorofile) # debug printByteArrayAsHex(header, "Header LFW + EC_RO") # MEC17xx payload ECDSA not used, 64 byte signature is # SHA256 + 32 zero bytes header_signature = SignByteArray(header) # debug printByteArrayAsHex(header_signature, "header_signature") tag = BuildTag(args) # MEC17xx truncate RW length to args.image_size to not overwrite LFW # offset may be different due to Header size and other changes # MCHP we want to append a SHA-256 to the end of the actual payload # to test SPI read routines. debug_print("Call to GetPayloadFromOffset") debug_print("args.input = ", args.input) debug_print("args.image_size = ", hex(args.image_size)) payload_rw = GetPayloadFromOffset(args.input, args.image_size) debug_print("type(payload_rw) is ", type(payload_rw)) debug_print("len(payload_rw) is ", hex(len(payload_rw))) # truncate to args.image_size rw_len = args.image_size payload_rw = payload_rw[:rw_len] payload_rw_len = len(payload_rw) debug_print("Truncated size of EC_RW = ", hex(payload_rw_len)) payload_entry_tuple = struct.unpack_from('> 8 payload_rw_sig = SignByteArray(payload_rw) # debug printByteArrayAsHex(payload_rw_sig, "payload_rw_sig") os.remove(rorofile) # clean up the temp file # MEC170x Boot-ROM Tags are located at SPI offset 0 spi_list.append((0, tag, "tag")) spi_list.append((args.header_loc, header, "header(lwf + ro)")) spi_list.append((args.header_loc + HEADER_SIZE, header_signature, "header(lwf + ro) signature")) spi_list.append((args.header_loc + args.payload_offset, payload, "payload(lfw + ro)")) offset = args.header_loc + args.payload_offset + payload_len # No SPI Header for EC_RW as its not loaded by BootROM spi_list.append((offset, payload_signature, "payload(lfw_ro) signature")) # EC_RW location rw_offset = int(spi_size // 2) if args.rw_loc >= 0: rw_offset = args.rw_loc debug_print("rw_offset = 0x{0:08x}".format(rw_offset)) if rw_offset < offset + len(payload_signature): print("ERROR: EC_RW overlaps EC_RO") spi_list.append((rw_offset, payload_rw, "payload(rw)")) # don't add to EC_RW. We don't know if Google will process # EC SPI flash binary with other tools during build of # coreboot and OS. #offset = rw_offset + payload_rw_len #spi_list.append((offset, payload_rw_sig, "payload(rw) signature")) spi_list = sorted(spi_list) dumpsects(spi_list) # # MEC17xx Boot-ROM locates TAG at SPI offset 0 instead of end of SPI. # with open(args.output, 'wb') as f: debug_print("Write spi list to file", args.output) addr = 0 for s in spi_list: if addr < s[0]: debug_print("Offset ",hex(addr)," Length", hex(s[0]-addr), "fill with 0xff") f.write(b'\xff' * (s[0] - addr)) addr = s[0] debug_print("Offset ",hex(addr), " Length", hex(len(s[1])), "write data") f.write(s[1]) addr += len(s[1]) if addr < spi_size: debug_print("Offset ",hex(addr), " Length", hex(spi_size - addr), "fill with 0xff") f.write(b'\xff' * (spi_size - addr)) f.flush() if __name__ == '__main__': main()