blob: c52cdc1c1403daba0795e90d4c8c459b2e7e1963 [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
Andrew Geissler517393d2023-01-13 08:55:19 -060073# This function is backported from Python 3.8. Remove it and replace call sites
74# with shlex.join once OE-core support for earlier Python versions is dropped.
75def shlex_join(split_command):
76 """Return a shell-escaped string from *split_command*."""
77 return ' '.join(shlex.quote(arg) for arg in split_command)
78
79
Brad Bishopbec4ebc2022-08-03 09:55:16 -040080class FVPRunner:
81 def __init__(self, logger):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040082 self._logger = logger
83 self._fvp_process = None
84 self._telnets = []
85 self._pexpects = []
86
Patrick Williams7784c422022-11-17 07:29:11 -060087 def start(self, config, extra_args=[], terminal_choice="none", stdout=subprocess.PIPE):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040088 cli = cli_from_config(config, terminal_choice)
89 cli += extra_args
Patrick Williams8dd68482022-10-04 07:57:18 -050090
91 # Pass through environment variables needed for GUI applications, such
92 # as xterm, to work.
93 env = config['env']
Andrew Geisslerea144b032023-01-27 16:03:57 -060094 for name in ('DISPLAY', 'WAYLAND_DISPLAY', 'XAUTHORITY'):
Patrick Williams8dd68482022-10-04 07:57:18 -050095 if name in os.environ:
96 env[name] = os.environ[name]
97
Andrew Geissler517393d2023-01-13 08:55:19 -060098 self._logger.debug(f"Constructed FVP call: {shlex_join(cli)}")
Patrick Williams7784c422022-11-17 07:29:11 -060099 self._fvp_process = subprocess.Popen(
100 cli,
101 stdin=subprocess.DEVNULL, stdout=stdout, stderr=subprocess.STDOUT,
Patrick Williams8dd68482022-10-04 07:57:18 -0500102 env=env)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400103
Patrick Williams7784c422022-11-17 07:29:11 -0600104 def stop(self):
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400105 if self._fvp_process:
106 self._logger.debug(f"Terminating FVP PID {self._fvp_process.pid}")
107 try:
108 self._fvp_process.terminate()
Patrick Williams7784c422022-11-17 07:29:11 -0600109 self._fvp_process.wait(10.0)
110 except subprocess.TimeoutExpired:
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400111 self._logger.debug(f"Killing FVP PID {self._fvp_process.pid}")
112 self._fvp_process.kill()
113 except ProcessLookupError:
114 pass
115
116 for telnet in self._telnets:
117 try:
118 telnet.terminate()
Patrick Williams7784c422022-11-17 07:29:11 -0600119 telnet.wait(10.0)
120 except subprocess.TimeoutExpired:
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400121 telnet.kill()
122 except ProcessLookupError:
123 pass
124
125 for console in self._pexpects:
126 import pexpect
127 # Ensure pexpect logs all remaining output to the logfile
128 console.expect(pexpect.EOF, timeout=5.0)
129 console.close()
130
Patrick Williams2194f502022-10-16 14:26:09 -0500131 if self._fvp_process and self._fvp_process.returncode and \
132 self._fvp_process.returncode > 0:
133 # Return codes < 0 indicate that the process was explicitly
134 # terminated above.
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400135 self._logger.info(f"FVP quit with code {self._fvp_process.returncode}")
136 return self._fvp_process.returncode
137 else:
138 return 0
139
Patrick Williams7784c422022-11-17 07:29:11 -0600140 def wait(self, timeout):
141 self._fvp_process.wait(timeout)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400142
Patrick Williams7784c422022-11-17 07:29:11 -0600143 @property
144 def stdout(self):
145 return self._fvp_process.stdout
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400146
Patrick Williams7784c422022-11-17 07:29:11 -0600147 def create_telnet(self, port):
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400148 check_telnet()
Patrick Williams7784c422022-11-17 07:29:11 -0600149 telnet = subprocess.Popen(["telnet", "localhost", str(port)], stdin=sys.stdin, stdout=sys.stdout)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400150 self._telnets.append(telnet)
151 return telnet
152
Patrick Williams7784c422022-11-17 07:29:11 -0600153 def create_pexpect(self, port, **kwargs):
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400154 import pexpect
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400155 instance = pexpect.spawn(f"telnet localhost {port}", **kwargs)
156 self._pexpects.append(instance)
157 return instance
158
159 def pid(self):
160 return self._fvp_process.pid