1# Copyright 2022 NXP 2# SPDX-License-Identifier: Apache-2.0 3 4'''Runner for Lauterbach TRACE32.''' 5 6import argparse 7import os 8import platform 9import subprocess 10from pathlib import Path 11from tempfile import TemporaryDirectory 12 13from runners.core import BuildConfiguration, RunnerCaps, RunnerConfig, ZephyrBinaryRunner 14 15DEFAULT_T32_CONFIG = Path('config.t32') 16 17 18class TRACE32BinaryRunner(ZephyrBinaryRunner): 19 ''' 20 Runner front-end for Lauterbach TRACE32. 21 22 The runner is a wrapper around Lauterbach TRACE32 PowerView. It executes a Lauterbach Practice 23 script (.cmm) after launching the debugger, which should be located at 24 zephyr/boards/<board>/support/<command>.cmm, where <board> is the board directory and <command> 25 is the name of the west runner command executed (e.g. flash or debug). Extra arguments can be 26 passed to the startup script by using the command line option --startup-args. 27 ''' 28 29 def __init__(self, 30 cfg: RunnerConfig, 31 t32_cfg: Path, 32 arch: str, 33 startup_args: list[str] | None = None, 34 timeout: int = 60) -> None: 35 super().__init__(cfg) 36 self.arch = arch 37 self.t32_cfg = t32_cfg 38 self.t32_exec: Path | None = None 39 self.startup_dir = Path(cfg.board_dir) / 'support' 40 self.startup_args = startup_args 41 self.timeout = timeout 42 43 @classmethod 44 def name(cls) -> str: 45 return 'trace32' 46 47 @classmethod 48 def capabilities(cls) -> RunnerCaps: 49 return RunnerCaps(commands={'flash', 'debug'}) 50 51 @classmethod 52 def do_add_parser(cls, parser: argparse.ArgumentParser) -> None: 53 parser.add_argument('--arch', 54 default='auto', 55 choices=('auto', 'arm', 'riscv', 'xtensa'), 56 help='Target architecture. Set to "auto" to select the architecture ' 57 'based on CONFIG_ARCH value') 58 parser.add_argument('--config', 59 default=DEFAULT_T32_CONFIG, 60 type=Path, 61 help='Override TRACE32 configuration file path. Can be a relative path ' 62 'to T32_DIR environment variable, or an absolute path') 63 parser.add_argument('--startup-args', 64 nargs='*', 65 help='Arguments to pass to the start-up script') 66 parser.add_argument('--timeout', 67 default=60, 68 type=int, 69 help='Timeout, in seconds, of the flash operation') 70 71 @classmethod 72 def do_create(cls, cfg: RunnerConfig, args: argparse.Namespace) -> 'TRACE32BinaryRunner': 73 build_conf = BuildConfiguration(cfg.build_dir) 74 if args.arch == 'auto': 75 arch = build_conf.get('CONFIG_ARCH').replace('"', '') 76 # there is a single binary for all ARM architectures 77 arch = arch.replace('arm64', 'arm') 78 else: 79 arch = args.arch 80 return TRACE32BinaryRunner(cfg, args.config, arch, startup_args=args.startup_args, 81 timeout=args.timeout) 82 83 def do_run(self, command, **kwargs) -> None: 84 t32_dir = os.environ.get('T32_DIR') 85 if not t32_dir: 86 raise RuntimeError('T32_DIR environment variable undefined') 87 88 if platform.system() == 'Windows': 89 os_name = 'windows64' 90 suffix = '.exe' 91 elif platform.system() == 'Linux': 92 os_name = 'pc_linux64' 93 suffix = '' 94 else: 95 raise RuntimeError('Host OS not supported by this runner') 96 97 self.t32_exec = Path(t32_dir) / 'bin' / os_name / f't32m{self.arch}{suffix}' 98 if not self.t32_exec.exists(): 99 raise RuntimeError(f'Cannot find Lauterbach executable at {self.t32_exec}') 100 101 if not self.t32_cfg.is_absolute(): 102 self.t32_cfg = Path(t32_dir) / self.t32_cfg 103 if not self.t32_cfg.exists(): 104 raise RuntimeError(f'Cannot find Lauterbach configuration at {self.t32_cfg}') 105 106 startup_script = self.startup_dir / f'{command}.cmm' 107 if not startup_script.exists(): 108 raise RuntimeError(f'Cannot find start-up script at {startup_script}') 109 110 if command == 'flash': 111 self.flash(**kwargs) 112 elif command == 'debug': 113 self.debug(**kwargs) 114 115 def flash(self, **kwargs) -> None: 116 with TemporaryDirectory(suffix='t32') as tmp_dir: 117 # use a temporary config file, based on the provided configuration, 118 # to hide the TRACE32 software graphical interface 119 cfg_content = f'{self.t32_cfg.read_text()}\n\nSCREEN=OFF\n' 120 tmp_cfg = Path(tmp_dir) / DEFAULT_T32_CONFIG.name 121 tmp_cfg.write_text(cfg_content) 122 123 cmd = self.get_launch_command('flash', cfg=tmp_cfg) 124 self.logger.info(f'Launching TRACE32: {" ".join(cmd)}') 125 try: 126 self.check_call(cmd, timeout=self.timeout) 127 self.logger.info('Finished') 128 except subprocess.TimeoutExpired: 129 self.logger.error(f'Timed out after {self.timeout} seconds') 130 131 def debug(self, **kwargs) -> None: 132 cmd = self.get_launch_command('debug') 133 self.logger.info(f'Launching TRACE32: {" ".join(cmd)}') 134 self.check_call(cmd) 135 136 def get_launch_command(self, command_name: str, 137 cfg: Path | None = None) -> list[str]: 138 cmd = [ 139 str(self.t32_exec), 140 '-c', str(cfg if cfg else self.t32_cfg), 141 '-s', str(self.startup_dir / f'{command_name}.cmm') 142 ] 143 if self.startup_args: 144 cmd.extend(self.startup_args) 145 return cmd 146