"Das U-Boot" Source Tree
1# SPDX-License-Identifier: GPL-2.0
2# Copyright (c) 2015 Stephen Warren
3# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
4
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.
10
11import multiplexed_log
12import os
13import pytest
14import re
15import sys
16import u_boot_spawn
17from u_boot_spawn import BootFail, Timeout, Unexpected, handle_exception
18
19# Regexes for text we expect U-Boot to send to the console.
20pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
21pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}[^\r\n]*\\))')
22pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
23pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
24pattern_error_notification = re.compile('## Error: ')
25pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
26pattern_ready_prompt = re.compile('{lab ready in (.*)s: (.*)}')
27pattern_lab_mode = re.compile('{lab mode.*}')
28
29PAT_ID = 0
30PAT_RE = 1
31
32# Timeout before expecting the console to be ready (in milliseconds)
33TIMEOUT_MS = 30000 # Standard timeout
34TIMEOUT_CMD_MS = 10000 # Command-echo timeout
35
36# Timeout for board preparation in lab mode. This needs to be enough to build
37# U-Boot, write it to the board and then boot the board. Since this process is
38# under the control of another program (e.g. Labgrid), it will failure sooner
39# if something goes way. So use a very long timeout here to cover all possible
40# situations.
41TIMEOUT_PREPARE_MS = 3 * 60 * 1000
42
43bad_pattern_defs = (
44 ('spl_signon', pattern_u_boot_spl_signon),
45 ('main_signon', pattern_u_boot_main_signon),
46 ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
47 ('unknown_command', pattern_unknown_command),
48 ('error_notification', pattern_error_notification),
49 ('error_please_reset', pattern_error_please_reset),
50)
51
52class ConsoleDisableCheck(object):
53 """Context manager (for Python's with statement) that temporarily disables
54 the specified console output error check. This is useful when deliberately
55 executing a command that is known to trigger one of the error checks, in
56 order to test that the error condition is actually raised. This class is
57 used internally by ConsoleBase::disable_check(); it is not intended for
58 direct usage."""
59
60 def __init__(self, console, check_type):
61 self.console = console
62 self.check_type = check_type
63
64 def __enter__(self):
65 self.console.disable_check_count[self.check_type] += 1
66 self.console.eval_bad_patterns()
67
68 def __exit__(self, extype, value, traceback):
69 self.console.disable_check_count[self.check_type] -= 1
70 self.console.eval_bad_patterns()
71
72class ConsoleEnableCheck(object):
73 """Context manager (for Python's with statement) that temporarily enables
74 the specified console output error check. This is useful when executing a
75 command that might raise an extra bad pattern, beyond the default bad
76 patterns, in order to validate that the extra bad pattern is actually
77 detected. This class is used internally by ConsoleBase::enable_check(); it
78 is not intended for direct usage."""
79
80 def __init__(self, console, check_type, check_pattern):
81 self.console = console
82 self.check_type = check_type
83 self.check_pattern = check_pattern
84
85 def __enter__(self):
86 global bad_pattern_defs
87 self.default_bad_patterns = bad_pattern_defs
88 bad_pattern_defs += ((self.check_type, self.check_pattern),)
89 self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
90 self.console.eval_bad_patterns()
91
92 def __exit__(self, extype, value, traceback):
93 global bad_pattern_defs
94 bad_pattern_defs = self.default_bad_patterns
95 self.console.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
96 self.console.eval_bad_patterns()
97
98class ConsoleSetupTimeout(object):
99 """Context manager (for Python's with statement) that temporarily sets up
100 timeout for specific command. This is useful when execution time is greater
101 then default 30s."""
102
103 def __init__(self, console, timeout):
104 self.p = console.p
105 self.orig_timeout = self.p.timeout
106 self.p.timeout = timeout
107
108 def __enter__(self):
109 return self
110
111 def __exit__(self, extype, value, traceback):
112 self.p.timeout = self.orig_timeout
113
114class ConsoleBase(object):
115 """The interface through which test functions interact with the U-Boot
116 console. This primarily involves executing shell commands, capturing their
117 results, and checking for common error conditions. Some common utilities
118 are also provided too."""
119
120 def __init__(self, log, config, max_fifo_fill):
121 """Initialize a U-Boot console connection.
122
123 Can only usefully be called by sub-classes.
124
125 Args:
126 log: A multiplexed_log.Logfile object, to which the U-Boot output
127 will be logged.
128 config: A configuration data structure, as built by conftest.py.
129 max_fifo_fill: The maximum number of characters to send to U-Boot
130 command-line before waiting for U-Boot to echo the characters
131 back. For UART-based HW without HW flow control, this value
132 should be set less than the UART RX FIFO size to avoid
133 overflow, assuming that U-Boot can't keep up with full-rate
134 traffic at the baud rate.
135
136 Returns:
137 Nothing.
138 """
139
140 self.log = log
141 self.config = config
142 self.max_fifo_fill = max_fifo_fill
143
144 self.logstream = self.log.get_stream('console', sys.stdout)
145
146 # Array slice removes leading/trailing quotes
147 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
148 self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
149 self.p = None
150 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
151 self.eval_bad_patterns()
152
153 self.at_prompt = False
154 self.at_prompt_logevt = None
155 self.lab_mode = False
156
157 def get_spawn(self):
158 # This is not called, ssubclass must define this.
159 # Return a value to avoid:
160 # u_boot_console_base.py:348:12: E1128: Assigning result of a function
161 # call, where the function returns None (assignment-from-none)
162 return u_boot_spawn.Spawn([])
163
164
165 def eval_bad_patterns(self):
166 self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
167 if self.disable_check_count[pat[PAT_ID]] == 0]
168 self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
169 if self.disable_check_count[pat[PAT_ID]] == 0]
170
171 def close(self):
172 """Terminate the connection to the U-Boot console.
173
174 This function is only useful once all interaction with U-Boot is
175 complete. Once this function is called, data cannot be sent to or
176 received from U-Boot.
177
178 Args:
179 None.
180
181 Returns:
182 Nothing.
183 """
184
185 if self.p:
186 self.log.start_section('Stopping U-Boot')
187 close_type = self.p.close()
188 self.log.info(f'Close type: {close_type}')
189 self.log.end_section('Stopping U-Boot')
190 self.logstream.close()
191
192 def set_lab_mode(self):
193 """Select lab mode
194
195 This tells us that we will get a 'lab ready' message when the board is
196 ready for use. We don't need to look for signon messages.
197 """
198 self.log.info(f'test.py: Lab mode is active')
199 self.p.timeout = TIMEOUT_PREPARE_MS
200 self.lab_mode = True
201
202 def wait_for_boot_prompt(self, loop_num = 1):
203 """Wait for the boot up until command prompt. This is for internal use only.
204 """
205 try:
206 self.log.info('Waiting for U-Boot to be ready')
207 bcfg = self.config.buildconfig
208 config_spl_serial = bcfg.get('config_spl_serial', 'n') == 'y'
209 env_spl_skipped = self.config.env.get('env__spl_skipped', False)
210 env_spl_banner_times = self.config.env.get('env__spl_banner_times', 1)
211
212 while not self.lab_mode and loop_num > 0:
213 loop_num -= 1
214 while config_spl_serial and not env_spl_skipped and env_spl_banner_times > 0:
215 m = self.p.expect([pattern_u_boot_spl_signon,
216 pattern_lab_mode] + self.bad_patterns)
217 if m == 1:
218 self.set_lab_mode()
219 break
220 elif m != 0:
221 raise BootFail('Bad pattern found on SPL console: ' +
222 self.bad_pattern_ids[m - 1])
223 env_spl_banner_times -= 1
224
225 if not self.lab_mode:
226 m = self.p.expect([pattern_u_boot_main_signon,
227 pattern_lab_mode] + self.bad_patterns)
228 if m == 1:
229 self.set_lab_mode()
230 elif m != 0:
231 raise BootFail('Bad pattern found on console: ' +
232 self.bad_pattern_ids[m - 1])
233 if not self.lab_mode:
234 self.u_boot_version_string = self.p.after
235 while True:
236 m = self.p.expect([self.prompt_compiled, pattern_ready_prompt,
237 pattern_stop_autoboot_prompt] + self.bad_patterns)
238 if m == 0:
239 self.log.info(f'Found ready prompt {m}')
240 break
241 elif m == 1:
242 m = pattern_ready_prompt.search(self.p.after)
243 self.u_boot_version_string = m.group(2)
244 self.log.info(f'Lab: Board is ready')
245 self.p.timeout = TIMEOUT_MS
246 break
247 if m == 2:
248 self.log.info(f'Found autoboot prompt {m}')
249 self.p.send(' ')
250 continue
251 if not self.lab_mode:
252 raise BootFail('Missing prompt / ready message on console: ' +
253 self.bad_pattern_ids[m - 3])
254 self.log.info(f'U-Boot is ready')
255
256 finally:
257 self.log.timestamp()
258
259 def run_command(self, cmd, wait_for_echo=True, send_nl=True,
260 wait_for_prompt=True, wait_for_reboot=False):
261 """Execute a command via the U-Boot console.
262
263 The command is always sent to U-Boot.
264
265 U-Boot echoes any command back to its output, and this function
266 typically waits for that to occur. The wait can be disabled by setting
267 wait_for_echo=False, which is useful e.g. when sending CTRL-C to
268 interrupt a long-running command such as "ums".
269
270 Command execution is typically triggered by sending a newline
271 character. This can be disabled by setting send_nl=False, which is
272 also useful when sending CTRL-C.
273
274 This function typically waits for the command to finish executing, and
275 returns the console output that it generated. This can be disabled by
276 setting wait_for_prompt=False, which is useful when invoking a long-
277 running command such as "ums".
278
279 Args:
280 cmd: The command to send.
281 wait_for_echo: Boolean indicating whether to wait for U-Boot to
282 echo the command text back to its output.
283 send_nl: Boolean indicating whether to send a newline character
284 after the command string.
285 wait_for_prompt: Boolean indicating whether to wait for the
286 command prompt to be sent by U-Boot. This typically occurs
287 immediately after the command has been executed.
288 wait_for_reboot: Boolean indication whether to wait for the
289 reboot U-Boot. If this sets True, wait_for_prompt must also
290 be True.
291
292 Returns:
293 If wait_for_prompt == False:
294 Nothing.
295 Else:
296 The output from U-Boot during command execution. In other
297 words, the text U-Boot emitted between the point it echod the
298 command string and emitted the subsequent command prompts.
299 """
300
301 if self.at_prompt and \
302 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
303 self.logstream.write(self.prompt, implicit=True)
304
305 try:
306 self.at_prompt = False
307 if not self.p:
308 raise BootFail(
309 f"Lab failure: Connection lost when sending command '{cmd}'")
310
311 if send_nl:
312 cmd += '\n'
313 rem = cmd # Remaining to be sent
314 with self.temporary_timeout(TIMEOUT_CMD_MS):
315 while rem:
316 # Limit max outstanding data, so UART FIFOs don't overflow
317 chunk = rem[:self.max_fifo_fill]
318 rem = rem[self.max_fifo_fill:]
319 self.p.send(chunk)
320 if not wait_for_echo:
321 continue
322 chunk = re.escape(chunk)
323 chunk = chunk.replace('\\\n', '[\r\n]')
324 m = self.p.expect([chunk] + self.bad_patterns)
325 if m != 0:
326 self.at_prompt = False
327 raise BootFail(f"Failed to get echo on console (cmd '{cmd}':rem '{rem}'): " +
328 self.bad_pattern_ids[m - 1])
329 if not wait_for_prompt:
330 return
331 if wait_for_reboot:
332 self.wait_for_boot_prompt()
333 else:
334 m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
335 if m != 0:
336 self.at_prompt = False
337 raise BootFail('Missing prompt on console: ' +
338 self.bad_pattern_ids[m - 1])
339 self.at_prompt = True
340 self.at_prompt_logevt = self.logstream.logfile.cur_evt
341 # Only strip \r\n; space/TAB might be significant if testing
342 # indentation.
343 return self.p.before.strip('\r\n')
344 except Timeout as exc:
345 handle_exception(self.config, self, self.log, exc,
346 f"Lab failure: Timeout executing '{cmd}'", True)
347 raise
348 except BootFail as exc:
349 handle_exception(self.config, self, self.log, exc,
350 f"'Boot fail '{cmd}'",
351 True, self.get_spawn_output())
352 raise
353 finally:
354 self.log.timestamp()
355
356 def run_command_list(self, cmds):
357 """Run a list of commands.
358
359 This is a helper function to call run_command() with default arguments
360 for each command in a list.
361
362 Args:
363 cmd: List of commands (each a string).
364 Returns:
365 A list of output strings from each command, one element for each
366 command.
367 """
368 output = []
369 for cmd in cmds:
370 output.append(self.run_command(cmd))
371 return output
372
373 def ctrlc(self):
374 """Send a CTRL-C character to U-Boot.
375
376 This is useful in order to stop execution of long-running synchronous
377 commands such as "ums".
378
379 Args:
380 None.
381
382 Returns:
383 Nothing.
384 """
385
386 self.log.action('Sending Ctrl-C')
387 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
388
389 def wait_for(self, text):
390 """Wait for a pattern to be emitted by U-Boot.
391
392 This is useful when a long-running command such as "dfu" is executing,
393 and it periodically emits some text that should show up at a specific
394 location in the log file.
395
396 Args:
397 text: The text to wait for; either a string (containing raw text,
398 not a regular expression) or an re object.
399
400 Returns:
401 Nothing.
402 """
403
404 if type(text) == type(''):
405 text = re.escape(text)
406 m = self.p.expect([text] + self.bad_patterns)
407 if m != 0:
408 raise Unexpected(
409 "Unexpected pattern found on console (exp '{text}': " +
410 self.bad_pattern_ids[m - 1])
411
412 def drain_console(self):
413 """Read from and log the U-Boot console for a short time.
414
415 U-Boot's console output is only logged when the test code actively
416 waits for U-Boot to emit specific data. There are cases where tests
417 can fail without doing this. For example, if a test asks U-Boot to
418 enable USB device mode, then polls until a host-side device node
419 exists. In such a case, it is useful to log U-Boot's console output
420 in case U-Boot printed clues as to why the host-side even did not
421 occur. This function will do that.
422
423 Args:
424 None.
425
426 Returns:
427 Nothing.
428 """
429
430 # If we are already not connected to U-Boot, there's nothing to drain.
431 # This should only happen when a previous call to run_command() or
432 # wait_for() failed (and hence the output has already been logged), or
433 # the system is shutting down.
434 if not self.p:
435 return
436
437 orig_timeout = self.p.timeout
438 try:
439 # Drain the log for a relatively short time.
440 self.p.timeout = 1000
441 # Wait for something U-Boot will likely never send. This will
442 # cause the console output to be read and logged.
443 self.p.expect(['This should never match U-Boot output'])
444 except:
445 # We expect a timeout, since U-Boot won't print what we waited
446 # for. Squash it when it happens.
447 #
448 # Squash any other exception too. This function is only used to
449 # drain (and log) the U-Boot console output after a failed test.
450 # The U-Boot process will be restarted, or target board reset, once
451 # this function returns. So, we don't care about detecting any
452 # additional errors, so they're squashed so that the rest of the
453 # post-test-failure cleanup code can continue operation, and
454 # correctly terminate any log sections, etc.
455 pass
456 finally:
457 self.p.timeout = orig_timeout
458
459 def ensure_spawned(self, expect_reset=False):
460 """Ensure a connection to a correctly running U-Boot instance.
461
462 This may require spawning a new Sandbox process or resetting target
463 hardware, as defined by the implementation sub-class.
464
465 This is an internal function and should not be called directly.
466
467 Args:
468 expect_reset: Boolean indication whether this boot is expected
469 to be reset while the 1st boot process after main boot before
470 prompt. False by default.
471
472 Returns:
473 Nothing.
474 """
475
476 if self.p:
477 # Reset the console timeout value as some tests may change
478 # its default value during the execution
479 if not self.config.gdbserver:
480 self.p.timeout = TIMEOUT_MS
481 return
482 try:
483 self.log.start_section('Starting U-Boot')
484 self.at_prompt = False
485 self.p = self.get_spawn()
486 # Real targets can take a long time to scroll large amounts of
487 # text if LCD is enabled. This value may need tweaking in the
488 # future, possibly per-test to be optimal. This works for 'help'
489 # on board 'seaboard'.
490 if not self.config.gdbserver:
491 self.p.timeout = TIMEOUT_MS
492 self.p.logfile_read = self.logstream
493 if self.config.use_running_system:
494 # Send an empty command to set up the 'expect' logic. This has
495 # the side effect of ensuring that there was no partial command
496 # line entered
497 self.run_command(' ')
498 else:
499 if expect_reset:
500 loop_num = 2
501 else:
502 loop_num = 1
503 self.wait_for_boot_prompt(loop_num = loop_num)
504 self.at_prompt = True
505 self.at_prompt_logevt = self.logstream.logfile.cur_evt
506 except Exception as ex:
507 self.log.error(str(ex))
508 self.cleanup_spawn()
509 raise
510 finally:
511 self.log.timestamp()
512 self.log.end_section('Starting U-Boot')
513
514 def cleanup_spawn(self):
515 """Shut down all interaction with the U-Boot instance.
516
517 This is used when an error is detected prior to re-establishing a
518 connection with a fresh U-Boot instance.
519
520 This is an internal function and should not be called directly.
521
522 Args:
523 None.
524
525 Returns:
526 Nothing.
527 """
528
529 try:
530 if self.p:
531 self.p.close()
532 except:
533 pass
534 self.p = None
535
536 def restart_uboot(self, expect_reset=False):
537 """Shut down and restart U-Boot."""
538 self.cleanup_spawn()
539 self.ensure_spawned(expect_reset)
540
541 def get_spawn_output(self):
542 """Return the start-up output from U-Boot
543
544 Returns:
545 The output produced by ensure_spawed(), as a string.
546 """
547 if self.p:
548 return self.p.get_expect_output()
549 return None
550
551 def validate_version_string_in_text(self, text):
552 """Assert that a command's output includes the U-Boot signon message.
553
554 This is primarily useful for validating the "version" command without
555 duplicating the signon text regex in a test function.
556
557 Args:
558 text: The command output text to check.
559
560 Returns:
561 Nothing. An exception is raised if the validation fails.
562 """
563
564 assert(self.u_boot_version_string in text)
565
566 def disable_check(self, check_type):
567 """Temporarily disable an error check of U-Boot's output.
568
569 Create a new context manager (for use with the "with" statement) which
570 temporarily disables a particular console output error check.
571
572 Args:
573 check_type: The type of error-check to disable. Valid values may
574 be found in self.disable_check_count above.
575
576 Returns:
577 A context manager object.
578 """
579
580 return ConsoleDisableCheck(self, check_type)
581
582 def enable_check(self, check_type, check_pattern):
583 """Temporarily enable an error check of U-Boot's output.
584
585 Create a new context manager (for use with the "with" statement) which
586 temporarily enables a particular console output error check. The
587 arguments form a new element of bad_pattern_defs defined above.
588
589 Args:
590 check_type: The type of error-check or bad pattern to enable.
591 check_pattern: The regexes for text error pattern or bad pattern
592 to be checked.
593
594 Returns:
595 A context manager object.
596 """
597
598 return ConsoleEnableCheck(self, check_type, check_pattern)
599
600 def temporary_timeout(self, timeout):
601 """Temporarily set up different timeout for commands.
602
603 Create a new context manager (for use with the "with" statement) which
604 temporarily change timeout.
605
606 Args:
607 timeout: Time in milliseconds.
608
609 Returns:
610 A context manager object.
611 """
612
613 return ConsoleSetupTimeout(self, timeout)