diff options
author | Scott Worley <scott.worley@microchip.corp-partner.google.com> | 2021-03-10 14:18:25 -0500 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-03-30 06:01:10 +0000 |
commit | 6954cd5df6c11d0d6b2c92d2c071fd78e4bbc573 (patch) | |
tree | 55fb2357f79f7841e69080eb304896e3bef3fc07 | |
parent | ba480aa4d17833c5dc88ffa3eafe650db705088a (diff) | |
download | chrome-ec-6954cd5df6c11d0d6b2c92d2c071fd78e4bbc573.tar.gz |
mchp: Add MEC172x build files
Update chip build rules for MEC172x. Add a new little-firmware (LFW)
linker file supporting the larger SRAM size of MEC172x. Add a new python
SPI image generator for MEC172x new SPI layout (no flash-map).
BRANCH=none
BUG=none
TEST=Build MEC170x and MEC152x boards
Signed-off-by: Scott Worley <scott.worley@microchip.corp-partner.google.com>
Change-Id: I9ff2302ca99d4e7296acdb8352849f5cd355adf0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2749674
Commit-Queue: Aseda Aboagye <aaboagye@chromium.org>
Reviewed-by: Martin Yan <martin.yan@microchip.corp-partner.google.com>
Reviewed-by: Aseda Aboagye <aaboagye@chromium.org>
Reviewed-by: Ravin Kumar <ravin.kumar@microchip.com>
Reviewed-by: Vijay P Hiremath <vijay.p.hiremath@intel.com>
Tested-by: Martin Yan <martin.yan@microchip.corp-partner.google.com>
-rw-r--r-- | chip/mchp/build.mk | 13 | ||||
-rw-r--r-- | chip/mchp/lfw/ec_lfw_416kb.ld | 89 | ||||
-rwxr-xr-x | chip/mchp/util/pack_ec_mec172x.py | 847 |
3 files changed, 949 insertions, 0 deletions
diff --git a/chip/mchp/build.mk b/chip/mchp/build.mk index 2d8edede13..506d0d3470 100644 --- a/chip/mchp/build.mk +++ b/chip/mchp/build.mk @@ -63,6 +63,9 @@ PACK_EC=pack_ec.py ifeq ($(CHIP_FAMILY),mec152x) PACK_EC=pack_ec_mec152x.py endif +ifeq ($(CHIP_FAMILY),mec172x) + PACK_EC=pack_ec_mec172x.py +endif # pack_ec.py creates SPI flash image for MEC # _rw_size is CONFIG_RW_SIZE @@ -97,6 +100,15 @@ $(out)/RW/%-lfw.o: %.c $(call quiet,c_to_o,CC ) # let lfw's elf link only with selected objects +ifeq ($(CHIP_FAMILY),mec172x) +$(out)/RW/%-lfw.elf: private objs = $(objs_lfw) +$(out)/RW/%-lfw.elf: override shlib := +$(out)/RW/%-lfw.elf: %_416kb.ld $(objs_lfw) + $(call quiet,elf,LD ) + +# final image needs lfw loader +$(out)/$(PROJECT).bin: $(chip-lfw-flat) +else $(out)/RW/%-lfw.elf: private objs = $(objs_lfw) $(out)/RW/%-lfw.elf: override shlib := $(out)/RW/%-lfw.elf: %.ld $(objs_lfw) @@ -104,3 +116,4 @@ $(out)/RW/%-lfw.elf: %.ld $(objs_lfw) # final image needs lfw loader $(out)/$(PROJECT).bin: $(chip-lfw-flat) +endif diff --git a/chip/mchp/lfw/ec_lfw_416kb.ld b/chip/mchp/lfw/ec_lfw_416kb.ld new file mode 100644 index 0000000000..97be2fe06a --- /dev/null +++ b/chip/mchp/lfw/ec_lfw_416kb.ld @@ -0,0 +1,89 @@ +/* Copyright 2021 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. + * + * MCHP MEC parts with 416KB SRAM SoC little FW + * + */ + +/* + * Memory Spaces Definitions + * LFW occupies first 4KB of CODE SRAM. + * First 24 bytes contain a minimal Cortex-M4 + * vector table. + */ +MEMORY +{ + VECTOR(r ) : ORIGIN = 0x0C0000, LENGTH = 0x18 + SRAM (xrw) : ORIGIN = 0x0C0018, LENGTH = 0xFE8 +} + +/* + * ld does not allow mathematical expressions in ORIGIN/LENGTH, so check the + * values here. + */ +ASSERT(ORIGIN(VECTOR) + LENGTH(VECTOR) == ORIGIN(SRAM), "Invalid SRAM origin.") +ASSERT(LENGTH(VECTOR) + LENGTH(SRAM) == 0x1000, "Invalid VECTOR+SRAM length.") + +/* + * The entry point is informative, for debuggers and simulators, + * since the Cortex-M vector points to it anyway. + */ +ENTRY(lfw_main) + +/* + * MEC172xN has 416KB total SRAM: 352KB CODE 64KB DATA + * CODE: 0x0C0000 - 0x117FFF + * DATA: 0x118000 - 0x127FFF + * Boot-ROM log is 0x11FF00 - 0x11FFFF + * MEC172x Top 1KB is not cleared if OTP customer flag enabled. + * !!! TODO !!! Does presence of PUF feature move customer area? + * Boot-ROM spec states 3.5KB from top is lost. + * 0x12_7800 - 0x12_7fff 2KB used by PUF option + * 0x12_7400 - 0x12_77ff 1KB Customer use. Not cleared by Boot-ROM + * 0x12_7200 - 0x12_73ff 512 byte Boot-ROM log + * CrOS EC puts panic data at Top of RAM. + * We must set Top of RAM to be customer region far enough to + * hold panic data. + * Set Top of SRAM to 0x12_7800. + * This requires size of SRAM = 0x127800 - 0x118000 = 0xF800 (62 KB) + */ +PROVIDE( lfw_stack_top = 0x127800 ); + +/* Sections Definitions */ + +SECTIONS +{ + + /* + * The vector table goes first + */ + .intvector : + { + . = ALIGN(4); + KEEP(*(.intvector)) + } > VECTOR + + /* + * The program code is stored in the .text section, + * which goes to FLASH. + */ + + .text : + { + *(.text .text.*) /* all remaining code */ + *(.rodata .rodata.*) /* read-only data (constants) */ + } >SRAM + + . = ALIGN(4); + + /* Padding */ + + .fill : { + FILL(0xFF); + . = ORIGIN(SRAM) + LENGTH(SRAM) - 1; + BYTE(0xFF); /* emit at least a byte to make linker happy */ + } + + __image_size = LOADADDR(.text) + SIZEOF(.text) - ORIGIN(VECTOR); +} diff --git a/chip/mchp/util/pack_ec_mec172x.py b/chip/mchp/util/pack_ec_mec172x.py new file mode 100755 index 0000000000..2f4727c483 --- /dev/null +++ b/chip/mchp/util/pack_ec_mec172x.py @@ -0,0 +1,847 @@ +#!/usr/bin/env python3 + +# Copyright 2021 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. + +# A script to pack EC binary into SPI flash image for MEC172x +# Based on MEC172x_ROM_Description.pdf revision 6/8/2020 +import argparse +import hashlib +import os +import struct +import subprocess +import tempfile +import zlib # CRC32 + +# MEC172x has 416KB SRAM from 0xC0000 - 0x127FFF +# SRAM is divided into contiguous CODE & DATA +# CODE at [0xC0000, 0x117FFF] DATA at [0x118000, 0x127FFF] +# Google EC SPI flash size for board is currently 512KB +# split into 1/2. +# EC_RO: 0 - 0x3FFFF +# EC_RW: 0x40000 - 0x7FFFF +# +SPI_ERASE_BLOCK_SIZE = 0x1000 +SPI_CLOCK_LIST = [48, 24, 16, 12, 96] +SPI_READ_CMD_LIST = [0x3, 0xb, 0x3b, 0x6b] +SPI_DRIVE_STR_DICT = {2:0, 4:1, 8:2, 12:3} +# Maximum EC_RO/EC_RW code size is based upon SPI flash erase +# sector size, MEC172x Boot-ROM TAG, Header, Footer. +# SPI Offset Description +# 0x00 - 0x07 TAG0 and TAG1 +# 0x1000 - 0x113F Boot-ROM SPI Header +# 0x1140 - 0x213F 4KB LFW +# 0x2040 - 0x3EFFF +# 0x3F000 - 0x3FFFF BootROM EC_INFO_BLK || COSIG || ENCR_KEY_HDR(optional) || TRAILER +CHIP_MAX_CODE_SRAM_KB = (256 - 12) + +MEC172X_DICT = { + "LFW_SIZE": 0x1000, + "LOAD_ADDR": 0xC0000, + "TAG_SIZE": 4, + "KEY_BLOB_SIZE": 1584, + "HEADER_SIZE":0x140, + "HEADER_VER":0x03, + "PAYLOAD_GRANULARITY":128, + "PAYLOAD_PAD_BYTE":b'\xff', + "EC_INFO_BLK_SZ":128, + "ENCR_KEY_HDR_SZ":128, + "COSIG_SZ":96, + "TRAILER_SZ":160, + "TAILER_PAD_BYTE":b'\xff', + "PAD_SIZE":128 + } + +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 + +# 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,"= ") + if ba == None: + debug_print("None") + return + + 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("") + +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 int.from_bytes(s, byteorder='little') + +def GetPayloadFromOffset(payload_file, offset, padsize): + """Read payload and pad it to padsize.""" + with open(payload_file, 'rb') as f: + f.seek(offset) + payload = bytearray(f.read()) + rem_len = len(payload) % padsize + debug_print("GetPayload: padsize={0:0x} len(payload)={1:0x} rem={2:0x}".format(padsize,len(payload),rem_len)) + + if rem_len: + payload += PAYLOAD_PAD_BYTE * (padsize - rem_len) + debug_print("GetPayload: Added {0} padding bytes".format(padsize - rem_len)) + + return payload + +def GetPayload(payload_file, padsize): + """Read payload and pad it to padsize""" + return GetPayloadFromOffset(payload_file, 0, padsize) + +def GetPublicKey(pem_file): + """Extract public exponent and modulus from PEM file.""" + result = subprocess.run(['openssl', 'rsa', '-in', pem_file, '-text', + '-noout'], stdout=subprocess.PIPE, encoding='utf-8') + modulus_raw = [] + in_modulus = False + for line in result.stdout.splitlines(): + if line.startswith('modulus'): + in_modulus = True + elif not line.startswith(' '): + in_modulus = False + elif in_modulus: + modulus_raw.extend(line.strip().strip(':').split(':')) + if line.startswith('publicExponent'): + exp = int(line.split(' ')[1], 10) + modulus_raw.reverse() + modulus = bytearray((int(x, 16) for x in modulus_raw[:256])) + return struct.pack('<Q', exp), modulus + +def GetSpiClockParameter(args): + assert args.spi_clock in SPI_CLOCK_LIST, \ + "Unsupported SPI clock speed %d MHz" % args.spi_clock + return SPI_CLOCK_LIST.index(args.spi_clock) + +def GetSpiReadCmdParameter(args): + assert args.spi_read_cmd in SPI_READ_CMD_LIST, \ + "Unsupported SPI read command 0x%x" % args.spi_read_cmd + return SPI_READ_CMD_LIST.index(args.spi_read_cmd) + +def GetEncodedSpiDriveStrength(args): + assert args.spi_drive_str in SPI_DRIVE_STR_DICT, \ + "Unsupported SPI drive strength %d mA" % args.spi_drive_str + return SPI_DRIVE_STR_DICT.get(args.spi_drive_str) + +# Return 0=Slow slew rate or 1=Fast slew rate +def GetSpiSlewRate(args): + if args.spi_slew_fast == True: + return 1 + return 0 + +# Return SPI CPOL = 0 or 1 +def GetSpiCpol(args): + if args.spi_cpol == 0: + return 0 + return 1 + +# Return SPI CPHA_MOSI +# 0 = SPI Master drives data is stable on inactive to clock edge +# 1 = SPI Master drives data is stable on active to inactive clock edge +def GetSpiCphaMosi(args): + if args.spi_cpha_mosi == 0: + return 0 + return 1 + +# Return SPI CPHA_MISO 0 or 1 +# 0 = SPI Master samples data on inactive to active clock edge +# 1 = SPI Master samples data on active to inactive clock edge +def GetSpiCphaMiso(args): + if args.spi_cpha_miso == 0: + return 0 + return 1 + +def PadZeroTo(data, size): + data.extend(b'\0' * (size - len(data))) + +# +# Boot-ROM SPI image encryption not used with Chromebooks +# +def EncryptPayload(args, chip_dict, payload): + return None + +# +# Build SPI image header for MEC172x +# MEC172x image header size = 320(0x140) bytes +# +# Description using Python slice notation [start:start+len] +# +# header[0:4] = 'PHCM' +# header[4] = header version = 0x03(MEC172x) +# header[5] = SPI clock speed, drive strength, sampling mode +# bits[1:0] = SPI clock speed: 0=48, 1=24, 2=16, 3=12 +# bits[3:2] = SPI controller pins drive strength +# 00b=2mA, 01b=4mA, 10b=8mA, 11b=12mA +# bit[4] = SPI controller pins slew rate: 0=slow, 1=fast +# bit[5] = SPI CPOL: 0=SPI clock idle is low, 1=idle is high +# bit[6] = CHPHA_MOSI +# 1:data change on first inactive to active clock edge +# 0:data change on first active to inactive clock edge +# bit[7] = CHPHA_MISO: +# 1: Data captured on first inactive to active clock edge +# 0: Data captured on first active to inactive clock edge +# header[6] Boot-ROM loader flags +# bits[0] = 0(use header[5] b[1:0]), 1(96 MHz) +# bits[2:1] = 0 reserved +# bits[5:3] = 111b +# bit[6]: For MEC172x controls authentication +# 0=Authentication disabled. Signature is SHA-384 of FW payload +# 1=Authentication enabled. Signature is ECDSA P-384 +# bit[7]: 0=FW pyload not encrypted, 1=FW payload is encrypted +# header[7]: SPI Flash read command +# 0 -> 0x03 1-1-1 read freq < 33MHz +# 1 -> 0x0B 1-1-1 + 8 clocks(data tri-stated) +# 2 -> 0x3B 1-1-2 + 8 clocks(data tri-stated). Data phase is dual I/O +# 3 -> 0x6B 1-1-4 + 8 clocks(data tri-stated). Data phase is Quad I/O +# NOTE: Quad requires SPI flash device QE(quad enable) bit +# to be factory set. Enabling QE disables HOLD# and WP# +# functionality of the SPI flash device. +# header[0x8:0xC] SRAM Load address little-endian format +# header[0xC:0x10] SRAM FW entry point. Boot-ROM jumps to +# this address on successful load. (little-endian) +# header[0x10:0x12] little-endian format: FW binary size in units of +# 128 bytes(MEC172x) +# header[0x12:0x14] = 0 reserved +# header[0x14:0x18] = Little-ending format: Unsigned offset from start of +# header to FW payload. +# MEC172x: Offset must be a multiple of 128 +# Offset must be >= header size. +# NOTE: If Authentication is enabled size includes +# the appended signature. +# MEC172x: +# header[0x18] = Authentication key select. Set to 0 for no Authentication. +# header[0x19] = SPI flash device(s) drive strength feature flags +# b[0]=1 SPI flash device 0 has drive strength feature +# b[1]=SPI flash 0: DrvStr Write format(0=2 bytes, 1=1 byte) +# b[2]=1 SPI flash device 1 has drive strength feature +# b[3]=SPI flash 1: DrvStr Write format(0=2 bytes, 1=1 byte) +# b[7:4] = 0 reserved +# header[0x1A:0x20] = 0 reserved +# header[0x20] = SPI opcode to read drive strength from SPI flash 0 +# header[0x21] = SPI opcode to write drive strength to SPI flash 0 +# header[0x22] = SPI flash 0: drvStr value +# header[0x23] = SPI flash 0: drvStr mask +# header[0x24] = SPI opcode to read drive strength from SPI flash 1 +# header[0x25] = SPI opcode to write drive strength to SPI flash 1 +# header[0x26] = SPI flash 1: drvStr value +# header[0x27] = SPI flash 1: drvStr mask +# header[0x28:0x48] = reserved, may be any value +# header[0x48:0x50] = reserved for customer use +# header[0x50:0x80] = ECDSA-384 public key x-coord. = 0 Auth. disabled +# header[0x80:0xB0] = ECDSA-384 public key y-coord. = 0 Auth. disabled +# header[0xB0:0xE0] = SHA-384 digest of header[0:0xB0] +# header[0xE0:0x110] = Header ECDSA-384 signature x-coord. = 0 Auth. disabled +# header[0x110:0x140] = Header ECDSA-384 signature y-coor. = 0 Auth. disabled +# +def BuildHeader2(args, chip_dict, payload_len, load_addr, payload_entry): + header_size = chip_dict["HEADER_SIZE"] + + # allocate zero filled header + header = bytearray(b'\x00' * header_size) + debug_print("len(header) = ", len(header)) + + # Identifier and header version + header[0:4] = b'PHCM' + header[4] = chip_dict["HEADER_VER"] + + # SPI frequency, drive strength, CPOL/CPHA encoding same for both chips + spiFreqIndex = GetSpiClockParameter(args) + if spiFreqIndex > 3: + header[6] |= 0x01 + else: + header[5] = spiFreqIndex + + header[5] |= ((GetEncodedSpiDriveStrength(args) & 0x03) << 2) + header[5] |= ((GetSpiSlewRate(args) & 0x01) << 4) + header[5] |= ((GetSpiCpol(args) & 0x01) << 5) + header[5] |= ((GetSpiCphaMosi(args) & 0x01) << 6) + header[5] |= ((GetSpiCphaMiso(args) & 0x01) << 7) + + # header[6] + # b[0] value set above + # b[2:1] = 00b, b[5:3]=111b + # b[7]=0 No encryption of FW payload + header[6] |= 0x7 << 3 + + # SPI read command set same for both chips + header[7] = GetSpiReadCmdParameter(args) & 0xFF + + # bytes 0x08 - 0x0b + header[0x08:0x0C] = load_addr.to_bytes(4, byteorder='little') + # bytes 0x0c - 0x0f + header[0x0C:0x10] = payload_entry.to_bytes(4, byteorder='little') + + # bytes 0x10 - 0x11 payload length in units of 128 bytes + assert payload_len % chip_dict["PAYLOAD_GRANULARITY"] == 0, \ + print("Payload size not a multiple of {0}".format(chip_dict["PAYLOAD_GRANULARITY"])) + + payload_units = int(payload_len // chip_dict["PAYLOAD_GRANULARITY"]) + assert payload_units < 0x10000, \ + print("Payload too large: len={0} units={1}".format(payload_len, payload_units)) + + header[0x10:0x12] = payload_units.to_bytes(2, 'little') + + # bytes 0x14 - 0x17 TODO offset from start of payload to FW payload to be + # loaded by Boot-ROM. We ask Boot-ROM to load (LFW || EC_RO). + # LFW location provided on the command line. + assert (args.lfw_loc % 4096 == 0), \ + print("LFW location not on a 4KB boundary! 0x{0:0x}".format(args.lfw_loc)) + + assert args.lfw_loc >= (args.header_loc + chip_dict["HEADER_SIZE"]), \ + print("LFW location not greater than header location + header size") + + lfw_ofs = args.lfw_loc - args.header_loc + header[0x14:0x18] = lfw_ofs.to_bytes(4, 'little') + + # MEC172x: authentication key select. Authentication not used, set to 0. + header[0x18] = 0 + + # header[0x19], header[0x20:0x28] + # header[0x1A:0x20] reserved 0 + # MEC172x: supports SPI flash devices with drive strength settings + # TODO leave these fields at 0 for now. We must add 6 command line + # arguments. + + # header[0x28:0x48] reserve can be any value + # header[0x48:0x50] Customer use. TODO + # authentication disabled, leave these 0. + # header[0x50:0x80] ECDSA P384 Authentication Public key Rx + # header[0x80:0xB0] ECDSA P384 Authentication Public key Ry + + # header[0xB0:0xE0] = SHA384(header[0:0xB0]) + header[0xB0:0xE0] = hashlib.sha384(header[0:0xB0]).digest() + # When ECDSA authentication is disabled MCHP SPI image generator + # is filling the last 48 bytes of the Header with 0xff + header[-48:] = b'\xff' * 48 + + debug_print("After hash: len(header) = ", len(header)) + + return header + +# +# MEC172x 128-byte EC Info Block appended to end of padded FW binary. +# Python slice notation +# byte[0:0x64] are undefined, we set to 0xFF +# byte[0x64] = FW Build LSB +# byte[0x65] = FW Build MSB +# byte[0x66:0x68] = undefined, set to 0xFF +# byte[0x68:0x78] = Roll back permissions bit maps +# byte[0x78:0x7c] = key revocation bit maps +# byte[0x7c] = platform ID LSB +# byte[0x7d] = platform ID MSB +# byte[0x7e] = auto-rollback protection enable +# byte[0x7f] = current imeage revision +# +def GenEcInfoBlock(args, chip_dict): + # ecinfo = bytearray([0xff] * chip_dict["EC_INFO_BLK_SZ"]) + ecinfo = bytearray(chip_dict["EC_INFO_BLK_SZ"]) + return ecinfo + +# +# Generate SPI FW image co-signature. +# MEC172x cosignature is 96 bytes used by OEM FW +# developer to sign their binary with ECDSA-P384-SHA384 or +# some other signature algorithm that fits in 96 bytes. +# At this time Cros-EC is not using this field, fill with 0xFF. +# If this feature is implemented we need to read the OEM's +# generated signature from a file and extract the binary +# signature. +# +def GenCoSignature(args, chip_dict, payload): + return bytearray(b'\xff' * chip_dict["COSIG_SZ"]) + +# +# Generate SPI FW Image trailer. +# MEC172x: Size = 160 bytes +# binary = payload || encryption_key_header || ec_info_block || cosignature +# trailer[0:48] = SHA384(binary) +# trailer[48:144] = 0xFF +# trailer[144:160] = 0xFF. Boot-ROM spec. says these bytes should be random. +# Authentication & encryption are not used therefore random data +# is not necessary. +def GenTrailer(args, chip_dict, payload, encryption_key_header, + ec_info_block, cosignature): + debug_print("GenTrailer SHA384 computation") + trailer = bytearray(chip_dict["TAILER_PAD_BYTE"] * chip_dict["TRAILER_SZ"]) + hasher = hashlib.sha384() + hasher.update(payload) + debug_print(" Update: payload len=0x{0:0x}".format(len(payload))) + if ec_info_block != None: + hasher.update(ec_info_block) + debug_print(" Update: ec_info_block len=0x{0:0x}".format(len(ec_info_block))) + if encryption_key_header != None: + hasher.update(encryption_key_header) + debug_print(" Update: encryption_key_header len=0x{0:0x}".format(len(encryption_key_header))) + if cosignature != None: + hasher.update(cosignature) + debug_print(" Update: cosignature len=0x{0:0x}".format(len(cosignature))) + trailer[0:48] = hasher.digest() + trailer[-16:] = 16 * b'\xff' + + return trailer + +# MEC172x supports two 32-bit Tags located at offsets 0x0 and 0x4 +# in the SPI flash. +# Tag format: +# bits[23:0] correspond to bits[31:8] of the Header SPI address +# Header is always on a 256-byte boundary. +# bits[31:24] = CRC8-ITU of bits[23:0]. +# Notice there is no chip-select field in the Tag both Tag's point +# to the same flash part. +# +def BuildTag(args): + tag = bytearray([(args.header_loc >> 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 + + +# FlashMap is an option for MEC172x +# It is a 32 bit structure +# bits[18:0] = bits[30:12] of second SPI flash base address +# bits[23:19] = 0 reserved +# bits[31:24] = CRC8 of bits[23:0] +# Input: +# integer containing base address of second SPI flash +# This value is usually equal to the size of the first +# SPI flash and should be a multiple of 4KB +# Output: +# bytearray of length 4 +def BuildFlashMap(secondSpiFlashBaseAddr): + flashmap = bytearray(4) + flashmap[0] = (secondSpiFlashBaseAddr >> 12) & 0xff + flashmap[1] = (secondSpiFlashBaseAddr >> 20) & 0xff + flashmap[2] = (secondSpiFlashBaseAddr >> 28) & 0xff + flashmap[3] = Crc8(0, flashmap) + return flashmap + +# +# 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("<L", pldrw[cookie1_pos+0x24:cookie1_pos+0x28]) + size = t[0] + debug_print("EC_RW size =", size, " = ", hex(size)) + + debug_print("Found cookie1 at ", hex(cookie1_pos)) + debug_print("Found cookie2 at ", hex(cookie2_pos)) + + if cookie1_pos > 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("--load_addr", type=int, + help="EC SRAM load address", + default=0xC0000) + 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. Must be on a 256 byte boundary", + default=0x0100) + parser.add_argument("--lfw_loc", type=int, + help="Location of LFW in SPI flash. Must be on a 4KB boundary", + default=0x1000) + parser.add_argument("--lfw_size", type=int, + help="LFW size in bytes", + default=0x1000) + 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, 0x3B, or 0x6B.", + default=0xb) + parser.add_argument("--image_size", type=int, + help="Size of a single image. Default 244KB", + default=(244 * 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) + parser.add_argument("--tag0_loc", type=int, + help="MEC172x TAG0 SPI offset", + default=0) + parser.add_argument("--tag1_loc", type=int, + help="MEC172x TAG1 SPI offset", + default=4) + parser.add_argument("--spi_drive_str", type=int, + help="Chip SPI drive strength in mA: 2, 4, 8, or 12", + default=4) + parser.add_argument("--spi_slew_fast", action='store_true', + help="SPI use fast slew rate. Default is False", + default=False) + parser.add_argument("--spi_cpol", type=int, + help="SPI clock polarity when idle. Defealt is 0(low)", + default=0) + parser.add_argument("--spi_cpha_mosi", type=int, + help="""SPI clock phase controller drives data. + 0=Data driven on active to inactive clock edge, + 1=Data driven on inactive to active clock edge""", + default=0) + parser.add_argument("--spi_cpha_miso", type=int, + help="""SPI clock phase controller samples data. + 0=Data sampled on inactive to active clock edge, + 1=Data sampled on active to inactive clock edge""", + default=0) + + return parser.parse_args() + +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(".load_addr", hex(args.load_addr)) + debug_print(".tag0_loc = ", hex(args.tag0_loc)) + debug_print(".tag1_loc = ", hex(args.tag1_loc)) + debug_print(".header_loc = ", hex(args.header_loc)) + debug_print(".lfw_loc = ", hex(args.lfw_loc)) + debug_print(".lfw_size = ", hex(args.lfw_size)) + 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 (MHz) = ", args.spi_clock) + debug_print(".spi_read_cmd = ", hex(args.spi_read_cmd)) + debug_print(".test_spi = ", args.test_spi) + debug_print(".test_ecrw = ", args.test_ecrw) + debug_print(".verbose = ", args.verbose) + debug_print(".spi_drive_str = ", args.spi_drive_str) + debug_print(".spi_slew_fast = ", args.spi_slew_fast) + debug_print(".spi_cpol = ", args.spi_cpol) + debug_print(".spi_cpha_mosi = ", args.spi_cpha_mosi) + debug_print(".spi_cpha_miso = ", args.spi_cpha_miso) + +def spi_list_append(mylist, loc, data, description): + """Append SPI data block tuple to list""" + t = (loc, data, description) + mylist.append(t) + debug_print("Add SPI entry: offset=0x{0:08x} len=0x{1:0x} descr={2}".format(loc, len(data), description)) + +# +# Handle quiet mode build from Makefile +# Quiet mode when V is unset or V=0 +# Verbose mode when V=1 +# +# MEC172x SPI Image Generator +# No authentication +# No payload encryption +# +# SPI Offset 0x0 = TAG0 points to Header for EC-RO FW +# SPI Offset 0x4 = TAG1 points to Header for EC-RO FW +# TAG Size = 4 bytes +# bits[23:0] = bits[31:8] of Header SPI offset +# bits[31:24] = CRC8-ITU checksum of bits[23:0]. +# +# MEC172x SPI header and payload layout for minimum size +# header offset aligned on 256 byte boundary +# header_spi_address: +# header[0:0x4F] = Header data +# header[0x50:0x80] = ECDSA-P384 public key x for Header authentication +# header[0x80:0xB0] = ECDSA-P384 public key y for Header authentication +# header[0xB0:0xE0] = SHA384 digest of header[0:0xB0] +# header[0xE0:0x110] = ECDSA-P384-SHA384 Signature.R of header[0:0xB0] +# header[0x110:0x140] = ECDSA-P384-SHA384 Signature.S of header[0:0xB0] +# payload_spi_address = header_spi_address + len(Header) +# Payload had been padded such that len(padded_payload) % 128 == 0 +# padded_payload[padded_payload_len] +# payload_signature_address = payload_spi_address + len(padded_payload) +# payload_encryption_key_header[128] Not present if encryption disabled +# payload_cosignature[96] = 0 if Authentication is disabled +# payload_trailer[160] = SHA384(padded_payload || +# optional payload_encryption_key_header) +# || 48 * [0] +# || 48 * [0] +# +def main(): + global debug_print + + args = parseargs() + + if args.verbose: + debug_print = print + + debug_print("Begin pack_ec_mec172x.py script") + + print_args(args) + + chip_dict = MEC172X_DICT + + # Boot-ROM requires header location aligned >= 256 bytes. + # CrOS EC flash image update code requires EC_RO/RW location to be aligned + # on a flash erase size boundary and EC_RO/RW size to be a multiple of + # the smallest flash erase block size. + + spi_size = args.spi_size * 1024 + spi_image_size = spi_size // 2 + + rorofile=PacklfwRoImage(args.input, args.loader_file, args.image_size) + debug_print("Temporary file containing LFW + EC_RO is ", rorofile) + + lfw_ecro = GetPayload(rorofile, chip_dict["PAD_SIZE"]) + lfw_ecro_len = len(lfw_ecro) + debug_print("Padded LFW + EC_RO length = ", hex(lfw_ecro_len)) + + # SPI test mode compute CRC32 of EC_RO and store in last 4 bytes + if args.test_spi: + crc32_ecro = zlib.crc32(bytes(lfw_ecro[LFW_SIZE:-4])) + crc32_ecro_bytes = crc32_ecro.to_bytes(4, byteorder='little') + lfw_ecro[-4:] = crc32_ecro_bytes + debug_print("ecro len = ", hex(len(lfw_ecro) - LFW_SIZE)) + debug_print("CRC32(ecro-4) = ", hex(crc32_ecro)) + + # Reads entry point from offset 4 of file. + # This assumes binary has Cortex-M4 vector table at offset 0. + # 32-bit word at offset 0x0 initial stack pointer value + # 32-bit word at offset 0x4 address of reset handler + # NOTE: reset address will have bit[0]=1 to ensure thumb mode. + lfw_ecro_entry = GetEntryPoint(rorofile) + debug_print("LFW Entry point from GetEntryPoint = 0x{0:08x}".format(lfw_ecro_entry)) + + # Chromebooks are not using MEC BootROM SPI header/payload authentication + # or payload encryption. In this case the header authentication signature + # is filled with the hash digest of the respective entity. + # BuildHeader2 computes the hash digest and stores it in the correct + # header location. + header = BuildHeader2(args, chip_dict, lfw_ecro_len, + args.load_addr, lfw_ecro_entry) + printByteArrayAsHex(header, "Header(lfw_ecro)") + + # If payload encryption used then encrypt payload and + # generate Payload Key Header. If encryption not used + # payload is not modified and the method returns None + encryption_key_header = EncryptPayload(args, chip_dict, lfw_ecro) + printByteArrayAsHex(encryption_key_header, + "LFW + EC_RO encryption_key_header") + + ec_info_block = GenEcInfoBlock(args, chip_dict) + printByteArrayAsHex(ec_info_block, "EC Info Block") + + cosignature = GenCoSignature(args, chip_dict, lfw_ecro) + printByteArrayAsHex(cosignature, "LFW + EC_RO cosignature") + + trailer = GenTrailer(args, chip_dict, lfw_ecro, encryption_key_header, + ec_info_block, cosignature) + + printByteArrayAsHex(trailer, "LFW + EC_RO trailer") + + # Build TAG0. Set TAG1=TAG0 Boot-ROM is allowed to load EC-RO only. + tag0 = BuildTag(args) + tag1 = tag0 + + debug_print("Call to GetPayloadFromOffset") + debug_print("args.input = ", args.input) + debug_print("args.image_size = ", hex(args.image_size)) + + ecrw = GetPayloadFromOffset(args.input, args.image_size, + chip_dict["PAD_SIZE"]) + debug_print("type(ecrw) is ", type(ecrw)) + debug_print("len(ecrw) is ", hex(len(ecrw))) + + # truncate to args.image_size + ecrw_len = len(ecrw) + if ecrw_len > args.image_size: + debug_print("Truncate EC_RW len={0:0x} to image_size={1:0x}".format(ecrw_len,args.image_size)) + ecrw = ecrw[:args.image_size] + ecrw_len = len(ecrw) + + debug_print("len(EC_RW) = ", hex(ecrw_len)) + + # SPI test mode compute CRC32 of EC_RW and store in last 4 bytes + if args.test_spi: + crc32_ecrw = zlib.crc32(bytes(ecrw[0:-4])) + crc32_ecrw_bytes = crc32_ecrw.to_bytes(4, byteorder='little') + ecrw[-4:] = crc32_ecrw_bytes + debug_print("ecrw len = ", hex(len(ecrw))) + debug_print("CRC32(ecrw) = ", hex(crc32_ecrw)) + + # Assume FW layout is standard Cortex-M style with vector + # table at start of binary. + # 32-bit word at offset 0x0 = Initial stack pointer + # 32-bit word at offset 0x4 = Address of reset handler + ecrw_entry_tuple = struct.unpack_from('<I', ecrw, 4) + debug_print("ecrw_entry_tuple[0] = ", hex(ecrw_entry_tuple[0])) + + ecrw_entry = ecrw_entry_tuple[0] + debug_print("ecrw_entry = ", hex(ecrw_entry)) + + # Note: payload_rw is a bytearray therefore is mutable + if args.test_ecrw: + gen_test_ecrw(ecrw) + + os.remove(rorofile) # clean up the temp file + + spi_list = [] + + # MEC172x Add TAG's + #spi_list.append((args.tag0_loc, tag0, "tag0")) + #spi_list.append((args.tag1_loc, tag1, "tag1")) + spi_list_append(spi_list, args.tag0_loc, tag0, "TAG0") + spi_list_append(spi_list, args.tag1_loc, tag1, "TAG1") + + # Boot-ROM SPI image header for LFW+EC-RO + #spi_list.append((args.header_loc, header, "header(lfw + ro)")) + spi_list_append(spi_list, args.header_loc, header, "LFW-EC_RO Header") + + spi_list_append(spi_list, args.lfw_loc, lfw_ecro, "LFW-EC_RO FW") + + offset = args.lfw_loc + len(lfw_ecro) + debug_print("SPI offset after LFW_ECRO = 0x{0:08x}".format(offset)) + + if ec_info_block != None: + spi_list_append(spi_list, offset, ec_info_block, "LFW-EC_RO Info Block") + offset += len(ec_info_block) + + debug_print("SPI offset after ec_info_block = 0x{0:08x}".format(offset)) + + if cosignature != None: + #spi_list.append((offset, co-signature, "ECRO Co-signature")) + spi_list_append(spi_list, offset, cosignature, "LFW-EC_RO Co-signature") + offset += len(cosignature) + + debug_print("SPI offset after co-signature = 0x{0:08x}".format(offset)) + + if trailer != None: + #spi_list.append((offset, trailer, "ECRO Trailer")) + spi_list_append(spi_list, offset, trailer, "LFW-EC_RO trailer") + offset += len(trailer) + + debug_print("SPI offset after trailer = 0x{0:08x}".format(offset)) + + # 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)) + + #spi_list.append((rw_offset, ecrw, "ecrw")) + spi_list_append(spi_list, rw_offset, ecrw, "EC_RW") + offset = rw_offset + len(ecrw) + + spi_list = sorted(spi_list) + + debug_print("Display spi_list:") + dumpsects(spi_list) + + # + # MEC172x Boot-ROM locates TAG0/1 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() |