| #! /usr/bin/env python3 | |
| import itertools | |
| import os | |
| import pathlib | |
| import signal | |
| import sys | |
| import threading | |
| import logging | |
| logger = logging.getLogger("RunFVP") | |
| # Add meta-arm/lib/ to path | |
| libdir = pathlib.Path(__file__).parents[1] / "meta-arm" / "lib" | |
| sys.path.insert(0, str(libdir)) | |
| from fvp import conffile, terminal, runner | |
| def parse_args(arguments): | |
| import argparse | |
| terminals = terminal.terminals | |
| parser = argparse.ArgumentParser(description="Run images in a FVP") | |
| parser.add_argument("config", nargs="?", help="Machine name or path to .fvpconf file") | |
| group = parser.add_mutually_exclusive_group() | |
| available_terminals=",".join(terminals.available_terminals()) | |
| group.add_argument("-t", "--terminals", choices=terminals.all_terminals(), default=terminals.preferred_terminal(), help=f"Automatically start terminals (default: %(default)s). Available terminals are ({available_terminals})") | |
| group.add_argument("-c", "--console", action="store_true", help="Attach the first uart to stdin/stdout") | |
| parser.add_argument("--verbose", action="store_true", help="Output verbose logging") | |
| parser.add_argument("--dry-run", action="store_true", help="Print the FVP command and exit") | |
| parser.usage = f"{parser.format_usage().strip()} -- [ arguments passed to FVP ]" | |
| # TODO option for telnet vs netcat | |
| # If the arguments contains -- then everything after it should be passed to the FVP binary directly. | |
| if "--" in arguments: | |
| i = arguments.index("--") | |
| fvp_args = arguments[i+1:] | |
| arguments = arguments[:i] | |
| else: | |
| fvp_args = [] | |
| args = parser.parse_args(args=arguments) | |
| logging.basicConfig(level=args.verbose and logging.DEBUG or logging.WARNING, | |
| format='\033[G%(levelname)s: %(message)s') | |
| # If we're hooking up the console, don't start any terminals | |
| if args.console: | |
| args.terminals = "none" | |
| logger.debug(f"Parsed arguments: {vars(args)}") | |
| logger.debug(f"FVP arguments: {fvp_args}") | |
| return args, fvp_args | |
| def start_fvp(args, fvpconf, extra_args): | |
| fvp = runner.FVPRunner(logger) | |
| try: | |
| if args.dry_run: | |
| config = conffile.load(fvpconf) | |
| cli = runner.cli_from_config(config, args.terminals) + extra_args | |
| print(runner.shlex_join(cli)) | |
| return 0 | |
| if args.terminals: | |
| if not terminal.terminals[args.terminals].is_ready(): | |
| return 1 | |
| fvp.start(fvpconf, extra_args, args.terminals) | |
| if args.console: | |
| config = fvp.getConfig() | |
| expected_terminal = config["consoles"].get("default") | |
| if expected_terminal is None: | |
| logger.error("--console used but FVP_CONSOLE not set in machine configuration") | |
| return 1 | |
| port_stdout, log_stdout = itertools.tee(fvp.stdout, 2) | |
| parser = runner.ConsolePortParser(port_stdout) | |
| port = parser.parse_port(expected_terminal) | |
| def debug_log(): | |
| for line in log_stdout: | |
| line = line.strip().decode(errors='ignore') | |
| logger.debug(f'FVP output: {line}') | |
| log_thread = threading.Thread(None, debug_log) | |
| log_thread.start() | |
| telnet = fvp.create_telnet(port) | |
| telnet.wait() | |
| logger.debug(f"Telnet quit, cancelling tasks") | |
| else: | |
| for line in fvp.stdout: | |
| print(line.strip().decode(errors='ignore')) | |
| except FileNotFoundError as e: | |
| logger.error(f"FVP executable not found ({e})") | |
| finally: | |
| rv=fvp.stop() | |
| return rv | |
| def runfvp(cli_args): | |
| args, extra_args = parse_args(cli_args) | |
| if args.config and pathlib.Path(args.config).exists(): | |
| config_file = args.config | |
| else: | |
| config_file = conffile.find(args.config) | |
| return start_fvp(args, config_file, extra_args) | |
| if __name__ == "__main__": | |
| try: | |
| # Set the process group so that it's possible to kill runfvp and | |
| # everything it spawns easily. | |
| # Ignore permission errors happening when spawned from an other process | |
| # for example run from except | |
| try: | |
| os.setpgid(0, 0) | |
| except PermissionError: | |
| pass | |
| if sys.stdin.isatty(): | |
| signal.signal(signal.SIGTTOU, signal.SIG_IGN) | |
| os.tcsetpgrp(sys.stdin.fileno(), os.getpgrp()) | |
| sys.exit(runfvp(sys.argv[1:])) | |
| except KeyboardInterrupt: | |
| pass |