scripts: west sign: use edtlib for flash configuration
This command needs access to DT configuration, but can no longer
access it through BuildConfiguration since
9da1d41a12c114a596a9413b84638b08013f6317.
Import edtlib and use that instead.
Fix up some other error handling and output issues while we're here to
make the script's behavior easier to inspect and debug.
Fixes: #20545
Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
diff --git a/scripts/west_commands/sign.py b/scripts/west_commands/sign.py
index c537b23..52bd5a9 100644
--- a/scripts/west_commands/sign.py
+++ b/scripts/west_commands/sign.py
@@ -8,19 +8,21 @@
import pathlib
import shutil
import subprocess
+import sys
-from west import cmake
from west import log
-from west.build import is_zephyr_build
from west.util import quote_sh_list
-from runners.core import BuildConfiguration
-
-from build_helpers import find_build_dir, \
+from build_helpers import find_build_dir, is_zephyr_build, \
FIND_BUILD_DIR_DESCRIPTION
+from runners.core import BuildConfiguration
+from zcmake import CMakeCache
+from zephyr_ext_common import Forceable, cached_runner_config
-from zephyr_ext_common import Forceable, \
- cached_runner_config
+# FIXME we should think of a nicer way to manage sys.path
+# for shared Zephyr code.
+sys.path.append(os.path.join(os.environ['ZEPHYR_BASE'], 'scripts', 'dts'))
+import edtlib
SIGN_DESCRIPTION = '''\
This command automates some of the drudgery of creating signed Zephyr
@@ -191,8 +193,68 @@
class ImgtoolSigner(Signer):
def sign(self, command, build_dir, bcfg, formats):
- args = command.args
+ if not formats:
+ return
+ args = command.args
+ b = pathlib.Path(build_dir)
+ cache = CMakeCache.from_build_dir(build_dir)
+
+ tool_path = self.find_imgtool(command, args)
+ # The vector table offset is set in Kconfig:
+ vtoff = self.get_cfg(command, bcfg, 'CONFIG_TEXT_SECTION_OFFSET')
+ # Flash device write alignment and the partition's slot size
+ # come from devicetree:
+ flash = self.edt_flash_node(b, cache)
+ align, addr, size = self.edt_flash_params(flash)
+
+ 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
+
+ log.banner('image configuration:')
+ log.inf('partition offset: {0} (0x{0:x})'.format(addr))
+ log.inf('partition size: {0} (0x{0:x})'.format(size))
+ log.inf('text section offset: {0} (0x{0:x})'.format(vtoff))
+
+ # 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.
+ 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:
+ out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin')
+ sign_bin = sign_base + [in_bin, out_bin]
+ log.inf('bin: {}'.format(out_bin))
+ log.dbg(quote_sh_list(sign_bin))
+ subprocess.check_call(sign_bin)
+ if in_hex:
+ out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex')
+ sign_hex = sign_base + [in_hex, out_hex]
+ log.inf('hex: {}'.format(out_hex))
+ log.dbg(quote_sh_list(sign_hex))
+ subprocess.check_call(sign_hex)
+
+ @staticmethod
+ def find_imgtool(command, args):
if args.tool_path:
command.check_force(shutil.which(args.tool_path),
'--tool-path {}: not an executable'.
@@ -203,66 +265,7 @@
if not tool_path:
log.die('imgtool not found; either install it',
'(e.g. "pip3 install imgtool") or provide --tool-path')
-
- align, vtoff, slot_size = [self.get_cfg(command, bcfg, x) for x in
- ('DT_FLASH_WRITE_BLOCK_SIZE',
- 'CONFIG_TEXT_SECTION_OFFSET',
- 'DT_FLASH_AREA_IMAGE_0_SIZE')]
-
- log.dbg('build config: --align={}, --header-size={}, --slot-size={}'.
- format(align, vtoff, slot_size))
-
- # 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.
- sign_base = [tool_path, 'sign', '--version', '0.0.0+0']
- if align:
- sign_base.extend(['--align', str(align)])
- else:
- log.wrn('expected nonzero flash alignment, but '
- 'DT_FLASH_WRITE_BLOCK_SIZE={} '
- "in build directory's ({}) device tree".
- format(align, build_dir))
-
- if vtoff:
- sign_base.extend(['--header-size', str(vtoff)])
- else:
- log.wrn('expected nonzero header size, but '
- 'CONFIG_TEXT_SECTION_OFFSET={} '
- "in build directory's ({}) .config".
- format(vtoff, build_dir))
-
- if slot_size:
- sign_base.extend(['--slot-size', str(slot_size)])
- else:
- log.wrn('expected nonzero slot size, but '
- 'DT_FLASH_AREA_IMAGE_0_SIZE={} '
- "in build directory's ({}) device tree".
- format(slot_size, build_dir))
-
- b = pathlib.Path(build_dir)
- cache = cmake.CMakeCache.from_build_dir(build_dir)
- runner_config = cached_runner_config(build_dir, cache)
-
- # Build a signed .bin
- if 'bin' in formats and runner_config.bin_file:
- out_bin = args.sbin or str(b / 'zephyr' / 'zephyr.signed.bin')
- log.inf('Generating:', out_bin)
- sign_bin = (sign_base + args.tool_args +
- [runner_config.bin_file, out_bin])
- log.dbg(quote_sh_list(sign_bin))
- subprocess.check_call(sign_bin)
-
- # Build a signed .hex
- if 'hex' in formats and runner_config.hex_file:
- out_hex = args.shex or str(b / 'zephyr' / 'zephyr.signed.hex')
- log.inf('Generating:', out_hex)
- sign_hex = (sign_base + args.tool_args +
- [runner_config.hex_file, out_hex])
- log.dbg(quote_sh_list(sign_hex))
- subprocess.check_call(sign_hex)
+ return tool_path
@staticmethod
def get_cfg(command, bcfg, item):
@@ -270,9 +273,81 @@
return bcfg[item]
except KeyError:
command.check_force(
- False,
- "imgtool parameter unknown, build directory has no {} {}".
- format('device tree define' if item.startswith('DT_') else
- 'Kconfig option',
- item))
+ False, "build .config is missing a {} value".format(item))
return None
+
+ @staticmethod
+ def edt_flash_node(b, cache):
+ # Get the EDT Node corresponding to the zephyr,flash chosen DT
+ # node.
+
+ # Retrieve the list of devicetree bindings from cache.
+ try:
+ bindings = cache.get_list('CACHED_DTS_ROOT_BINDINGS')
+ log.dbg('DTS bindings:', bindings, level=log.VERBOSE_VERY)
+ except KeyError:
+ log.die('CMake cache has no CACHED_DTS_ROOT_BINDINGS.'
+ '\n Try again after re-building your application.')
+
+ # Ensure the build directory has a compiled DTS file
+ # where we expect it to be.
+ dts = b / 'zephyr' / (cache['CACHED_BOARD'] + '.dts.pre.tmp')
+ if not dts.is_file():
+ log.die("can't find DTS; expected:", dts)
+ log.dbg('DTS file:', dts, level=log.VERBOSE_VERY)
+
+ # Parse the devicetree using bindings from cache.
+ try:
+ edt = edtlib.EDT(dts, bindings)
+ except edtlib.EDTError as e:
+ log.die("can't parse devicetree:", e)
+
+ # 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)