| # Copyright (c) 2017 Linaro Limited. |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| '''Runner for debugging with J-Link.''' |
| |
| import argparse |
| import os |
| import shlex |
| import sys |
| import tempfile |
| |
| from runners.core import ZephyrBinaryRunner, RunnerCaps, \ |
| BuildConfiguration |
| |
| DEFAULT_JLINK_EXE = 'JLink.exe' if sys.platform == 'win32' else 'JLinkExe' |
| DEFAULT_JLINK_GDB_PORT = 2331 |
| |
| class ToggleAction(argparse.Action): |
| |
| def __call__(self, parser, args, ignored, option): |
| setattr(args, self.dest, not option.startswith('--no-')) |
| |
| class JLinkBinaryRunner(ZephyrBinaryRunner): |
| '''Runner front-end for the J-Link GDB server.''' |
| |
| def __init__(self, cfg, device, |
| commander=DEFAULT_JLINK_EXE, |
| flash_addr=0x0, erase=True, reset_after_load=False, |
| iface='swd', speed='auto', |
| gdbserver='JLinkGDBServer', gdb_port=DEFAULT_JLINK_GDB_PORT, |
| tui=False, tool_opt=[]): |
| super(JLinkBinaryRunner, self).__init__(cfg) |
| self.bin_name = cfg.bin_file |
| self.elf_name = cfg.elf_file |
| self.gdb_cmd = [cfg.gdb] if cfg.gdb else None |
| self.device = device |
| self.commander = commander |
| self.flash_addr = flash_addr |
| self.erase = erase |
| self.reset_after_load = reset_after_load |
| self.gdbserver = gdbserver |
| self.iface = iface |
| self.speed = speed |
| self.gdb_port = gdb_port |
| self.tui_arg = ['-tui'] if tui else [] |
| |
| self.tool_opt = [] |
| for opts in [shlex.split(opt) for opt in tool_opt]: |
| self.tool_opt += opts |
| |
| @classmethod |
| def name(cls): |
| return 'jlink' |
| |
| @classmethod |
| def capabilities(cls): |
| return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'}, |
| flash_addr=True) |
| |
| @classmethod |
| def do_add_parser(cls, parser): |
| # Required: |
| parser.add_argument('--device', required=True, help='device name') |
| |
| # Optional: |
| parser.add_argument('--iface', default='swd', |
| help='interface to use, default is swd') |
| parser.add_argument('--speed', default='auto', |
| help='interface speed, default is autodetect') |
| parser.add_argument('--tui', default=False, action='store_true', |
| help='if given, GDB uses -tui') |
| parser.add_argument('--gdbserver', default='JLinkGDBServer', |
| help='GDB server, default is JLinkGDBServer') |
| parser.add_argument('--gdb-port', default=DEFAULT_JLINK_GDB_PORT, |
| help='pyocd gdb port, defaults to {}'.format( |
| DEFAULT_JLINK_GDB_PORT)) |
| parser.add_argument('--tool-opt', default=[], action='append', |
| help='''Additional options for JLink Commander, |
| e.g. \'-autoconnect 1\' ''') |
| parser.add_argument('--commander', default=DEFAULT_JLINK_EXE, |
| help='J-Link Commander, default is JLinkExe') |
| parser.add_argument('--erase', default=False, action='store_true', |
| help='if given, mass erase flash before loading') |
| parser.add_argument('--reset-after-load', '--no-reset-after-load', |
| dest='reset_after_load', nargs=0, |
| action=ToggleAction, |
| help='reset after loading? (default: no)') |
| |
| parser.set_defaults(reset_after_load=False) |
| |
| @classmethod |
| def create(cls, cfg, args): |
| build_conf = BuildConfiguration(cfg.build_dir) |
| flash_addr = cls.get_flash_address(args, build_conf) |
| return JLinkBinaryRunner(cfg, args.device, |
| commander=args.commander, |
| flash_addr=flash_addr, erase=args.erase, |
| reset_after_load=args.reset_after_load, |
| iface=args.iface, speed=args.speed, |
| gdbserver=args.gdbserver, |
| gdb_port=args.gdb_port, |
| tui=args.tui, tool_opt=args.tool_opt) |
| |
| def print_gdbserver_message(self): |
| self.logger.info('J-Link GDB server running on port {}'. |
| format(self.gdb_port)) |
| |
| def do_run(self, command, **kwargs): |
| server_cmd = ([self.gdbserver] + |
| ['-select', 'usb', # only USB connections supported |
| '-port', str(self.gdb_port), |
| '-if', self.iface, |
| '-speed', self.speed, |
| '-device', self.device, |
| '-silent', |
| '-singlerun'] + |
| self.tool_opt) |
| |
| if command == 'flash': |
| self.flash(**kwargs) |
| elif command == 'debugserver': |
| self.require(self.gdbserver) |
| self.print_gdbserver_message() |
| self.check_call(server_cmd) |
| else: |
| self.require(self.gdbserver) |
| if self.gdb_cmd is None: |
| raise ValueError('Cannot debug; gdb is missing') |
| if self.elf_name is None: |
| raise ValueError('Cannot debug; elf is missing') |
| client_cmd = (self.gdb_cmd + |
| self.tui_arg + |
| [self.elf_name] + |
| ['-ex', 'target remote :{}'.format(self.gdb_port)]) |
| if command == 'debug': |
| client_cmd += ['-ex', 'monitor halt', |
| '-ex', 'monitor reset', |
| '-ex', 'load'] |
| if self.reset_after_load: |
| client_cmd += ['-ex', 'monitor reset'] |
| |
| self.print_gdbserver_message() |
| self.run_server_and_client(server_cmd, client_cmd) |
| |
| def flash(self, **kwargs): |
| self.require(self.commander) |
| if self.bin_name is None: |
| raise ValueError('Cannot flash; bin_name is missing') |
| |
| lines = ['r'] # Reset and halt the target |
| |
| if self.erase: |
| lines.append('erase') # Erase all flash sectors |
| |
| lines.append('loadfile {} 0x{:x}'.format(self.bin_name, |
| self.flash_addr)) |
| if self.reset_after_load: |
| lines.append('r') # Reset and halt the target |
| |
| lines.append('g') # Start the CPU |
| lines.append('q') # Close the connection and quit |
| |
| self.logger.debug('JLink commander script:') |
| self.logger.debug('\n'.join(lines)) |
| |
| # Don't use NamedTemporaryFile: the resulting file can't be |
| # opened again on Windows. |
| with tempfile.TemporaryDirectory(suffix='jlink') as d: |
| fname = os.path.join(d, 'runner.jlink') |
| with open(fname, 'wb') as f: |
| f.writelines(bytes(line + '\n', 'utf-8') for line in lines) |
| |
| cmd = ([self.commander] + |
| ['-if', self.iface, |
| '-speed', self.speed, |
| '-device', self.device, |
| '-CommanderScript', fname] + |
| self.tool_opt) |
| |
| self.logger.info('Flashing file: {}'.format(self.bin_name)) |
| self.check_call(cmd) |