nrfjprog.py 14 KB

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