1# Copyright (c) 2017 Linaro Limited. 2# Copyright (c) 2019 Nordic Semiconductor ASA. 3# 4# SPDX-License-Identifier: Apache-2.0 5 6'''Runner for flashing with nrfjprog.''' 7 8import os 9from pathlib import Path 10import shlex 11import subprocess 12import sys 13from re import fullmatch, escape 14 15from runners.core import ZephyrBinaryRunner, RunnerCaps 16 17try: 18 from intelhex import IntelHex 19except ImportError: 20 IntelHex = None 21 22# Helper function for inspecting hex files. 23# has_region returns True if hex file has any contents in a specific region 24# region_filter is a callable that takes an address as argument and 25# returns True if that address is in the region in question 26def has_region(regions, hex_file): 27 if IntelHex is None: 28 raise RuntimeError('one or more Python dependencies were missing; ' 29 "see the getting started guide for details on " 30 "how to fix") 31 32 try: 33 ih = IntelHex(hex_file) 34 return any((len(ih[rs:re]) > 0) for (rs, re) in regions) 35 except FileNotFoundError: 36 return False 37 38# https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf_cltools%2FUG%2Fcltools%2Fnrf_nrfjprogexe_return_codes.html&cp=9_1_3_1 39UnavailableOperationBecauseProtectionError = 16 40 41class NrfJprogBinaryRunner(ZephyrBinaryRunner): 42 '''Runner front-end for nrfjprog.''' 43 44 def __init__(self, cfg, family, softreset, snr, erase=False, 45 tool_opt=[], force=False, recover=False): 46 super().__init__(cfg) 47 self.hex_ = cfg.hex_file 48 self.family = family 49 self.softreset = softreset 50 self.snr = snr 51 self.erase = bool(erase) 52 self.force = force 53 self.recover = bool(recover) 54 55 self.tool_opt = [] 56 for opts in [shlex.split(opt) for opt in tool_opt]: 57 self.tool_opt += opts 58 59 @classmethod 60 def name(cls): 61 return 'nrfjprog' 62 63 @classmethod 64 def capabilities(cls): 65 return RunnerCaps(commands={'flash'}, erase=True) 66 67 @classmethod 68 def do_add_parser(cls, parser): 69 parser.add_argument('--nrf-family', 70 choices=['NRF51', 'NRF52', 'NRF53', 'NRF91'], 71 help='''MCU family; still accepted for 72 compatibility only''') 73 parser.add_argument('--softreset', required=False, 74 action='store_true', 75 help='use reset instead of pinreset') 76 parser.add_argument('--snr', required=False, 77 help="""Serial number of board to use. 78 '*' matches one or more characters/digits.""") 79 parser.add_argument('--tool-opt', default=[], action='append', 80 help='''Additional options for nrfjprog, 81 e.g. "--recover"''') 82 parser.add_argument('--force', required=False, 83 action='store_true', 84 help='Flash even if the result cannot be guaranteed.') 85 parser.add_argument('--recover', required=False, 86 action='store_true', 87 help='''erase all user available non-volatile 88 memory and disable read back protection before 89 flashing (erases flash for both cores on nRF53)''') 90 91 @classmethod 92 def do_create(cls, cfg, args): 93 return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset, 94 args.snr, erase=args.erase, 95 tool_opt=args.tool_opt, force=args.force, 96 recover=args.recover) 97 98 def ensure_snr(self): 99 if not self.snr or "*" in self.snr: 100 self.snr = self.get_board_snr(self.snr or "*") 101 self.snr = self.snr.lstrip("0") 102 103 def get_boards(self): 104 snrs = self.check_output(['nrfjprog', '--ids']) 105 snrs = snrs.decode(sys.getdefaultencoding()).strip().splitlines() 106 if not snrs: 107 raise RuntimeError('"nrfjprog --ids" did not find a board; ' 108 'is the board connected?') 109 return snrs 110 111 @staticmethod 112 def verify_snr(snr): 113 if snr == '0': 114 raise RuntimeError('"nrfjprog --ids" returned 0; ' 115 'is a debugger already connected?') 116 117 def get_board_snr(self, glob): 118 # Use nrfjprog --ids to discover connected boards. 119 # 120 # If there's exactly one board connected, it's safe to assume 121 # the user wants that one. Otherwise, bail unless there are 122 # multiple boards and we are connected to a terminal, in which 123 # case use print() and input() to ask what the user wants. 124 125 re_glob = escape(glob).replace(r"\*", ".+") 126 snrs = [snr for snr in self.get_boards() if fullmatch(re_glob, snr)] 127 128 if len(snrs) == 0: 129 raise RuntimeError( 130 'There are no boards connected{}.'.format( 131 f" matching '{glob}'" if glob != "*" else "")) 132 elif len(snrs) == 1: 133 board_snr = snrs[0] 134 self.verify_snr(board_snr) 135 print("Using board {}".format(board_snr)) 136 return board_snr 137 elif not sys.stdin.isatty(): 138 raise RuntimeError( 139 f'refusing to guess which of {len(snrs)} ' 140 'connected boards to use. (Interactive prompts ' 141 'disabled since standard input is not a terminal.) ' 142 'Please specify a serial number on the command line.') 143 144 snrs = sorted(snrs) 145 print('There are multiple boards connected{}.'.format( 146 f" matching '{glob}'" if glob != "*" else "")) 147 for i, snr in enumerate(snrs, 1): 148 print('{}. {}'.format(i, snr)) 149 150 p = 'Please select one with desired serial number (1-{}): '.format( 151 len(snrs)) 152 while True: 153 try: 154 value = input(p) 155 except EOFError: 156 sys.exit(0) 157 try: 158 value = int(value) 159 except ValueError: 160 continue 161 if 1 <= value <= len(snrs): 162 break 163 164 return snrs[value - 1] 165 166 def ensure_family(self): 167 # Ensure self.family is set. 168 169 if self.family is not None: 170 return 171 172 if self.build_conf.getboolean('CONFIG_SOC_SERIES_NRF51X'): 173 self.family = 'NRF51' 174 elif self.build_conf.getboolean('CONFIG_SOC_SERIES_NRF52X'): 175 self.family = 'NRF52' 176 elif self.build_conf.getboolean('CONFIG_SOC_SERIES_NRF53X'): 177 self.family = 'NRF53' 178 elif self.build_conf.getboolean('CONFIG_SOC_SERIES_NRF91X'): 179 self.family = 'NRF91' 180 else: 181 raise RuntimeError(f'unknown nRF; update {__file__}') 182 183 def check_force_uicr(self): 184 # On SoCs without --sectoranduicrerase, we want to fail by 185 # default if the application contains UICR data and we're not sure 186 # that the flash will succeed. 187 188 # A map from SoCs which need this check to their UICR address 189 # ranges. If self.family isn't in here, do nothing. 190 uicr_ranges = { 191 'NRF53': ((0x00FF8000, 0x00FF8800), 192 (0x01FF8000, 0x01FF8800)), 193 'NRF91': ((0x00FF8000, 0x00FF8800),), 194 } 195 196 if self.family not in uicr_ranges: 197 return 198 199 uicr = uicr_ranges[self.family] 200 201 if not self.uicr_data_ok and has_region(uicr, self.hex_): 202 # Hex file has UICR contents, and that's not OK. 203 raise RuntimeError( 204 'The hex file contains data placed in the UICR, which ' 205 'needs a full erase before reprogramming. Run west ' 206 'flash again with --force, --erase, or --recover.') 207 208 @property 209 def uicr_data_ok(self): 210 # True if it's OK to try to flash even with UICR data 211 # in the image; False otherwise. 212 213 return self.force or self.erase or self.recover 214 215 def recover_target(self): 216 if self.family == 'NRF53': 217 self.logger.info( 218 'Recovering and erasing flash memory for both the network ' 219 'and application cores.') 220 else: 221 self.logger.info('Recovering and erasing all flash memory.') 222 223 if self.family == 'NRF53': 224 self.check_call(['nrfjprog', '--recover', '-f', self.family, 225 '--coprocessor', 'CP_NETWORK', 226 '--snr', self.snr]) 227 228 self.check_call(['nrfjprog', '--recover', '-f', self.family, 229 '--snr', self.snr]) 230 231 def program_hex(self): 232 # Get the nrfjprog command use to actually program self.hex_. 233 self.logger.info('Flashing file: {}'.format(self.hex_)) 234 235 # What type of erase argument should we pass to nrfjprog? 236 if self.erase: 237 erase_arg = '--chiperase' 238 else: 239 if self.family == 'NRF52': 240 erase_arg = '--sectoranduicrerase' 241 else: 242 erase_arg = '--sectorerase' 243 244 # What nrfjprog commands do we need to flash this target? 245 program_commands = [] 246 if self.family == 'NRF53': 247 # nRF53 requires special treatment due to the extra coprocessor. 248 self.program_hex_nrf53(erase_arg, program_commands) 249 else: 250 # It's important for tool_opt to come last, so it can override 251 # any options that we set here. 252 program_commands.append(['nrfjprog', '--program', self.hex_, 253 erase_arg, '-f', self.family, 254 '--snr', self.snr] + 255 self.tool_opt) 256 257 try: 258 for command in program_commands: 259 self.check_call(command) 260 except subprocess.CalledProcessError as cpe: 261 if cpe.returncode == UnavailableOperationBecauseProtectionError: 262 if self.family == 'NRF53': 263 family_help = ( 264 ' Note: your target is an nRF53; all flash memory ' 265 'for both the network and application cores will be ' 266 'erased prior to reflashing.') 267 else: 268 family_help = ( 269 ' Note: this will recover and erase all flash memory ' 270 'prior to reflashing.') 271 self.logger.error( 272 'Flashing failed because the target ' 273 'must be recovered.\n' 274 ' To fix, run "west flash --recover" instead.\n' + 275 family_help) 276 raise 277 278 def program_hex_nrf53(self, erase_arg, program_commands): 279 # program_hex() helper for nRF53. 280 281 # *********************** NOTE ******************************* 282 # self.hex_ can contain code for both the application core and 283 # the network core. 284 # 285 # We can't assume, for example, that 286 # CONFIG_SOC_NRF5340_CPUAPP=y means self.hex_ only contains 287 # data for the app core's flash: the user can put arbitrary 288 # addresses into one of the files in HEX_FILES_TO_MERGE. 289 # 290 # Therefore, on this family, we may need to generate two new 291 # hex files, one for each core, and flash them individually 292 # with the correct '--coprocessor' arguments. 293 # 294 # Kind of hacky, but it works, and nrfjprog is not capable of 295 # flashing to both cores at once. If self.hex_ only affects 296 # one core's flash, then we skip the extra work to save time. 297 # ************************************************************ 298 299 def add_program_cmd(hex_file, coprocessor): 300 program_commands.append( 301 ['nrfjprog', '--program', hex_file, erase_arg, 302 '-f', 'NRF53', '--snr', self.snr, 303 '--coprocessor', coprocessor] + self.tool_opt) 304 305 full_hex = IntelHex() 306 full_hex.loadfile(self.hex_, format='hex') 307 min_addr, max_addr = full_hex.minaddr(), full_hex.maxaddr() 308 309 # Base address of network coprocessor's flash. From nRF5340 310 # OPS. We should get this from DTS instead if multiple values 311 # are possible, but this is fine for now. 312 net_base = 0x01000000 313 314 if min_addr < net_base <= max_addr: 315 net_hex, app_hex = IntelHex(), IntelHex() 316 317 for start, stop in full_hex.segments(): 318 segment_hex = net_hex if start >= net_base else app_hex 319 segment_hex.merge(full_hex[start:stop]) 320 321 hex_path = Path(self.hex_) 322 hex_dir, hex_name = hex_path.parent, hex_path.name 323 324 net_hex_file = os.fspath(hex_dir / f'GENERATED_CP_NETWORK_{hex_name}') 325 app_hex_file = os.fspath( 326 hex_dir / f'GENERATED_CP_APPLICATION_{hex_name}') 327 328 self.logger.info( 329 f'{self.hex_} targets both nRF53 coprocessors; ' 330 f'splitting it into: {net_hex_file} and {app_hex_file}') 331 332 net_hex.write_hex_file(net_hex_file) 333 app_hex.write_hex_file(app_hex_file) 334 335 add_program_cmd(net_hex_file, 'CP_NETWORK') 336 add_program_cmd(app_hex_file, 'CP_APPLICATION') 337 else: 338 coprocessor = 'CP_NETWORK' if max_addr >= net_base else 'CP_APPLICATION' 339 add_program_cmd(self.hex_, coprocessor) 340 341 def reset_target(self): 342 if self.family == 'NRF52' and not self.softreset: 343 self.check_call(['nrfjprog', '--pinresetenable', '-f', self.family, 344 '--snr', self.snr]) # Enable pin reset 345 346 if self.softreset: 347 self.check_call(['nrfjprog', '--reset', '-f', self.family, 348 '--snr', self.snr]) 349 else: 350 self.check_call(['nrfjprog', '--pinreset', '-f', self.family, 351 '--snr', self.snr]) 352 353 def do_run(self, command, **kwargs): 354 self.require('nrfjprog') 355 356 self.ensure_output('hex') 357 self.ensure_snr() 358 self.ensure_family() 359 self.check_force_uicr() 360 361 if self.recover: 362 self.recover_target() 363 self.program_hex() 364 self.reset_target() 365 366 self.logger.info(f'Board with serial number {self.snr} ' 367 'flashed successfully.') 368