1# Copyright (c) 2015 Stephen Warren 2# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. 3# 4# SPDX-License-Identifier: GPL-2.0 5 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. 11 12import multiplexed_log 13import os 14import pytest 15import re 16import sys 17import u_boot_spawn 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 ###') 26 27PAT_ID = 0 28PAT_RE = 1 29 30bad_pattern_defs = ( 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), 37) 38 39class 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 45 direct usage.""" 46 47 def __init__(self, console, check_type): 48 self.console = console 49 self.check_type = check_type 50 51 def __enter__(self): 52 self.console.disable_check_count[self.check_type] += 1 53 self.console.eval_bad_patterns() 54 55 def __exit__(self, extype, value, traceback): 56 self.console.disable_check_count[self.check_type] -= 1 57 self.console.eval_bad_patterns() 58 59class 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.""" 64 65 def __init__(self, log, config, max_fifo_fill): 66 """Initialize a U-Boot console connection. 67 68 Can only usefully be called by sub-classes. 69 70 Args: 71 log: A mulptiplex_log.Logfile object, to which the U-Boot output 72 will be logged. 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. 80 81 Returns: 82 Nothing. 83 """ 84 85 self.log = log 86 self.config = config 87 self.max_fifo_fill = max_fifo_fill 88 89 self.logstream = self.log.get_stream('console', sys.stdout) 90 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) 94 self.p = None 95 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} 96 self.eval_bad_patterns() 97 98 self.at_prompt = False 99 self.at_prompt_logevt = None 100 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] 106 107 def close(self): 108 """Terminate the connection to the U-Boot console. 109 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. 113 114 Args: 115 None. 116 117 Returns: 118 Nothing. 119 """ 120 121 if self.p: 122 self.p.close() 123 self.logstream.close() 124 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. 128 129 The command is always sent to U-Boot. 130 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". 135 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. 139 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". 144 145 Args: 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. 154 155 Returns: 156 If wait_for_prompt == False: 157 Nothing. 158 Else: 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. 162 """ 163 164 if self.at_prompt and \ 165 self.at_prompt_logevt != self.logstream.logfile.cur_evt: 166 self.logstream.write(self.prompt, implicit=True) 167 168 try: 169 self.at_prompt = False 170 if send_nl: 171 cmd += '\n' 172 while cmd: 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:] 176 self.p.send(chunk) 177 if not wait_for_echo: 178 continue 179 chunk = re.escape(chunk) 180 chunk = chunk.replace('\\\n', '[\r\n]') 181 m = self.p.expect([chunk] + self.bad_patterns) 182 if m != 0: 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: 187 return 188 m = self.p.expect([self.prompt_escaped] + self.bad_patterns) 189 if m != 0: 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 196 # indentation. 197 return self.p.before.strip('\r\n') 198 except Exception as ex: 199 self.log.error(str(ex)) 200 self.cleanup_spawn() 201 raise 202 203 def ctrlc(self): 204 """Send a CTRL-C character to U-Boot. 205 206 This is useful in order to stop execution of long-running synchronous 207 commands such as "ums". 208 209 Args: 210 None. 211 212 Returns: 213 Nothing. 214 """ 215 216 self.log.action('Sending Ctrl-C') 217 self.run_command(chr(3), wait_for_echo=False, send_nl=False) 218 219 def wait_for(self, text): 220 """Wait for a pattern to be emitted by U-Boot. 221 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. 225 226 Args: 227 text: The text to wait for; either a string (containing raw text, 228 not a regular expression) or an re object. 229 230 Returns: 231 Nothing. 232 """ 233 234 if type(text) == type(''): 235 text = re.escape(text) 236 m = self.p.expect([text] + self.bad_patterns) 237 if m != 0: 238 raise Exception('Bad pattern found on console: ' + 239 self.bad_pattern_ids[m - 1]) 240 241 def drain_console(self): 242 """Read from and log the U-Boot console for a short time. 243 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. 251 252 Args: 253 None. 254 255 Returns: 256 Nothing. 257 """ 258 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. 263 if not self.p: 264 return 265 266 orig_timeout = self.p.timeout 267 try: 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: 274 pass 275 finally: 276 self.p.timeout = orig_timeout 277 278 def ensure_spawned(self): 279 """Ensure a connection to a correctly running U-Boot instance. 280 281 This may require spawning a new Sandbox process or resetting target 282 hardware, as defined by the implementation sub-class. 283 284 This is an internal function and should not be called directly. 285 286 Args: 287 None. 288 289 Returns: 290 Nothing. 291 """ 292 293 if self.p: 294 return 295 try: 296 self.at_prompt = False 297 self.log.action('Starting U-Boot') 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 self.p.timeout = 30000 304 self.p.logfile_read = self.logstream 305 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': 306 m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns) 307 if m != 0: 308 raise Exception('Bad pattern found on console: ' + 309 self.bad_pattern_ids[m - 1]) 310 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns) 311 if m != 0: 312 raise Exception('Bad pattern found on console: ' + 313 self.bad_pattern_ids[m - 1]) 314 signon = self.p.after 315 build_idx = signon.find(', Build:') 316 if build_idx == -1: 317 self.u_boot_version_string = signon 318 else: 319 self.u_boot_version_string = signon[:build_idx] 320 while True: 321 m = self.p.expect([self.prompt_escaped, 322 pattern_stop_autoboot_prompt] + self.bad_patterns) 323 if m == 0: 324 break 325 if m == 1: 326 self.p.send(chr(3)) # CTRL-C 327 continue 328 raise Exception('Bad pattern found on console: ' + 329 self.bad_pattern_ids[m - 2]) 330 self.at_prompt = True 331 self.at_prompt_logevt = self.logstream.logfile.cur_evt 332 except Exception as ex: 333 self.log.error(str(ex)) 334 self.cleanup_spawn() 335 raise 336 337 def cleanup_spawn(self): 338 """Shut down all interaction with the U-Boot instance. 339 340 This is used when an error is detected prior to re-establishing a 341 connection with a fresh U-Boot instance. 342 343 This is an internal function and should not be called directly. 344 345 Args: 346 None. 347 348 Returns: 349 Nothing. 350 """ 351 352 try: 353 if self.p: 354 self.p.close() 355 except: 356 pass 357 self.p = None 358 359 def validate_version_string_in_text(self, text): 360 """Assert that a command's output includes the U-Boot signon message. 361 362 This is primarily useful for validating the "version" command without 363 duplicating the signon text regex in a test function. 364 365 Args: 366 text: The command output text to check. 367 368 Returns: 369 Nothing. An exception is raised if the validation fails. 370 """ 371 372 assert(self.u_boot_version_string in text) 373 374 def disable_check(self, check_type): 375 """Temporarily disable an error check of U-Boot's output. 376 377 Create a new context manager (for use with the "with" statement) which 378 temporarily disables a particular console output error check. 379 380 Args: 381 check_type: The type of error-check to disable. Valid values may 382 be found in self.disable_check_count above. 383 384 Returns: 385 A context manager object. 386 """ 387 388 return ConsoleDisableCheck(self, check_type) 389