1# Copyright (c) 2017 Linaro Limited.
2# Copyright (c) 2023 Nordic Semiconductor ASA.
3#
4# SPDX-License-Identifier: Apache-2.0
5
6'''Runner for flashing with nrfjprog.'''
7
8import subprocess
9import sys
10
11from runners.nrf_common import ErrNotAvailableBecauseProtection, ErrVerify, NrfBinaryRunner
12
13# https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf_cltools%2FUG%2Fcltools%2Fnrf_nrfjprogexe_return_codes.html&cp=9_1_3_1
14UnavailableOperationBecauseProtectionError = 16
15VerifyError = 55
16
17class NrfJprogBinaryRunner(NrfBinaryRunner):
18    '''Runner front-end for nrfjprog.'''
19
20    def __init__(self, cfg, family, softreset, dev_id, erase=False,
21                 reset=True, tool_opt=None, force=False, recover=False,
22                 qspi_ini=None):
23
24        super().__init__(cfg, family, softreset, dev_id, erase, reset,
25                         tool_opt, force, recover)
26
27        self.qspi_ini = qspi_ini
28
29    @classmethod
30    def name(cls):
31        return 'nrfjprog'
32
33    @classmethod
34    def tool_opt_help(cls) -> str:
35        return 'Additional options for nrfjprog, e.g. "--clockspeed"'
36
37    @classmethod
38    def do_create(cls, cfg, args):
39        return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset,
40                                    args.dev_id, erase=args.erase,
41                                    reset=args.reset,
42                                    tool_opt=args.tool_opt, force=args.force,
43                                    recover=args.recover, qspi_ini=args.qspi_ini)
44    @classmethod
45    def do_add_parser(cls, parser):
46        super().do_add_parser(parser)
47        parser.add_argument('--qspiini', required=False, dest='qspi_ini',
48                            help='path to an .ini file with qspi configuration')
49
50    def do_get_boards(self):
51        snrs = self.check_output(['nrfjprog', '--ids'])
52        return snrs.decode(sys.getdefaultencoding()).strip().splitlines()
53
54    def do_require(self):
55        self.require('nrfjprog')
56
57    def do_exec_op(self, op, force=False):
58        self.logger.debug(f'Executing op: {op}')
59        # Translate the op
60
61        families = {'NRF51_FAMILY': 'NRF51', 'NRF52_FAMILY': 'NRF52',
62                    'NRF53_FAMILY': 'NRF53', 'NRF54L_FAMILY': 'NRF54L',
63                    'NRF91_FAMILY': 'NRF91'}
64        cores = {'NRFDL_DEVICE_CORE_APPLICATION': 'CP_APPLICATION',
65                 'NRFDL_DEVICE_CORE_NETWORK': 'CP_NETWORK'}
66
67        core_opt = ['--coprocessor', cores[op['core']]] \
68                   if op.get('core') else []
69
70        cmd = ['nrfjprog']
71        _op = op['operation']
72        op_type = _op['type']
73        # options that are an empty dict must use "in" instead of get()
74        if op_type == 'pinreset-enable':
75            cmd.append('--pinresetenable')
76        elif op_type == 'program':
77            cmd.append('--program')
78            cmd.append(_op['firmware']['file'])
79            erase = _op['chip_erase_mode']
80            if erase == 'ERASE_ALL':
81                cmd.append('--chiperase')
82            elif erase == 'ERASE_PAGES':
83                cmd.append('--sectorerase')
84            elif erase == 'ERASE_PAGES_INCLUDING_UICR':
85                cmd.append('--sectoranduicrerase')
86            elif erase == 'NO_ERASE':
87                pass
88            else:
89                raise RuntimeError(f'Invalid erase mode: {erase}')
90
91            if _op.get('qspi_erase_mode'):
92                # In the future there might be multiple QSPI erase modes
93                cmd.append('--qspisectorerase')
94            if _op.get('verify'):
95                # In the future there might be multiple verify modes
96                cmd.append('--verify')
97            if self.qspi_ini:
98                cmd.append('--qspiini')
99                cmd.append(self.qspi_ini)
100        elif op_type == 'recover':
101            cmd.append('--recover')
102        elif op_type == 'reset':
103            if _op['option'] == 'RESET_SYSTEM':
104                cmd.append('--reset')
105            if _op['option'] == 'RESET_PIN':
106                cmd.append('--pinreset')
107        elif op_type == 'erasepage':
108            cmd.append('--erasepage')
109            cmd.append(f"0x{_op['page']:08x}")
110        else:
111            raise RuntimeError(f'Invalid operation: {op_type}')
112
113        try:
114            self.check_call(cmd + ['-f', families[self.family]] + core_opt +
115                            ['--snr', self.dev_id] + self.tool_opt)
116        except subprocess.CalledProcessError as cpe:
117            # Translate error codes
118            if cpe.returncode == UnavailableOperationBecauseProtectionError:
119                cpe.returncode = ErrNotAvailableBecauseProtection
120            elif cpe.returncode == VerifyError:
121                cpe.returncode = ErrVerify
122            raise cpe
123        return True
124