"Das U-Boot" Source Tree
at master 613 lines 24 kB view raw
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)