1# Copyright (c) 2025 Core Devices LLC 2# Copyright (c) 2025 SiFli Technologies(Nanjing) Co., Ltd 3# SPDX-License-Identifier: Apache-2.0 4 5import argparse 6import shlex 7import subprocess 8 9from runners.core import RunnerCaps, RunnerConfig, ZephyrBinaryRunner 10 11 12class SftoolRunner(ZephyrBinaryRunner): 13 """Runner front-end for sftool CLI.""" 14 15 def __init__( 16 self, 17 cfg: RunnerConfig, 18 chip: str, 19 port: str, 20 erase: bool, 21 dt_flash: bool, 22 tool_opt: list[str], 23 flash_files: list[str], 24 ) -> None: 25 super().__init__(cfg) 26 27 self._chip = chip 28 self._port = port 29 self._erase = erase 30 self._dt_flash = dt_flash 31 self._flash_files = flash_files 32 33 self._tool_opt: list[str] = [] 34 for opts in [shlex.split(opt) for opt in tool_opt]: 35 self._tool_opt += opts 36 37 @classmethod 38 def name(cls): 39 return "sftool" 40 41 @classmethod 42 def capabilities(cls): 43 return RunnerCaps(commands={"flash"}, erase=True, flash_addr=True, file=True, tool_opt=True) 44 45 @classmethod 46 def do_add_parser(cls, parser): 47 parser.add_argument( 48 "--chip", 49 type=str, 50 required=True, 51 help="Target chip, e.g. SF32LB52", 52 ) 53 54 parser.add_argument( 55 "--port", 56 type=str, 57 required=True, 58 help="Serial port device, e.g. /dev/ttyUSB0", 59 ) 60 parser.add_argument( 61 "--flash-file", 62 dest="flash_files", 63 action="append", 64 default=[], 65 help="Additional file@address entries that must be flashed before the Zephyr image", 66 ) 67 68 @classmethod 69 def do_create(cls, cfg: RunnerConfig, args: argparse.Namespace) -> "SftoolRunner": 70 return SftoolRunner( 71 cfg, 72 chip=args.chip, 73 port=args.port, 74 erase=args.erase, 75 dt_flash=args.dt_flash, 76 tool_opt=args.tool_opt, 77 flash_files=args.flash_files, 78 ) 79 80 def do_run(self, command: str, **kwargs): 81 sftool = self.require("sftool") 82 83 cmd = [sftool, "--chip", self._chip, "--port", self._port] 84 cmd += self._tool_opt 85 86 flash_targets: list[str] = [] 87 flash_targets.extend(self._flash_files) 88 89 if self.cfg.file: 90 flash_targets.append(self.cfg.file) 91 else: 92 if self.cfg.bin_file and self._dt_flash: 93 addr = self.flash_address_from_build_conf(self.build_conf) 94 flash_targets.append(f"{self.cfg.bin_file}@0x{addr:08x}") 95 elif self.cfg.hex_file: 96 flash_targets.append(self.cfg.hex_file) 97 else: 98 raise RuntimeError("No file available for flashing") 99 100 write_args = ["write_flash"] 101 if self._erase: 102 write_args.append("-e") 103 104 full_cmd = cmd + write_args + flash_targets 105 self._log_cmd(full_cmd) 106 if not self.dry_run: 107 subprocess.run(full_cmd, check=True) 108