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