scripts: runner: dfu-util: support DT-based flashing for DfuSe

Enable DT support in the dfu-util flasher when the target is a
DfuSe (DFU + ST extensions) device.

Untangling DfuSe-specific options (currently, the default is 'leave',
to immediately start running after the flashing is done) from the
actual address makes this cleaner, and sets up a subsequent patch to
let callers set DfuSe options.

It also lets us fix an unnecessary printline when flashing DfuSe
devices. There's no need to reset those, since the 'leave' modifier
starts execution immediately.

Signed-off-by: Marti Bolivar <marti@opensourcefoundries.com>
diff --git a/boards/arm/96b_carbon/board.cmake b/boards/arm/96b_carbon/board.cmake
index ca9346e..f998b71 100644
--- a/boards/arm/96b_carbon/board.cmake
+++ b/boards/arm/96b_carbon/board.cmake
@@ -1,4 +1,3 @@
-board_runner_args(dfu-util "--pid=0483:df11" "--alt=0")
-board_runner_args(dfu-util "--dfuse-addr=${CONFIG_FLASH_BASE_ADDRESS}")
+board_runner_args(dfu-util "--pid=0483:df11" "--alt=0" "--dfuse")
 
 include($ENV{ZEPHYR_BASE}/boards/common/dfu-util.board.cmake)
diff --git a/scripts/support/runner/dfu.py b/scripts/support/runner/dfu.py
index e7303d5..d41f162 100644
--- a/scripts/support/runner/dfu.py
+++ b/scripts/support/runner/dfu.py
@@ -4,33 +4,44 @@
 
 '''Runner for flashing with dfu-util.'''
 
+from collections import namedtuple
+import os
 import sys
 import time
 
-from .core import ZephyrBinaryRunner, RunnerCaps
+from .core import ZephyrBinaryRunner, RunnerCaps, BuildConfiguration
+
+
+DfuSeConfig = namedtuple('DfuSeConfig', ['address', 'options'])
 
 
 class DfuUtilBinaryRunner(ZephyrBinaryRunner):
     '''Runner front-end for dfu-util.'''
 
-    def __init__(self, pid, alt, img, dfuse=None, exe='dfu-util', debug=False):
+    def __init__(self, pid, alt, img, exe='dfu-util',
+                 dfuse_config=None, debug=False):
         super(DfuUtilBinaryRunner, self).__init__(debug=debug)
         self.alt = alt
         self.img = img
-        self.dfuse = dfuse
         self.cmd = [exe, '-d,{}'.format(pid)]
         try:
             self.list_pattern = ', alt={},'.format(int(self.alt))
         except ValueError:
             self.list_pattern = ', name="{}",'.format(self.alt)
 
+        if dfuse_config is None:
+            self.dfuse = False
+        else:
+            self.dfuse = True
+        self.dfuse_config = dfuse_config
+
     @classmethod
     def name(cls):
         return 'dfu-util'
 
     @classmethod
     def capabilities(cls):
-        return RunnerCaps(commands={'flash'})
+        return RunnerCaps(commands={'flash'}, flash_addr=True)
 
     @classmethod
     def do_add_parser(cls, parser):
@@ -43,9 +54,9 @@
         # Optional:
         parser.add_argument("--img",
                             help="binary to flash, default is --kernel-bin")
-        parser.add_argument("--dfuse-addr", default=None,
-                            help='''target address if the board is a DfuSe
-                            device; ignored it not present''')
+        parser.add_argument("--dfuse", default=False, action='store_true',
+                            help='''set if target is a DfuSe device;
+                            implies --dt-flash.''')
         parser.add_argument('--dfu-util', default='dfu-util',
                             help='dfu-util executable; defaults to "dfu-util"')
 
@@ -53,8 +64,17 @@
     def create_from_args(cls, args):
         if args.img is None:
             args.img = args.kernel_bin
+
+        if args.dfuse:
+            args.dt_flash = True  # --dfuse implies --dt-flash.
+            build_conf = BuildConfiguration(os.getcwd())
+            dcfg = DfuSeConfig(address=cls.get_flash_address(args, build_conf),
+                               options="leave")
+        else:
+            dcfg = None
+
         return DfuUtilBinaryRunner(args.pid, args.alt, args.img,
-                                   dfuse=args.dfuse_addr, exe=args.dfu_util,
+                                   exe=args.dfu_util, dfuse_config=dcfg,
                                    debug=args.verbose)
 
     def find_device(self):
@@ -64,17 +84,28 @@
         return self.list_pattern in output
 
     def do_run(self, command, **kwargs):
-        reset = 0
+        reset = False
         if not self.find_device():
-            reset = 1
+            reset = True
             print('Please reset your board to switch to DFU mode...')
             while not self.find_device():
                 time.sleep(0.1)
 
         cmd = list(self.cmd)
-        if self.dfuse is not None:
-            cmd.extend(['-s', '{}:leave'.format(self.dfuse)])
+        if self.dfuse:
+            # http://dfu-util.sourceforge.net/dfuse.html
+            dcfg = self.dfuse_config
+            addr_opts = hex(dcfg.address) + ':' + dcfg.options
+            cmd.extend(['-s', addr_opts])
         cmd.extend(['-a', self.alt, '-D', self.img])
         self.check_call(cmd)
+
+        if self.dfuse and 'leave' in dcfg.options.split(':'):
+            # Normal DFU devices generally need to be reset to switch
+            # back to the flashed program.
+            #
+            # DfuSe targets do as well, except when 'leave' is given
+            # as an option.
+            reset = False
         if reset:
             print('Now reset your board again to switch back to runtime mode.')