blob: bdf9d353685ebee677e31113cb73b329d537f39b [file] [log] [blame]
Marti Bolivard1780aa2019-01-30 20:30:42 -07001# Copyright (c) 2018 Foundries.io
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import abc
6import argparse
7import os
Marti Bolivar2f839da2019-06-12 11:25:23 -06008import pathlib
Martí Bolívar9c92baa2020-07-08 14:43:07 -07009import pickle
Martí Bolívar698db692021-02-02 12:20:47 -080010import platform
Marti Bolivar4a0f1f22019-02-14 14:49:03 -070011import shutil
Marc Herbert030b7402023-02-24 02:52:14 +000012import shlex
Marti Bolivard1780aa2019-01-30 20:30:42 -070013import subprocess
Martí Bolívard8f459a2019-11-12 14:55:03 -080014import sys
Marti Bolivard1780aa2019-01-30 20:30:42 -070015
Marti Bolivard1780aa2019-01-30 20:30:42 -070016from west import log
Marc Herbertefb85512023-02-24 02:24:35 +000017from west import manifest
Marti Bolivard1780aa2019-01-30 20:30:42 -070018from west.util import quote_sh_list
19
Martí Bolívard8f459a2019-11-12 14:55:03 -080020from build_helpers import find_build_dir, is_zephyr_build, \
Marti Bolivar3bd07a22019-06-12 11:48:50 -060021 FIND_BUILD_DIR_DESCRIPTION
Martí Bolívard8f459a2019-11-12 14:55:03 -080022from runners.core import BuildConfiguration
23from zcmake import CMakeCache
Dominik Ermel09980d62021-01-19 18:19:36 +000024from zephyr_ext_common import Forceable, ZEPHYR_SCRIPTS
Jun Li2d5fb6d2019-05-07 02:00:20 -070025
Martí Bolívar9c92baa2020-07-08 14:43:07 -070026# This is needed to load edt.pickle files.
Jordan Yates8e4107f2022-04-30 21:13:52 +100027sys.path.insert(0, str(ZEPHYR_SCRIPTS / 'dts' / 'python-devicetree' / 'src'))
Martí Bolívar6dab1632020-02-10 16:53:06 -080028
Marti Bolivard1780aa2019-01-30 20:30:42 -070029SIGN_DESCRIPTION = '''\
30This command automates some of the drudgery of creating signed Zephyr
31binaries for chain-loading by a bootloader.
32
33In the simplest usage, run this from your build directory:
34
35 west sign -t your_tool -- ARGS_FOR_YOUR_TOOL
36
Andrei Emeltchenkofb441882020-07-07 11:57:51 +030037The "ARGS_FOR_YOUR_TOOL" value can be any additional
Marti Bolivard1780aa2019-01-30 20:30:42 -070038arguments you want to pass to the tool, such as the location of a
Torsten Rasmussen49389b52023-03-23 11:02:49 +010039signing key etc.
Marti Bolivard1780aa2019-01-30 20:30:42 -070040
41See tool-specific help below for details.'''
42
43SIGN_EPILOG = '''\
44imgtool
45-------
46
Andrei Emeltchenko51182ab2020-07-06 15:37:00 +030047To build a signed binary you can load with MCUboot using imgtool,
48run this from your build directory:
Marti Bolivard1780aa2019-01-30 20:30:42 -070049
50 west sign -t imgtool -- --key YOUR_SIGNING_KEY.pem
51
Marti Bolivar4a0f1f22019-02-14 14:49:03 -070052For this to work, either imgtool must be installed (e.g. using pip3),
53or you must pass the path to imgtool.py using the -p option.
54
Andrei Emeltchenkofb441882020-07-07 11:57:51 +030055Assuming your binary was properly built for processing and handling by
56imgtool, this creates zephyr.signed.bin and zephyr.signed.hex
57files which are ready for use by your bootloader.
58
Torsten Rasmussen49389b52023-03-23 11:02:49 +010059The version number, image header size, alignment, and slot sizes are
60determined from the build directory using .config and the device tree.
61As shown above, extra arguments after a '--' are passed to imgtool
62directly.
Andrei Emeltchenko51182ab2020-07-06 15:37:00 +030063
64rimage
65------
66
67To create a signed binary with the rimage tool, run this from your build
68directory:
69
70 west sign -t rimage -- -k YOUR_SIGNING_KEY.pem
71
72For this to work, either rimage must be installed or you must pass
Marc Herbert030b7402023-02-24 02:52:14 +000073the path to rimage using the -p option.
74
75You can also pass additional arguments to rimage thanks to [sign] and
76[rimage] sections in your west config file(s); this is especially useful
77when invoking west sign _indirectly_ through CMake/ninja. See how at
78https://docs.zephyrproject.org/latest/develop/west/sign.html
79'''
80
81
82def config_get_words(west_config, section_key, fallback=None):
83 unparsed = west_config.get(section_key)
84 log.dbg(f'west config {section_key}={unparsed}')
85 return fallback if unparsed is None else shlex.split(unparsed)
86
87
88def config_get(west_config, section_key, fallback=None):
89 words = config_get_words(west_config, section_key)
90 if words is None:
91 return fallback
92 if len(words) != 1:
93 log.die(f'Single word expected for: {section_key}={words}. Use quotes?')
94 return words[0]
Marti Bolivard1780aa2019-01-30 20:30:42 -070095
96
97class ToggleAction(argparse.Action):
98
99 def __call__(self, parser, args, ignored, option):
100 setattr(args, self.dest, not option.startswith('--no-'))
101
102
103class Sign(Forceable):
104 def __init__(self):
105 super(Sign, self).__init__(
106 'sign',
107 # Keep this in sync with the string in west-commands.yml.
108 'sign a Zephyr binary for bootloader chain-loading',
109 SIGN_DESCRIPTION,
110 accepts_unknown_args=False)
111
112 def do_add_parser(self, parser_adder):
113 parser = parser_adder.add_parser(
114 self.name,
115 epilog=SIGN_EPILOG,
116 help=self.help,
117 formatter_class=argparse.RawDescriptionHelpFormatter,
118 description=self.description)
119
Marti Bolivar3bd07a22019-06-12 11:48:50 -0600120 parser.add_argument('-d', '--build-dir',
121 help=FIND_BUILD_DIR_DESCRIPTION)
Martí Bolívar9b673672020-08-19 15:29:37 -0700122 parser.add_argument('-q', '--quiet', action='store_true',
123 help='suppress non-error output')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700124 self.add_force_arg(parser)
125
126 # general options
127 group = parser.add_argument_group('tool control options')
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200128 group.add_argument('-t', '--tool', choices=['imgtool', 'rimage'],
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200129 help='''image signing tool name; imgtool and rimage
130 are currently supported''')
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700131 group.add_argument('-p', '--tool-path', default=None,
Marti Bolivard1780aa2019-01-30 20:30:42 -0700132 help='''path to the tool itself, if needed''')
Anas Nashifb5531662021-01-25 13:57:20 -0500133 group.add_argument('-D', '--tool-data', default=None,
Marc Herbertefb85512023-02-24 02:24:35 +0000134 help='''path to a tool-specific data/configuration directory, if needed''')
Marc Herbert2c80c4d2023-03-03 01:45:45 +0000135 group.add_argument('--if-tool-available', action='store_true',
Iuliana Prodan4cf9d672023-08-22 18:58:44 +0300136 help='''Do not fail if the rimage tool is not found or the rimage signing
137schema (rimage "target") is not defined in board.cmake.''')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700138 group.add_argument('tool_args', nargs='*', metavar='tool_opt',
139 help='extra option(s) to pass to the signing tool')
140
141 # bin file options
142 group = parser.add_argument_group('binary (.bin) file options')
143 group.add_argument('--bin', '--no-bin', dest='gen_bin', nargs=0,
144 action=ToggleAction,
145 help='''produce a signed .bin file?
Marti Bolivard371c542019-06-12 11:39:09 -0600146 (default: yes, if supported and unsigned bin
147 exists)''')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700148 group.add_argument('-B', '--sbin', metavar='BIN',
Marti Bolivard1780aa2019-01-30 20:30:42 -0700149 help='''signed .bin file name
Marti Bolivar33cbba32019-06-12 10:29:01 -0600150 (default: zephyr.signed.bin in the build
151 directory, next to zephyr.bin)''')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700152
153 # hex file options
154 group = parser.add_argument_group('Intel HEX (.hex) file options')
155 group.add_argument('--hex', '--no-hex', dest='gen_hex', nargs=0,
156 action=ToggleAction,
157 help='''produce a signed .hex file?
Marti Bolivard371c542019-06-12 11:39:09 -0600158 (default: yes, if supported and unsigned hex
159 exists)''')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700160 group.add_argument('-H', '--shex', metavar='HEX',
Marti Bolivard1780aa2019-01-30 20:30:42 -0700161 help='''signed .hex file name
Marti Bolivar33cbba32019-06-12 10:29:01 -0600162 (default: zephyr.signed.hex in the build
163 directory, next to zephyr.hex)''')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700164
Marti Bolivard1780aa2019-01-30 20:30:42 -0700165 return parser
166
167 def do_run(self, args, ignored):
Marti Bolivarf13fa532019-02-14 10:01:00 -0700168 self.args = args # for check_force
Marti Bolivard371c542019-06-12 11:39:09 -0600169
170 # Find the build directory and parse .config and DT.
171 build_dir = find_build_dir(args.build_dir)
Marti Bolivar06c9f8e2019-06-12 11:49:02 -0600172 self.check_force(os.path.isdir(build_dir),
173 'no such build directory {}'.format(build_dir))
174 self.check_force(is_zephyr_build(build_dir),
Marti Bolivard1780aa2019-01-30 20:30:42 -0700175 "build directory {} doesn't look like a Zephyr build "
Marti Bolivar06c9f8e2019-06-12 11:49:02 -0600176 'directory'.format(build_dir))
Martí Bolívar250b2132021-04-28 16:20:38 -0700177 build_conf = BuildConfiguration(build_dir)
Marti Bolivard1780aa2019-01-30 20:30:42 -0700178
Marc Herbert58311642023-02-24 03:23:30 +0000179 if not args.tool:
180 args.tool = config_get(self.config, 'sign.tool')
181
Marti Bolivard371c542019-06-12 11:39:09 -0600182 # Decide on output formats.
183 formats = []
Martí Bolívar250b2132021-04-28 16:20:38 -0700184 bin_exists = build_conf.getboolean('CONFIG_BUILD_OUTPUT_BIN')
Marti Bolivard371c542019-06-12 11:39:09 -0600185 if args.gen_bin:
186 self.check_force(bin_exists,
187 '--bin given but CONFIG_BUILD_OUTPUT_BIN not set '
188 "in build directory's ({}) .config".
189 format(build_dir))
190 formats.append('bin')
191 elif args.gen_bin is None and bin_exists:
192 formats.append('bin')
193
Martí Bolívar250b2132021-04-28 16:20:38 -0700194 hex_exists = build_conf.getboolean('CONFIG_BUILD_OUTPUT_HEX')
Marti Bolivard371c542019-06-12 11:39:09 -0600195 if args.gen_hex:
196 self.check_force(hex_exists,
Marti Bolivard371c542019-06-12 11:39:09 -0600197 '--hex given but CONFIG_BUILD_OUTPUT_HEX not set '
198 "in build directory's ({}) .config".
199 format(build_dir))
200 formats.append('hex')
201 elif args.gen_hex is None and hex_exists:
202 formats.append('hex')
203
Marti Bolivard371c542019-06-12 11:39:09 -0600204 # Delegate to the signer.
Marti Bolivard1780aa2019-01-30 20:30:42 -0700205 if args.tool == 'imgtool':
Marc Herbert2c80c4d2023-03-03 01:45:45 +0000206 if args.if_tool_available:
207 log.die('imgtool does not support --if-tool-available')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700208 signer = ImgtoolSigner()
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200209 elif args.tool == 'rimage':
210 signer = RimageSigner()
Marti Bolivard1780aa2019-01-30 20:30:42 -0700211 # (Add support for other signers here in elif blocks)
212 else:
Marc Herbert58311642023-02-24 03:23:30 +0000213 if args.tool is None:
214 log.die('one --tool is required')
215 else:
216 log.die(f'invalid tool: {args.tool}')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700217
Martí Bolívar250b2132021-04-28 16:20:38 -0700218 signer.sign(self, build_dir, build_conf, formats)
Marti Bolivard1780aa2019-01-30 20:30:42 -0700219
220
221class Signer(abc.ABC):
222 '''Common abstract superclass for signers.
223
224 To add support for a new tool, subclass this and add support for
225 it in the Sign.do_run() method.'''
226
227 @abc.abstractmethod
Martí Bolívar250b2132021-04-28 16:20:38 -0700228 def sign(self, command, build_dir, build_conf, formats):
Marti Bolivard1780aa2019-01-30 20:30:42 -0700229 '''Abstract method to perform a signature; subclasses must implement.
230
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700231 :param command: the Sign instance
Marti Bolivar06c9f8e2019-06-12 11:49:02 -0600232 :param build_dir: the build directory
Martí Bolívar250b2132021-04-28 16:20:38 -0700233 :param build_conf: BuildConfiguration for build directory
Marti Bolivard371c542019-06-12 11:39:09 -0600234 :param formats: list of formats to generate ('bin', 'hex')
Marti Bolivard1780aa2019-01-30 20:30:42 -0700235 '''
236
237
238class ImgtoolSigner(Signer):
239
Martí Bolívar250b2132021-04-28 16:20:38 -0700240 def sign(self, command, build_dir, build_conf, formats):
Martí Bolívard8f459a2019-11-12 14:55:03 -0800241 if not formats:
242 return
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700243
Martí Bolívard8f459a2019-11-12 14:55:03 -0800244 args = command.args
245 b = pathlib.Path(build_dir)
Martí Bolívard8f459a2019-11-12 14:55:03 -0800246
Martí Bolívarb4903d42021-02-02 12:05:58 -0800247 imgtool = self.find_imgtool(command, args)
Torsten Rasmussen49389b52023-03-23 11:02:49 +0100248 # The vector table offset and application version are set in Kconfig:
249 appver = self.get_cfg(command, build_conf, 'CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION')
Martí Bolívar250b2132021-04-28 16:20:38 -0700250 vtoff = self.get_cfg(command, build_conf, 'CONFIG_ROM_START_OFFSET')
Martí Bolívard8f459a2019-11-12 14:55:03 -0800251 # Flash device write alignment and the partition's slot size
252 # come from devicetree:
Martí Bolívar9b673672020-08-19 15:29:37 -0700253 flash = self.edt_flash_node(b, args.quiet)
Martí Bolívard8f459a2019-11-12 14:55:03 -0800254 align, addr, size = self.edt_flash_params(flash)
255
Arvin Farahmand2de6bf92021-05-06 14:49:07 -0400256 if not build_conf.getboolean('CONFIG_BOOTLOADER_MCUBOOT'):
Martí Bolívar74929972020-08-07 15:40:05 -0700257 log.wrn("CONFIG_BOOTLOADER_MCUBOOT is not set to y in "
Martí Bolívar250b2132021-04-28 16:20:38 -0700258 f"{build_conf.path}; this probably won't work")
Martí Bolívar74929972020-08-07 15:40:05 -0700259
Martí Bolívar250b2132021-04-28 16:20:38 -0700260 kernel = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr')
Martí Bolívar74929972020-08-07 15:40:05 -0700261
Martí Bolívard8f459a2019-11-12 14:55:03 -0800262 if 'bin' in formats:
Martí Bolívar74929972020-08-07 15:40:05 -0700263 in_bin = b / 'zephyr' / f'{kernel}.bin'
264 if not in_bin.is_file():
265 log.die(f"no unsigned .bin found at {in_bin}")
266 in_bin = os.fspath(in_bin)
Martí Bolívard8f459a2019-11-12 14:55:03 -0800267 else:
268 in_bin = None
269 if 'hex' in formats:
Martí Bolívar74929972020-08-07 15:40:05 -0700270 in_hex = b / 'zephyr' / f'{kernel}.hex'
271 if not in_hex.is_file():
272 log.die(f"no unsigned .hex found at {in_hex}")
273 in_hex = os.fspath(in_hex)
Martí Bolívard8f459a2019-11-12 14:55:03 -0800274 else:
275 in_hex = None
276
Martí Bolívar9b673672020-08-19 15:29:37 -0700277 if not args.quiet:
278 log.banner('image configuration:')
279 log.inf('partition offset: {0} (0x{0:x})'.format(addr))
280 log.inf('partition size: {0} (0x{0:x})'.format(size))
281 log.inf('rom start offset: {0} (0x{0:x})'.format(vtoff))
Martí Bolívard8f459a2019-11-12 14:55:03 -0800282
283 # Base sign command.
Martí Bolívarb4903d42021-02-02 12:05:58 -0800284 sign_base = imgtool + ['sign',
Torsten Rasmussen49389b52023-03-23 11:02:49 +0100285 '--version', str(appver),
Martí Bolívarb4903d42021-02-02 12:05:58 -0800286 '--align', str(align),
287 '--header-size', str(vtoff),
288 '--slot-size', str(size)]
Martí Bolívard8f459a2019-11-12 14:55:03 -0800289 sign_base.extend(args.tool_args)
290
Martí Bolívar9b673672020-08-19 15:29:37 -0700291 if not args.quiet:
292 log.banner('signing binaries')
Martí Bolívard8f459a2019-11-12 14:55:03 -0800293 if in_bin:
294 out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin')
295 sign_bin = sign_base + [in_bin, out_bin]
Martí Bolívar9b673672020-08-19 15:29:37 -0700296 if not args.quiet:
297 log.inf(f'unsigned bin: {in_bin}')
298 log.inf(f'signed bin: {out_bin}')
299 log.dbg(quote_sh_list(sign_bin))
Martí Bolívard8f459a2019-11-12 14:55:03 -0800300 subprocess.check_call(sign_bin)
301 if in_hex:
302 out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex')
303 sign_hex = sign_base + [in_hex, out_hex]
Martí Bolívar9b673672020-08-19 15:29:37 -0700304 if not args.quiet:
305 log.inf(f'unsigned hex: {in_hex}')
306 log.inf(f'signed hex: {out_hex}')
307 log.dbg(quote_sh_list(sign_hex))
Martí Bolívard8f459a2019-11-12 14:55:03 -0800308 subprocess.check_call(sign_hex)
309
310 @staticmethod
311 def find_imgtool(command, args):
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700312 if args.tool_path:
Martí Bolívar698db692021-02-02 12:20:47 -0800313 imgtool = args.tool_path
314 if not os.path.isfile(imgtool):
315 log.die(f'--tool-path {imgtool}: no such file')
316 else:
317 imgtool = shutil.which('imgtool') or shutil.which('imgtool.py')
318 if not imgtool:
319 log.die('imgtool not found; either install it',
320 '(e.g. "pip3 install imgtool") or provide --tool-path')
Martí Bolívarb4903d42021-02-02 12:05:58 -0800321
Martí Bolívar698db692021-02-02 12:20:47 -0800322 if platform.system() == 'Windows' and imgtool.endswith('.py'):
323 # Windows users may not be able to run .py files
324 # as executables in subprocesses, regardless of
325 # what the mode says. Always run imgtool as
326 # 'python path/to/imgtool.py' instead of
327 # 'path/to/imgtool.py' in these cases.
328 # https://github.com/zephyrproject-rtos/zephyr/issues/31876
329 return [sys.executable, imgtool]
330
Martí Bolívarb4903d42021-02-02 12:05:58 -0800331 return [imgtool]
Marti Bolivar2f839da2019-06-12 11:25:23 -0600332
Ulf Magnussonbb634162019-09-04 16:28:50 +0200333 @staticmethod
Martí Bolívar250b2132021-04-28 16:20:38 -0700334 def get_cfg(command, build_conf, item):
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700335 try:
Martí Bolívar250b2132021-04-28 16:20:38 -0700336 return build_conf[item]
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700337 except KeyError:
338 command.check_force(
Martí Bolívard8f459a2019-11-12 14:55:03 -0800339 False, "build .config is missing a {} value".format(item))
Marti Bolivar4a0f1f22019-02-14 14:49:03 -0700340 return None
Martí Bolívard8f459a2019-11-12 14:55:03 -0800341
342 @staticmethod
Martí Bolívar9b673672020-08-19 15:29:37 -0700343 def edt_flash_node(b, quiet=False):
Martí Bolívard8f459a2019-11-12 14:55:03 -0800344 # Get the EDT Node corresponding to the zephyr,flash chosen DT
Martí Bolívar9c92baa2020-07-08 14:43:07 -0700345 # node; 'b' is the build directory as a pathlib object.
Martí Bolívard8f459a2019-11-12 14:55:03 -0800346
347 # Ensure the build directory has a compiled DTS file
348 # where we expect it to be.
Martí Bolívar9c92baa2020-07-08 14:43:07 -0700349 dts = b / 'zephyr' / 'zephyr.dts'
Martí Bolívar9b673672020-08-19 15:29:37 -0700350 if not quiet:
351 log.dbg('DTS file:', dts, level=log.VERBOSE_VERY)
Martí Bolívar9c92baa2020-07-08 14:43:07 -0700352 edt_pickle = b / 'zephyr' / 'edt.pickle'
353 if not edt_pickle.is_file():
354 log.die("can't load devicetree; expected to find:", edt_pickle)
Martí Bolívard8f459a2019-11-12 14:55:03 -0800355
Martí Bolívar9c92baa2020-07-08 14:43:07 -0700356 # Load the devicetree.
357 with open(edt_pickle, 'rb') as f:
358 edt = pickle.load(f)
Martí Bolívard8f459a2019-11-12 14:55:03 -0800359
360 # By convention, the zephyr,flash chosen node contains the
361 # partition information about the zephyr image to sign.
362 flash = edt.chosen_node('zephyr,flash')
363 if not flash:
364 log.die('devicetree has no chosen zephyr,flash node;',
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000365 "can't infer flash write block or slot0_partition slot sizes")
Martí Bolívard8f459a2019-11-12 14:55:03 -0800366
367 return flash
368
369 @staticmethod
370 def edt_flash_params(flash):
Fabio Utzig716ab472020-08-17 08:59:36 -0300371 # Get the flash device's write alignment and offset from the
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000372 # slot0_partition and the size from slot1_partition , out of the
373 # build directory's devicetree. slot1_partition size is used,
Fabio Utzig716ab472020-08-17 08:59:36 -0300374 # when available, because in swap-move mode it can be one sector
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000375 # smaller. When not available, fallback to slot0_partition (single slot dfu).
Martí Bolívard8f459a2019-11-12 14:55:03 -0800376
377 # The node must have a "partitions" child node, which in turn
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000378 # must have child nodes with label slot0_partition and may have a child node
379 # with label slot1_partition. By convention, the slots for consumption by
Fabio Utzig716ab472020-08-17 08:59:36 -0300380 # imgtool are linked into these partitions.
Martí Bolívard8f459a2019-11-12 14:55:03 -0800381 if 'partitions' not in flash.children:
382 log.die("DT zephyr,flash chosen node has no partitions,",
Fabio Utzig716ab472020-08-17 08:59:36 -0300383 "can't find partitions for MCUboot slots")
Martí Bolívard8f459a2019-11-12 14:55:03 -0800384
Fabio Utzig716ab472020-08-17 08:59:36 -0300385 partitions = flash.children['partitions']
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000386 slots = {
387 label: node for node in partitions.children.values()
388 for label in node.labels
389 if label in set(['slot0_partition', 'slot1_partition'])
Fabio Utzig716ab472020-08-17 08:59:36 -0300390 }
391
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000392 if 'slot0_partition' not in slots:
393 log.die("DT zephyr,flash chosen node has no slot0_partition partition,",
Fabio Utzig716ab472020-08-17 08:59:36 -0300394 "can't determine its address")
Martí Bolívard8f459a2019-11-12 14:55:03 -0800395
396 # Die on missing or zero alignment or slot_size.
397 if "write-block-size" not in flash.props:
398 log.die('DT zephyr,flash node has no write-block-size;',
399 "can't determine imgtool write alignment")
400 align = flash.props['write-block-size'].val
401 if align == 0:
402 log.die('expected nonzero flash alignment, but got '
403 'DT flash device write-block-size {}'.format(align))
Martí Bolívard8f459a2019-11-12 14:55:03 -0800404
Fabio Utzig716ab472020-08-17 08:59:36 -0300405 # The partitions node, and its subnode, must provide
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000406 # the size of slot1_partition or slot0_partition partition via the regs property.
407 slot_key = 'slot0_partition' if 'slot1_partition' in slots else 'slot0_partition'
408 if not slots[slot_key].regs:
409 log.die(f'{slot_key} flash partition has no regs property;',
410 "can't determine size of slot")
Fabio Utzig716ab472020-08-17 08:59:36 -0300411
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000412 # always use addr of slot0_partition, which is where slots are run
413 addr = slots['slot0_partition'].regs[0].addr
Fabio Utzig716ab472020-08-17 08:59:36 -0300414
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000415 size = slots[slot_key].regs[0].size
Fabio Utzig716ab472020-08-17 08:59:36 -0300416 if size == 0:
Dominik Ermel86c4b4c2022-11-16 16:18:54 +0000417 log.die('expected nonzero slot size for {}'.format(slot_key))
Fabio Utzig716ab472020-08-17 08:59:36 -0300418
419 return (align, addr, size)
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200420
421class RimageSigner(Signer):
422
Martí Bolívar250b2132021-04-28 16:20:38 -0700423 def sign(self, command, build_dir, build_conf, formats):
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200424 args = command.args
425
Marc Herbert5bdb1462023-03-03 01:42:05 +0000426 b = pathlib.Path(build_dir)
427 cache = CMakeCache.from_build_dir(build_dir)
428
Iuliana Prodan4cf9d672023-08-22 18:58:44 +0300429 # Warning: RIMAGE_TARGET in Zephyr is a duplicate of
430 # CONFIG_RIMAGE_SIGNING_SCHEMA in SOF.
Marc Herbert5bdb1462023-03-03 01:42:05 +0000431 target = cache.get('RIMAGE_TARGET')
Iuliana Prodandc49a312023-07-07 02:24:21 +0300432
Marc Herbert5bdb1462023-03-03 01:42:05 +0000433 if not target:
Iuliana Prodan4cf9d672023-08-22 18:58:44 +0300434 msg = 'rimage target not defined in board.cmake'
435 if args.if_tool_available:
436 log.inf(msg)
437 sys.exit(0)
438 else:
439 log.die(msg)
440
441 kernel_name = build_conf.get('CONFIG_KERNEL_BIN_NAME', 'zephyr')
Marc Herbert5bdb1462023-03-03 01:42:05 +0000442
Marc Herberte1f36c42023-06-23 19:14:40 +0000443 # TODO: make this a new sign.py --bootloader option.
Marc Herbert5bdb1462023-03-03 01:42:05 +0000444 if target in ('imx8', 'imx8m'):
Marc Herberte1f36c42023-06-23 19:14:40 +0000445 bootloader = None
Iuliana Prodandc49a312023-07-07 02:24:21 +0300446 kernel = str(b / 'zephyr' / f'{kernel_name}.elf')
447 out_bin = str(b / 'zephyr' / f'{kernel_name}.ri')
448 out_xman = str(b / 'zephyr' / f'{kernel_name}.ri.xman')
449 out_tmp = str(b / 'zephyr' / f'{kernel_name}.rix')
Marc Herbert5bdb1462023-03-03 01:42:05 +0000450 else:
451 bootloader = str(b / 'zephyr' / 'boot.mod')
452 kernel = str(b / 'zephyr' / 'main.mod')
Iuliana Prodandc49a312023-07-07 02:24:21 +0300453 out_bin = str(b / 'zephyr' / f'{kernel_name}.ri')
454 out_xman = str(b / 'zephyr' / f'{kernel_name}.ri.xman')
455 out_tmp = str(b / 'zephyr' / f'{kernel_name}.rix')
Marc Herbert5bdb1462023-03-03 01:42:05 +0000456
Marc Herbert5c4319d2023-03-03 01:44:35 +0000457 # Clean any stale output. This is especially important when using --if-tool-available
458 # (but not just)
459 for o in [ out_bin, out_xman, out_tmp ]:
460 pathlib.Path(o).unlink(missing_ok=True)
461
Marc Herbert66ac6252023-02-24 03:29:39 +0000462 tool_path = (
463 args.tool_path if args.tool_path else
464 config_get(command.config, 'rimage.path', None)
465 )
466 err_prefix = '--tool-path' if args.tool_path else 'west config'
467
468 if tool_path:
469 command.check_force(shutil.which(tool_path),
470 f'{err_prefix} {tool_path}: not an executable')
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200471 else:
472 tool_path = shutil.which('rimage')
473 if not tool_path:
Marc Herbert2c80c4d2023-03-03 01:45:45 +0000474 err_msg = 'rimage not found; either install it or provide --tool-path'
475 if args.if_tool_available:
476 log.wrn(err_msg)
477 log.wrn('zephyr binary _not_ signed!')
478 return
479 else:
480 log.die(err_msg)
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200481
Marc Herbert2fdc5512022-02-19 07:29:03 +0000482 #### -c sof/rimage/config/signing_schema.toml ####
483
Marc Herbert2fdc5512022-02-19 07:29:03 +0000484 cmake_toml = target + '.toml'
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200485
Martí Bolívar9b673672020-08-19 15:29:37 -0700486 if not args.quiet:
487 log.inf('Signing with tool {}'.format(tool_path))
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200488
Marc Herbertefb85512023-02-24 02:24:35 +0000489 try:
490 sof_proj = command.manifest.get_projects(['sof'], allow_paths=False)
491 sof_src_dir = pathlib.Path(sof_proj[0].abspath)
492 except ValueError: # sof is the manifest
493 sof_src_dir = pathlib.Path(manifest.manifest_path()).parent
494
Marc Herbert2fdc5512022-02-19 07:29:03 +0000495 if '-c' in args.tool_args:
Marc Herbertdedb0022023-02-10 08:30:15 +0000496 # Precedence to the arguments passed after '--': west sign ... -- -c ...
Marc Herbert2fdc5512022-02-19 07:29:03 +0000497 if args.tool_data:
Marc Herbertdedb0022023-02-10 08:30:15 +0000498 log.wrn('--tool-data ' + args.tool_data + ' ignored, overridden by: -- -c ... ')
499 conf_dir = None
Anas Nashiffee9af22021-01-25 13:59:57 -0500500 elif args.tool_data:
501 conf_dir = pathlib.Path(args.tool_data)
Marc Herbert2fdc5512022-02-19 07:29:03 +0000502 elif cache.get('RIMAGE_CONFIG_PATH'):
Marc Herbertdedb0022023-02-10 08:30:15 +0000503 conf_dir = pathlib.Path(cache['RIMAGE_CONFIG_PATH'])
Anas Nashiffee9af22021-01-25 13:59:57 -0500504 else:
Daniel Leung42019782023-10-23 15:26:22 -0700505 conf_dir = sof_src_dir / 'tools' / 'rimage' / 'config'
Marc Herbert2fdc5512022-02-19 07:29:03 +0000506
Marc Herbertdedb0022023-02-10 08:30:15 +0000507 conf_path_cmd = ['-c', str(conf_dir / cmake_toml)] if conf_dir else []
508
509 log.inf('Signing for SOC target ' + target)
Marc Herbert2fdc5512022-02-19 07:29:03 +0000510
Marc Herbert72495942023-02-24 02:03:12 +0000511 # FIXME: deprecate --no-manifest and replace it with a much
512 # simpler and more direct `-- -e` which the user can _already_
513 # pass today! With unclear consequences right now...
Jian Kang8c9b06a2021-02-03 17:05:24 +0800514 if '--no-manifest' in args.tool_args:
515 no_manifest = True
516 args.tool_args.remove('--no-manifest')
517 else:
518 no_manifest = False
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200519
Daniel Leung3be57322023-10-23 15:24:31 -0700520 # Non-SOF build does not have extended manifest data for
521 # rimage to process, which might result in rimage error.
522 # So skip it when not doing SOF builds.
523 is_sof_build = build_conf.getboolean('CONFIG_SOF')
524 if not is_sof_build:
525 no_manifest = True
526
Daniel Leung10eab232021-09-21 13:37:33 -0700527 if no_manifest:
Adrian Bonislawski30e4a4b2022-10-26 09:37:47 +0200528 extra_ri_args = [ ]
Daniel Leung10eab232021-09-21 13:37:33 -0700529 else:
Adrian Bonislawski30e4a4b2022-10-26 09:37:47 +0200530 extra_ri_args = ['-e']
Daniel Leung10eab232021-09-21 13:37:33 -0700531
Marc Herbert29fd1822022-03-05 00:05:31 +0000532 sign_base = [tool_path]
533
534 # Sub-command arg '-q' takes precedence over west '-v'
535 if not args.quiet and args.verbose:
536 sign_base += ['-v'] * args.verbose
537
Marc Herberte1f36c42023-06-23 19:14:40 +0000538 components = [ ] if bootloader is None else [ bootloader ]
Iuliana Prodan602e6752021-10-01 19:10:57 +0300539 components += [ kernel ]
Marc Herbert2fbcdb52023-01-28 02:27:17 +0000540
541 sign_config_extra_args = config_get_words(command.config, 'rimage.extra-args', [])
542
543 if '-k' not in sign_config_extra_args + args.tool_args:
Marc Herbert07f2c7a2023-05-27 01:32:32 +0000544 # rimage requires a key argument even when it does not sign
545 cmake_default_key = cache.get('RIMAGE_SIGN_KEY', 'key placeholder from sign.py')
Marc Herbert2fbcdb52023-01-28 02:27:17 +0000546 extra_ri_args += [ '-k', str(sof_src_dir / 'keys' / cmake_default_key) ]
547
Marc Herbert6697c5a2023-05-11 05:56:52 +0000548 if '-c' not in sign_config_extra_args + args.tool_args:
549 extra_ri_args += conf_path_cmd
550
Marc Herbert2fbcdb52023-01-28 02:27:17 +0000551 # Warning: while not officially supported (yet?), the rimage --option that is last
552 # on the command line currently wins in case of duplicate options. So pay
553 # attention to the _args order below.
Marc Herbert6697c5a2023-05-11 05:56:52 +0000554 sign_base += (['-o', out_bin] + sign_config_extra_args +
Marc Herbert2fbcdb52023-01-28 02:27:17 +0000555 extra_ri_args + args.tool_args + components)
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200556
Martí Bolívar9b673672020-08-19 15:29:37 -0700557 if not args.quiet:
558 log.inf(quote_sh_list(sign_base))
Andrei Emeltchenkod44d9862019-11-19 12:35:49 +0200559 subprocess.check_call(sign_base)
Guennadi Liakhovetski3de40b42020-10-27 12:48:36 +0100560
Jian Kang8c9b06a2021-02-03 17:05:24 +0800561 if no_manifest:
562 filenames = [out_bin]
563 else:
564 filenames = [out_xman, out_bin]
Marc Herbert72495942023-02-24 02:03:12 +0000565 if not args.quiet:
566 log.inf('Prefixing ' + out_bin + ' with manifest ' + out_xman)
Guennadi Liakhovetski3de40b42020-10-27 12:48:36 +0100567 with open(out_tmp, 'wb') as outfile:
568 for fname in filenames:
569 with open(fname, 'rb') as infile:
570 outfile.write(infile.read())
571
572 os.remove(out_bin)
573 os.rename(out_tmp, out_bin)