1 # SPDX-License-Identifier: GPL-2.0
2 # Copyright (c) 2015 Stephen Warren
3 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
5 # Common logic to interact with U-Boot via the console. This class provides
6 # the interface that tests use to execute U-Boot shell commands and wait for
7 # their results. Sub-classes exist to perform board-type-specific setup
8 # operations, such as spawning a sub-process for Sandbox, or attaching to the
9 # serial console of real hardware.
11 import multiplexed_log
18 # Regexes for text we expect U-Boot to send to the console.
19 pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
20 pattern_u_boot_spl2_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 ('spl2_signon', pattern_u_boot_spl2_signon),
33 ('main_signon', pattern_u_boot_main_signon),
34 ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
35 ('unknown_command', pattern_unknown_command),
36 ('error_notification', pattern_error_notification),
37 ('error_please_reset', pattern_error_please_reset),
40 class ConsoleDisableCheck(object):
41 """Context manager (for Python's with statement) that temporarily disables
42 the specified console output error check. This is useful when deliberately
43 executing a command that is known to trigger one of the error checks, in
44 order to test that the error condition is actually raised. This class is
45 used internally by ConsoleBase::disable_check(); it is not intended for
48 def __init__(self, console, check_type):
49 self.console = console
50 self.check_type = check_type
53 self.console.disable_check_count[self.check_type] += 1
54 self.console.eval_bad_patterns()
56 def __exit__(self, extype, value, traceback):
57 self.console.disable_check_count[self.check_type] -= 1
58 self.console.eval_bad_patterns()
60 class ConsoleSetupTimeout(object):
61 """Context manager (for Python's with statement) that temporarily sets up
62 timeout for specific command. This is useful when execution time is greater
65 def __init__(self, console, timeout):
67 self.orig_timeout = self.p.timeout
68 self.p.timeout = timeout
73 def __exit__(self, extype, value, traceback):
74 self.p.timeout = self.orig_timeout
76 class ConsoleBase(object):
77 """The interface through which test functions interact with the U-Boot
78 console. This primarily involves executing shell commands, capturing their
79 results, and checking for common error conditions. Some common utilities
80 are also provided too."""
82 def __init__(self, log, config, max_fifo_fill):
83 """Initialize a U-Boot console connection.
85 Can only usefully be called by sub-classes.
88 log: A mulptiplex_log.Logfile object, to which the U-Boot output
90 config: A configuration data structure, as built by conftest.py.
91 max_fifo_fill: The maximum number of characters to send to U-Boot
92 command-line before waiting for U-Boot to echo the characters
93 back. For UART-based HW without HW flow control, this value
94 should be set less than the UART RX FIFO size to avoid
95 overflow, assuming that U-Boot can't keep up with full-rate
96 traffic at the baud rate.
104 self.max_fifo_fill = max_fifo_fill
106 self.logstream = self.log.get_stream('console', sys.stdout)
108 # Array slice removes leading/trailing quotes
109 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
110 self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
112 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
113 self.eval_bad_patterns()
115 self.at_prompt = False
116 self.at_prompt_logevt = None
119 # This is not called, ssubclass must define this.
120 # Return a value to avoid:
121 # u_boot_console_base.py:348:12: E1128: Assigning result of a function
122 # call, where the function returns None (assignment-from-none)
123 return u_boot_spawn.Spawn([])
126 def eval_bad_patterns(self):
127 self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
128 if self.disable_check_count[pat[PAT_ID]] == 0]
129 self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
130 if self.disable_check_count[pat[PAT_ID]] == 0]
133 """Terminate the connection to the U-Boot console.
135 This function is only useful once all interaction with U-Boot is
136 complete. Once this function is called, data cannot be sent to or
137 received from U-Boot.
148 self.logstream.close()
150 def wait_for_boot_prompt(self, loop_num = 1):
151 """Wait for the boot up until command prompt. This is for internal use only.
154 bcfg = self.config.buildconfig
155 config_spl = bcfg.get('config_spl', 'n') == 'y'
156 config_spl_serial = bcfg.get('config_spl_serial', 'n') == 'y'
157 env_spl_skipped = self.config.env.get('env__spl_skipped', False)
158 env_spl2_skipped = self.config.env.get('env__spl2_skipped', True)
162 if config_spl and config_spl_serial and not env_spl_skipped:
163 m = self.p.expect([pattern_u_boot_spl_signon] +
166 raise Exception('Bad pattern found on SPL console: ' +
167 self.bad_pattern_ids[m - 1])
168 if not env_spl2_skipped:
169 m = self.p.expect([pattern_u_boot_spl2_signon] +
172 raise Exception('Bad pattern found on SPL2 console: ' +
173 self.bad_pattern_ids[m - 1])
174 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
176 raise Exception('Bad pattern found on console: ' +
177 self.bad_pattern_ids[m - 1])
178 self.u_boot_version_string = self.p.after
180 m = self.p.expect([self.prompt_compiled,
181 pattern_stop_autoboot_prompt] + self.bad_patterns)
187 raise Exception('Bad pattern found on console: ' +
188 self.bad_pattern_ids[m - 2])
190 except Exception as ex:
191 self.log.error(str(ex))
197 def run_command(self, cmd, wait_for_echo=True, send_nl=True,
198 wait_for_prompt=True, wait_for_reboot=False):
199 """Execute a command via the U-Boot console.
201 The command is always sent to U-Boot.
203 U-Boot echoes any command back to its output, and this function
204 typically waits for that to occur. The wait can be disabled by setting
205 wait_for_echo=False, which is useful e.g. when sending CTRL-C to
206 interrupt a long-running command such as "ums".
208 Command execution is typically triggered by sending a newline
209 character. This can be disabled by setting send_nl=False, which is
210 also useful when sending CTRL-C.
212 This function typically waits for the command to finish executing, and
213 returns the console output that it generated. This can be disabled by
214 setting wait_for_prompt=False, which is useful when invoking a long-
215 running command such as "ums".
218 cmd: The command to send.
219 wait_for_echo: Boolean indicating whether to wait for U-Boot to
220 echo the command text back to its output.
221 send_nl: Boolean indicating whether to send a newline character
222 after the command string.
223 wait_for_prompt: Boolean indicating whether to wait for the
224 command prompt to be sent by U-Boot. This typically occurs
225 immediately after the command has been executed.
226 wait_for_reboot: Boolean indication whether to wait for the
227 reboot U-Boot. If this sets True, wait_for_prompt must also
231 If wait_for_prompt == False:
234 The output from U-Boot during command execution. In other
235 words, the text U-Boot emitted between the point it echod the
236 command string and emitted the subsequent command prompts.
239 if self.at_prompt and \
240 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
241 self.logstream.write(self.prompt, implicit=True)
244 self.at_prompt = False
248 # Limit max outstanding data, so UART FIFOs don't overflow
249 chunk = cmd[:self.max_fifo_fill]
250 cmd = cmd[self.max_fifo_fill:]
252 if not wait_for_echo:
254 chunk = re.escape(chunk)
255 chunk = chunk.replace('\\\n', '[\r\n]')
256 m = self.p.expect([chunk] + self.bad_patterns)
258 self.at_prompt = False
259 raise Exception('Bad pattern found on console: ' +
260 self.bad_pattern_ids[m - 1])
261 if not wait_for_prompt:
264 self.wait_for_boot_prompt()
266 m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
268 self.at_prompt = False
269 raise Exception('Bad pattern found on console: ' +
270 self.bad_pattern_ids[m - 1])
271 self.at_prompt = True
272 self.at_prompt_logevt = self.logstream.logfile.cur_evt
273 # Only strip \r\n; space/TAB might be significant if testing
275 return self.p.before.strip('\r\n')
276 except Exception as ex:
277 self.log.error(str(ex))
283 def run_command_list(self, cmds):
284 """Run a list of commands.
286 This is a helper function to call run_command() with default arguments
287 for each command in a list.
290 cmd: List of commands (each a string).
292 A list of output strings from each command, one element for each
297 output.append(self.run_command(cmd))
301 """Send a CTRL-C character to U-Boot.
303 This is useful in order to stop execution of long-running synchronous
304 commands such as "ums".
313 self.log.action('Sending Ctrl-C')
314 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
316 def wait_for(self, text):
317 """Wait for a pattern to be emitted by U-Boot.
319 This is useful when a long-running command such as "dfu" is executing,
320 and it periodically emits some text that should show up at a specific
321 location in the log file.
324 text: The text to wait for; either a string (containing raw text,
325 not a regular expression) or an re object.
331 if type(text) == type(''):
332 text = re.escape(text)
333 m = self.p.expect([text] + self.bad_patterns)
335 raise Exception('Bad pattern found on console: ' +
336 self.bad_pattern_ids[m - 1])
338 def drain_console(self):
339 """Read from and log the U-Boot console for a short time.
341 U-Boot's console output is only logged when the test code actively
342 waits for U-Boot to emit specific data. There are cases where tests
343 can fail without doing this. For example, if a test asks U-Boot to
344 enable USB device mode, then polls until a host-side device node
345 exists. In such a case, it is useful to log U-Boot's console output
346 in case U-Boot printed clues as to why the host-side even did not
347 occur. This function will do that.
356 # If we are already not connected to U-Boot, there's nothing to drain.
357 # This should only happen when a previous call to run_command() or
358 # wait_for() failed (and hence the output has already been logged), or
359 # the system is shutting down.
363 orig_timeout = self.p.timeout
365 # Drain the log for a relatively short time.
366 self.p.timeout = 1000
367 # Wait for something U-Boot will likely never send. This will
368 # cause the console output to be read and logged.
369 self.p.expect(['This should never match U-Boot output'])
371 # We expect a timeout, since U-Boot won't print what we waited
372 # for. Squash it when it happens.
374 # Squash any other exception too. This function is only used to
375 # drain (and log) the U-Boot console output after a failed test.
376 # The U-Boot process will be restarted, or target board reset, once
377 # this function returns. So, we don't care about detecting any
378 # additional errors, so they're squashed so that the rest of the
379 # post-test-failure cleanup code can continue operation, and
380 # correctly terminate any log sections, etc.
383 self.p.timeout = orig_timeout
385 def ensure_spawned(self, expect_reset=False):
386 """Ensure a connection to a correctly running U-Boot instance.
388 This may require spawning a new Sandbox process or resetting target
389 hardware, as defined by the implementation sub-class.
391 This is an internal function and should not be called directly.
394 expect_reset: Boolean indication whether this boot is expected
395 to be reset while the 1st boot process after main boot before
396 prompt. False by default.
403 # Reset the console timeout value as some tests may change
404 # its default value during the execution
405 if not self.config.gdbserver:
406 self.p.timeout = 30000
409 self.log.start_section('Starting U-Boot')
410 self.at_prompt = False
411 self.p = self.get_spawn()
412 # Real targets can take a long time to scroll large amounts of
413 # text if LCD is enabled. This value may need tweaking in the
414 # future, possibly per-test to be optimal. This works for 'help'
415 # on board 'seaboard'.
416 if not self.config.gdbserver:
417 self.p.timeout = 30000
418 self.p.logfile_read = self.logstream
423 self.wait_for_boot_prompt(loop_num = loop_num)
424 self.at_prompt = True
425 self.at_prompt_logevt = self.logstream.logfile.cur_evt
426 except Exception as ex:
427 self.log.error(str(ex))
432 self.log.end_section('Starting U-Boot')
434 def cleanup_spawn(self):
435 """Shut down all interaction with the U-Boot instance.
437 This is used when an error is detected prior to re-establishing a
438 connection with a fresh U-Boot instance.
440 This is an internal function and should not be called directly.
456 def restart_uboot(self, expect_reset=False):
457 """Shut down and restart U-Boot."""
459 self.ensure_spawned(expect_reset)
461 def get_spawn_output(self):
462 """Return the start-up output from U-Boot
465 The output produced by ensure_spawed(), as a string.
468 return self.p.get_expect_output()
471 def validate_version_string_in_text(self, text):
472 """Assert that a command's output includes the U-Boot signon message.
474 This is primarily useful for validating the "version" command without
475 duplicating the signon text regex in a test function.
478 text: The command output text to check.
481 Nothing. An exception is raised if the validation fails.
484 assert(self.u_boot_version_string in text)
486 def disable_check(self, check_type):
487 """Temporarily disable an error check of U-Boot's output.
489 Create a new context manager (for use with the "with" statement) which
490 temporarily disables a particular console output error check.
493 check_type: The type of error-check to disable. Valid values may
494 be found in self.disable_check_count above.
497 A context manager object.
500 return ConsoleDisableCheck(self, check_type)
502 def temporary_timeout(self, timeout):
503 """Temporarily set up different timeout for commands.
505 Create a new context manager (for use with the "with" statement) which
506 temporarily change timeout.
509 timeout: Time in milliseconds.
512 A context manager object.
515 return ConsoleSetupTimeout(self, timeout)