1# Copyright (c) 2017 Linaro Limited.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5'''Runner for flashing with dfu-util.'''
6
7from collections import namedtuple
8import sys
9import time
10
11from runners.core import ZephyrBinaryRunner, RunnerCaps, \
12    BuildConfiguration
13
14
15DfuSeConfig = namedtuple('DfuSeConfig', ['address', 'options'])
16
17
18class DfuUtilBinaryRunner(ZephyrBinaryRunner):
19    '''Runner front-end for dfu-util.'''
20
21    def __init__(self, cfg, pid, alt, img, exe='dfu-util',
22                 dfuse_config=None):
23        super().__init__(cfg)
24        self.alt = alt
25        self.img = img
26        self.cmd = [exe, '-d,{}'.format(pid)]
27        try:
28            self.list_pattern = ', alt={},'.format(int(self.alt))
29        except ValueError:
30            self.list_pattern = ', name="{}",'.format(self.alt)
31
32        if dfuse_config is None:
33            self.dfuse = False
34        else:
35            self.dfuse = True
36        self.dfuse_config = dfuse_config
37        self.reset = False
38
39    @classmethod
40    def name(cls):
41        return 'dfu-util'
42
43    @classmethod
44    def capabilities(cls):
45        return RunnerCaps(commands={'flash'}, flash_addr=True)
46
47    @classmethod
48    def do_add_parser(cls, parser):
49        # Required:
50        parser.add_argument("--pid", required=True,
51                            help="USB VID:PID of the board")
52        parser.add_argument("--alt", required=True,
53                            help="interface alternate setting number or name")
54
55        # Optional:
56        parser.add_argument("--img",
57                            help="binary to flash, default is --bin-file")
58        parser.add_argument("--dfuse", default=False, action='store_true',
59                            help='''use the DfuSe protocol extensions
60                                 supported by STMicroelectronics
61                                 devices (if given, the image flash
62                                 address respects
63                                 CONFIG_FLASH_BASE_ADDRESS and
64                                 CONFIG_FLASH_LOAD_OFFSET)''')
65        parser.add_argument("--dfuse-modifiers", default='leave',
66                            help='''colon-separated list of additional
67                                 DfuSe modifiers for dfu-util's -s
68                                 option (default is
69                                 "-s <flash-address>:leave", which starts
70                                 execution immediately); requires
71                                 --dfuse
72                                 ''')
73        parser.add_argument('--dfu-util', default='dfu-util',
74                            help='dfu-util executable; defaults to "dfu-util"')
75
76    @classmethod
77    def do_create(cls, cfg, args):
78        if args.img is None:
79            args.img = cfg.bin_file
80
81        if args.dfuse:
82            args.dt_flash = True  # --dfuse implies --dt-flash.
83            build_conf = BuildConfiguration(cfg.build_dir)
84            dcfg = DfuSeConfig(address=cls.get_flash_address(args, build_conf),
85                               options=args.dfuse_modifiers)
86        else:
87            dcfg = None
88
89        ret = DfuUtilBinaryRunner(cfg, args.pid, args.alt, args.img,
90                                  exe=args.dfu_util, dfuse_config=dcfg)
91        ret.ensure_device()
92        return ret
93
94    def ensure_device(self):
95        if not self.find_device():
96            self.reset = True
97            print('Please reset your board to switch to DFU mode...')
98            while not self.find_device():
99                time.sleep(0.1)
100
101    def find_device(self):
102        cmd = list(self.cmd) + ['-l']
103        output = self.check_output(cmd)
104        output = output.decode(sys.getdefaultencoding())
105        return self.list_pattern in output
106
107    def do_run(self, command, **kwargs):
108        self.require(self.cmd[0])
109        self.ensure_output('bin')
110
111        if not self.find_device():
112            raise RuntimeError('device not found')
113
114        cmd = list(self.cmd)
115        if self.dfuse:
116            # http://dfu-util.sourceforge.net/dfuse.html
117            dcfg = self.dfuse_config
118            addr_opts = hex(dcfg.address) + ':' + dcfg.options
119            cmd.extend(['-s', addr_opts])
120        cmd.extend(['-a', self.alt, '-D', self.img])
121        self.check_call(cmd)
122
123        if self.dfuse and 'leave' in dcfg.options.split(':'):
124            # Normal DFU devices generally need to be reset to switch
125            # back to the flashed program.
126            #
127            # DfuSe targets do as well, except when 'leave' is given
128            # as an option.
129            self.reset = False
130        if self.reset:
131            print('Now reset your board again to switch back to runtime mode.')
132