| # Copyright 2022 NXP |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| '''Runner for Lauterbach TRACE32.''' |
| |
| import argparse |
| import os |
| import platform |
| import subprocess |
| from pathlib import Path |
| from tempfile import TemporaryDirectory |
| from typing import List, Optional |
| |
| from runners.core import BuildConfiguration, RunnerCaps, RunnerConfig, ZephyrBinaryRunner |
| |
| DEFAULT_T32_CONFIG = Path('config.t32') |
| |
| |
| class TRACE32BinaryRunner(ZephyrBinaryRunner): |
| ''' |
| Runner front-end for Lauterbach TRACE32. |
| |
| The runner is a wrapper around Lauterbach TRACE32 PowerView. It executes a Lauterbach Practice |
| script (.cmm) after launching the debugger, which should be located at |
| zephyr/boards/<board>/support/<command>.cmm, where <board> is the board directory and <command> |
| is the name of the west runner command executed (e.g. flash or debug). Extra arguments can be |
| passed to the startup script by using the command line option --startup-args. |
| ''' |
| |
| def __init__(self, |
| cfg: RunnerConfig, |
| t32_cfg: Path, |
| arch: str, |
| startup_args: Optional[List[str]] = None, |
| timeout: int = 60) -> None: |
| super(TRACE32BinaryRunner, self).__init__(cfg) |
| self.arch = arch |
| self.t32_cfg = t32_cfg |
| self.t32_exec: Optional[Path] = None |
| self.startup_dir = Path(cfg.board_dir) / 'support' |
| self.startup_args = startup_args |
| self.timeout = timeout |
| |
| @classmethod |
| def name(cls) -> str: |
| return 'trace32' |
| |
| @classmethod |
| def capabilities(cls) -> RunnerCaps: |
| return RunnerCaps(commands={'flash', 'debug'}) |
| |
| @classmethod |
| def do_add_parser(cls, parser: argparse.ArgumentParser) -> None: |
| parser.add_argument('--arch', |
| default='auto', |
| choices=('auto', 'arm', 'riscv', 'xtensa'), |
| help='Target architecture. Set to "auto" to select the architecture ' |
| 'based on CONFIG_ARCH value') |
| parser.add_argument('--config', |
| default=DEFAULT_T32_CONFIG, |
| type=Path, |
| help='Override TRACE32 configuration file path. Can be a relative path ' |
| 'to T32_DIR environment variable, or an absolute path') |
| parser.add_argument('--startup-args', |
| nargs='*', |
| help='Arguments to pass to the start-up script') |
| parser.add_argument('--timeout', |
| default=60, |
| type=int, |
| help='Timeout, in seconds, of the flash operation') |
| |
| @classmethod |
| def do_create(cls, cfg: RunnerConfig, args: argparse.Namespace) -> 'TRACE32BinaryRunner': |
| build_conf = BuildConfiguration(cfg.build_dir) |
| if args.arch == 'auto': |
| arch = build_conf.get('CONFIG_ARCH').replace('"', '') |
| # there is a single binary for all ARM architectures |
| arch = arch.replace('arm64', 'arm') |
| else: |
| arch = args.arch |
| return TRACE32BinaryRunner(cfg, args.config, arch, startup_args=args.startup_args, |
| timeout=args.timeout) |
| |
| def do_run(self, command, **kwargs) -> None: |
| t32_dir = os.environ.get('T32_DIR') |
| if not t32_dir: |
| raise RuntimeError('T32_DIR environment variable undefined') |
| |
| if platform.system() == 'Windows': |
| os_name = 'windows64' |
| suffix = '.exe' |
| elif platform.system() == 'Linux': |
| os_name = 'pc_linux64' |
| suffix = '' |
| else: |
| raise RuntimeError('Host OS not supported by this runner') |
| |
| self.t32_exec = Path(t32_dir) / 'bin' / os_name / f't32m{self.arch}{suffix}' |
| if not self.t32_exec.exists(): |
| raise RuntimeError(f'Cannot find Lauterbach executable at {self.t32_exec}') |
| |
| if not self.t32_cfg.is_absolute(): |
| self.t32_cfg = Path(t32_dir) / self.t32_cfg |
| if not self.t32_cfg.exists(): |
| raise RuntimeError(f'Cannot find Lauterbach configuration at {self.t32_cfg}') |
| |
| startup_script = self.startup_dir / f'{command}.cmm' |
| if not startup_script.exists(): |
| raise RuntimeError(f'Cannot find start-up script at {startup_script}') |
| |
| if command == 'flash': |
| self.flash(**kwargs) |
| elif command == 'debug': |
| self.debug(**kwargs) |
| |
| def flash(self, **kwargs) -> None: |
| with TemporaryDirectory(suffix='t32') as tmp_dir: |
| # use a temporary config file, based on the provided configuration, |
| # to hide the TRACE32 software graphical interface |
| cfg_content = f'{self.t32_cfg.read_text()}\n\nSCREEN=OFF\n' |
| tmp_cfg = Path(tmp_dir) / DEFAULT_T32_CONFIG.name |
| tmp_cfg.write_text(cfg_content) |
| |
| cmd = self.get_launch_command('flash', cfg=tmp_cfg) |
| self.logger.info(f'Launching TRACE32: {" ".join(cmd)}') |
| try: |
| self.check_call(cmd, timeout=self.timeout) |
| self.logger.info('Finished') |
| except subprocess.TimeoutExpired: |
| self.logger.error(f'Timed out after {self.timeout} seconds') |
| |
| def debug(self, **kwargs) -> None: |
| cmd = self.get_launch_command('debug') |
| self.logger.info(f'Launching TRACE32: {" ".join(cmd)}') |
| self.check_call(cmd) |
| |
| def get_launch_command(self, command_name: str, |
| cfg: Optional[Path] = None) -> List[str]: |
| cmd = [ |
| str(self.t32_exec), |
| '-c', str(cfg if cfg else self.t32_cfg), |
| '-s', str(self.startup_dir / f'{command_name}.cmm') |
| ] |
| if self.startup_args: |
| cmd.extend(self.startup_args) |
| return cmd |