blob: 90b580a7fde17eae1bb780f2390e38c9392d5ab2 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
r"""Takes a compiled VMEM image and processes it for loading into flash.
Specifically, this takes a raw flash image and adds both layers of ECC
(integrity and reliablity), and optionally, scrambles the data using the
same XEX scrambling scheme used in the flash controller. This enables
backdoor loading the flash on simulation platforms (e.g., DV and Verilator).
"""
import argparse
import functools
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import List
from pyfinite import ffield
from util.design.lib.Present import Present
import prince
import secded_gen
# Fixed OTP data / scrambling parameters.
OTP_WORD_SIZE = 16 # bits
OTP_SECRET1_RE = re.compile(r"SECRET1")
OTP_SECRET1_BLOCK_SIZE = 64 # bits
OTP_SECRET1_PRESENT_KEY = 0x5703C3EB2BB563689E00A67814EFBDE8
OTP_SECRET1_PRESENT_KEY_LENGTH = 128 # bits
OTP_SECRET1_PRESENT_NUM_ROUNDS = 32
OTP_FLASH_ADDR_KEY_SEED_SIZE = 256 # bits
OTP_FLASH_DATA_KEY_SEED_SIZE = 256 # bits
# Computed OTP data / scrambling parameters.
# ------------------------------------------------------------------------------
# DO NOT EDIT: edit fixed parameters above instead.
# ------------------------------------------------------------------------------
OTP_SECRET1_PRESENT_CIPHER = Present(OTP_SECRET1_PRESENT_KEY,
rounds=OTP_SECRET1_PRESENT_NUM_ROUNDS,
keylen=OTP_SECRET1_PRESENT_KEY_LENGTH)
OTP_SECRET1_FLASH_ADDR_KEY_SEED_START = 0
OTP_SECRET1_FLASH_ADDR_KEY_SEED_STOP = (OTP_FLASH_ADDR_KEY_SEED_SIZE //
OTP_SECRET1_BLOCK_SIZE)
OTP_SECRET1_FLASH_DATA_KEY_SEED_START = (OTP_FLASH_ADDR_KEY_SEED_SIZE //
OTP_SECRET1_BLOCK_SIZE)
OTP_SECRET1_FLASH_DATA_KEY_SEED_STOP = OTP_SECRET1_FLASH_DATA_KEY_SEED_START + (
OTP_FLASH_DATA_KEY_SEED_SIZE // OTP_SECRET1_BLOCK_SIZE)
# ------------------------------------------------------------------------------
# Flash data / scrambling parameters.
FLASH_ADDR_KEY_SIZE = 128 # bits
FLASH_DATA_KEY_SIZE = 128 # bits
FLASH_WORD_SIZE = 64 # bits
FLASH_ADDR_SIZE = 16 # bits
FLASH_INTEGRITY_ECC_SIZE = 4 # bits
FLASH_RELIABILITY_ECC_SIZE = 8 # bits
FLASH_PRINCE_NUM_HALF_ROUNDS = 5
# Computed flash data / scrambling parameters.
# ------------------------------------------------------------------------------
# DO NOT EDIT: edit fixed parameters above instead.
# ------------------------------------------------------------------------------
FLASH_GF_OPERAND_B_MASK = (2**FLASH_WORD_SIZE) - 1
FLASH_GF_OPERAND_A_MASK = (
FLASH_GF_OPERAND_B_MASK &
~(0xffff << (FLASH_WORD_SIZE - FLASH_ADDR_SIZE))) << FLASH_WORD_SIZE
# Create GF(2^64) with irreducible_polynomial = x^64 + x^4 + x^3 + x + 1
FLASH_GF_2_64 = ffield.FField(64,
gen=((0x1 << 64) | (0x1 << 4) | (0x1 << 3) |
(0x1 << 1) | 0x1))
# ------------------------------------------------------------------------------
@dataclass
class FlashScramblingConfigs:
scrambling_enabled: bool = False
addr_key_seed: int = None
data_key_seed: int = None
addr_key: int = None
data_key: int = None
@functools.lru_cache(maxsize=None)
def _xex_scramble(data: int, word_addr: int, flash_addr_key: int,
flash_data_key: int) -> int:
operand_a = ((flash_addr_key & FLASH_GF_OPERAND_A_MASK) >>
(FLASH_WORD_SIZE - FLASH_ADDR_SIZE)) | word_addr
operand_b = flash_addr_key & FLASH_GF_OPERAND_B_MASK
mask = FLASH_GF_2_64.Multiply(operand_a, operand_b)
masked_data = data ^ mask
return prince.prince(masked_data, flash_data_key,
FLASH_PRINCE_NUM_HALF_ROUNDS) ^ mask
def _convert_array_2_int(data_array: List[int],
data_size: int,
little_endian=True) -> int:
"""Converts array of data blocks to an int."""
reformatted_data = 0
if not little_endian:
data_array.reverse()
for i, data in enumerate(data_array):
reformatted_data |= (data << (i * data_size))
return reformatted_data
def _get_flash_scrambling_configs(otp_vmem_file: str,
configs: FlashScramblingConfigs) -> None:
# Open OTP VMEM file and read into memory, skipping comment lines.
try:
otp_vmem = Path(otp_vmem_file).read_text()
except IOError:
raise Exception(f"Unable to open {otp_vmem}")
otp_vmem_lines = re.findall(r"^@.*$", otp_vmem, flags=re.MULTILINE)
# TODO: Retrieve partition with the flash scrambling enablement flags.
configs.scrambling_enabled = False
# Retrieve SECRET1 partition which contains the flash scrambling key seeds,
# stripping ECC bits from each data word when processing.
data_blocks_64bit = []
data_block_64bit = 0
idx = 0
for line in otp_vmem_lines:
if OTP_SECRET1_RE.search(line):
otp_data_word_w_ecc = int(line.split()[1], 16)
otp_data_word = otp_data_word_w_ecc & (2**OTP_WORD_SIZE - 1)
data_block_64bit |= otp_data_word << (idx * OTP_WORD_SIZE)
idx += 1
if idx == (64 // OTP_WORD_SIZE):
data_blocks_64bit.append(data_block_64bit)
data_block_64bit = 0
idx = 0
# Descramble SECRET1 partition data blocks and extract flash scrambling key
# seeds. The SECRET1 partition layout looks like:
# {FLASH_ADDR_KEY_SEED, FLASH_DATA_KEY_SEED, SRAM_DATA_KEY_SEED, DIGEST}
descrambled_data_blocks = list(
map(OTP_SECRET1_PRESENT_CIPHER.decrypt, data_blocks_64bit))
configs.addr_key_seed = _convert_array_2_int(
descrambled_data_blocks[OTP_SECRET1_FLASH_ADDR_KEY_SEED_START:
OTP_SECRET1_FLASH_ADDR_KEY_SEED_STOP],
OTP_SECRET1_BLOCK_SIZE)
configs.data_key_seed = _convert_array_2_int(
descrambled_data_blocks[OTP_SECRET1_FLASH_DATA_KEY_SEED_START:
OTP_SECRET1_FLASH_DATA_KEY_SEED_STOP],
OTP_SECRET1_BLOCK_SIZE)
def _compute_flash_scrambling_keys(
scrambling_configs: FlashScramblingConfigs) -> None:
# TODO: implement key computation
scrambling_configs.addr_key = 0
scrambling_configs.data_key = 0
def _reformat_flash_vmem(
flash_vmem_file: str,
scrambling_configs: FlashScramblingConfigs) -> List[str]:
# Open (raw) flash VMEM file and read into memory, skipping comment lines.
try:
flash_vmem = Path(flash_vmem_file).read_text()
except IOError:
raise Exception(f"Unable to open {flash_vmem_file}")
flash_vmem_lines = re.findall(r"^@.*$", flash_vmem, flags=re.MULTILINE)
# Load project SECDED configuration.
ecc_configs = secded_gen.load_secded_config()
# Add integrity/reliability ECC, and potentially scramble, each flash word.
reformatted_vmem_lines = []
for line in flash_vmem_lines:
line_items = line.split()
reformatted_line = ""
address = None
data = None
for item in line_items:
# Process the address first.
if re.match(r"^@", item):
reformatted_line += item
address = int(item.lstrip("@"), 16)
# Process the data words.
else:
data = int(item, 16)
# `data_w_intg_ecc` will be in format {ECC bits, data bits}.
data_w_intg_ecc, _ = secded_gen.ecc_encode(
ecc_configs, "hamming", FLASH_WORD_SIZE, data)
# Due to storage constraints the first nibble of ECC is dropped.
data_w_intg_ecc &= 0xF_FFFF_FFFF_FFFF_FFFF
if scrambling_configs.scrambling_enabled:
intg_ecc = data_w_intg_ecc & (0xF << FLASH_WORD_SIZE)
data = _xex_scramble(data, address,
scrambling_configs.addr_key,
scrambling_configs.data_key)
data_w_intg_ecc = intg_ecc | data
# `data_w_full_ecc` will be in format {reliablity ECC bits,
# integrity ECC bits, data bits}.
data_w_full_ecc, _ = secded_gen.ecc_encode(
ecc_configs, "hamming",
FLASH_WORD_SIZE + FLASH_INTEGRITY_ECC_SIZE,
data_w_intg_ecc)
reformatted_line += f" {data_w_full_ecc:x}"
# Append reformatted line to what will be the new output VMEM file.
reformatted_vmem_lines.append(reformatted_line)
return reformatted_vmem_lines
def main(argv: List[str]):
# Parse command line args.
parser = argparse.ArgumentParser()
parser.add_argument("--in-flash-vmem",
type=str,
help="Input VMEM file to reformat.")
parser.add_argument("--in-otp-vmem",
type=str,
help="Input OTP (VMEM) file to retrieve data from.")
parser.add_argument("--out-flash-vmem", type=str, help="Output VMEM file.")
args = parser.parse_args(argv)
# Read flash scrambling configurations (including: enablement, address and
# data key seeds) directly from OTP VMEM file.
scrambling_configs = FlashScramblingConfigs()
if args.in_otp_vmem:
_get_flash_scrambling_configs(args.in_otp_vmem, scrambling_configs)
# Compute flash scrambling keys from seeds.
if scrambling_configs.scrambling_enabled:
_compute_flash_scrambling_keys(scrambling_configs)
# Reformat flash VMEM file to add integrity/reliablity ECC and scrambling.
reformatted_vmem_lines = _reformat_flash_vmem(args.in_flash_vmem,
scrambling_configs)
# Write re-formatted output file.
with open(args.out_flash_vmem, "w") as of:
of.writelines(reformatted_vmem_lines)
if __name__ == "__main__":
main(sys.argv[1:])