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