zephyr/scripts/west_commands/runners/nrfjprog.py
Carles Cufi f20168fe89 scripts: runners: nrf: Honor the --erase flag for external memory
Both backends supported as runners for nRF ICs, nrfjprog and nrfutil,
support erasing external memory as part of the programming operation.
Before this patch, and when the firmware was detected to be partially or
fully placed in external flash by inspecting the .hex address range, the
runner would instruct the backend tool to fully erase the external
flash (but the nrfjprog runner would ignore that, always erasing only
the sectors required). Instead, correctly default to erasing only the
sectors that are required to program the new firmware image in both tools,
and erase it completely only when the --erase flag is provided by the user.

Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
2025-02-05 21:01:57 +01:00

128 lines
4.8 KiB
Python

# Copyright (c) 2017 Linaro Limited.
# Copyright (c) 2023 Nordic Semiconductor ASA.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with nrfjprog.'''
import subprocess
import sys
from runners.nrf_common import ErrNotAvailableBecauseProtection, ErrVerify, NrfBinaryRunner
# https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf_cltools%2FUG%2Fcltools%2Fnrf_nrfjprogexe_return_codes.html&cp=9_1_3_1
UnavailableOperationBecauseProtectionError = 16
VerifyError = 55
class NrfJprogBinaryRunner(NrfBinaryRunner):
'''Runner front-end for nrfjprog.'''
def __init__(self, cfg, family, softreset, pinreset, dev_id, erase=False,
reset=True, tool_opt=None, force=False, recover=False,
qspi_ini=None):
super().__init__(cfg, family, softreset, pinreset, dev_id, erase, reset,
tool_opt, force, recover)
self.qspi_ini = qspi_ini
@classmethod
def name(cls):
return 'nrfjprog'
@classmethod
def tool_opt_help(cls) -> str:
return 'Additional options for nrfjprog, e.g. "--clockspeed"'
@classmethod
def do_create(cls, cfg, args):
return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset,
args.pinreset, args.dev_id, erase=args.erase,
reset=args.reset,
tool_opt=args.tool_opt, force=args.force,
recover=args.recover, qspi_ini=args.qspi_ini)
@classmethod
def do_add_parser(cls, parser):
super().do_add_parser(parser)
parser.add_argument('--qspiini', required=False, dest='qspi_ini',
help='path to an .ini file with qspi configuration')
def do_get_boards(self):
snrs = self.check_output(['nrfjprog', '--ids'])
return snrs.decode(sys.getdefaultencoding()).strip().splitlines()
def do_require(self):
self.require('nrfjprog')
def do_exec_op(self, op, force=False):
self.logger.debug(f'Executing op: {op}')
# Translate the op
families = {'nrf51': 'NRF51', 'nrf52': 'NRF52',
'nrf53': 'NRF53', 'nrf54l': 'NRF54L',
'nrf91': 'NRF91'}
cores = {'Application': 'CP_APPLICATION',
'Network': 'CP_NETWORK'}
core_opt = ['--coprocessor', cores[op['core']]] \
if op.get('core') else []
cmd = ['nrfjprog']
_op = op['operation']
op_type = _op['type']
# options that are an empty dict must use "in" instead of get()
if op_type == 'pinreset-enable':
cmd.append('--pinresetenable')
elif op_type == 'program':
cmd.append('--program')
cmd.append(_op['firmware']['file'])
opts = _op['options']
erase = opts['chip_erase_mode']
if erase == 'ERASE_ALL':
cmd.append('--chiperase')
elif erase == 'ERASE_RANGES_TOUCHED_BY_FIRMWARE':
if self.family == 'nrf52':
cmd.append('--sectoranduicrerase')
else:
cmd.append('--sectorerase')
elif erase == 'NO_ERASE':
pass
else:
raise RuntimeError(f'Invalid erase mode: {erase}')
if opts.get('ext_mem_erase_mode'):
if opts['ext_mem_erase_mode'] == 'ERASE_RANGES_TOUCHED_BY_FIRMWARE':
cmd.append('--qspisectorerase')
elif opts['ext_mem_erase_mode'] == 'ERASE_ALL':
cmd.append('--qspichiperase')
if opts.get('verify'):
# In the future there might be multiple verify modes
cmd.append('--verify')
if self.qspi_ini:
cmd.append('--qspiini')
cmd.append(self.qspi_ini)
elif op_type == 'recover':
cmd.append('--recover')
elif op_type == 'reset':
if _op['kind'] == 'RESET_SYSTEM':
cmd.append('--reset')
if _op['kind'] == 'RESET_PIN':
cmd.append('--pinreset')
elif op_type == 'erase':
cmd.append(f'--erase{_op["kind"]}')
else:
raise RuntimeError(f'Invalid operation: {op_type}')
try:
self.check_call(cmd + ['-f', families[self.family]] + core_opt +
['--snr', self.dev_id] + self.tool_opt)
except subprocess.CalledProcessError as cpe:
# Translate error codes
if cpe.returncode == UnavailableOperationBecauseProtectionError:
cpe.returncode = ErrNotAvailableBecauseProtection
elif cpe.returncode == VerifyError:
cpe.returncode = ErrVerify
raise cpe
return True