scripts: zephyr_flash_debug: flash like dfuutil.sh

Add support for flashing targets compatible with dfuutil.sh.

Tested on 96b_carbon.

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 113a1b7..a73303c 100755
--- a/scripts/support/zephyr_flash_debug.py
+++ b/scripts/support/zephyr_flash_debug.py
@@ -13,8 +13,20 @@
 import abc
 from os import path
 import os
+import pprint
 import sys
 import subprocess
+import time
+
+
+def get_env_or_bail(env_var):
+    try:
+        return os.environ[env_var]
+    except KeyError:
+        print('Variable {} not in environment:'.format(
+                  env_var), file=sys.stderr)
+        pprint.pprint(dict(os.environ), stream=sys.stderr)
+        raise
 
 
 def get_env_bool_or(env_var, default_value):
@@ -30,6 +42,12 @@
     subprocess.check_call(cmd)
 
 
+def check_output(cmd, debug):
+    if debug:
+        print(' '.join(cmd))
+    return subprocess.check_output(cmd)
+
+
 class ZephyrBinaryFlasher(abc.ABC):
     '''Abstract superclass for flasher objects.'''
 
@@ -66,6 +84,70 @@
         '''Flash the board.'''
 
 
+class DfuUtilBinaryFlasher(ZephyrBinaryFlasher):
+    '''Flasher front-end for dfu-util.'''
+
+    def __init__(self, pid, alt, img, dfuse=None, exe='dfu-util', debug=False):
+        super(DfuUtilBinaryFlasher, self).__init__(debug=debug)
+        self.pid = pid
+        self.alt = alt
+        self.img = img
+        self.dfuse = dfuse
+        self.exe = exe
+        try:
+            self.list_pattern = ', alt={}'.format(int(self.alt))
+        except ValueError:
+            self.list_pattern = ', name={}'.format(self.alt)
+
+    def replaces_shell_script(shell_script):
+        return shell_script == 'dfuutil.sh'
+
+    def create_from_env(debug):
+        '''Create flasher from environment.
+
+        Required:
+
+        - DFUUTIL_PID: USB VID:PID of the board
+        - DFUUTIL_ALT: interface alternate setting number or name
+        - DFUUTIL_IMG: binary to flash
+
+        Optional:
+
+        - DFUUTIL_DFUSE_ADDR: target address if the board is a
+          DfuSe device. Ignored if not present.
+        - DFUUTIL: dfu-util executable, defaults to dfu-util.
+        '''
+        pid = get_env_or_bail('DFUUTIL_PID')
+        alt = get_env_or_bail('DFUUTIL_ALT')
+        img = get_env_or_bail('DFUUTIL_IMG')
+        dfuse = os.environ.get('DFUUTIL_DFUSE_ADDR', None)
+        exe = os.environ.get('DFUUTIL', 'dfu-util')
+
+        return DfuUtilBinaryFlasher(pid, alt, img, dfuse=dfuse, exe=exe,
+                                    debug=debug)
+
+    def find_device(self):
+        output = check_output([self.exe, '-l'], self.debug)
+        output = output.decode(sys.getdefaultencoding())
+        return self.list_pattern in output
+
+    def flash(self, **kwargs):
+        reset = 0
+        if not self.find_device():
+            reset = 1
+            print('Please reset your board to switch to DFU mode...')
+            while not self.find_device():
+                time.sleep(0.1)
+
+        cmd = [self.exe, '-d,{}'.format(self.pid)]
+        if self.dfuse is not None:
+            cmd.extend(['-s', '{}:leave'.format(self.dfuse)])
+        cmd.extend(['-a', self.alt, '-D', self.img])
+        check_call(cmd, self.debug)
+        if reset:
+            print('Now reset your board again to switch back to runtime mode.')
+
+
 # TODO: Stop using environment variables.
 #
 # Migrate the build system so we can use an argparse.ArgumentParser and