1#! /usr/bin/env python3 2 3import itertools 4import os 5import pathlib 6import signal 7import sys 8import threading 9 10import logging 11logger = logging.getLogger("RunFVP") 12 13# Add meta-arm/lib/ to path 14libdir = pathlib.Path(__file__).parents[1] / "meta-arm" / "lib" 15sys.path.insert(0, str(libdir)) 16 17from fvp import conffile, terminal, runner 18 19def parse_args(arguments): 20 import argparse 21 terminals = terminal.terminals 22 23 parser = argparse.ArgumentParser(description="Run images in a FVP") 24 parser.add_argument("config", nargs="?", help="Machine name or path to .fvpconf file") 25 group = parser.add_mutually_exclusive_group() 26 group.add_argument("-t", "--terminals", choices=terminals.all_terminals(), default=terminals.preferred_terminal(), help="Automatically start terminals (default: %(default)s)") 27 group.add_argument("-c", "--console", action="store_true", help="Attach the first uart to stdin/stdout") 28 parser.add_argument("--verbose", action="store_true", help="Output verbose logging") 29 parser.usage = f"{parser.format_usage().strip()} -- [ arguments passed to FVP ]" 30 # TODO option for telnet vs netcat 31 32 # If the arguments contains -- then everything after it should be passed to the FVP binary directly. 33 if "--" in arguments: 34 i = arguments.index("--") 35 fvp_args = arguments[i+1:] 36 arguments = arguments[:i] 37 else: 38 fvp_args = [] 39 40 args = parser.parse_args(args=arguments) 41 logging.basicConfig(level=args.verbose and logging.DEBUG or logging.WARNING, 42 format='\033[G%(levelname)s: %(message)s') 43 44 # If we're hooking up the console, don't start any terminals 45 if args.console: 46 args.terminals = "none" 47 48 logger.debug(f"Parsed arguments: {vars(args)}") 49 logger.debug(f"FVP arguments: {fvp_args}") 50 return args, fvp_args 51 52def start_fvp(args, fvpconf, extra_args): 53 fvp = runner.FVPRunner(logger) 54 try: 55 fvp.start(fvpconf, extra_args, args.terminals) 56 57 if args.console: 58 config = fvp.getConfig() 59 expected_terminal = config["consoles"].get("default") 60 if expected_terminal is None: 61 logger.error("--console used but FVP_CONSOLE not set in machine configuration") 62 return 1 63 port_stdout, log_stdout = itertools.tee(fvp.stdout, 2) 64 parser = runner.ConsolePortParser(port_stdout) 65 port = parser.parse_port(expected_terminal) 66 67 def debug_log(): 68 for line in log_stdout: 69 line = line.strip().decode(errors='ignore') 70 logger.debug(f'FVP output: {line}') 71 log_thread = threading.Thread(None, debug_log) 72 log_thread.start() 73 74 telnet = fvp.create_telnet(port) 75 telnet.wait() 76 logger.debug(f"Telnet quit, cancelling tasks") 77 else: 78 for line in fvp.stdout: 79 print(line.strip().decode(errors='ignore')) 80 81 finally: 82 return fvp.stop() 83 84 85def runfvp(cli_args): 86 args, extra_args = parse_args(cli_args) 87 if args.config and pathlib.Path(args.config).exists(): 88 config_file = args.config 89 else: 90 config_file = conffile.find(args.config) 91 return start_fvp(args, config_file, extra_args) 92 93 94if __name__ == "__main__": 95 try: 96 # Set the process group so that it's possible to kill runfvp and 97 # everything it spawns easily. 98 # Ignore permission errors happening when spawned from an other process 99 # for example run from except 100 try: 101 os.setpgid(0, 0) 102 except PermissionError: 103 pass 104 if sys.stdin.isatty(): 105 signal.signal(signal.SIGTTOU, signal.SIG_IGN) 106 os.tcsetpgrp(sys.stdin.fileno(), os.getpgrp()) 107 sys.exit(runfvp(sys.argv[1:])) 108 except KeyboardInterrupt: 109 pass 110