1 # Copyright (c) 2015 Stephen Warren
2 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
4 # SPDX-License-Identifier: GPL-2.0
6 # Common logic to interact with U-Boot via the console. This class provides
7 # the interface that tests use to execute U-Boot shell commands and wait for
8 # their results. Sub-classes exist to perform board-type-specific setup
9 # operations, such as spawning a sub-process for Sandbox, or attaching to the
10 # serial console of real hardware.
12 import multiplexed_log
19 # Regexes for text we expect U-Boot to send to the console.
20 pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
21 pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}[^\r\n]*\\))')
22 pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
23 pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
24 pattern_error_notification = re.compile('## Error: ')
25 pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
31 ('spl_signon', pattern_u_boot_spl_signon),
32 ('main_signon', pattern_u_boot_main_signon),
33 ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
34 ('unknown_command', pattern_unknown_command),
35 ('error_notification', pattern_error_notification),
36 ('error_please_reset', pattern_error_please_reset),
39 class ConsoleDisableCheck(object):
40 """Context manager (for Python's with statement) that temporarily disables
41 the specified console output error check. This is useful when deliberately
42 executing a command that is known to trigger one of the error checks, in
43 order to test that the error condition is actually raised. This class is
44 used internally by ConsoleBase::disable_check(); it is not intended for
47 def __init__(self, console, check_type):
48 self.console = console
49 self.check_type = check_type
52 self.console.disable_check_count[self.check_type] += 1
53 self.console.eval_bad_patterns()
55 def __exit__(self, extype, value, traceback):
56 self.console.disable_check_count[self.check_type] -= 1
57 self.console.eval_bad_patterns()
59 class ConsoleBase(object):
60 """The interface through which test functions interact with the U-Boot
61 console. This primarily involves executing shell commands, capturing their
62 results, and checking for common error conditions. Some common utilities
63 are also provided too."""
65 def __init__(self, log, config, max_fifo_fill):
66 """Initialize a U-Boot console connection.
68 Can only usefully be called by sub-classes.
71 log: A mulptiplex_log.Logfile object, to which the U-Boot output
73 config: A configuration data structure, as built by conftest.py.
74 max_fifo_fill: The maximum number of characters to send to U-Boot
75 command-line before waiting for U-Boot to echo the characters
76 back. For UART-based HW without HW flow control, this value
77 should be set less than the UART RX FIFO size to avoid
78 overflow, assuming that U-Boot can't keep up with full-rate
79 traffic at the baud rate.
87 self.max_fifo_fill = max_fifo_fill
89 self.logstream = self.log.get_stream('console', sys.stdout)
91 # Array slice removes leading/trailing quotes
92 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
93 self.prompt_escaped = re.escape(self.prompt)
95 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
96 self.eval_bad_patterns()
98 self.at_prompt = False
99 self.at_prompt_logevt = None
101 def eval_bad_patterns(self):
102 self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
103 if self.disable_check_count[pat[PAT_ID]] == 0]
104 self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
105 if self.disable_check_count[pat[PAT_ID]] == 0]
108 """Terminate the connection to the U-Boot console.
110 This function is only useful once all interaction with U-Boot is
111 complete. Once this function is called, data cannot be sent to or
112 received from U-Boot.
123 self.logstream.close()
125 def run_command(self, cmd, wait_for_echo=True, send_nl=True,
126 wait_for_prompt=True):
127 """Execute a command via the U-Boot console.
129 The command is always sent to U-Boot.
131 U-Boot echoes any command back to its output, and this function
132 typically waits for that to occur. The wait can be disabled by setting
133 wait_for_echo=False, which is useful e.g. when sending CTRL-C to
134 interrupt a long-running command such as "ums".
136 Command execution is typically triggered by sending a newline
137 character. This can be disabled by setting send_nl=False, which is
138 also useful when sending CTRL-C.
140 This function typically waits for the command to finish executing, and
141 returns the console output that it generated. This can be disabled by
142 setting wait_for_prompt=False, which is useful when invoking a long-
143 running command such as "ums".
146 cmd: The command to send.
147 wait_for_each: Boolean indicating whether to wait for U-Boot to
148 echo the command text back to its output.
149 send_nl: Boolean indicating whether to send a newline character
150 after the command string.
151 wait_for_prompt: Boolean indicating whether to wait for the
152 command prompt to be sent by U-Boot. This typically occurs
153 immediately after the command has been executed.
156 If wait_for_prompt == False:
159 The output from U-Boot during command execution. In other
160 words, the text U-Boot emitted between the point it echod the
161 command string and emitted the subsequent command prompts.
164 if self.at_prompt and \
165 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
166 self.logstream.write(self.prompt, implicit=True)
169 self.at_prompt = False
173 # Limit max outstanding data, so UART FIFOs don't overflow
174 chunk = cmd[:self.max_fifo_fill]
175 cmd = cmd[self.max_fifo_fill:]
177 if not wait_for_echo:
179 chunk = re.escape(chunk)
180 chunk = chunk.replace('\\\n', '[\r\n]')
181 m = self.p.expect([chunk] + self.bad_patterns)
183 self.at_prompt = False
184 raise Exception('Bad pattern found on console: ' +
185 self.bad_pattern_ids[m - 1])
186 if not wait_for_prompt:
188 m = self.p.expect([self.prompt_escaped] + self.bad_patterns)
190 self.at_prompt = False
191 raise Exception('Bad pattern found on console: ' +
192 self.bad_pattern_ids[m - 1])
193 self.at_prompt = True
194 self.at_prompt_logevt = self.logstream.logfile.cur_evt
195 # Only strip \r\n; space/TAB might be significant if testing
197 return self.p.before.strip('\r\n')
198 except Exception as ex:
199 self.log.error(str(ex))
204 """Send a CTRL-C character to U-Boot.
206 This is useful in order to stop execution of long-running synchronous
207 commands such as "ums".
216 self.log.action('Sending Ctrl-C')
217 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
219 def wait_for(self, text):
220 """Wait for a pattern to be emitted by U-Boot.
222 This is useful when a long-running command such as "dfu" is executing,
223 and it periodically emits some text that should show up at a specific
224 location in the log file.
227 text: The text to wait for; either a string (containing raw text,
228 not a regular expression) or an re object.
234 if type(text) == type(''):
235 text = re.escape(text)
236 m = self.p.expect([text] + self.bad_patterns)
238 raise Exception('Bad pattern found on console: ' +
239 self.bad_pattern_ids[m - 1])
241 def drain_console(self):
242 """Read from and log the U-Boot console for a short time.
244 U-Boot's console output is only logged when the test code actively
245 waits for U-Boot to emit specific data. There are cases where tests
246 can fail without doing this. For example, if a test asks U-Boot to
247 enable USB device mode, then polls until a host-side device node
248 exists. In such a case, it is useful to log U-Boot's console output
249 in case U-Boot printed clues as to why the host-side even did not
250 occur. This function will do that.
259 # If we are already not connected to U-Boot, there's nothing to drain.
260 # This should only happen when a previous call to run_command() or
261 # wait_for() failed (and hence the output has already been logged), or
262 # the system is shutting down.
266 orig_timeout = self.p.timeout
268 # Drain the log for a relatively short time.
269 self.p.timeout = 1000
270 # Wait for something U-Boot will likely never send. This will
271 # cause the console output to be read and logged.
272 self.p.expect(['This should never match U-Boot output'])
273 except u_boot_spawn.Timeout:
276 self.p.timeout = orig_timeout
278 def ensure_spawned(self):
279 """Ensure a connection to a correctly running U-Boot instance.
281 This may require spawning a new Sandbox process or resetting target
282 hardware, as defined by the implementation sub-class.
284 This is an internal function and should not be called directly.
296 self.log.start_section('Starting U-Boot')
297 self.at_prompt = False
298 self.p = self.get_spawn()
299 # Real targets can take a long time to scroll large amounts of
300 # text if LCD is enabled. This value may need tweaking in the
301 # future, possibly per-test to be optimal. This works for 'help'
302 # on board 'seaboard'.
303 if not self.config.gdbserver:
304 self.p.timeout = 30000
305 self.p.logfile_read = self.logstream
306 if self.config.buildconfig.get('config_spl', False) == 'y':
307 m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns)
309 raise Exception('Bad pattern found on console: ' +
310 self.bad_pattern_ids[m - 1])
311 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
313 raise Exception('Bad pattern found on console: ' +
314 self.bad_pattern_ids[m - 1])
315 self.u_boot_version_string = self.p.after
317 m = self.p.expect([self.prompt_escaped,
318 pattern_stop_autoboot_prompt] + self.bad_patterns)
322 self.p.send(chr(3)) # CTRL-C
324 raise Exception('Bad pattern found on console: ' +
325 self.bad_pattern_ids[m - 2])
326 self.at_prompt = True
327 self.at_prompt_logevt = self.logstream.logfile.cur_evt
328 except Exception as ex:
329 self.log.error(str(ex))
333 self.log.end_section('Starting U-Boot')
335 def cleanup_spawn(self):
336 """Shut down all interaction with the U-Boot instance.
338 This is used when an error is detected prior to re-establishing a
339 connection with a fresh U-Boot instance.
341 This is an internal function and should not be called directly.
357 def validate_version_string_in_text(self, text):
358 """Assert that a command's output includes the U-Boot signon message.
360 This is primarily useful for validating the "version" command without
361 duplicating the signon text regex in a test function.
364 text: The command output text to check.
367 Nothing. An exception is raised if the validation fails.
370 assert(self.u_boot_version_string in text)
372 def disable_check(self, check_type):
373 """Temporarily disable an error check of U-Boot's output.
375 Create a new context manager (for use with the "with" statement) which
376 temporarily disables a particular console output error check.
379 check_type: The type of error-check to disable. Valid values may
380 be found in self.disable_check_count above.
383 A context manager object.
386 return ConsoleDisableCheck(self, check_type)