scripts: zephyr_flash_debug: add debug support to pyocd

Signed-off-by: Marti Bolivar <marti.bolivar@linaro.org>
diff --git a/scripts/support/zephyr_flash_debug.py b/scripts/support/zephyr_flash_debug.py
index 2271a9a..343775b 100755
--- a/scripts/support/zephyr_flash_debug.py
+++ b/scripts/support/zephyr_flash_debug.py
@@ -855,17 +855,27 @@
         check_call(cmd, self.debug)
 
 
+DEFAULT_PYOCD_GDB_PORT = 3333
+
+
 class PyOcdBinaryRunner(ZephyrBinaryRunner):
     '''Runner front-end for pyocd-flashtool.'''
 
     def __init__(self, target, flashtool='pyocd-flashtool',
-                 bin_name=None,
+                 gdb=None, gdbserver='pyocd-gdbserver',
+                 gdb_port=DEFAULT_PYOCD_GDB_PORT, tui=None,
+                 bin_name=None, elf_name=None,
                  board_id=None, daparg=None, debug=False):
         super(PyOcdBinaryRunner, self).__init__(debug=debug)
 
         self.target_args = ['-t', target]
         self.flashtool = flashtool
+        self.gdb_cmd = [gdb] if gdb is not None else None
+        self.gdbserver = gdbserver
+        self.gdb_port = gdb_port
+        self.tui_args = [tui] if tui is not None else []
         self.bin_name = bin_name
+        self.elf_name = elf_name
 
         board_args = []
         if board_id is not None:
@@ -878,15 +888,24 @@
         self.daparg_args = daparg_args
 
     def replaces_shell_script(shell_script, command):
-        return command == 'flash' and shell_script == 'pyocd.sh'
+        return (command in {'flash', 'debug', 'debugserver'} and
+                shell_script == 'pyocd.sh')
+
+    def port_args(self):
+        return ['-p', str(self.gdb_port)]
 
     def create_from_env(command, debug):
-        '''Create flasher from environment.
+        '''Create runner from environment.
 
         Required:
 
         - PYOCD_TARGET: target override
 
+        Optional:
+
+        - PYOCD_DAPARG: arguments to pass to pyocd tool, default is none
+        - PYOCD_BOARD_ID: ID of board to flash, default is to prompt
+
         Required for 'flash':
 
         - O: build output directory
@@ -895,25 +914,44 @@
         Optional for 'flash':
 
         - PYOCD_FLASHTOOL: flash tool path, defaults to pyocd-flashtool
-        - PYOCD_BOARD_ID: ID of board to flash, default is to guess
-        - PYOCD_DAPARG: arguments to pass to flashtool, default is none
+
+        Required for 'debug':
+
+        - O: build output directory
+        - KERNEL_ELF_NAME
+        - GDB: gdb executable
+
+        Optional for 'debug', 'debugserver':
+
+        - TUI: one additional argument to GDB (e.g. -tui)
+        - GDB_PORT: pyocd gdb port, defaults to 3333
+        - PYOCD_GDBSERVER: gdb server executable, defaults to pyocd-gdbserver
         '''
         target = get_env_or_bail('PYOCD_TARGET')
 
         o = os.environ.get('O', None)
         bin_ = os.environ.get('KERNEL_BIN_NAME', None)
+        elf = os.environ.get('KERNEL_ELF_NAME', None)
         bin_name = None
+        elf_name = None
         if o is not None:
             if bin_ is not None:
                 bin_name = path.join(o, bin_)
+            if elf is not None:
+                elf_name = path.join(o, elf)
 
         flashtool = os.environ.get('PYOCD_FLASHTOOL', 'pyocd-flashtool')
         board_id = os.environ.get('PYOCD_BOARD_ID', None)
         daparg = os.environ.get('PYOCD_DAPARG', None)
+        gdb = os.environ.get('GDB', None)
+        gdbserver = os.environ.get('PYOCD_GDBSERVER', 'pyocd-gdbserver')
+        gdb_port = os.environ.get('GDB_PORT', DEFAULT_PYOCD_GDB_PORT)
+        tui = os.environ.get('TUI', None)
 
-        return PyOcdBinaryRunner(target, flashtool=flashtool,
-                                 bin_name=bin_name, board_id=board_id,
-                                 daparg=daparg, debug=debug)
+        return PyOcdBinaryRunner(target, flashtool=flashtool, gdb=gdb,
+                                 gdbserver=gdbserver, gdb_port=gdb_port,
+                                 tui=tui, bin_name=bin_name, elf_name=elf_name,
+                                 board_id=board_id, daparg=daparg, debug=debug)
 
     def run(self, command, **kwargs):
         if command not in {'flash', 'debug', 'debugserver'}:
@@ -937,8 +975,32 @@
         print('Flashing Target Device')
         check_call(cmd, self.debug)
 
-    def debug_debugserver(command, **kwargs):
-        raise NotImplementedError()
+    def print_gdbserver_message(self):
+        print('pyOCD GDB server running on port {}'.format(self.gdb_port))
+
+    def debug_debugserver(self, command, **kwargs):
+        server_cmd = ([self.gdbserver] +
+                      self.daparg_args +
+                      self.port_args() +
+                      self.target_args +
+                      self.board_args)
+
+        if command == 'debugserver':
+            self.print_gdbserver_message()
+            check_call(server_cmd, self.debug)
+        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_args +
+                          [self.elf_name] +
+                          ['-ex', 'target remote :{}'.format(self.gdb_port),
+                           '-ex', 'load',
+                           '-ex', 'monitor reset halt'])
+            self.print_gdbserver_message()
+            self.run_server_and_client(server_cmd, client_cmd)
 
 
 # TODO: Stop using environment variables.