1#! /usr/bin/env python3 2# 3# Copyright 2017 Linaro Limited 4# 5# SPDX-License-Identifier: Apache-2.0 6# 7# Licensed under the Apache License, Version 2.0 (the "License"); 8# you may not use this file except in compliance with the License. 9# You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, software 14# distributed under the License is distributed on an "AS IS" BASIS, 15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16# See the License for the specific language governing permissions and 17# limitations under the License. 18 19""" 20Assemble multiple images into a single image that can be flashed on the device. 21""" 22 23import argparse 24import errno 25import io 26import re 27import os 28import os.path 29import pickle 30import sys 31 32def same_keys(a, b): 33 """Determine if the dicts a and b have the same keys in them""" 34 for ak in a.keys(): 35 if ak not in b: 36 return False 37 for bk in b.keys(): 38 if bk not in a: 39 return False 40 return True 41 42offset_re = re.compile(r"^#define DT_FLASH_AREA_([0-9A-Z_]+)_OFFSET(_0)?\s+(0x[0-9a-fA-F]+|[0-9]+)$") 43size_re = re.compile(r"^#define DT_FLASH_AREA_([0-9A-Z_]+)_SIZE(_0)?\s+(0x[0-9a-fA-F]+|[0-9]+)$") 44 45class Assembly(): 46 def __init__(self, output, bootdir, edt): 47 self.find_slots(edt) 48 try: 49 os.unlink(output) 50 except OSError as e: 51 if e.errno != errno.ENOENT: 52 raise 53 self.output = output 54 55 def find_slots(self, edt): 56 offsets = {} 57 sizes = {} 58 59 part_nodes = edt.compat2nodes["fixed-partitions"] 60 for node in part_nodes: 61 for child in node.children.values(): 62 if "label" in child.props: 63 label = child.props["label"].val 64 offsets[label] = child.regs[0].addr 65 sizes[label] = child.regs[0].size 66 67 if not same_keys(offsets, sizes): 68 raise Exception("Inconsistent data in devicetree.h") 69 70 # We care about the mcuboot, image-0, and image-1 partitions. 71 if 'mcuboot' not in offsets: 72 raise Exception("Board partition table does not have mcuboot partition") 73 74 if 'image-0' not in offsets: 75 raise Exception("Board partition table does not have image-0 partition") 76 77 if 'image-1' not in offsets: 78 raise Exception("Board partition table does not have image-1 partition") 79 80 self.offsets = offsets 81 self.sizes = sizes 82 83 def add_image(self, source, partition): 84 with open(self.output, 'ab') as ofd: 85 pos = ofd.tell() 86 print("partition {}, pos={}, offset={}".format(partition, pos, self.offsets[partition])) 87 if pos > self.offsets[partition]: 88 raise Exception("Partitions not in order, unsupported") 89 if pos < self.offsets[partition]: 90 buf = b'\xFF' * (self.offsets[partition] - pos) 91 ofd.write(buf) 92 with open(source, 'rb') as rfd: 93 ibuf = rfd.read() 94 if len(ibuf) > self.sizes[partition]: 95 raise Exception("Image {} is too large for partition".format(source)) 96 ofd.write(ibuf) 97 98def find_board_name(bootdir): 99 dot_config = os.path.join(bootdir, "zephyr", ".config") 100 with open(dot_config, "r") as f: 101 for line in f: 102 if line.startswith("CONFIG_BOARD="): 103 return line.split("=", 1)[1].strip('"') 104 raise Exception("Expected CONFIG_BOARD line in {}".format(dot_config)) 105 106def main(): 107 parser = argparse.ArgumentParser() 108 109 parser.add_argument('-b', '--bootdir', required=True, 110 help='Directory of built bootloader') 111 parser.add_argument('-p', '--primary', required=True, 112 help='Signed image file for primary image') 113 parser.add_argument('-s', '--secondary', 114 help='Signed image file for secondary image') 115 parser.add_argument('-o', '--output', required=True, 116 help='Filename to write full image to') 117 parser.add_argument('-z', '--zephyr-base', 118 help='Zephyr base containing the Zephyr repository') 119 120 args = parser.parse_args() 121 122 zephyr_base = args.zephyr_base 123 if zephyr_base is None: 124 try: 125 zephyr_base = os.environ['ZEPHYR_BASE'] 126 except KeyError: 127 print('Need to either have ZEPHYR_BASE in environment or pass in -z') 128 sys.exit(1) 129 130 sys.path.insert(0, os.path.join(zephyr_base, "scripts", "dts", "python-devicetree", "src")) 131 import devicetree.edtlib 132 133 board = find_board_name(args.bootdir) 134 135 edt_pickle = os.path.join(args.bootdir, "zephyr", "edt.pickle") 136 with open(edt_pickle, 'rb') as f: 137 edt = pickle.load(f) 138 assert isinstance(edt, devicetree.edtlib.EDT) 139 140 output = Assembly(args.output, args.bootdir, edt) 141 142 output.add_image(os.path.join(args.bootdir, 'zephyr', 'zephyr.bin'), 'mcuboot') 143 output.add_image(args.primary, "image-0") 144 if args.secondary is not None: 145 output.add_image(args.secondary, "image-1") 146 147if __name__ == '__main__': 148 main() 149