From c9151be798bc6d03ec6e507aedbf82608ea37229 Mon Sep 17 00:00:00 2001 From: Carles Cufi Date: Fri, 28 Feb 2025 18:34:44 +0100 Subject: [PATCH] west: runners: Add support for multiple device IDs In order to enable the use case where the underlying flash tool supports bulk-flashing using multiple device IDs, augment the core runner class with this new runner capability and implement it in the nrfutil runner, since the nrfutil tool supports it natively. Signed-off-by: Carles Cufi --- scripts/west_commands/runners/core.py | 13 ++++++++- scripts/west_commands/runners/nrf_common.py | 29 ++++++++++++++------- scripts/west_commands/runners/nrfjprog.py | 8 ++++++ scripts/west_commands/runners/nrfutil.py | 17 +++++++++++- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/scripts/west_commands/runners/core.py b/scripts/west_commands/runners/core.py index 20e4b72a8c3..fb160155a49 100644 --- a/scripts/west_commands/runners/core.py +++ b/scripts/west_commands/runners/core.py @@ -268,6 +268,9 @@ class RunnerCaps: connected to a single computer, in order to select which one will be used with the command provided. + - mult_dev_ids: whether the runner supports multiple device identifiers + for a single operation, allowing for bulk flashing of devices. + - flash_addr: whether the runner supports flashing to an arbitrary address. Default is False. If true, the runner must honor the --dt-flash option. @@ -305,6 +308,7 @@ class RunnerCaps: commands: set[str] = field(default_factory=lambda: set(_RUNNERCAPS_COMMANDS)) dev_id: bool = False + mult_dev_ids: bool = False flash_addr: bool = False erase: bool = False reset: bool = False @@ -316,6 +320,8 @@ class RunnerCaps: # to allow other commands to use the rtt address def __post_init__(self): + if self.mult_dev_ids and not self.dev_id: + raise RuntimeError('dev_id must be set along mult_dev_ids') if not self.commands.issubset(_RUNNERCAPS_COMMANDS): raise ValueError(f'{self.commands=} contains invalid command') @@ -543,7 +549,9 @@ class ZephyrBinaryRunner(abc.ABC): caps = cls.capabilities() if caps.dev_id: + action = 'append' if caps.mult_dev_ids else 'store' parser.add_argument('-i', '--dev-id', + action=action, dest='dev_id', help=cls.dev_id_help()) else: @@ -749,10 +757,13 @@ class ZephyrBinaryRunner(abc.ABC): @classmethod def dev_id_help(cls) -> str: ''' Get the ArgParse help text for the --dev-id option.''' - return '''Device identifier. Use it to select + help = '''Device identifier. Use it to select which debugger, device, node or instance to target when multiple ones are available or connected.''' + addendum = '''\nThis option can be present multiple times.''' if \ + cls.capabilities().mult_dev_ids else '' + return help + addendum @classmethod def extload_help(cls) -> str: diff --git a/scripts/west_commands/runners/nrf_common.py b/scripts/west_commands/runners/nrf_common.py index 5895456d8ea..a5c3c7bdb3c 100644 --- a/scripts/west_commands/runners/nrf_common.py +++ b/scripts/west_commands/runners/nrf_common.py @@ -101,12 +101,13 @@ class NrfBinaryRunner(ZephyrBinaryRunner): self.tool_opt += opts @classmethod - def capabilities(cls): - return RunnerCaps(commands={'flash'}, dev_id=True, erase=True, - reset=True, tool_opt=True) + def _capabilities(cls, mult_dev_ids=False): + return RunnerCaps(commands={'flash'}, dev_id=True, + mult_dev_ids=mult_dev_ids, erase=True, reset=True, + tool_opt=True) @classmethod - def dev_id_help(cls) -> str: + def _dev_id_help(cls) -> str: return '''Device identifier. Use it to select the J-Link Serial Number of the device connected over USB. '*' matches one or more characters/digits''' @@ -146,9 +147,19 @@ class NrfBinaryRunner(ZephyrBinaryRunner): args.dev_id = previous_runner.dev_id def ensure_snr(self): - if not self.dev_id or "*" in self.dev_id: - self.dev_id = self.get_board_snr(self.dev_id or "*") - self.dev_id = self.dev_id.lstrip("0") + # dev_id can be None, str or list of str + dev_id = self.dev_id + if isinstance(dev_id, list): + if len(dev_id) == 0: + dev_id = None + elif len(dev_id) == 1: + dev_id = dev_id[0] + else: + self.dev_id = [d.lstrip("0") for d in dev_id] + return + if not dev_id or "*" in dev_id: + dev_id = self.get_board_snr(dev_id or "*") + self.dev_id = dev_id.lstrip("0") @abc.abstractmethod def do_get_boards(self): @@ -528,5 +539,5 @@ class NrfBinaryRunner(ZephyrBinaryRunner): # All done, now flush any outstanding ops self.flush(force=True) - self.logger.info(f'Board with serial number {self.dev_id} ' - 'flashed successfully.') + self.logger.info(f'Board(s) with serial number(s) {self.dev_id} ' + 'flashed successfully.') diff --git a/scripts/west_commands/runners/nrfjprog.py b/scripts/west_commands/runners/nrfjprog.py index 36461b7236d..1573a696c1c 100644 --- a/scripts/west_commands/runners/nrfjprog.py +++ b/scripts/west_commands/runners/nrfjprog.py @@ -30,6 +30,14 @@ class NrfJprogBinaryRunner(NrfBinaryRunner): def name(cls): return 'nrfjprog' + @classmethod + def capabilities(cls): + return NrfBinaryRunner._capabilities() + + @classmethod + def dev_id_help(cls) -> str: + return NrfBinaryRunner._dev_id_help() + @classmethod def tool_opt_help(cls) -> str: return 'Additional options for nrfjprog, e.g. "--clockspeed"' diff --git a/scripts/west_commands/runners/nrfutil.py b/scripts/west_commands/runners/nrfutil.py index f937da74c07..e119af744d9 100644 --- a/scripts/west_commands/runners/nrfutil.py +++ b/scripts/west_commands/runners/nrfutil.py @@ -33,6 +33,15 @@ class NrfUtilBinaryRunner(NrfBinaryRunner): 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"' @@ -107,6 +116,12 @@ class NrfUtilBinaryRunner(NrfBinaryRunner): 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'] @@ -151,7 +166,7 @@ class NrfUtilBinaryRunner(NrfBinaryRunner): precmd = ['--x-ext-mem-config-file', self.ext_mem_config_file] self._exec(precmd + ['x-execute-batch', '--batch-path', f'{json_file}', - '--serial-number', f'{self.dev_id}']) + '--serial-number', self._format_dev_ids()]) def do_exec_op(self, op, force=False): self.logger.debug(f'Executing op: {op}')