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 ConsoleSetupTimeout(object):
60 """Context manager (for Python's with statement) that temporarily sets up
61 timeout for specific command. This is useful when execution time is greater
64 def __init__(self, console, timeout):
66 self.orig_timeout = self.p.timeout
67 self.p.timeout = timeout
72 def __exit__(self, extype, value, traceback):
73 self.p.timeout = self.orig_timeout
75 class ConsoleBase(object):
76 """The interface through which test functions interact with the U-Boot
77 console. This primarily involves executing shell commands, capturing their
78 results, and checking for common error conditions. Some common utilities
79 are also provided too."""
81 def __init__(self, log, config, max_fifo_fill):
82 """Initialize a U-Boot console connection.
84 Can only usefully be called by sub-classes.
87 log: A mulptiplex_log.Logfile object, to which the U-Boot output
89 config: A configuration data structure, as built by conftest.py.
90 max_fifo_fill: The maximum number of characters to send to U-Boot
91 command-line before waiting for U-Boot to echo the characters
92 back. For UART-based HW without HW flow control, this value
93 should be set less than the UART RX FIFO size to avoid
94 overflow, assuming that U-Boot can't keep up with full-rate
95 traffic at the baud rate.
103 self.max_fifo_fill = max_fifo_fill
105 self.logstream = self.log.get_stream('console', sys.stdout)
107 # Array slice removes leading/trailing quotes
108 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
109 self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
111 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
112 self.eval_bad_patterns()
114 self.at_prompt = False
115 self.at_prompt_logevt = None
117 def eval_bad_patterns(self):
118 self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
119 if self.disable_check_count[pat[PAT_ID]] == 0]
120 self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
121 if self.disable_check_count[pat[PAT_ID]] == 0]
124 """Terminate the connection to the U-Boot console.
126 This function is only useful once all interaction with U-Boot is
127 complete. Once this function is called, data cannot be sent to or
128 received from U-Boot.
139 self.logstream.close()
141 def run_command(self, cmd, wait_for_echo=True, send_nl=True,
142 wait_for_prompt=True):
143 """Execute a command via the U-Boot console.
145 The command is always sent to U-Boot.
147 U-Boot echoes any command back to its output, and this function
148 typically waits for that to occur. The wait can be disabled by setting
149 wait_for_echo=False, which is useful e.g. when sending CTRL-C to
150 interrupt a long-running command such as "ums".
152 Command execution is typically triggered by sending a newline
153 character. This can be disabled by setting send_nl=False, which is
154 also useful when sending CTRL-C.
156 This function typically waits for the command to finish executing, and
157 returns the console output that it generated. This can be disabled by
158 setting wait_for_prompt=False, which is useful when invoking a long-
159 running command such as "ums".
162 cmd: The command to send.
163 wait_for_echo: Boolean indicating whether to wait for U-Boot to
164 echo the command text back to its output.
165 send_nl: Boolean indicating whether to send a newline character
166 after the command string.
167 wait_for_prompt: Boolean indicating whether to wait for the
168 command prompt to be sent by U-Boot. This typically occurs
169 immediately after the command has been executed.
172 If wait_for_prompt == False:
175 The output from U-Boot during command execution. In other
176 words, the text U-Boot emitted between the point it echod the
177 command string and emitted the subsequent command prompts.
180 if self.at_prompt and \
181 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
182 self.logstream.write(self.prompt, implicit=True)
185 self.at_prompt = False
189 # Limit max outstanding data, so UART FIFOs don't overflow
190 chunk = cmd[:self.max_fifo_fill]
191 cmd = cmd[self.max_fifo_fill:]
193 if not wait_for_echo:
195 chunk = re.escape(chunk)
196 chunk = chunk.replace('\\\n', '[\r\n]')
197 m = self.p.expect([chunk] + self.bad_patterns)
199 self.at_prompt = False
200 raise Exception('Bad pattern found on console: ' +
201 self.bad_pattern_ids[m - 1])
202 if not wait_for_prompt:
204 m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
206 self.at_prompt = False
207 raise Exception('Bad pattern found on console: ' +
208 self.bad_pattern_ids[m - 1])
209 self.at_prompt = True
210 self.at_prompt_logevt = self.logstream.logfile.cur_evt
211 # Only strip \r\n; space/TAB might be significant if testing
213 return self.p.before.strip('\r\n')
214 except Exception as ex:
215 self.log.error(str(ex))
221 def run_command_list(self, cmds):
222 """Run a list of commands.
224 This is a helper function to call run_command() with default arguments
225 for each command in a list.
228 cmd: List of commands (each a string).
230 A list of output strings from each command, one element for each
235 output.append(self.run_command(cmd))
239 """Send a CTRL-C character to U-Boot.
241 This is useful in order to stop execution of long-running synchronous
242 commands such as "ums".
251 self.log.action('Sending Ctrl-C')
252 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
254 def wait_for(self, text):
255 """Wait for a pattern to be emitted by U-Boot.
257 This is useful when a long-running command such as "dfu" is executing,
258 and it periodically emits some text that should show up at a specific
259 location in the log file.
262 text: The text to wait for; either a string (containing raw text,
263 not a regular expression) or an re object.
269 if type(text) == type(''):
270 text = re.escape(text)
271 m = self.p.expect([text] + self.bad_patterns)
273 raise Exception('Bad pattern found on console: ' +
274 self.bad_pattern_ids[m - 1])
276 def drain_console(self):
277 """Read from and log the U-Boot console for a short time.
279 U-Boot's console output is only logged when the test code actively
280 waits for U-Boot to emit specific data. There are cases where tests
281 can fail without doing this. For example, if a test asks U-Boot to
282 enable USB device mode, then polls until a host-side device node
283 exists. In such a case, it is useful to log U-Boot's console output
284 in case U-Boot printed clues as to why the host-side even did not
285 occur. This function will do that.
294 # If we are already not connected to U-Boot, there's nothing to drain.
295 # This should only happen when a previous call to run_command() or
296 # wait_for() failed (and hence the output has already been logged), or
297 # the system is shutting down.
301 orig_timeout = self.p.timeout
303 # Drain the log for a relatively short time.
304 self.p.timeout = 1000
305 # Wait for something U-Boot will likely never send. This will
306 # cause the console output to be read and logged.
307 self.p.expect(['This should never match U-Boot output'])
308 except u_boot_spawn.Timeout:
311 self.p.timeout = orig_timeout
313 def ensure_spawned(self):
314 """Ensure a connection to a correctly running U-Boot instance.
316 This may require spawning a new Sandbox process or resetting target
317 hardware, as defined by the implementation sub-class.
319 This is an internal function and should not be called directly.
331 self.log.start_section('Starting U-Boot')
332 self.at_prompt = False
333 self.p = self.get_spawn()
334 # Real targets can take a long time to scroll large amounts of
335 # text if LCD is enabled. This value may need tweaking in the
336 # future, possibly per-test to be optimal. This works for 'help'
337 # on board 'seaboard'.
338 if not self.config.gdbserver:
339 self.p.timeout = 30000
340 self.p.logfile_read = self.logstream
341 bcfg = self.config.buildconfig
342 config_spl = bcfg.get('config_spl', 'n') == 'y'
343 config_spl_serial_support = bcfg.get('config_spl_serial_support',
345 env_spl_skipped = self.config.env.get('env__spl_skipped',
347 if config_spl and config_spl_serial_support and not env_spl_skipped:
348 m = self.p.expect([pattern_u_boot_spl_signon] +
351 raise Exception('Bad pattern found on SPL console: ' +
352 self.bad_pattern_ids[m - 1])
353 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
355 raise Exception('Bad pattern found on console: ' +
356 self.bad_pattern_ids[m - 1])
357 self.u_boot_version_string = self.p.after
359 m = self.p.expect([self.prompt_compiled,
360 pattern_stop_autoboot_prompt] + self.bad_patterns)
366 raise Exception('Bad pattern found on console: ' +
367 self.bad_pattern_ids[m - 2])
368 self.at_prompt = True
369 self.at_prompt_logevt = self.logstream.logfile.cur_evt
370 except Exception as ex:
371 self.log.error(str(ex))
376 self.log.end_section('Starting U-Boot')
378 def cleanup_spawn(self):
379 """Shut down all interaction with the U-Boot instance.
381 This is used when an error is detected prior to re-establishing a
382 connection with a fresh U-Boot instance.
384 This is an internal function and should not be called directly.
400 def restart_uboot(self):
401 """Shut down and restart U-Boot."""
403 self.ensure_spawned()
405 def get_spawn_output(self):
406 """Return the start-up output from U-Boot
409 The output produced by ensure_spawed(), as a string.
412 return self.p.get_expect_output()
415 def validate_version_string_in_text(self, text):
416 """Assert that a command's output includes the U-Boot signon message.
418 This is primarily useful for validating the "version" command without
419 duplicating the signon text regex in a test function.
422 text: The command output text to check.
425 Nothing. An exception is raised if the validation fails.
428 assert(self.u_boot_version_string in text)
430 def disable_check(self, check_type):
431 """Temporarily disable an error check of U-Boot's output.
433 Create a new context manager (for use with the "with" statement) which
434 temporarily disables a particular console output error check.
437 check_type: The type of error-check to disable. Valid values may
438 be found in self.disable_check_count above.
441 A context manager object.
444 return ConsoleDisableCheck(self, check_type)
446 def temporary_timeout(self, timeout):
447 """Temporarily set up different timeout for commands.
449 Create a new context manager (for use with the "with" statement) which
450 temporarily change timeout.
453 timeout: Time in milliseconds.
456 A context manager object.
459 return ConsoleSetupTimeout(self, timeout)