jlink.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. # Copyright (c) 2017 Linaro Limited.
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. '''Runner for debugging with J-Link.'''
  5. import argparse
  6. import logging
  7. import os
  8. from pathlib import Path
  9. import shlex
  10. import subprocess
  11. import sys
  12. import tempfile
  13. from runners.core import ZephyrBinaryRunner, RunnerCaps
  14. try:
  15. from pylink.library import Library
  16. MISSING_REQUIREMENTS = False
  17. except ImportError:
  18. MISSING_REQUIREMENTS = True
  19. DEFAULT_JLINK_EXE = 'JLink.exe' if sys.platform == 'win32' else 'JLinkExe'
  20. DEFAULT_JLINK_GDB_PORT = 2331
  21. class ToggleAction(argparse.Action):
  22. def __call__(self, parser, args, ignored, option):
  23. setattr(args, self.dest, not option.startswith('--no-'))
  24. class JLinkBinaryRunner(ZephyrBinaryRunner):
  25. '''Runner front-end for the J-Link GDB server.'''
  26. def __init__(self, cfg, device, did=None,
  27. commander=DEFAULT_JLINK_EXE,
  28. dt_flash=True, erase=True, reset_after_load=False,
  29. iface='swd', speed='auto',
  30. gdbserver='JLinkGDBServer',
  31. gdb_host='',
  32. gdb_port=DEFAULT_JLINK_GDB_PORT,
  33. tui=False, tool_opt=[]):
  34. super().__init__(cfg)
  35. self.hex_name = cfg.hex_file
  36. self.bin_name = cfg.bin_file
  37. self.elf_name = cfg.elf_file
  38. self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
  39. self.device = device
  40. self.did = did # Debugger Identifier
  41. self.commander = commander
  42. self.dt_flash = dt_flash
  43. self.erase = erase
  44. self.reset_after_load = reset_after_load
  45. self.gdbserver = gdbserver
  46. self.iface = iface
  47. self.speed = speed
  48. self.gdb_host = gdb_host
  49. self.gdb_port = gdb_port
  50. self.tui_arg = ['-tui'] if tui else []
  51. self.tool_opt = []
  52. for opts in [shlex.split(opt) for opt in tool_opt]:
  53. self.tool_opt += opts
  54. @classmethod
  55. def name(cls):
  56. return 'jlink'
  57. @classmethod
  58. def capabilities(cls):
  59. return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
  60. flash_addr=True, erase=True)
  61. @classmethod
  62. def do_add_parser(cls, parser):
  63. # Required:
  64. parser.add_argument('--device', required=True, help='device name')
  65. # Optional:
  66. parser.add_argument('--id', required=False,
  67. dest='did',
  68. help='Serial number of J-Link to use')
  69. parser.add_argument('--iface', default='swd',
  70. help='interface to use, default is swd')
  71. parser.add_argument('--speed', default='auto',
  72. help='interface speed, default is autodetect')
  73. parser.add_argument('--tui', default=False, action='store_true',
  74. help='if given, GDB uses -tui')
  75. parser.add_argument('--gdbserver', default='JLinkGDBServer',
  76. help='GDB server, default is JLinkGDBServer')
  77. parser.add_argument('--gdb-host', default='',
  78. help='custom gdb host, defaults to the empty string '
  79. 'and runs a gdb server')
  80. parser.add_argument('--gdb-port', default=DEFAULT_JLINK_GDB_PORT,
  81. help='pyocd gdb port, defaults to {}'.format(
  82. DEFAULT_JLINK_GDB_PORT))
  83. parser.add_argument('--tool-opt', default=[], action='append',
  84. help='''Additional options for JLink Commander,
  85. e.g. \'-autoconnect 1\' ''')
  86. parser.add_argument('--commander', default=DEFAULT_JLINK_EXE,
  87. help=f'''J-Link Commander, default is
  88. {DEFAULT_JLINK_EXE}''')
  89. parser.add_argument('--reset-after-load', '--no-reset-after-load',
  90. dest='reset_after_load', nargs=0,
  91. action=ToggleAction,
  92. help='reset after loading? (default: no)')
  93. parser.set_defaults(reset_after_load=False)
  94. @classmethod
  95. def do_create(cls, cfg, args):
  96. return JLinkBinaryRunner(cfg, args.device,
  97. did=args.did,
  98. commander=args.commander,
  99. dt_flash=args.dt_flash,
  100. erase=args.erase,
  101. reset_after_load=args.reset_after_load,
  102. iface=args.iface, speed=args.speed,
  103. gdbserver=args.gdbserver,
  104. gdb_host=args.gdb_host,
  105. gdb_port=args.gdb_port,
  106. tui=args.tui, tool_opt=args.tool_opt)
  107. def print_gdbserver_message(self):
  108. if not self.thread_info_enabled:
  109. thread_msg = '; no thread info available'
  110. elif self.supports_thread_info:
  111. thread_msg = '; thread info enabled'
  112. else:
  113. thread_msg = '; update J-Link software for thread info'
  114. self.logger.info('J-Link GDB server running on port '
  115. f'{self.gdb_port}{thread_msg}')
  116. @property
  117. def jlink_version(self):
  118. # Get the J-Link version as a (major, minor, rev) tuple of integers.
  119. #
  120. # J-Link's command line tools provide neither a standalone
  121. # "--version" nor help output that contains the version. Hack
  122. # around this deficiency by using the third-party pylink library
  123. # to load the shared library distributed with the tools, which
  124. # provides an API call for getting the version.
  125. if not hasattr(self, '_jlink_version'):
  126. plat = sys.platform
  127. if plat.startswith('win32'):
  128. libname = Library.get_appropriate_windows_sdk_name() + '.dll'
  129. elif plat.startswith('linux'):
  130. libname = Library.JLINK_SDK_NAME + '.so'
  131. elif plat.startswith('darwin'):
  132. libname = Library.JLINK_SDK_NAME + '.dylib'
  133. else:
  134. self.logger.warning(f'unknown platform {plat}; assuming UNIX')
  135. libname = Library.JLINK_SDK_NAME + '.so'
  136. lib = Library(dllpath=os.fspath(Path(self.commander).parent /
  137. libname))
  138. version = int(lib.dll().JLINKARM_GetDLLVersion())
  139. self.logger.debug('JLINKARM_GetDLLVersion()=%s', version)
  140. # The return value is an int with 2 decimal digits per
  141. # version subfield.
  142. self._jlink_version = (version // 10000,
  143. (version // 100) % 100,
  144. version % 100)
  145. return self._jlink_version
  146. @property
  147. def jlink_version_str(self):
  148. # Converts the numeric revision tuple to something human-readable.
  149. if not hasattr(self, '_jlink_version_str'):
  150. major, minor, rev = self.jlink_version
  151. rev_str = chr(ord('a') + rev - 1) if rev else ''
  152. self._jlink_version_str = f'{major}.{minor:02}{rev_str}'
  153. return self._jlink_version_str
  154. @property
  155. def supports_nogui(self):
  156. # -nogui was introduced in J-Link Commander v6.80
  157. return self.jlink_version >= (6, 80, 0)
  158. @property
  159. def supports_thread_info(self):
  160. # RTOSPlugin_Zephyr was introduced in 7.11b
  161. return self.jlink_version >= (7, 11, 2)
  162. def do_run(self, command, **kwargs):
  163. if MISSING_REQUIREMENTS:
  164. raise RuntimeError('one or more Python dependencies were missing; '
  165. "see the getting started guide for details on "
  166. "how to fix")
  167. # Convert commander to a real absolute path. We need this to
  168. # be able to find the shared library that tells us what
  169. # version of the tools we're using.
  170. self.commander = os.fspath(
  171. Path(self.require(self.commander)).resolve())
  172. self.logger.info(f'JLink version: {self.jlink_version_str}')
  173. rtos = self.thread_info_enabled and self.supports_thread_info
  174. plugin_dir = os.fspath(Path(self.commander).parent / 'GDBServer' /
  175. 'RTOSPlugin_Zephyr')
  176. server_cmd = ([self.gdbserver] +
  177. # only USB connections supported
  178. ['-select', 'usb' + (f'={self.did}' if self.did else ''),
  179. '-port', str(self.gdb_port),
  180. '-if', self.iface,
  181. '-speed', self.speed,
  182. '-device', self.device,
  183. '-silent',
  184. '-singlerun'] +
  185. (['-nogui'] if self.supports_nogui else []) +
  186. (['-rtos', plugin_dir] if rtos else []) +
  187. self.tool_opt)
  188. if command == 'flash':
  189. self.flash(**kwargs)
  190. elif command == 'debugserver':
  191. if self.gdb_host:
  192. raise ValueError('Cannot run debugserver with --gdb-host')
  193. self.require(self.gdbserver)
  194. self.print_gdbserver_message()
  195. self.check_call(server_cmd)
  196. else:
  197. if self.gdb_cmd is None:
  198. raise ValueError('Cannot debug; gdb is missing')
  199. if self.elf_name is None:
  200. raise ValueError('Cannot debug; elf is missing')
  201. client_cmd = (self.gdb_cmd +
  202. self.tui_arg +
  203. [self.elf_name] +
  204. ['-ex', 'target remote {}:{}'.format(self.gdb_host, self.gdb_port)])
  205. if command == 'debug':
  206. client_cmd += ['-ex', 'monitor halt',
  207. '-ex', 'monitor reset',
  208. '-ex', 'load']
  209. if self.reset_after_load:
  210. client_cmd += ['-ex', 'monitor reset']
  211. if not self.gdb_host:
  212. self.require(self.gdbserver)
  213. self.print_gdbserver_message()
  214. self.run_server_and_client(server_cmd, client_cmd)
  215. else:
  216. self.run_client(client_cmd)
  217. def flash(self, **kwargs):
  218. lines = ['r'] # Reset and halt the target
  219. if self.erase:
  220. lines.append('erase') # Erase all flash sectors
  221. # Get the build artifact to flash, prefering .hex over .bin
  222. if self.hex_name is not None and os.path.isfile(self.hex_name):
  223. flash_file = self.hex_name
  224. flash_cmd = f'loadfile {self.hex_name}'
  225. elif self.bin_name is not None and os.path.isfile(self.bin_name):
  226. if self.dt_flash:
  227. flash_addr = self.flash_address_from_build_conf(self.build_conf)
  228. else:
  229. flash_addr = 0
  230. flash_file = self.bin_name
  231. flash_cmd = f'loadfile {self.bin_name} 0x{flash_addr:x}'
  232. else:
  233. err = 'Cannot flash; no hex ({}) or bin ({}) files found.'
  234. raise ValueError(err.format(self.hex_name, self.bin_name))
  235. # Flash the selected build artifact
  236. lines.append(flash_cmd)
  237. if self.reset_after_load:
  238. lines.append('r') # Reset and halt the target
  239. lines.append('g') # Start the CPU
  240. # Reset the Debug Port CTRL/STAT register
  241. # Under normal operation this is done automatically, but if other
  242. # JLink tools are running, it is not performed.
  243. # The J-Link scripting layer chains commands, meaning that writes are
  244. # not actually performed until after the next operation. After writing
  245. # the register, read it back to perform this flushing.
  246. lines.append('writeDP 1 0')
  247. lines.append('readDP 1')
  248. lines.append('q') # Close the connection and quit
  249. self.logger.debug('JLink commander script:\n' +
  250. '\n'.join(lines))
  251. # Don't use NamedTemporaryFile: the resulting file can't be
  252. # opened again on Windows.
  253. with tempfile.TemporaryDirectory(suffix='jlink') as d:
  254. fname = os.path.join(d, 'runner.jlink')
  255. with open(fname, 'wb') as f:
  256. f.writelines(bytes(line + '\n', 'utf-8') for line in lines)
  257. cmd = ([self.commander] +
  258. # only USB connections supported
  259. (['-USB', f'{self.did}'] if self.did else []) +
  260. (['-nogui', '1'] if self.supports_nogui else []) +
  261. ['-if', self.iface,
  262. '-speed', self.speed,
  263. '-device', self.device,
  264. '-CommanderScript', fname] +
  265. (['-nogui', '1'] if self.supports_nogui else []) +
  266. self.tool_opt)
  267. self.logger.info('Flashing file: {}'.format(flash_file))
  268. kwargs = {}
  269. if not self.logger.isEnabledFor(logging.DEBUG):
  270. kwargs['stdout'] = subprocess.DEVNULL
  271. self.check_call(cmd, **kwargs)