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