1# Copyright (c) 2023, Antmicro <www.antmicro.com> 2# 3# Based on J-Link runner 4# Copyright (c) 2017 Linaro Limited. 5# SPDX-License-Identifier: Apache-2.0 6 7""" 8Runner that implements flashing with SiLabs Simplicity Commander binary tool. 9See SiLabs UG162: "Simplicity Commander Reference Guide" for more info. 10""" 11 12import ipaddress 13import os 14import re 15import shlex 16 17from runners.core import FileType, RunnerCaps, ZephyrBinaryRunner 18 19DEFAULT_APP = 'commander' 20 21def is_ip(ip): 22 if not ip: 23 return False 24 try: 25 ipaddress.ip_address(ip.split(':')[0]) 26 except ValueError: 27 return False 28 return True 29 30class SiLabsCommanderBinaryRunner(ZephyrBinaryRunner): 31 def __init__(self, cfg, device, dev_id, commander, dt_flash, erase, speed, tool_opt, 32 dev_id_type): 33 super().__init__(cfg) 34 self.file = cfg.file 35 self.file_type = cfg.file_type 36 self.hex_name = cfg.hex_file 37 self.bin_name = cfg.bin_file 38 self.elf_name = cfg.elf_file 39 self.device = device 40 self.dev_id = dev_id 41 self.commander = commander 42 self.dt_flash = dt_flash 43 self.erase = erase 44 self.speed = speed 45 self.dev_id_type = dev_id_type 46 47 self.tool_opt = [] 48 for opts in [shlex.split(opt) for opt in tool_opt]: 49 self.tool_opt += opts 50 51 @staticmethod 52 def _detect_dev_id_type(dev_id): 53 """Detect device type based on dev_id pattern.""" 54 if not dev_id: 55 return None 56 57 # Check if dev_id is numeric (serialno) 58 if re.match(r'^[0-9]+$', dev_id): 59 return 'serialno' 60 61 # Check if dev_id is an existing file (tty) 62 if os.path.exists(dev_id): 63 return 'tty' 64 65 # Check if dev_id is a valid IPv4 66 if is_ip(dev_id): 67 return 'ip' 68 69 # No match found 70 return None 71 72 @classmethod 73 def name(cls): 74 return 'silabs_commander' 75 76 @classmethod 77 def capabilities(cls): 78 return RunnerCaps(commands={'flash'}, 79 dev_id=True, flash_addr=True, erase=True, 80 tool_opt=True, file=True) 81 82 @classmethod 83 def dev_id_help(cls) -> str: 84 return '''Device identifier. Can be either a serial number (for a USB connection), a path 85 to a tty device, an IP address or a tunnel address. User can enforce the type of 86 the identifier with --dev-id-type. 87 If not specified, the first USB device detected is used. ''' 88 89 @classmethod 90 def tool_opt_help(cls) -> str: 91 return "Additional options for Simplicity Commander, e.g. '--noreset'" 92 93 @classmethod 94 def do_add_parser(cls, parser): 95 # Required: 96 parser.add_argument('--device', required=True, 97 help='device part number') 98 99 # Optional: 100 parser.add_argument('--commander', default=DEFAULT_APP, 101 help='path to Simplicity Commander executable') 102 parser.add_argument('--speed', default=None, 103 help='JTAG/SWD speed to use') 104 parser.add_argument('--dev-id-type', choices=['auto', 'serialno', 'tty', 'ip'], 105 default='auto', help='Device type. "auto" (default) auto-detects ' 106 'the type, or specify explicitly') 107 108 @classmethod 109 def do_create(cls, cfg, args): 110 return SiLabsCommanderBinaryRunner( 111 cfg, args.device, 112 dev_id=args.dev_id, 113 commander=args.commander, 114 dt_flash=args.dt_flash, 115 erase=args.erase, 116 speed=args.speed, 117 tool_opt=args.tool_opt, 118 dev_id_type=args.dev_id_type) 119 120 def do_run(self, command, **kwargs): 121 self.require(self.commander) 122 123 opts = ['--device', self.device] 124 if self.erase: 125 opts.append('--masserase') 126 if self.dev_id: 127 # Determine dev_id_type 128 if self.dev_id_type == 'auto': 129 # Auto-detect device type 130 detected_type = self._detect_dev_id_type(self.dev_id) 131 else: 132 # Use manually specified device type 133 detected_type = self.dev_id_type 134 135 # Add appropriate option based on device type 136 if detected_type == 'serialno': 137 opts.extend(['--serialno', self.dev_id]) 138 elif detected_type == 'tty': 139 opts.extend(['--identifybyserialport', self.dev_id]) 140 elif detected_type == 'ip': 141 opts.extend(['--ip', self.dev_id]) 142 else: 143 # Fallback to legacy behavior (serialno) 144 opts.extend(['--serialno', self.dev_id]) 145 self.logger.warning(f'"{self.dev_id}" does not match any known pattern') 146 147 if self.speed is not None: 148 opts.extend(['--speed', self.speed]) 149 150 # Get the build artifact to flash 151 152 if self.dt_flash: 153 flash_addr = self.flash_address_from_build_conf(self.build_conf) 154 else: 155 flash_addr = 0 156 157 if self.file is not None: 158 # use file provided by the user 159 if not os.path.isfile(self.file): 160 raise ValueError(f'Cannot flash; file ({self.file}) not found') 161 162 flash_file = self.file 163 164 if self.file_type == FileType.HEX: 165 flash_args = [flash_file] 166 elif self.file_type == FileType.BIN: 167 flash_args = ['--binary', '--address', f'0x{flash_addr:x}', flash_file] 168 else: 169 raise ValueError('Cannot flash; this runner only supports hex and bin files') 170 171 else: 172 # use hex or bin file provided by the buildsystem, preferring .hex over .bin 173 if self.hex_name is not None and os.path.isfile(self.hex_name): 174 flash_file = self.hex_name 175 flash_args = [flash_file] 176 elif self.bin_name is not None and os.path.isfile(self.bin_name): 177 flash_file = self.bin_name 178 flash_args = ['--binary', '--address', f'0x{flash_addr:x}', flash_file] 179 else: 180 raise ValueError(f'Cannot flash; no hex ({self.hex_name}) or ' 181 f'bin ({self.bin_name}) files found.') 182 183 args = [self.commander, 'flash'] + opts + self.tool_opt + flash_args 184 185 self.logger.info(f'Flashing file: {flash_file}') 186 self.check_call(args) 187