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