blob: c3b0896539c7af9d6709823e830ea28f80f7d5cb [file] [log] [blame]
# 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