blob: f55c579d7e18fc6a35bfcb73bdb4d1036e9fd5a8 [file] [log] [blame]
Brad Bishopbec4ebc2022-08-03 09:55:16 -04001import re
2import subprocess
3import os
Patrick Williams7784c422022-11-17 07:29:11 -06004import shlex
Brad Bishopbec4ebc2022-08-03 09:55:16 -04005import shutil
6import sys
7
8from .terminal import terminals
9
10
11def cli_from_config(config, terminal_choice):
12 cli = []
13 if config["fvp-bindir"]:
14 cli.append(os.path.join(config["fvp-bindir"], config["exe"]))
15 else:
16 cli.append(config["exe"])
17
18 for param, value in config["parameters"].items():
19 cli.extend(["--parameter", f"{param}={value}"])
20
21 for value in config["data"]:
22 cli.extend(["--data", value])
23
24 for param, value in config["applications"].items():
25 cli.extend(["--application", f"{param}={value}"])
26
27 for terminal, name in config["terminals"].items():
28 # If terminals are enabled and this terminal has been named
29 if terminal_choice != "none" and name:
30 # TODO if raw mode
31 # cli.extend(["--parameter", f"{terminal}.mode=raw"])
32 # TODO put name into terminal title
33 cli.extend(["--parameter", f"{terminal}.terminal_command={terminals[terminal_choice].command}"])
34 else:
35 # Disable terminal
36 cli.extend(["--parameter", f"{terminal}.start_telnet=0"])
37
38 cli.extend(config["args"])
39
40 return cli
41
42def check_telnet():
43 # Check that telnet is present
44 if not bool(shutil.which("telnet")):
45 raise RuntimeError("Cannot find telnet, this is needed to connect to the FVP.")
46
Patrick Williams7784c422022-11-17 07:29:11 -060047
48class ConsolePortParser:
49 def __init__(self, lines):
50 self._lines = lines
51 self._console_ports = {}
52
53 def parse_port(self, console):
54 if console in self._console_ports:
55 return self._console_ports[console]
56
57 while True:
58 try:
59 line = next(self._lines).strip().decode(errors='ignore')
60 m = re.match(r"^(\S+): Listening for serial connection on port (\d+)$", line)
61 if m:
62 matched_console = m.group(1)
63 matched_port = int(m.group(2))
64 if matched_console == console:
65 return matched_port
66 else:
67 self._console_ports[matched_console] = matched_port
68 except StopIteration:
69 # self._lines might be a growing log file
70 pass
71
72
Brad Bishopbec4ebc2022-08-03 09:55:16 -040073class FVPRunner:
74 def __init__(self, logger):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040075 self._logger = logger
76 self._fvp_process = None
77 self._telnets = []
78 self._pexpects = []
79
Patrick Williams7784c422022-11-17 07:29:11 -060080 def start(self, config, extra_args=[], terminal_choice="none", stdout=subprocess.PIPE):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040081 cli = cli_from_config(config, terminal_choice)
82 cli += extra_args
Patrick Williams8dd68482022-10-04 07:57:18 -050083
84 # Pass through environment variables needed for GUI applications, such
85 # as xterm, to work.
86 env = config['env']
87 for name in ('DISPLAY', 'WAYLAND_DISPLAY'):
88 if name in os.environ:
89 env[name] = os.environ[name]
90
Patrick Williams7784c422022-11-17 07:29:11 -060091 self._logger.debug(f"Constructed FVP call: {shlex.join(cli)}")
92 self._fvp_process = subprocess.Popen(
93 cli,
94 stdin=subprocess.DEVNULL, stdout=stdout, stderr=subprocess.STDOUT,
Patrick Williams8dd68482022-10-04 07:57:18 -050095 env=env)
Brad Bishopbec4ebc2022-08-03 09:55:16 -040096
Patrick Williams7784c422022-11-17 07:29:11 -060097 def stop(self):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040098 if self._fvp_process:
99 self._logger.debug(f"Terminating FVP PID {self._fvp_process.pid}")
100 try:
101 self._fvp_process.terminate()
Patrick Williams7784c422022-11-17 07:29:11 -0600102 self._fvp_process.wait(10.0)
103 except subprocess.TimeoutExpired:
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400104 self._logger.debug(f"Killing FVP PID {self._fvp_process.pid}")
105 self._fvp_process.kill()
106 except ProcessLookupError:
107 pass
108
109 for telnet in self._telnets:
110 try:
111 telnet.terminate()
Patrick Williams7784c422022-11-17 07:29:11 -0600112 telnet.wait(10.0)
113 except subprocess.TimeoutExpired:
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400114 telnet.kill()
115 except ProcessLookupError:
116 pass
117
118 for console in self._pexpects:
119 import pexpect
120 # Ensure pexpect logs all remaining output to the logfile
121 console.expect(pexpect.EOF, timeout=5.0)
122 console.close()
123
Patrick Williams2194f502022-10-16 14:26:09 -0500124 if self._fvp_process and self._fvp_process.returncode and \
125 self._fvp_process.returncode > 0:
126 # Return codes < 0 indicate that the process was explicitly
127 # terminated above.
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400128 self._logger.info(f"FVP quit with code {self._fvp_process.returncode}")
129 return self._fvp_process.returncode
130 else:
131 return 0
132
Patrick Williams7784c422022-11-17 07:29:11 -0600133 def wait(self, timeout):
134 self._fvp_process.wait(timeout)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400135
Patrick Williams7784c422022-11-17 07:29:11 -0600136 @property
137 def stdout(self):
138 return self._fvp_process.stdout
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400139
Patrick Williams7784c422022-11-17 07:29:11 -0600140 def create_telnet(self, port):
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400141 check_telnet()
Patrick Williams7784c422022-11-17 07:29:11 -0600142 telnet = subprocess.Popen(["telnet", "localhost", str(port)], stdin=sys.stdin, stdout=sys.stdout)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400143 self._telnets.append(telnet)
144 return telnet
145
Patrick Williams7784c422022-11-17 07:29:11 -0600146 def create_pexpect(self, port, **kwargs):
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400147 import pexpect
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400148 instance = pexpect.spawn(f"telnet localhost {port}", **kwargs)
149 self._pexpects.append(instance)
150 return instance
151
152 def pid(self):
153 return self._fvp_process.pid