blob: 3215dc41c152e8cf2c699f3dc0a36be44852b39e [file] [log] [blame]
"""MCUBoot Flash Map Converter (JSON to .h)
Copyright (c) 2025 Infineon Technologies AG
"""
import sys
import getopt
import json
import os
from enum import Enum
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
MAX_IMAGE_NUMBERS = 16
NONCE_GEN_RETRIES = 10
NONCE_RAND_SIZE = 12
AES_BLOCK_SIZE = 16
IMG_OK_OFFSET = 8 + 16 # flag_size + magic_size
IMG_OK_VALUE = 0x01
SHAB_ADDR = 0x08000000
class Error(Enum):
''' Application error codes '''
ARG = 1
POLICY = 2
FLASH = 3
IO = 4
JSON = 5
VALUE = 6
CONFIG_MISMATCH = 7
SERVICE_APP_SZ = 0x20
c_array = 'flash_areas'
# Supported Platforms
cm0pCore = {
'cortex-m0+': 'CM0P',
'cm0+': 'CM0P',
'm0+': 'CM0P',
'cortex-m0p': 'CM0P',
'cm0p': 'CM0P',
'm0p': 'CM0P',
'cortex-m0plus': 'CM0P',
'cm0plus': 'CM0P',
'm0plus': 'CM0P'
}
cm4Core = {
'cortex-m4': 'CM4',
'cm4': 'CM4',
'm4': 'CM4'
}
cm33Core = {
'cortex-m33': 'CM33',
'cm33': 'CM33',
'm33': 'CM33'
}
cm7Core = {
'cortex-m7': 'CM7',
'cm7': 'CM7',
'm7': 'CM7'
}
allCores_PSOC_06x = {**cm0pCore, **cm4Core}
common_PSOC_061 = {
'flashAddr': 0x10000000,
'eraseSize': 0x200, # 512 bytes
'smifAddr': 0x18000000,
'smifSize': 0x8000000, # i.e., window size
'VTAlign': 0x400, # Vector Table alignment
'allCores': cm4Core,
'bootCore': 'Cortex-M4',
'appCore': 'Cortex-M4'
}
common_PSOC_06x = {
'flashAddr': 0x10000000,
'eraseSize': 0x200, # 512 bytes
'smifAddr': 0x18000000,
'smifSize': 0x8000000, # i.e., window size
'VTAlign': 0x400, # Vector Table alignment
'allCores': allCores_PSOC_06x,
'bootCore': 'Cortex-M0+',
'appCore': 'Cortex-M4'
}
common_CYW20829 = {
'flashSize': 0, # n/a
'smifAddr': 0x60000000,
'smifSize': 0x8000000, # i.e., window size
'VTAlign': 0x200, # Vector Table alignment
'allCores': cm33Core,
'bootCore': 'Cortex-M33',
'appCore': 'Cortex-M33',
'bitsPerCnt': False
}
common_XMC7000 = {
'flashAddr': 0x10000000,
'eraseSize': 0x8000, # 32k
'smifAddr': 0x18000000,
'smifSize': 0x8000000, # i.e., window size
'VTAlign': 0x400, # Vector Table alignment
'allCores': cm7Core,
'bootCore': 'Cortex-M7',
'appCore': 'Cortex-M7'
}
platDict = {
'PSOC_061_2M': {
'flashSize': 0x200000, # 2 MBytes
**common_PSOC_061
},
'PSOC_061_1M': {
'flashSize': 0x100000, # 1 MByte
**common_PSOC_061
},
'PSOC_061_512K': {
'flashSize': 0x80000, # 512 KBytes
**common_PSOC_061
},
'PSOC_062_2M': {
'flashSize': 0x200000, # 2 MBytes
**common_PSOC_06x
},
'PSOC_062_1M': {
'flashSize': 0x100000, # 1 MByte
**common_PSOC_06x
},
'PSOC_062_512K': {
'flashSize': 0x80000, # 512 KBytes
**common_PSOC_06x
},
'PSOC_063_1M': {
'flashSize': 0x100000, # 1 MByte
**common_PSOC_06x
},
'XMC7200': {
'flashSize': 0x100000, # 1 MByte
**common_XMC7000
},
'XMC7100': {
'flashSize': 0x100000, # 1 MByte
**common_PSOC_06x
},
'CYW20829': {
**common_CYW20829
},
'CYW89829': {
**common_CYW20829
}
}
# Supported SPI Flash ICs
flashDict = {
# Fudan
'FM25Q04': {
'flashSize': 0x80000, # 4 Mbits
'eraseSize': 0x1000, # 128 uniform sectors with 4K-byte each
},
'FM25W04': {
'flashSize': 0x80000, # 4 Mbits
'eraseSize': 0x1000, # 128 uniform sectors with 4K-byte each
},
'FM25Q08': {
'flashSize': 0x100000, # 8 Mbits
'eraseSize': 0x1000, # 256 uniform sectors with 4K-byte each
},
'FM25W08': {
'flashSize': 0x100000, # 8 Mbits
'eraseSize': 0x1000, # 256 uniform sectors with 4K-byte each
},
# Puya
'P25Q05H': {
'flashSize': 0x10000, # 512 Kbits
'eraseSize': 0x1000, # Uniform 4K-byte Sector Erase
},
'P25Q10H': {
'flashSize': 0x20000, # 1 Mbit
'eraseSize': 0x1000, # Uniform 4K-byte Sector Erase
},
'P25Q20H': {
'flashSize': 0x40000, # 2 Mbits
'eraseSize': 0x1000, # Uniform 4K-byte Sector Erase
},
'P25Q40H': {
'flashSize': 0x80000, # 4 Mbits
'eraseSize': 0x1000, # Uniform 4K-byte Sector Erase
},
# Infineon
'S25HS256T': {
'flashSize': 0x2000000, # 256 Mbits
'eraseSize': 0x40000, # Uniform Sector Architecture
},
'S25HS512T': {
'flashSize': 0x4000000, # 512 Mbits
'eraseSize': 0x40000, # Uniform Sector Architecture
},
'S25HS01GT': {
'flashSize': 0x8000000, # 1 Gbit
'eraseSize': 0x40000, # Uniform Sector Architecture
}
}
def is_overlap(fa1off, fa1size, fa2off, fa2size, align):
"""Check if two flash areas on the same device overlap"""
mask = align - 1
assert align > 0 and (align & mask) == 0 # ensure align is a power of 2
fa1end = (fa1off + fa1size + mask) & ~mask
fa2end = (fa2off + fa2size + mask) & ~mask
fa1off = fa1off & ~mask
fa2off = fa2off & ~mask
return fa1off < fa2end and fa2off < fa1end
def is_same_mem(fa1addr, fa2addr):
"""Check if two addresses belong to the same memory"""
if fa1addr is None or fa2addr is None:
return False
mask = 0xFF000000
return (fa1addr & mask) == (fa2addr & mask)
class CmdLineParams:
"""Command line parameters"""
def __init__(self):
self.plat_id = ''
self.in_file = ''
self.out_file = ''
self.mk_file = ''
self.fa_file = ''
self.nonce_file = None
self.img_id = None
self.policy = None
self.set_core = False
self.image_boot_config = False
usage = 'USAGE:\n' + sys.argv[0] + \
''' -p <platform> -i <flash_map.json> -o <memorymap.c> -k <memorymap.mk> -a <memorymap.h> -d <img_id> -c <policy.json>
OPTIONS:
-h --help Display the usage information
-p --platform Target (e.g., PSOC_062_512K)
-i --ifile JSON flash map file
-o --ofile C file to be generated
-k --mk_file Path where to create 'memorymap.mk'
-a --fa_file Path where to create 'memorymap.h'
-n --nonce_file Path where to create file with NONCE
-d --img_id ID of application to build
-c --policy Policy file in JSON format
-m --core Detect and set Cortex-M CORE
-x --image_boot_config Generate image boot config structure
'''
try:
opts, unused = getopt.getopt(
sys.argv[1:], 'h:i:o:k:a:n:p:d:c:x:m',
['help', 'platform=', 'ifile=', 'ofile=', 'mk_file=', 'fa_file=',
'nonce_file=', 'img_id=', 'policy=', 'core', 'image_boot_config'])
except getopt.GetoptError:
print(usage, file=sys.stderr)
sys.exit(Error.ARG)
for opt, arg in opts:
if opt in ('-h', '--help'):
print(usage, file=sys.stderr)
sys.exit()
elif opt in ('-p', '--platform'):
self.plat_id = arg
elif opt in ('-i', '--ifile'):
self.in_file = arg
elif opt in ('-o', '--ofile'):
self.out_file = arg
elif opt in ('-k', '--mk_file'):
self.mk_file = arg
elif opt in ('-a', '--fa_file'):
self.fa_file = arg
elif opt in ('-n', '--nonce_file'):
self.nonce_file = arg
elif opt in ('-d', '--img_id'):
self.img_id = arg
elif opt in ('-c', '--policy'):
self.policy = arg
elif opt in ('-m', '--core'):
self.set_core = True
elif opt in ('x', '--image_boot_config'):
self.image_boot_config = True
if any(len(x) == 0 for x in (self.in_file, self.out_file, self.mk_file, self.fa_file)):
print(usage, file=sys.stderr)
sys.exit(Error.ARG)
class AreaList:
'''
A List of flash areas
...
Attributes
----------
plat : dict
Platform settings
flash : dict
External flash settings
use_overwrite : bool
Overwrite configuration in use
areas : list
Flash area parameter list
peers : set
Peers
trailers : set
Flash area trailers
internal_flash : bool
Internal flash in use
external_flash : bool
External flash in use
external_flash_xip : bool
External XIP in use
Methods
-------
get_min_erase_size:
Calculate minimum erase block size for int./ext. Flash
get_img_trailer_size:
Calculate image trailer size
process_int_area:
Process internal flash area
process_ext_area:
Process external flash area
chk_area:
Check area location (internal/external flash)
add_area:
Add flash area to AreaList.
Internal/external flash is detected by address.
generate_c_source:
Generate C source
create_flash_area_id:
Creates flash_area_id.h file.
'''
def __init__(self, plat, flash, use_overwrite):
self.plat = plat
self.flash = flash
self.use_overwrite = use_overwrite
self.use_direct_xip = False
self.areas = []
self.peers = {}
self.trailers = {}
self.internal_flash = False
self.external_flash = False
self.external_flash_xip = False
def get_min_erase_size(self):
'''Calculate minimum erase block size for int./ext. Flash '''
return self.plat['eraseSize'] if self.plat['flashSize'] > 0 \
else self.flash['eraseSize']
def get_img_trailer_size(self):
'''Calculate image trailer size'''
return self.get_min_erase_size()
def process_int_area(self, title, addr, fa_size,
img_trailer_size, shared_slot):
'''
Process internal flash area
Parameters:
----------
title : str
Area name
addr : int
Area address
fa_size : int
Area size
img_trailer_size : int
Trailer size
shared_slot : bool
Shared slot option in use
Returns:
----------
fa_device_id : str
fa_off : int
slot_sectors : int
'''
fa_device_id = 'FLASH_DEVICE_INTERNAL_FLASH'
fa_off = addr - self.plat['flashAddr']
if img_trailer_size is not None:
if self.use_overwrite:
if shared_slot:
print('Shared slot', title,
'is not supported in OVERWRITE mode',
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
else:
# Check trailer alignment (start at the sector boundary)
align = (fa_off + fa_size - img_trailer_size) % \
self.plat['eraseSize']
if align != 0:
addr += self.plat['eraseSize'] - align
if addr + fa_size <= \
self.plat['flashAddr'] + self.plat['flashSize']:
print('Misaligned', title,
'- suggested address', hex(addr),
file=sys.stderr)
else:
print('Misaligned', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
else:
# Check alignment (flash area should start at the sector boundary)
if fa_off % self.plat['eraseSize'] != 0:
print('Misaligned', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
slot_sectors = int((fa_off % self.plat['eraseSize'] +
fa_size + self.plat['eraseSize'] - 1) //
self.plat['eraseSize'])
return fa_device_id, fa_off, slot_sectors
def process_ext_area(self, title, addr, fa_size,
img_trailer_size, shared_slot):
'''
Process external flash area
Parameters:
----------
title : str
Area name
addr : int
Area address
fa_size : int
Area size
img_trailer_size : int
Trailer size
shared_slot : bool
Shared slot option in use
Returns:
----------
fa_device_id : str
fa_off : int
slot_sectors : int
'''
if self.flash is None:
print('Unspecified SPI Flash IC',
file=sys.stderr)
sys.exit(Error.FLASH)
if addr + fa_size <= \
self.plat['smifAddr'] + self.flash['flashSize']:
flash_idx = 'CY_BOOT_EXTERNAL_DEVICE_INDEX'
fa_device_id = f'FLASH_DEVICE_EXTERNAL_FLASH({flash_idx})'
fa_off = addr - self.plat['smifAddr']
else:
print('Misfitting', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
if img_trailer_size is not None:
if self.use_overwrite:
if shared_slot:
print('Shared slot', title,
'is not supported in OVERWRITE mode',
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
else:
# Check trailer alignment (start at the sector boundary)
align = (fa_off + fa_size - img_trailer_size) % \
self.flash['eraseSize']
if align != 0:
peer_addr = self.peers.get(addr)
if shared_slot:
# Special case when using both int. and ext. memory
if self.plat['flashSize'] > 0 and \
align % self.plat['eraseSize'] == 0:
print('Note:', title, 'requires', align,
'padding bytes before trailer',
file=sys.stderr)
else:
print('Misaligned', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
elif is_same_mem(addr, peer_addr) and \
addr % self.flash['eraseSize'] == \
peer_addr % self.flash['eraseSize']:
pass # postpone checking
else:
addr += self.flash['eraseSize'] - align
if addr + fa_size <= \
self.plat['smifAddr'] + self.flash['flashSize']:
print('Misaligned', title,
'- suggested address', hex(addr),
file=sys.stderr)
else:
print('Misaligned', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
else:
# Check alignment (flash area should start at the sector boundary)
if fa_off % self.flash['eraseSize'] != 0:
print('Misaligned', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
slot_sectors = int((fa_off % self.flash['eraseSize'] +
fa_size + self.flash['eraseSize'] - 1) //
self.flash['eraseSize'])
self.external_flash = True
if self.flash['XIP']:
self.external_flash_xip = True
return fa_device_id, fa_off, slot_sectors
def chk_area(self, addr, fa_size, peer_addr=None):
'''
Check area location (internal/external flash)
Parameters:
----------
addr : int
Area address
fa_size : int
Area size
peer_addr : bool (optional)
Shared slot option in use
Returns:
----------
None
'''
if peer_addr is not None:
self.peers[peer_addr] = addr
fa_limit = addr + fa_size
if self.plat['flashSize'] and \
addr >= self.plat['flashAddr'] and \
fa_limit <= self.plat['flashAddr'] + self.plat['flashSize']:
# Internal flash
self.internal_flash = True
def add_area(self, title,
fa_id, addr, fa_size,
img_trailer_size=None, shared_slot=False):
'''
Add flash area to AreaList.
Internal/external flash is detected by address.
Parameters:
----------
title : str
Area name
fa_id : str
Area id
addr : int
Area address
fa_size : int
Area size
img_trailer_size : int
Trailer size (optional)
shared_slot : bool
Shared slot option in use (optional)
Returns:
----------
slot_sectors : int
Number of sectors in a slot
'''
if fa_size == 0:
print('Empty', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
fa_limit = addr + fa_size
if self.plat['flashSize'] and \
addr >= self.plat['flashAddr'] and \
fa_limit <= self.plat['flashAddr'] + self.plat['flashSize']:
# Internal flash
fa_device_id, fa_off, slot_sectors = self.process_int_area(
title, addr, fa_size, img_trailer_size, shared_slot)
align = self.plat['eraseSize']
elif self.plat['smifSize'] and \
addr >= self.plat['smifAddr'] and \
fa_limit <= self.plat['smifAddr'] + self.plat['smifSize']:
# External flash
fa_device_id, fa_off, slot_sectors = self.process_ext_area(
title, addr, fa_size, img_trailer_size, shared_slot)
align = self.flash['eraseSize']
else:
print('Invalid', title, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
if shared_slot:
assert img_trailer_size is not None
tr_addr = addr + fa_size - img_trailer_size
tr_name = self.trailers.get(tr_addr)
if tr_name is not None:
print('Same trailer address for', title, 'and', tr_name,
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
self.trailers[tr_addr] = title
# Ensure no flash areas on this device will overlap, except the
# shared slot
for area in self.areas:
if fa_device_id == area['fa_device_id']:
over = is_overlap(fa_off, fa_size,
area['fa_off'], area['fa_size'],
align)
if shared_slot and area['shared_slot']:
if not over: # images in shared slot should overlap
print(title, 'is not shared with', area['title'],
file=sys.stderr)
elif over:
print(title, 'overlaps with', area['title'],
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
self.areas.append({'title': title,
'shared_slot': shared_slot,
'fa_id': fa_id,
'fa_device_id': fa_device_id,
'fa_off': fa_off,
'fa_size': fa_size})
return slot_sectors
def generate_c_source(self, params, boot_and_upgrade):
'''
Generate C source
Parameters:
----------
params : CmdLineParams
Application parameters
Returns:
----------
None
'''
c_array = 'flash_areas'
try:
with open(params.out_file, "w", encoding='UTF-8') as out_f:
out_f.write(f'#include "{params.fa_file}"\n')
out_f.write(f'#include "flash_map_backend.h"\n\n')
out_f.write(f'#include "flash_map_backend_platform.h"\n\n')
out_f.write(f'const struct flash_area {c_array}[] = {{\n')
comma = len(self.areas)
area_count = 0
for area in self.areas:
comma -= 1
if area['fa_id'] is not None:
sss = ' /* Shared secondary slot */' \
if area['shared_slot'] else ''
out_f.writelines('\n'.join([
' {' + sss,
f" .fa_id = {area['fa_id']},",
f" .fa_device_id = {area['fa_device_id']},",
f" .fa_off = {hex(area['fa_off'])}U,",
f" .fa_size = {hex(area['fa_size'])}U",
' },' if comma else ' }', '']))
area_count += 1
out_f.write('};\n\n'
'const struct flash_area * const boot_area_descs[] = {\n')
for area_index in range(area_count):
out_f.write(f' &{c_array}[{area_index}U],\n')
out_f.write(' NULL\n};\n')
image_boot_mode = None
if params.image_boot_config:
image_boot_mode = process_boot_type(boot_and_upgrade)
if image_boot_mode:
out_f.write('\nimage_boot_config_t image_boot_config[BOOT_IMAGE_NUMBER] = {\n')
for mode in image_boot_mode:
out_f.writelines('\n'.join([
'\t{\n'
f"\t\t.mode = {mode['mode']},",
f"\t\t.address = {mode['address']},",
f"\t\t.size = {mode['size']},",
'\t},\n']))
out_f.write('};\n')
out_f.close()
except (FileNotFoundError, OSError):
print('Cannot create', params.out_file, file=sys.stderr)
sys.exit(Error.IO)
def create_flash_area_id(self, img_number, params):
""" Get 'img_number' and generate flash_area_id.h file' """
#check if params.fa_file already exists and it has FLASH_AREA_ID
if os.path.exists(params.fa_file):
with open(params.fa_file, "r", encoding='UTF-8') as fa_f:
content = fa_f.read()
res = content.find(f"FLASH_AREA_IMG_{img_number}_SECONDARY")
if res != -1:
fa_f.close()
return
fa_f.close()
try:
with open(params.fa_file, "w", encoding='UTF-8') as fa_f:
fa_f.write("#pragma once\n")
fa_f.write('/* AUTO-GENERATED FILE, DO NOT EDIT.'
' ALL CHANGES WILL BE LOST! */\n')
fa_f.write(f'#include "flash_map_backend.h"\n\n')
fa_f.write('#include "bootutil/bootutil.h"\n')
fa_f.write(f'extern const struct flash_area {c_array}[];\n')
fa_f.write(f'extern const struct flash_area * const boot_area_descs[];\n')
#we always have BOOTLOADER and IMG_1_
fa_f.write("#define FLASH_AREA_BOOTLOADER ( 0u)\n\n")
fa_f.write("#define FLASH_AREA_IMG_1_PRIMARY ( 1u)\n")
fa_f.write("#define FLASH_AREA_IMG_1_SECONDARY ( 2u)\n\n")
fa_f.write("#define FLASH_AREA_IMAGE_SCRATCH ( 3u)\n")
fa_f.write("#define FLASH_AREA_IMAGE_SWAP_STATUS ( 7u)\n\n")
for img in range(2, img_number + 1):
""" img_id_primary and img_id_secondary must be aligned with the
flash_area_id, calculated in the functions
__STATIC_INLINE uint8_t FLASH_AREA_IMAGE_PRIMARY(uint32_t img_idx) and
__STATIC_INLINE uint8_t FLASH_AREA_IMAGE_SECONDARY(uint32_t img_idx),
in boot/cypress/platforms/memory/sysflash/sysflash.h
"""
slots_for_image = 2
img_id_primary = None
img_id_secondary = None
if img == 2:
img_id_primary = int(slots_for_image * img)
img_id_secondary = int(slots_for_image * img + 1)
#number 7 is used for FLASH_AREA_IMAGE_SWAP_STATUS, so the next is 8
if img >= 3:
img_id_primary = int(slots_for_image * img + 2)
img_id_secondary = int(slots_for_image * img + 3)
fa_f.write(f"#define FLASH_AREA_IMG_{img}_PRIMARY ( {img_id_primary}u)\n")
fa_f.write(f"#define FLASH_AREA_IMG_{img}_SECONDARY ( {img_id_secondary}u)\n\n")
if self.plat.get('bitsPerCnt'):
list_counters = process_policy_20829(params.policy)
if list_counters is not None:
form_max_counter_array(list_counters, fa_f)
fa_f.writelines('\n'.join([
'',
'typedef enum',
'{',
'\tIMAGE_BOOT_MODE_FLASH = 0U,',
'\tIMAGE_BOOT_MODE_RAM = 1U,',
'} image_boot_mode_t;',
'',
'typedef struct image_boot_config_s {',
'\timage_boot_mode_t mode;',
'\tuint32_t address;',
'\tuint32_t size;',
'} image_boot_config_t;',
'',
'extern image_boot_config_t image_boot_config[BOOT_IMAGE_NUMBER];'
]))
fa_f.close()
except (FileNotFoundError, OSError):
print('\nERROR: Cannot create ', params.fa_file, file=sys.stderr)
sys.exit(Error.IO)
def cvt_dec_or_hex(val, desc):
"""Convert (hexa)decimal string to number"""
try:
return int(val, 0)
except ValueError:
print('Invalid value', val, 'for', desc, file=sys.stderr)
sys.exit(Error.VALUE)
def get_val(obj, attr):
"""Get JSON 'value'"""
obj = obj[attr]
try:
return cvt_dec_or_hex(obj['value'], obj['description'])
except KeyError as key:
print('Malformed JSON:', key,
'is missing in', "'" + attr + "'",
file=sys.stderr)
sys.exit(Error.JSON)
def get_bool(obj, attr, def_val=False):
"""Get JSON boolean value (returns def_val if it is missing)"""
ret_val = def_val
obj = obj.get(attr)
if obj is not None:
try:
val = str(obj['value']).lower()
desc = obj['description']
if val == 'true':
ret_val = True
elif val == 'false':
ret_val = False
else:
print('Invalid value', val, 'for', desc, file=sys.stderr)
sys.exit(Error.VALUE)
except KeyError as key:
print('Malformed JSON:', key,
'is missing in', "'" + attr + "'",
file=sys.stderr)
sys.exit(Error.JSON)
return ret_val
def get_str(obj, attr, def_val=None):
"""Get JSON string value (returns def_val if it is missing)"""
ret_val = def_val
obj = obj.get(attr)
if obj is not None:
try:
ret_val = str(obj['value'])
except KeyError as key:
print('Malformed JSON:', key,
'is missing in', "'" + attr + "'",
file=sys.stderr)
sys.exit(Error.JSON)
return ret_val
class AddrSize:
"""Bootloader area"""
def __init__(self, bootloader, addr_name, size_name):
self.addr = get_val(bootloader, addr_name)
self.size = get_val(bootloader, size_name)
def calc_status_size(boot_swap_status_row_sz, max_img_sectors,
img_number, scratch_flag=True):
"""Estimate status size, see swap_status.h"""
boot_swap_status_cnt_sz = 4
boot_swap_status_crc_sz = 4
boot_swap_status_mgcrec_sz = 4
boot_swap_status_trailer_size = 64
boot_swap_status_payld_sz = \
boot_swap_status_row_sz - boot_swap_status_mgcrec_sz - \
boot_swap_status_cnt_sz - boot_swap_status_crc_sz
boot_swap_status_sect_rows_num = \
int((max_img_sectors - 1) //
boot_swap_status_payld_sz) + 1
boot_swap_status_trail_rows_num = \
int((boot_swap_status_trailer_size - 1) //
boot_swap_status_payld_sz) + 1
boot_swap_status_d_size = \
boot_swap_status_row_sz * \
(boot_swap_status_sect_rows_num + boot_swap_status_trail_rows_num)
boot_swap_status_mult = 2
boot_swap_status_size = boot_swap_status_mult * boot_swap_status_d_size
status_zone_cnt = 2 * img_number
if scratch_flag:
status_zone_cnt += 1
return boot_swap_status_size * status_zone_cnt
def process_json(in_file):
"""Process JSON"""
try:
with open(in_file, encoding='UTF-8') as in_f:
try:
flash_map = json.load(in_f)
except ValueError:
print('Cannot parse', in_file, file=sys.stderr)
sys.exit(Error.IO)
except (FileNotFoundError, OSError):
print('Cannot open', in_file, file=sys.stderr)
sys.exit(Error.IO)
flash = flash_map.get('external_flash')
if flash is not None:
flash = flash[0]
model = flash.get('model')
mode = flash.get('mode')
if model is not None:
try:
flash = flashDict[model]
except KeyError:
print('Supported SPI Flash ICs are:',
', '.join(flashDict.keys()),
file=sys.stderr)
sys.exit(Error.FLASH)
else:
try:
flash = {'flashSize': cvt_dec_or_hex(flash['flash-size'],
'flash-size'),
'eraseSize': cvt_dec_or_hex(flash['erase-size'],
'erase-size')}
except KeyError as key:
print('Malformed JSON:', key,
"is missing in 'external_flash'",
file=sys.stderr)
sys.exit(Error.FLASH)
flash.update({'XIP': str(mode).upper() == 'XIP'})
return flash_map.get('boot_and_upgrade', None), flash_map.get('ram_app_staging', None), flash
def process_boot_type(boot_and_upgrade):
image_boot_mode = []
for app_index in range(1, 5):
app_ident = f'application_{app_index}'
application = boot_and_upgrade.get(app_ident)
if application:
mem = application.get('ram_boot')
if mem:
image_boot_mode.append(
{
'mode': 'IMAGE_BOOT_MODE_RAM',
'address': mem.get('address', {}).get('value', 0),
'size': mem.get('size', {}).get('value', 0),
}
)
else :
mem = application.get('flash')
image_boot_mode.append(
{
'mode': 'IMAGE_BOOT_MODE_FLASH',
'address': mem.get('address', {}).get('value', 0),
'size': mem.get('size', {}).get('value', 0),
}
)
return image_boot_mode
def process_images(area_list, boot_and_upgrade):
"""Process images"""
app_count = 0
slot_sectors_max = 0
all_shared = get_bool(boot_and_upgrade['bootloader'], 'shared_slot')
any_shared = all_shared
app_core = None
apps_flash_map = [None, ]
apps_ram_map = [None, ]
for stage in range(2):
for app_index in range(1, MAX_IMAGE_NUMBERS):
app_flash_map = {}
app_ram_map = {}
app_ram_boot = False
try:
app_ident = f'application_{app_index}'
application = boot_and_upgrade[app_ident]
try:
flash = application['flash']
except KeyError:
#Backward compatibility
flash = application
try:
config = application['config']
except KeyError:
#Backward compatibility
config = application
try:
ram = application['ram']
except KeyError:
try:
ram = application['ram_boot']
app_ram_boot = True
except KeyError:
ram = None
try:
try:
primary_addr = get_val(flash, 'primary_slot')
secondary_addr = get_val(flash, 'secondary_slot')
primary_size = get_val(flash, 'primary_slot_size')
secondary_size = get_val(flash, 'secondary_slot_size')
area_list.use_direct_xip = True
except KeyError:
primary_addr = get_val(flash, 'address')
secondary_addr = get_val(flash, 'upgrade_address')
primary_size = get_val(flash, 'size')
secondary_size = get_val(flash, 'upgrade_size')
if ram is not None:
app_ram_addr = get_val(ram, 'address')
app_ram_size = get_val(ram, 'size')
else:
app_ram_addr = None
app_ram_size = None
except KeyError as key:
print('Malformed JSON:', key, 'is missing',
file=sys.stderr)
sys.exit(Error.JSON)
if stage == 0:
if primary_size != secondary_size:
print('Primary and secondary slot sizes'
' are different for', app_ident,
file=sys.stderr)
sys.exit(Error.VALUE)
area_list.chk_area(primary_addr, primary_size)
area_list.chk_area(secondary_addr, secondary_size,
primary_addr)
if config is None or config.get('core') is None:
if app_index == 1:
app_core = area_list.plat['appCore']
elif app_index > 1:
print('"core" makes sense only for the 1st app',
file=sys.stderr)
sys.exit(Error.VALUE)
else:
app_core = get_str(config, 'core',
area_list.plat['appCore'])
if app_index == 1:
app_core = area_list.plat['allCores'].get(app_core.lower())
if app_core is None:
print('Unknown "core"', file=sys.stderr)
sys.exit(Error.VALUE)
else:
slot_sectors_max = max(
slot_sectors_max,
area_list.add_area(
f'{app_ident} (primary slot)',
f'FLASH_AREA_IMG_{app_index}_PRIMARY',
primary_addr, primary_size,
area_list.get_img_trailer_size()))
shared_slot = get_bool(flash, 'shared_slot', all_shared)
any_shared = any_shared or shared_slot
slot_sectors_max = max(
slot_sectors_max,
area_list.add_area(
f'{app_ident} (secondary slot)',
f'FLASH_AREA_IMG_{app_index}_SECONDARY',
secondary_addr, secondary_size,
area_list.get_img_trailer_size(),
shared_slot))
app_slot_prim = {"address": hex(primary_addr), "size": hex(primary_size)}
app_slot_sec = {"address": hex(secondary_addr), "size": hex(secondary_size)}
app_flash_map.update({"primary": app_slot_prim, "secondary": app_slot_sec})
apps_flash_map.append(app_flash_map)
if ram is not None:
app_ram_map.update({"address": app_ram_addr
, "size": app_ram_size
, "ram_boot": app_ram_boot})
apps_ram_map.append(app_ram_map)
else:
apps_ram_map = None
app_count = app_index
except KeyError:
break
if app_count == 0:
print('Malformed JSON: no application(s) found',
file=sys.stderr)
sys.exit(Error.JSON)
return app_core, app_count, slot_sectors_max, apps_flash_map, apps_ram_map, any_shared
def process_policy_20829(in_policy):
"""Process policy file to get data of NV-counter"""
list_counters = None
try:
with open(in_policy, encoding='UTF-8') as in_f:
try:
policy = json.load(in_f)
except ValueError:
print('\nERROR: Cannot parse', in_policy,'\n', file=sys.stderr)
sys.exit(Error.IO)
finally:
in_f.close()
except (FileNotFoundError, OSError):
print('Cannot open', in_policy, file=sys.stderr)
sys.exit(Error.IO)
try:
nv_cnt = policy["device_policy"]['reprovisioning']['nv_counter']
list_values = nv_cnt["value"]
list_counters = nv_cnt["bits_per_cnt"]
except KeyError:
print("\nERROR: Check path to 'nv_counter' and its correctness in policy file", in_policy,
".\n", file=sys.stderr)
sys.exit(Error.POLICY)
#Check correctness of NV-counter
try:
len_list_value = len(list_values)
len_list_counters = len(list_counters)
except TypeError:
print("\nERROR: Fields 'value' and 'bits_per_cnt' of 'nv_counter' in policy file",
in_policy,"must be arrays.\n", file=sys.stderr)
sys.exit(Error.POLICY)
if len_list_value != len_list_counters:
print("\nERROR: Fields 'value' and 'bits_per_cnt' of 'nv_counter' in policy file",
in_policy,"must have the same size.\n", file=sys.stderr)
sys.exit(Error.POLICY)
sum_all_counters = 0
for i in range(len_list_value):
sum_all_counters += list_counters[i]
if list_values[i] > list_counters[i]:
print("\nERROR: Field 'value' cannot be more then 'bits_per_cnt'.", file=sys.stderr)
print("Check 'nv_counter' in policy file", in_policy,"\n", file=sys.stderr)
sys.exit(Error.POLICY)
sum_all_bit_nv_counter = 32
if sum_all_counters != sum_all_bit_nv_counter:
print("\nERROR: The sum of all 'bits_per_cnt' must be equal to 32.", file=sys.stderr)
print("Check 'nv_counter' in policy file", in_policy,"\n", file=sys.stderr)
sys.exit(Error.POLICY)
return list_counters
def form_max_counter_array(in_list, fa_f):
'''Write bit_per_count array to output file '''
#ifdef here is needed to fix Rule 12.2 MISRA violation
out_array_str = "\n#ifdef NEED_MAX_COUNTERS\nstatic const uint8_t bits_per_cnt[] = {"
#in_list is checked in prior function 'process_policy()'
for i, list_member in enumerate(in_list):
out_array_str += str(list_member)
if i < len(in_list) - 1:
out_array_str += ", "
out_array_str += "};\n#endif\n"
fa_f.write(out_array_str)
def main():
"""Flash map converter"""
params = CmdLineParams()
try:
plat = platDict[params.plat_id]
except KeyError:
print('Supported platforms are:', ', '.join(platDict.keys()),
file=sys.stderr)
sys.exit(Error.POLICY)
try:
boot_and_upgrade, ram_app_staging, flash = process_json(params.in_file)
bootloader = boot_and_upgrade['bootloader']
try:
bootloader_config = bootloader['config']
except KeyError:
#Backward compatibility
bootloader_config = bootloader
if ram_app_staging is not None:
try:
ram_app_staging_size = get_val(ram_app_staging, 'size')
ram_app_staging_ext_mem_addr = get_val(ram_app_staging, 'external_mememory_address')
ram_app_staging_sram_stage_addr = get_val(ram_app_staging, 'sram_stage_address')
ram_app_staging_reset_trigger = get_bool(ram_app_staging, 'reset_after_staging')
except KeyError:
ram_app_staging = None
try:
bootloader_flash = bootloader['flash']
except KeyError:
#Backward compatibility
bootloader_flash = bootloader
try:
bootloader_ram = bootloader['ram']
except KeyError:
#Backward compatibility
bootloader_ram = bootloader
boot_flash_area = AddrSize(bootloader_flash, 'address', 'size')
boot_ram_area = AddrSize(bootloader_ram, 'address', 'size')
except KeyError as key:
print('Malformed JSON:', key, 'is missing',
file=sys.stderr)
sys.exit(Error.JSON)
try:
scratch = AddrSize(bootloader_flash, 'scratch_address', 'scratch_size')
except KeyError:
scratch = None
try:
swap_status = AddrSize(bootloader_flash, 'status_address', 'status_size')
except KeyError:
swap_status = None
try:
bootloader_shared_data = bootloader['shared_data']
boot_shared_data_area = AddrSize(bootloader_shared_data, 'address', 'size')
except KeyError:
boot_shared_data_area = None
try:
bootloader_startup = get_bool(bootloader_config, 'startup')
except KeyError:
bootloader_startup = None
try:
ram_app_area = AddrSize(bootloader_config['ram_boot'], 'address', 'size')
except KeyError:
ram_app_area = None
# Create flash areas
area_list = AreaList(plat, flash, scratch is None and swap_status is None)
area_list.add_area('bootloader', 'FLASH_AREA_BOOTLOADER',
boot_flash_area.addr, boot_flash_area.size)
# Service RAM app (optional)
service_app = boot_and_upgrade.get('service_app')
app_binary = None
input_params = None
app_desc = None
if service_app is not None:
if plat['flashSize'] > 0:
print('service_app is unsupported on this platform', file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
try:
app_binary = AddrSize(service_app, 'address', 'size')
input_params = AddrSize(service_app, 'params_address', 'params_size')
app_desc = AddrSize(service_app, 'desc_address', 'desc_size')
if input_params.addr != app_binary.addr + app_binary.size or \
app_desc.addr != input_params.addr + input_params.size or \
app_desc.size != SERVICE_APP_SZ:
print('Malformed service_app definition', file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
area_list.add_area('service_app', None, app_binary.addr,
app_binary.size + input_params.size + app_desc.size)
except KeyError as key:
print('Malformed JSON:', key, 'is missing', file=sys.stderr)
sys.exit(Error.JSON)
# Fill flash areas
app_core, app_count, slot_sectors_max, apps_flash_map, apps_ram_map, shared_slot = \
process_images(area_list, boot_and_upgrade)
cy_img_hdr_size = 0x400
app_start = int(apps_flash_map[1].get("primary").get("address"), 0) + cy_img_hdr_size
if app_start % plat['VTAlign'] != 0:
print('Starting address', apps_flash_map[1].get("primary").get("address"),
'+', hex(cy_img_hdr_size), 'must be aligned to', hex(plat['VTAlign']),
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
slot_sectors_max = max(slot_sectors_max, 32)
if swap_status is not None:
status_size_min = calc_status_size(area_list.get_min_erase_size(),
slot_sectors_max,
app_count,
scratch is not None)
if swap_status.size < status_size_min:
print('Insufficient swap status area - suggested size',
hex(status_size_min),
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
area_list.add_area('swap status partition',
'FLASH_AREA_IMAGE_SWAP_STATUS',
swap_status.addr, swap_status.size)
if scratch is not None:
area_list.add_area('scratch area',
'FLASH_AREA_IMAGE_SCRATCH',
scratch.addr, scratch.size)
# Compare size 'bit_per_cnt' and number of images.
# 'service_app' is used only when HW rollback counter exists
if plat.get('bitsPerCnt') is not None and service_app is not None:
plat['bitsPerCnt'] = True
list_counters = process_policy_20829(params.policy)
if list_counters is not None and len(list_counters) != app_count:
print("\nERROR: 'bits_per_cnt' must be present for each image!",
file=sys.stderr)
print("Please, check secure provisioning and reprovisioning policies.\n",
file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
# Image id parameter is not used for MCUBootApp
if params.img_id is None:
area_list.generate_c_source(params, boot_and_upgrade)
area_list.create_flash_area_id(app_count, params)
# Write necessary values to makefile
try:
mk_file = open(params.mk_file, "w")
except (FileNotFoundError, OSError):
print('\nERROR: Cannot create ', params.mk_file, file=sys.stderr)
sys.exit(Error.IO)
print('# AUTO-GENERATED FILE, DO NOT EDIT. ALL CHANGES WILL BE LOST!', file=mk_file)
if params.set_core:
print('CORE :=', plat['allCores'][plat['bootCore'].lower()], file=mk_file)
if ram_app_staging is not None:
print('USE_STAGE_RAM_APPS := 1', file=mk_file)
print('RAM_APP_STAGING_EXT_MEM_ADDR := ', hex(ram_app_staging_ext_mem_addr), file=mk_file)
print('RAM_APP_STAGING_SRAM_MEM_ADDR :=', hex(ram_app_staging_sram_stage_addr), file=mk_file)
print('RAM_APP_STAGING_SIZE := ', hex(ram_app_staging_size), file=mk_file)
if ram_app_staging_reset_trigger is True:
print('RAM_APP_RESET_TRIGGER := 1', file=mk_file)
if bootloader_startup is True:
print('BOOTLOADER_STARTUP := 1', file=mk_file)
if ram_app_area is not None:
print('USE_MCUBOOT_RAM_LOAD := 1', file=mk_file)
print('IMAGE_EXECUTABLE_RAM_START :=', hex(ram_app_area.addr), file=mk_file)
print('IMAGE_EXECUTABLE_RAM_SIZE :=', hex(ram_app_area.size), file=mk_file)
if boot_shared_data_area is not None:
print('USE_MEASURED_BOOT := 1', file=mk_file)
print('USE_DATA_SHARING := 1', file=mk_file)
print('BOOT_SHARED_DATA_ADDRESS :=', hex(boot_shared_data_area.addr)+'U', file=mk_file)
print('BOOT_SHARED_DATA_SIZE :=', hex(boot_shared_data_area.size)+'U', file=mk_file)
print('BOOT_SHARED_DATA_RECORD_SIZE :=', hex(boot_shared_data_area.size)+'U', file=mk_file)
print('BOOTLOADER_ORIGIN :=', hex(boot_flash_area.addr), file=mk_file)
print('BOOTLOADER_SIZE :=', hex(boot_flash_area.size), file=mk_file)
print('BOOTLOADER_RAM_ORIGIN :=', hex(boot_ram_area.addr), file=mk_file)
print('BOOTLOADER_RAM_SIZE :=', hex(boot_ram_area.size), file=mk_file)
print('APP_CORE :=', app_core, file=mk_file)
# for blinky
if params.img_id is not None:
primary_img_start = apps_flash_map[int(params.img_id)].get("primary").get("address")
secondary_img_start = apps_flash_map[int(params.img_id)].get("secondary").get("address")
slot_size = apps_flash_map[int(params.img_id)].get("primary").get("size")
if apps_ram_map:
image_ram_address = apps_ram_map[int(params.img_id)].get("address")
image_ram_size = apps_ram_map[int(params.img_id)].get("size")
image_ram_boot = apps_ram_map[int(params.img_id)].get("ram_boot")
if image_ram_address and image_ram_size:
print('IMG_RAM_ORIGIN := ' + hex(image_ram_address), file=mk_file)
print('IMG_RAM_SIZE := ' + hex(image_ram_size), file=mk_file)
if image_ram_boot is True:
print('USE_MCUBOOT_RAM_LOAD := 1', file=mk_file)
print('PRIMARY_IMG_START := ' + primary_img_start, file=mk_file)
print('SECONDARY_IMG_START := ' + secondary_img_start, file=mk_file)
print('SLOT_SIZE := ' + slot_size, file=mk_file)
# for bootloader
else:
if apps_ram_map:
ram_load_counter = 0
for img in apps_ram_map:
if img is not None and img.get("ram_boot"):
ram_load_counter += 1
if ram_load_counter != 0:
print('USE_MCUBOOT_RAM_LOAD := 1', file=mk_file)
if ram_load_counter == 1:
print(f'IMAGE_EXECUTABLE_RAM_START := {hex(apps_ram_map[1].get("address"))}', file=mk_file)
print(f'IMAGE_EXECUTABLE_RAM_SIZE := {hex(apps_ram_map[1].get("size"))}', file=mk_file)
else:
print('USE_MCUBOOT_MULTI_MEMORY_LOAD := 1', file=mk_file)
print('MAX_IMG_SECTORS :=', slot_sectors_max, file=mk_file)
print('MCUBOOT_IMAGE_NUMBER :=', app_count, file=mk_file)
if area_list.external_flash:
print('USE_EXTERNAL_FLASH := 1', file=mk_file)
if area_list.external_flash_xip:
print('USE_XIP := 1', file=mk_file)
if area_list.use_direct_xip:
print('USE_DIRECT_XIP := 1', file=mk_file)
elif area_list.use_overwrite:
print('USE_OVERWRITE := 1', file=mk_file)
if shared_slot:
print('USE_SHARED_SLOT := 1', file=mk_file)
if service_app is not None:
print('PLATFORM_SERVICE_APP_OFFSET :=',
hex(app_binary.addr - plat['smifAddr']), file=mk_file)
print('PLATFORM_SERVICE_APP_INPUT_PARAMS_OFFSET :=',
hex(input_params.addr - plat['smifAddr']), file=mk_file)
print('PLATFORM_SERVICE_APP_DESC_OFFSET :=',
hex(app_desc.addr - plat['smifAddr']), file=mk_file)
print('USE_HW_ROLLBACK_PROT := 1', file=mk_file)
mk_file.close()
if params.nonce_file and not os.path.isfile(params.nonce_file):
try:
with open(params.policy, encoding='UTF-8') as in_f:
try:
policy = json.load(in_f)
except ValueError:
print('\nERROR: Cannot parse', params.policy, '\n', file=sys.stderr)
sys.exit(Error.IO)
except (FileNotFoundError, OSError):
print('\nERROR: Cannot open', params.policy, file=sys.stderr)
sys.exit(Error.IO)
policy_key_path = policy.get('pre_build', {}).get('keys', {}).get('encrypt_key', {}).get('value', {})
if policy_key_path:
full_key_path = os.path.join(os.path.dirname(params.policy), policy_key_path)
try:
with open(full_key_path, "rb") as aes_key_file:
aes_key = aes_key_file.read()
except (FileNotFoundError, OSError):
print('Missing encryption key', full_key_path, '\nNonce file generation skipped')
sys.exit(0)
if len(aes_key) != AES_BLOCK_SIZE:
print('\nERROR: Wrong AES key size in ', full_key_path, file=sys.stderr)
sys.exit(Error.CONFIG_MISMATCH)
for i in range(NONCE_GEN_RETRIES):
random_bytes = os.urandom(NONCE_RAND_SIZE)
fa_ids = [entry['fa_off'] + entry['fa_size'] for entry in area_list.areas
if entry['fa_id'].startswith('FLASH_AREA_IMG_')]
for fa_end_addr in fa_ids:
img_ok_addr = SHAB_ADDR + fa_end_addr - IMG_OK_OFFSET
img_ok_block_addr = img_ok_addr & ~(AES_BLOCK_SIZE - 1)
nonce = img_ok_block_addr.to_bytes(AES_BLOCK_SIZE - NONCE_RAND_SIZE,
byteorder='little') + random_bytes
cipher = Cipher(algorithms.AES(aes_key), modes.CTR(nonce))
encryptor_0 = cipher.encryptor()
encryptor_1 = cipher.encryptor()
encrypted_block_0 = encryptor_0.update(b'\x00' * AES_BLOCK_SIZE)
encrypted_block_1 = encryptor_1.update(b'\xFF' * AES_BLOCK_SIZE)
if encrypted_block_0[img_ok_addr - img_ok_block_addr] == IMG_OK_VALUE or \
encrypted_block_1[img_ok_addr - img_ok_block_addr] == IMG_OK_VALUE:
break
else:
break
else:
print("\nERROR: Can't generate valid NONCE sequence", file=sys.stderr)
sys.exit(Error.VALUE)
try:
if '/' in params.nonce_file or '\\' in params.nonce_file:
os.makedirs(os.path.dirname(params.nonce_file), exist_ok=True)
with open(params.nonce_file, "wb") as nonce_f:
nonce_f.write(random_bytes)
except (FileNotFoundError, OSError):
print('\nERROR: Cannot create ', params.nonce_file, file=sys.stderr)
sys.exit(Error.IO)
if __name__ == '__main__':
main()