| #! /usr/bin/env python3 |
| |
| import asyncio |
| import os |
| import pathlib |
| import signal |
| import sys |
| |
| 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 terminal, runner, conffile |
| |
| 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() |
| group.add_argument("-t", "--terminals", choices=terminals.all_terminals(), default=terminals.preferred_terminal(), help="Automatically start terminals (default: %(default)s)") |
| 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.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) |
| |
| # 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 |
| |
| |
| async def start_fvp(args, config, extra_args): |
| fvp = runner.FVPRunner(logger) |
| try: |
| await fvp.start(config, extra_args, args.terminals) |
| |
| if args.console: |
| fvp.add_line_callback(lambda line: logger.debug(f"FVP output: {line}")) |
| expected_terminal = config["consoles"]["default"] |
| if not expected_terminal: |
| logger.error("--console used but FVP_CONSOLE not set in machine configuration") |
| return 1 |
| telnet = await fvp.create_telnet(expected_terminal) |
| await telnet.wait() |
| logger.debug(f"Telnet quit, cancelling tasks") |
| else: |
| fvp.add_line_callback(lambda line: print(line)) |
| |
| await fvp.run() |
| finally: |
| await fvp.stop() |
| |
| 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) |
| logger.debug(f"Loading {config_file}") |
| config = conffile.load(config_file) |
| |
| try: |
| # When we can assume Py3.7+, this can simply be asyncio.run() |
| loop = asyncio.get_event_loop() |
| return loop.run_until_complete(start_fvp(args, config, extra_args)) |
| except asyncio.CancelledError: |
| # This means telnet exited, which isn't an error |
| return 0 |
| |
| 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 |