Added automatic KMU key provisioning, when keyfile.json file exists in the build directory. This enables automated key provisioning during the flashing process to enable testing nRF54L aplications using Twister. Only applicable on nrfutil runner. Signed-off-by: Grzegorz Chwierut <grzegorz.chwierut@nordicsemi.no>
193 lines
6.8 KiB
Python
193 lines
6.8 KiB
Python
# Copyright (c) 2023 Nordic Semiconductor ASA.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
'''Runner for flashing with nrfutil.'''
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from runners.core import _DRY_RUN
|
|
from runners.nrf_common import NrfBinaryRunner
|
|
|
|
|
|
class NrfUtilBinaryRunner(NrfBinaryRunner):
|
|
'''Runner front-end for nrfutil.'''
|
|
|
|
def __init__(self, cfg, family, softreset, pinreset, dev_id, erase=False,
|
|
erase_mode=None, ext_erase_mode=None, reset=True, tool_opt=None,
|
|
force=False, recover=False, suit_starter=False,
|
|
ext_mem_config_file=None):
|
|
|
|
super().__init__(cfg, family, softreset, pinreset, dev_id, erase,
|
|
erase_mode, ext_erase_mode, reset, tool_opt, force,
|
|
recover)
|
|
|
|
self.suit_starter = suit_starter
|
|
self.ext_mem_config_file = ext_mem_config_file
|
|
|
|
self._ops = []
|
|
self._op_id = 1
|
|
|
|
@classmethod
|
|
def name(cls):
|
|
return 'nrfutil'
|
|
|
|
@classmethod
|
|
def capabilities(cls):
|
|
return NrfBinaryRunner._capabilities(mult_dev_ids=True)
|
|
|
|
@classmethod
|
|
def dev_id_help(cls) -> str:
|
|
return NrfBinaryRunner._dev_id_help() + \
|
|
'''.\n This option can be specified multiple times'''
|
|
|
|
@classmethod
|
|
def tool_opt_help(cls) -> str:
|
|
return 'Additional options for nrfutil, e.g. "--log-level"'
|
|
|
|
@classmethod
|
|
def do_create(cls, cfg, args):
|
|
return NrfUtilBinaryRunner(cfg, args.nrf_family, args.softreset,
|
|
args.pinreset, args.dev_id, erase=args.erase,
|
|
erase_mode=args.erase_mode,
|
|
ext_erase_mode=args.ext_erase_mode,
|
|
reset=args.reset, tool_opt=args.tool_opt,
|
|
force=args.force, recover=args.recover,
|
|
suit_starter=args.suit_manifest_starter,
|
|
ext_mem_config_file=args.ext_mem_config_file)
|
|
|
|
@classmethod
|
|
def do_add_parser(cls, parser):
|
|
super().do_add_parser(parser)
|
|
parser.add_argument('--suit-manifest-starter', required=False,
|
|
action='store_true',
|
|
help='Use the SUIT manifest starter file')
|
|
parser.add_argument('--ext-mem-config-file', required=False,
|
|
dest='ext_mem_config_file',
|
|
help='path to an JSON file with external memory configuration')
|
|
|
|
def _exec(self, args):
|
|
jout_all = []
|
|
|
|
cmd = ['nrfutil', '--json', 'device'] + args
|
|
self._log_cmd(cmd)
|
|
|
|
if _DRY_RUN:
|
|
return {}
|
|
|
|
with subprocess.Popen(cmd, stdout=subprocess.PIPE) as p:
|
|
for line in iter(p.stdout.readline, b''):
|
|
# https://github.com/ndjson/ndjson-spec
|
|
jout = json.loads(line.decode(sys.getdefaultencoding()))
|
|
jout_all.append(jout)
|
|
|
|
if 'x-execute-batch' in args:
|
|
if jout['type'] == 'batch_update':
|
|
pld = jout['data']['data']
|
|
if (
|
|
pld['type'] == 'task_progress' and
|
|
pld['data']['progress']['progressPercentage'] == 0
|
|
):
|
|
self.logger.info(pld['data']['progress']['description'])
|
|
elif jout['type'] == 'batch_end' and jout['data']['error']:
|
|
raise subprocess.CalledProcessError(
|
|
jout['data']['error']['code'], cmd
|
|
)
|
|
if p.returncode != 0:
|
|
raise subprocess.CalledProcessError(p.returncode, cmd)
|
|
|
|
return jout_all
|
|
|
|
def do_get_boards(self):
|
|
out = self._exec(['list'])
|
|
devs = []
|
|
for o in out:
|
|
if o['type'] == 'task_end':
|
|
devs = o['data']['data']['devices']
|
|
snrs = [dev['serialNumber'] for dev in devs if dev['traits']['jlink']]
|
|
|
|
self.logger.debug(f'Found boards: {snrs}')
|
|
return snrs
|
|
|
|
def do_require(self):
|
|
self.require('nrfutil')
|
|
|
|
def _insert_op(self, op):
|
|
op['operationId'] = f'{self._op_id}'
|
|
self._op_id += 1
|
|
self._ops.append(op)
|
|
|
|
def _format_dev_ids(self):
|
|
if isinstance(self.dev_id, list):
|
|
return ','.join(self.dev_id)
|
|
else:
|
|
return self.dev_id
|
|
|
|
def _append_batch(self, op, json_file):
|
|
_op = op['operation']
|
|
op_type = _op['type']
|
|
|
|
cmd = [f'{op_type}']
|
|
|
|
if op_type == 'program':
|
|
cmd += ['--firmware', _op['firmware']['file']]
|
|
opts = _op['options']
|
|
# populate the options
|
|
cmd.append('--options')
|
|
cli_opts = f"chip_erase_mode={opts['chip_erase_mode']}"
|
|
if opts.get('ext_mem_erase_mode'):
|
|
cli_opts += f",ext_mem_erase_mode={opts['ext_mem_erase_mode']}"
|
|
if opts.get('verify'):
|
|
cli_opts += f",verify={opts['verify']}"
|
|
cmd.append(cli_opts)
|
|
elif op_type == 'reset':
|
|
cmd += ['--reset-kind', _op['kind']]
|
|
elif op_type == 'erase':
|
|
cmd.append(f'--{_op["kind"]}')
|
|
elif op_type == 'x-provision-keys':
|
|
cmd += ['--key-file', _op['keyfile']]
|
|
|
|
cmd += ['--core', op['core']] if op.get('core') else []
|
|
cmd += ['--x-family', f'{self.family}']
|
|
cmd += ['--x-append-batch', f'{json_file}']
|
|
self._exec(cmd)
|
|
|
|
def _exec_batch(self):
|
|
# Use x-append-batch to get the JSON from nrfutil itself
|
|
json_file = Path(self.hex_).parent / 'generated_nrfutil_batch.json'
|
|
json_file.unlink(missing_ok=True)
|
|
for op in self._ops:
|
|
self._append_batch(op, json_file)
|
|
|
|
# reset first in case an exception is thrown
|
|
self._ops = []
|
|
self._op_id = 1
|
|
self.logger.debug(f'Executing batch in: {json_file}')
|
|
precmd = []
|
|
if self.ext_mem_config_file:
|
|
# This needs to be prepended, as it's a global option
|
|
precmd = ['--x-ext-mem-config-file', self.ext_mem_config_file]
|
|
|
|
self._exec(precmd + ['x-execute-batch', '--batch-path', f'{json_file}',
|
|
'--serial-number', self._format_dev_ids()])
|
|
|
|
def do_exec_op(self, op, force=False):
|
|
self.logger.debug(f'Executing op: {op}')
|
|
if force:
|
|
if len(self._ops) != 0:
|
|
raise RuntimeError(f'Forced exec with {len(self._ops)} ops')
|
|
self._insert_op(op)
|
|
self._exec_batch()
|
|
return True
|
|
# Defer by default
|
|
return False
|
|
|
|
def flush_ops(self, force=True):
|
|
if not force:
|
|
return
|
|
while self.ops:
|
|
self._insert_op(self.ops.popleft())
|
|
self._exec_batch()
|