2019-01-31 04:30:42 +01:00
|
|
|
# Copyright (c) 2018 Foundries.io
|
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
import abc
|
|
|
|
import argparse
|
|
|
|
import os
|
2019-06-12 19:25:23 +02:00
|
|
|
import pathlib
|
2020-07-08 23:43:07 +02:00
|
|
|
import pickle
|
2019-02-14 22:49:03 +01:00
|
|
|
import shutil
|
2019-01-31 04:30:42 +01:00
|
|
|
import subprocess
|
2019-11-12 23:55:03 +01:00
|
|
|
import sys
|
2019-01-31 04:30:42 +01:00
|
|
|
|
|
|
|
from west import log
|
|
|
|
from west.util import quote_sh_list
|
|
|
|
|
2019-11-12 23:55:03 +01:00
|
|
|
from build_helpers import find_build_dir, is_zephyr_build, \
|
2019-06-12 19:48:50 +02:00
|
|
|
FIND_BUILD_DIR_DESCRIPTION
|
2019-11-12 23:55:03 +01:00
|
|
|
from runners.core import BuildConfiguration
|
|
|
|
from zcmake import CMakeCache
|
2020-02-11 01:53:06 +01:00
|
|
|
from zephyr_ext_common import Forceable, cached_runner_config, \
|
2020-03-10 14:52:35 +01:00
|
|
|
ZEPHYR_SCRIPTS
|
2020-02-11 01:53:06 +01:00
|
|
|
|
2020-07-08 23:43:07 +02:00
|
|
|
# This is needed to load edt.pickle files.
|
2020-03-10 14:52:35 +01:00
|
|
|
sys.path.append(str(ZEPHYR_SCRIPTS / 'dts'))
|
2019-05-07 11:00:20 +02:00
|
|
|
|
2019-01-31 04:30:42 +01:00
|
|
|
SIGN_DESCRIPTION = '''\
|
|
|
|
This command automates some of the drudgery of creating signed Zephyr
|
|
|
|
binaries for chain-loading by a bootloader.
|
|
|
|
|
|
|
|
In the simplest usage, run this from your build directory:
|
|
|
|
|
|
|
|
west sign -t your_tool -- ARGS_FOR_YOUR_TOOL
|
|
|
|
|
|
|
|
Assuming your binary was properly built for processing and handling by
|
|
|
|
tool "your_tool", this creates zephyr.signed.bin and zephyr.signed.hex
|
|
|
|
files (if supported by "your_tool") which are ready for use by your
|
|
|
|
bootloader. The "ARGS_FOR_YOUR_TOOL" value can be any additional
|
|
|
|
arguments you want to pass to the tool, such as the location of a
|
|
|
|
signing key, a version identifier, etc.
|
|
|
|
|
|
|
|
See tool-specific help below for details.'''
|
|
|
|
|
|
|
|
SIGN_EPILOG = '''\
|
|
|
|
imgtool
|
|
|
|
-------
|
|
|
|
|
|
|
|
Currently, MCUboot's 'imgtool' tool is supported. To build a signed
|
|
|
|
binary you can load with MCUboot using imgtool, run this from your
|
|
|
|
build directory:
|
|
|
|
|
|
|
|
west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem
|
|
|
|
|
2019-02-14 22:49:03 +01:00
|
|
|
For this to work, either imgtool must be installed (e.g. using pip3),
|
|
|
|
or you must pass the path to imgtool.py using the -p option.
|
|
|
|
|
2019-01-31 04:30:42 +01:00
|
|
|
The image header size, alignment, and slot sizes are determined from
|
2019-02-14 22:49:03 +01:00
|
|
|
the build directory using .config and the device tree. A default
|
|
|
|
version number of 0.0.0+0 is used (which can be overridden by passing
|
|
|
|
"--version x.y.z+w" after "--key"). As shown above, extra arguments
|
|
|
|
after a '--' are passed to imgtool directly.'''
|
2019-01-31 04:30:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ToggleAction(argparse.Action):
|
|
|
|
|
|
|
|
def __call__(self, parser, args, ignored, option):
|
|
|
|
setattr(args, self.dest, not option.startswith('--no-'))
|
|
|
|
|
|
|
|
|
|
|
|
class Sign(Forceable):
|
|
|
|
def __init__(self):
|
|
|
|
super(Sign, self).__init__(
|
|
|
|
'sign',
|
|
|
|
# Keep this in sync with the string in west-commands.yml.
|
|
|
|
'sign a Zephyr binary for bootloader chain-loading',
|
|
|
|
SIGN_DESCRIPTION,
|
|
|
|
accepts_unknown_args=False)
|
|
|
|
|
|
|
|
def do_add_parser(self, parser_adder):
|
|
|
|
parser = parser_adder.add_parser(
|
|
|
|
self.name,
|
|
|
|
epilog=SIGN_EPILOG,
|
|
|
|
help=self.help,
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
description=self.description)
|
|
|
|
|
2019-06-12 19:48:50 +02:00
|
|
|
parser.add_argument('-d', '--build-dir',
|
|
|
|
help=FIND_BUILD_DIR_DESCRIPTION)
|
2019-01-31 04:30:42 +01:00
|
|
|
self.add_force_arg(parser)
|
|
|
|
|
|
|
|
# general options
|
|
|
|
group = parser.add_argument_group('tool control options')
|
2019-11-19 11:35:49 +01:00
|
|
|
group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'],
|
|
|
|
required=True,
|
|
|
|
help='''image signing tool name; imgtool and rimage
|
|
|
|
are currently supported''')
|
2019-02-14 22:49:03 +01:00
|
|
|
group.add_argument('-p', '--tool-path', default=None,
|
2019-01-31 04:30:42 +01:00
|
|
|
help='''path to the tool itself, if needed''')
|
|
|
|
group.add_argument('tool_args', nargs='*', metavar='tool_opt',
|
|
|
|
help='extra option(s) to pass to the signing tool')
|
|
|
|
|
|
|
|
# bin file options
|
|
|
|
group = parser.add_argument_group('binary (.bin) file options')
|
|
|
|
group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0,
|
|
|
|
action=ToggleAction,
|
|
|
|
help='''produce a signed .bin file?
|
2019-06-12 19:39:09 +02:00
|
|
|
(default: yes, if supported and unsigned bin
|
|
|
|
exists)''')
|
2019-01-31 04:30:42 +01:00
|
|
|
group.add_argument('-B', '--sbin', metavar='BIN',
|
|
|
|
help='''signed .bin file name
|
2019-06-12 18:29:01 +02:00
|
|
|
(default: zephyr.signed.bin in the build
|
|
|
|
directory, next to zephyr.bin)''')
|
2019-01-31 04:30:42 +01:00
|
|
|
|
|
|
|
# hex file options
|
|
|
|
group = parser.add_argument_group('Intel HEX (.hex) file options')
|
|
|
|
group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0,
|
|
|
|
action=ToggleAction,
|
|
|
|
help='''produce a signed .hex file?
|
2019-06-12 19:39:09 +02:00
|
|
|
(default: yes, if supported and unsigned hex
|
|
|
|
exists)''')
|
2019-01-31 04:30:42 +01:00
|
|
|
group.add_argument('-H', '--shex', metavar='HEX',
|
|
|
|
help='''signed .hex file name
|
2019-06-12 18:29:01 +02:00
|
|
|
(default: zephyr.signed.hex in the build
|
|
|
|
directory, next to zephyr.hex)''')
|
2019-01-31 04:30:42 +01:00
|
|
|
|
|
|
|
return parser
|
|
|
|
|
|
|
|
def do_run(self, args, ignored):
|
2019-06-12 19:39:09 +02:00
|
|
|
self.args = args # for check_force
|
2019-01-31 04:30:42 +01:00
|
|
|
|
2019-06-12 19:39:09 +02:00
|
|
|
# Find the build directory and parse .config and DT.
|
2019-06-12 19:49:02 +02:00
|
|
|
build_dir = find_build_dir(args.build_dir)
|
|
|
|
self.check_force(os.path.isdir(build_dir),
|
|
|
|
'no such build directory {}'.format(build_dir))
|
|
|
|
self.check_force(is_zephyr_build(build_dir),
|
2019-01-31 04:30:42 +01:00
|
|
|
"build directory {} doesn't look like a Zephyr build "
|
2019-06-12 19:49:02 +02:00
|
|
|
'directory'.format(build_dir))
|
2019-06-12 19:39:09 +02:00
|
|
|
bcfg = BuildConfiguration(build_dir)
|
|
|
|
|
|
|
|
# Decide on output formats.
|
|
|
|
formats = []
|
|
|
|
bin_exists = 'CONFIG_BUILD_OUTPUT_BIN' in bcfg
|
|
|
|
if args.gen_bin:
|
|
|
|
self.check_force(bin_exists,
|
|
|
|
'--bin given but CONFIG_BUILD_OUTPUT_BIN not set '
|
|
|
|
"in build directory's ({}) .config".
|
|
|
|
format(build_dir))
|
|
|
|
formats.append('bin')
|
|
|
|
elif args.gen_bin is None and bin_exists:
|
|
|
|
formats.append('bin')
|
|
|
|
|
|
|
|
hex_exists = 'CONFIG_BUILD_OUTPUT_HEX' in bcfg
|
|
|
|
if args.gen_hex:
|
|
|
|
self.check_force(hex_exists,
|
|
|
|
|
|
|
|
'--hex given but CONFIG_BUILD_OUTPUT_HEX not set '
|
|
|
|
"in build directory's ({}) .config".
|
|
|
|
format(build_dir))
|
|
|
|
formats.append('hex')
|
|
|
|
elif args.gen_hex is None and hex_exists:
|
|
|
|
formats.append('hex')
|
|
|
|
|
|
|
|
if not formats:
|
|
|
|
log.dbg('nothing to do: no output files')
|
|
|
|
return
|
2019-01-31 04:30:42 +01:00
|
|
|
|
2019-06-12 19:39:09 +02:00
|
|
|
# Delegate to the signer.
|
2019-01-31 04:30:42 +01:00
|
|
|
if args.tool == 'imgtool':
|
|
|
|
signer = ImgtoolSigner()
|
2019-11-19 11:35:49 +01:00
|
|
|
elif args.tool == 'rimage':
|
|
|
|
signer = RimageSigner()
|
2019-01-31 04:30:42 +01:00
|
|
|
# (Add support for other signers here in elif blocks)
|
|
|
|
else:
|
|
|
|
raise RuntimeError("can't happen")
|
|
|
|
|
2019-06-12 19:39:09 +02:00
|
|
|
signer.sign(self, build_dir, bcfg, formats)
|
2019-01-31 04:30:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Signer(abc.ABC):
|
|
|
|
'''Common abstract superclass for signers.
|
|
|
|
|
|
|
|
To add support for a new tool, subclass this and add support for
|
|
|
|
it in the Sign.do_run() method.'''
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2019-06-12 19:39:09 +02:00
|
|
|
def sign(self, command, build_dir, bcfg, formats):
|
2019-01-31 04:30:42 +01:00
|
|
|
'''Abstract method to perform a signature; subclasses must implement.
|
|
|
|
|
2019-02-14 22:49:03 +01:00
|
|
|
:param command: the Sign instance
|
2019-06-12 19:49:02 +02:00
|
|
|
:param build_dir: the build directory
|
2019-06-12 19:39:09 +02:00
|
|
|
:param bcfg: BuildConfiguration for build directory
|
|
|
|
:param formats: list of formats to generate ('bin', 'hex')
|
2019-01-31 04:30:42 +01:00
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
class ImgtoolSigner(Signer):
|
|
|
|
|
2019-06-12 19:39:09 +02:00
|
|
|
def sign(self, command, build_dir, bcfg, formats):
|
2019-11-12 23:55:03 +01:00
|
|
|
if not formats:
|
|
|
|
return
|
|
|
|
|
2019-02-14 22:49:03 +01:00
|
|
|
args = command.args
|
2019-11-12 23:55:03 +01:00
|
|
|
b = pathlib.Path(build_dir)
|
|
|
|
cache = CMakeCache.from_build_dir(build_dir)
|
2019-02-14 22:49:03 +01:00
|
|
|
|
2019-11-12 23:55:03 +01:00
|
|
|
tool_path = self.find_imgtool(command, args)
|
|
|
|
# The vector table offset is set in Kconfig:
|
2020-02-10 08:37:24 +01:00
|
|
|
vtoff = self.get_cfg(command, bcfg, 'CONFIG_ROM_START_OFFSET')
|
2019-11-12 23:55:03 +01:00
|
|
|
# Flash device write alignment and the partition's slot size
|
|
|
|
# come from devicetree:
|
2020-07-08 23:43:07 +02:00
|
|
|
flash = self.edt_flash_node(b)
|
2019-11-12 23:55:03 +01:00
|
|
|
align, addr, size = self.edt_flash_params(flash)
|
2019-06-12 19:25:23 +02:00
|
|
|
|
2019-11-12 23:55:03 +01:00
|
|
|
runner_config = cached_runner_config(build_dir, cache)
|
|
|
|
if 'bin' in formats:
|
|
|
|
in_bin = runner_config.bin_file
|
|
|
|
if not in_bin:
|
|
|
|
log.die("can't find unsigned .bin to sign")
|
|
|
|
else:
|
|
|
|
in_bin = None
|
|
|
|
if 'hex' in formats:
|
|
|
|
in_hex = runner_config.hex_file
|
|
|
|
if not in_hex:
|
|
|
|
log.die("can't find unsigned .hex to sign")
|
|
|
|
else:
|
|
|
|
in_hex = None
|
2019-06-12 19:25:23 +02:00
|
|
|
|
2019-11-12 23:55:03 +01:00
|
|
|
log.banner('image configuration:')
|
|
|
|
log.inf('partition offset: {0} (0x{0:x})'.format(addr))
|
|
|
|
log.inf('partition size: {0} (0x{0:x})'.format(size))
|
2020-02-10 08:37:24 +01:00
|
|
|
log.inf('rom start offset: {0} (0x{0:x})'.format(vtoff))
|
2019-06-12 19:25:23 +02:00
|
|
|
|
|
|
|
# Base sign command.
|
|
|
|
#
|
|
|
|
# We provide a default --version in case the user is just
|
|
|
|
# messing around and doesn't want to set one. It will be
|
|
|
|
# overridden if there is a --version in args.tool_args.
|
2019-11-12 23:55:03 +01:00
|
|
|
sign_base = [tool_path, 'sign',
|
|
|
|
'--version', '0.0.0+0',
|
|
|
|
'--align', str(align),
|
|
|
|
'--header-size', str(vtoff),
|
|
|
|
'--slot-size', str(size)]
|
|
|
|
sign_base.extend(args.tool_args)
|
|
|
|
|
|
|
|
log.banner('signed binaries:')
|
|
|
|
if in_bin:
|
2019-06-12 19:25:23 +02:00
|
|
|
out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin')
|
2019-11-12 23:55:03 +01:00
|
|
|
sign_bin = sign_base + [in_bin, out_bin]
|
|
|
|
log.inf('bin: {}'.format(out_bin))
|
2019-01-31 04:30:42 +01:00
|
|
|
log.dbg(quote_sh_list(sign_bin))
|
|
|
|
subprocess.check_call(sign_bin)
|
2019-11-12 23:55:03 +01:00
|
|
|
if in_hex:
|
2019-06-12 19:25:23 +02:00
|
|
|
out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex')
|
2019-11-12 23:55:03 +01:00
|
|
|
sign_hex = sign_base + [in_hex, out_hex]
|
|
|
|
log.inf('hex: {}'.format(out_hex))
|
2019-01-31 04:30:42 +01:00
|
|
|
log.dbg(quote_sh_list(sign_hex))
|
|
|
|
subprocess.check_call(sign_hex)
|
|
|
|
|
2019-11-12 23:55:03 +01:00
|
|
|
@staticmethod
|
|
|
|
def find_imgtool(command, args):
|
|
|
|
if args.tool_path:
|
|
|
|
command.check_force(shutil.which(args.tool_path),
|
|
|
|
'--tool-path {}: not an executable'.
|
|
|
|
format(args.tool_path))
|
|
|
|
tool_path = args.tool_path
|
|
|
|
else:
|
2020-01-30 12:35:00 +01:00
|
|
|
tool_path = shutil.which('imgtool') or shutil.which('imgtool.py')
|
2019-11-12 23:55:03 +01:00
|
|
|
if not tool_path:
|
|
|
|
log.die('imgtool not found; either install it',
|
|
|
|
'(e.g. "pip3 install imgtool") or provide --tool-path')
|
|
|
|
return tool_path
|
|
|
|
|
2019-09-04 16:28:50 +02:00
|
|
|
@staticmethod
|
|
|
|
def get_cfg(command, bcfg, item):
|
2019-02-14 22:49:03 +01:00
|
|
|
try:
|
2019-06-12 19:25:23 +02:00
|
|
|
return bcfg[item]
|
2019-02-14 22:49:03 +01:00
|
|
|
except KeyError:
|
|
|
|
command.check_force(
|
2019-11-12 23:55:03 +01:00
|
|
|
False, "build .config is missing a {} value".format(item))
|
2019-02-14 22:49:03 +01:00
|
|
|
return None
|
2019-11-12 23:55:03 +01:00
|
|
|
|
|
|
|
@staticmethod
|
2020-07-08 23:43:07 +02:00
|
|
|
def edt_flash_node(b):
|
2019-11-12 23:55:03 +01:00
|
|
|
# Get the EDT Node corresponding to the zephyr,flash chosen DT
|
2020-07-08 23:43:07 +02:00
|
|
|
# node; 'b' is the build directory as a pathlib object.
|
2019-11-12 23:55:03 +01:00
|
|
|
|
|
|
|
# Ensure the build directory has a compiled DTS file
|
|
|
|
# where we expect it to be.
|
2020-07-08 23:43:07 +02:00
|
|
|
dts = b / 'zephyr' / 'zephyr.dts'
|
2019-11-12 23:55:03 +01:00
|
|
|
log.dbg('DTS file:', dts, level=log.VERBOSE_VERY)
|
2020-07-08 23:43:07 +02:00
|
|
|
edt_pickle = b / 'zephyr' / 'edt.pickle'
|
|
|
|
if not edt_pickle.is_file():
|
|
|
|
log.die("can't load devicetree; expected to find:", edt_pickle)
|
2019-11-12 23:55:03 +01:00
|
|
|
|
2020-07-08 23:43:07 +02:00
|
|
|
# Load the devicetree.
|
|
|
|
with open(edt_pickle, 'rb') as f:
|
|
|
|
edt = pickle.load(f)
|
2019-11-12 23:55:03 +01:00
|
|
|
|
|
|
|
# By convention, the zephyr,flash chosen node contains the
|
|
|
|
# partition information about the zephyr image to sign.
|
|
|
|
flash = edt.chosen_node('zephyr,flash')
|
|
|
|
if not flash:
|
|
|
|
log.die('devicetree has no chosen zephyr,flash node;',
|
|
|
|
"can't infer flash write block or image-0 slot sizes")
|
|
|
|
|
|
|
|
return flash
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def edt_flash_params(flash):
|
|
|
|
# Get the flash device's write alignment and the image-0
|
|
|
|
# partition's size out of the build directory's devicetree.
|
|
|
|
|
|
|
|
# The node must have a "partitions" child node, which in turn
|
|
|
|
# must have a child node labeled "image-0". By convention, the
|
|
|
|
# primary slot for consumption by imgtool is linked into this
|
|
|
|
# partition.
|
|
|
|
if 'partitions' not in flash.children:
|
|
|
|
log.die("DT zephyr,flash chosen node has no partitions,",
|
|
|
|
"can't find partition for MCUboot slot 0")
|
|
|
|
for node in flash.children['partitions'].children.values():
|
|
|
|
if node.label == 'image-0':
|
|
|
|
image_0 = node
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
log.die("DT zephyr,flash chosen node has no image-0 partition,",
|
|
|
|
"can't determine its size")
|
|
|
|
|
|
|
|
# The partitions node, and its subnode, must provide
|
|
|
|
# the size of the image-0 partition via the regs property.
|
|
|
|
if not image_0.regs:
|
|
|
|
log.die('image-0 flash partition has no regs property;',
|
|
|
|
"can't determine size of image slot 0")
|
|
|
|
|
|
|
|
# Die on missing or zero alignment or slot_size.
|
|
|
|
if "write-block-size" not in flash.props:
|
|
|
|
log.die('DT zephyr,flash node has no write-block-size;',
|
|
|
|
"can't determine imgtool write alignment")
|
|
|
|
align = flash.props['write-block-size'].val
|
|
|
|
if align == 0:
|
|
|
|
log.die('expected nonzero flash alignment, but got '
|
|
|
|
'DT flash device write-block-size {}'.format(align))
|
|
|
|
reg = image_0.regs[0]
|
|
|
|
if reg.size == 0:
|
|
|
|
log.die('expected nonzero slot size, but got '
|
|
|
|
'DT image-0 partition size {}'.format(reg.size))
|
|
|
|
|
|
|
|
return (align, reg.addr, reg.size)
|
2019-11-19 11:35:49 +01:00
|
|
|
|
|
|
|
class RimageSigner(Signer):
|
|
|
|
|
|
|
|
def sign(self, command, build_dir, bcfg, formats):
|
|
|
|
args = command.args
|
|
|
|
|
|
|
|
if args.tool_path:
|
|
|
|
command.check_force(shutil.which(args.tool_path),
|
|
|
|
'--tool-path {}: not an executable'.
|
|
|
|
format(args.tool_path))
|
|
|
|
tool_path = args.tool_path
|
|
|
|
else:
|
|
|
|
tool_path = shutil.which('rimage')
|
|
|
|
if not tool_path:
|
|
|
|
log.die('rimage not found; either install it',
|
|
|
|
'or provide --tool-path')
|
|
|
|
|
|
|
|
b = pathlib.Path(build_dir)
|
|
|
|
cache = CMakeCache.from_build_dir(build_dir)
|
|
|
|
|
|
|
|
board = cache['CACHED_BOARD']
|
|
|
|
if board != 'up_squared_adsp':
|
|
|
|
log.die('Supported only for up_squared_adsp board')
|
|
|
|
|
|
|
|
log.inf('Signing with tool {}'.format(tool_path))
|
|
|
|
|
|
|
|
bootloader = str(b / 'zephyr' / 'bootloader.elf.mod')
|
|
|
|
kernel = str(b / 'zephyr' / 'zephyr.elf.mod')
|
|
|
|
out_bin = str(b / 'zephyr' / 'zephyr.ri')
|
|
|
|
|
|
|
|
sign_base = ([tool_path] + args.tool_args +
|
|
|
|
['-o', out_bin, '-m', 'apl', '-i', '3'] +
|
|
|
|
[bootloader, kernel])
|
|
|
|
|
|
|
|
log.inf(quote_sh_list(sign_base))
|
|
|
|
subprocess.check_call(sign_base)
|