scripts: runner: add jlink.py, with debug and debugserver support

Signed-off-by: Marti Bolivar <marti.bolivar@linaro.org>
diff --git a/scripts/support/runner/__init__.py b/scripts/support/runner/__init__.py
index d14f6a4..35a2bde 100644
--- a/scripts/support/runner/__init__.py
+++ b/scripts/support/runner/__init__.py
@@ -14,6 +14,7 @@
 from . import bossac
 from . import dfu
 from . import esp32
+from . import jlink
 from . import nios2
 from . import nrfjprog
 from . import openocd
diff --git a/scripts/support/runner/jlink.py b/scripts/support/runner/jlink.py
new file mode 100644
index 0000000..35770ab
--- /dev/null
+++ b/scripts/support/runner/jlink.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2017 Linaro Limited.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+'''Runner for debugging with JLink.'''
+
+from os import path
+import os
+
+from .core import ZephyrBinaryRunner, get_env_or_bail
+
+DEFAULT_JLINK_GDB_PORT = 2331
+
+
+class JLinkBinaryRunner(ZephyrBinaryRunner):
+    '''Runner front-end for the J-Link GDB server.'''
+
+    def __init__(self, device,
+                 gdbserver='JLinkGDBServer', iface='swd', elf_name=None,
+                 gdb=None, gdb_port=DEFAULT_JLINK_GDB_PORT, tui=None,
+                 debug=False):
+        super(JLinkBinaryRunner, self).__init__(debug=debug)
+        self.device = device
+        self.gdbserver_cmd = [gdbserver]
+        self.iface = iface
+        self.elf_name = elf_name
+        self.gdb_cmd = [gdb] if gdb is not None else None
+        self.gdb_port = gdb_port
+        self.tui_arg = [tui] if tui is not None else []
+
+    def replaces_shell_script(shell_script, command):
+        return (command in {'debug', 'debugserver'} and
+                shell_script == 'jlink.sh')
+
+    def create_from_env(command, debug):
+        '''Create runner from environment.
+
+        Required:
+
+        - JLINK_DEVICE: device name
+
+        Required for 'debug':
+
+        - GDB: gdb to use
+        - O: build output directory
+        - KERNEL_ELF_NAME: zephyr kernel binary in ELF format
+
+        Optional for 'debug':
+
+        - TUI: if present, passed to gdb server used to flash
+
+        Optional for 'debug', 'debugserver':
+
+        - JLINK_GDBSERVER: default is JLinkGDBServer
+        - GDB_PORT: default is 2331
+        - JLINK_IF: default is swd
+        '''
+        device = get_env_or_bail('JLINK_DEVICE')
+
+        gdb = os.environ.get('GDB', None)
+        o = os.environ.get('O', None)
+        elf = os.environ.get('KERNEL_ELF_NAME', None)
+        elf_name = None
+        if o is not None:
+            if elf is not None:
+                elf_name = path.join(o, elf)
+        tui = os.environ.get('TUI', None)
+
+        gdbserver = os.environ.get('JLINK_GDBSERVER', 'JLinkGDBServer')
+        gdb_port = int(os.environ.get('GDB_PORT',
+                                      str(DEFAULT_JLINK_GDB_PORT)))
+        iface = os.environ.get('JLINK_IF', 'swd')
+
+        return JLinkBinaryRunner(device, gdbserver=gdbserver,
+                                 iface=iface, elf_name=elf_name,
+                                 gdb=gdb, gdb_port=gdb_port, tui=tui,
+                                 debug=debug)
+
+    def print_gdbserver_message(self):
+        print('JLink GDB server running on port {}'.format(self.gdb_port))
+
+    def run(self, command, **kwargs):
+        if command not in {'debug', 'debugserver'}:
+            raise ValueError('{} is not supported'.format(command))
+
+        server_cmd = (self.gdbserver_cmd +
+                      ['-port', str(self.gdb_port),
+                       '-if', self.iface,
+                       '-device', self.device,
+                       '-silent',
+                       '-singlerun'])
+
+        if command == 'debugserver':
+            self.print_gdbserver_message()
+            self.check_call(server_cmd)
+        else:
+            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),
+                           '-ex', 'monitor halt',
+                           '-ex', 'load',
+                           '-ex', 'monitor reset'])
+            self.print_gdbserver_message()
+            self.run_server_and_client(server_cmd, client_cmd)