scripts: runners: nrf: Add support for the new nRF Util tool

Add support for Nordic's new nRF Util tool, aka nrfutil. Via its
"device" command, nrfutil now supports most of the functionality offered
by nrfjprog.

The tool itself can be found here:

https://www.nordicsemi.com/Products/Development-tools/nrf-util

Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
This commit is contained in:
Carles Cufi 2023-03-07 11:09:13 +01:00 committed by Marti Bolivar
parent 480b6fb7c0
commit b022835146
3 changed files with 123 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0
board_set_flasher_ifnset(nrfutil)
board_finalize_runner_args(nrfutil) # No default arguments to provide.

View file

@ -41,6 +41,7 @@ _names = [
'misc',
'nios2',
'nrfjprog',
'nrfutil',
'nsim',
'openocd',
'pyocd',

View file

@ -0,0 +1,118 @@
# Copyright (c) 2023 Nordic Semiconductor ASA.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with nrfutil.'''
import json
import os
from pathlib import Path
import sys
import subprocess
from runners.nrf_common import NrfBinaryRunner
class NrfUtilBinaryRunner(NrfBinaryRunner):
'''Runner front-end for nrfutil.'''
def __init__(self, cfg, family, softreset, dev_id, erase=False,
tool_opt=[], force=False, recover=False):
super().__init__(cfg, family, softreset, dev_id, erase,
tool_opt, force, recover)
self._ops = []
self._op_id = 1
@classmethod
def name(cls):
return 'nrfutil'
@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.dev_id, erase=args.erase,
tool_opt=args.tool_opt, force=args.force,
recover=args.recover)
def _exec(self, args):
try:
out = self.check_output(['nrfutil', '--json', 'device'] + args)
except subprocess.CalledProcessError as e:
# https://docs.python.org/3/reference/compound_stmts.html#except-clause
cpe = e
out = cpe.stdout
else:
cpe = None
finally:
# https://github.com/ndjson/ndjson-spec
out = [json.loads(s) for s in
out.decode(sys.getdefaultencoding()).split('\n') if len(s)]
self.logger.debug(f'output: {out}')
if cpe:
if 'execute-batch' in args:
for o in out:
if o['type'] == 'batch_end' and o['data']['error']:
cpe.returncode = o['data']['error']['code']
raise cpe
return out
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]
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 _exec_batch(self):
# prepare the dictionary and convert to JSON
batch = json.dumps({'family': f'{self.family}',
'operations': [op for op in self._ops]},
indent=4) + '\n'
hex_dir = Path(self.hex_).parent
json_file = os.fspath(hex_dir / f'generated_nrfutil_batch.json')
with open(json_file, "w") as f:
f.write(batch)
# reset first in case an exception is thrown
self._ops = []
self._op_id = 1
self.logger.debug(f'Executing batch in: {json_file}')
self._exec(['execute-batch', '--batch-path', f'{json_file}',
'--serial-number', f'{self.dev_id}'])
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()