xref: /openbmc/openbmc/meta-arm/scripts/runfvp (revision ac13d5f36a6bd845f1709b7f41c02bd3b412ad15)
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