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>
128 lines
4.8 KiB
Python
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
|