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, pinreset, 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, pinreset, 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 capabilities(cls):
35        return NrfBinaryRunner._capabilities()
36
37    @classmethod
38    def dev_id_help(cls) -> str:
39        return NrfBinaryRunner._dev_id_help()
40
41    @classmethod
42    def tool_opt_help(cls) -> str:
43        return 'Additional options for nrfjprog, e.g. "--clockspeed"'
44
45    @classmethod
46    def do_create(cls, cfg, args):
47        return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset,
48                                    args.pinreset, args.dev_id, erase=args.erase,
49                                    reset=args.reset,
50                                    tool_opt=args.tool_opt, force=args.force,
51                                    recover=args.recover, qspi_ini=args.qspi_ini)
52    @classmethod
53    def do_add_parser(cls, parser):
54        super().do_add_parser(parser)
55        parser.add_argument('--qspiini', required=False, dest='qspi_ini',
56                            help='path to an .ini file with qspi configuration')
57
58    def do_get_boards(self):
59        snrs = self.check_output(['nrfjprog', '--ids'])
60        return snrs.decode(sys.getdefaultencoding()).strip().splitlines()
61
62    def do_require(self):
63        self.require('nrfjprog')
64
65    def do_exec_op(self, op, force=False):
66        self.logger.debug(f'Executing op: {op}')
67        # Translate the op
68
69        families = {'nrf51': 'NRF51', 'nrf52': 'NRF52',
70                    'nrf53': 'NRF53', 'nrf54l': 'NRF54L',
71                    'nrf91': 'NRF91'}
72        cores = {'Application': 'CP_APPLICATION',
73                 'Network': 'CP_NETWORK'}
74
75        core_opt = ['--coprocessor', cores[op['core']]] \
76                   if op.get('core') else []
77
78        cmd = ['nrfjprog']
79        _op = op['operation']
80        op_type = _op['type']
81        # options that are an empty dict must use "in" instead of get()
82        if op_type == 'pinreset-enable':
83            cmd.append('--pinresetenable')
84        elif op_type == 'program':
85            cmd.append('--program')
86            cmd.append(_op['firmware']['file'])
87            opts = _op['options']
88            erase = opts['chip_erase_mode']
89            if erase == 'ERASE_ALL':
90                cmd.append('--chiperase')
91            elif erase == 'ERASE_RANGES_TOUCHED_BY_FIRMWARE':
92                if self.family == 'nrf52':
93                    cmd.append('--sectoranduicrerase')
94                else:
95                    cmd.append('--sectorerase')
96            elif erase == 'NO_ERASE':
97                pass
98            else:
99                raise RuntimeError(f'Invalid erase mode: {erase}')
100
101            if opts.get('ext_mem_erase_mode'):
102                if opts['ext_mem_erase_mode'] == 'ERASE_RANGES_TOUCHED_BY_FIRMWARE':
103                    cmd.append('--qspisectorerase')
104                elif opts['ext_mem_erase_mode'] == 'ERASE_ALL':
105                    cmd.append('--qspichiperase')
106
107            if opts.get('verify'):
108                # In the future there might be multiple verify modes
109                cmd.append('--verify')
110            if self.qspi_ini:
111                cmd.append('--qspiini')
112                cmd.append(self.qspi_ini)
113        elif op_type == 'recover':
114            cmd.append('--recover')
115        elif op_type == 'reset':
116            if _op['kind'] == 'RESET_SYSTEM':
117                cmd.append('--reset')
118            if _op['kind'] == 'RESET_PIN':
119                cmd.append('--pinreset')
120        elif op_type == 'erase':
121            cmd.append(f'--erase{_op["kind"]}')
122        else:
123            raise RuntimeError(f'Invalid operation: {op_type}')
124
125        try:
126            self.check_call(cmd + ['-f', families[self.family]] + core_opt +
127                            ['--snr', self.dev_id] + self.tool_opt)
128        except subprocess.CalledProcessError as cpe:
129            # Translate error codes
130            if cpe.returncode == UnavailableOperationBecauseProtectionError:
131                cpe.returncode = ErrNotAvailableBecauseProtection
132            elif cpe.returncode == VerifyError:
133                cpe.returncode = ErrVerify
134            raise cpe
135        return True
136