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: ') 25 26PAT_ID = 0 27PAT_RE = 1 28 29bad_pattern_defs = ( 30 ('spl_signon', pattern_u_boot_spl_signon), 31 ('main_signon', pattern_u_boot_main_signon), 32 ('stop_autoboot_prompt', pattern_stop_autoboot_prompt), 33 ('unknown_command', pattern_unknown_command), 34 ('error_notification', pattern_error_notification), 35) 36 37class ConsoleDisableCheck(object): 38 """Context manager (for Python's with statement) that temporarily disables 39 the specified console output error check. This is useful when deliberately 40 executing a command that is known to trigger one of the error checks, in 41 order to test that the error condition is actually raised. This class is 42 used internally by ConsoleBase::disable_check(); it is not intended for 43 direct usage.""" 44 45 def __init__(self, console, check_type): 46 self.console = console 47 self.check_type = check_type 48 49 def __enter__(self): 50 self.console.disable_check_count[self.check_type] += 1 51 self.console.eval_bad_patterns() 52 53 def __exit__(self, extype, value, traceback): 54 self.console.disable_check_count[self.check_type] -= 1 55 self.console.eval_bad_patterns() 56 57class ConsoleBase(object): 58 """The interface through which test functions interact with the U-Boot 59 console. This primarily involves executing shell commands, capturing their 60 results, and checking for common error conditions. Some common utilities 61 are also provided too.""" 62 63 def __init__(self, log, config, max_fifo_fill): 64 """Initialize a U-Boot console connection. 65 66 Can only usefully be called by sub-classes. 67 68 Args: 69 log: A mulptiplex_log.Logfile object, to which the U-Boot output 70 will be logged. 71 config: A configuration data structure, as built by conftest.py. 72 max_fifo_fill: The maximum number of characters to send to U-Boot 73 command-line before waiting for U-Boot to echo the characters 74 back. For UART-based HW without HW flow control, this value 75 should be set less than the UART RX FIFO size to avoid 76 overflow, assuming that U-Boot can't keep up with full-rate 77 traffic at the baud rate. 78 79 Returns: 80 Nothing. 81 """ 82 83 self.log = log 84 self.config = config 85 self.max_fifo_fill = max_fifo_fill 86 87 self.logstream = self.log.get_stream('console', sys.stdout) 88 89 # Array slice removes leading/trailing quotes 90 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1] 91 self.prompt_escaped = re.escape(self.prompt) 92 self.p = None 93 self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs} 94 self.eval_bad_patterns() 95 96 self.at_prompt = False 97 self.at_prompt_logevt = None 98 99 def eval_bad_patterns(self): 100 self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \ 101 if self.disable_check_count[pat[PAT_ID]] == 0] 102 self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \ 103 if self.disable_check_count[pat[PAT_ID]] == 0] 104 105 def close(self): 106 """Terminate the connection to the U-Boot console. 107 108 This function is only useful once all interaction with U-Boot is 109 complete. Once this function is called, data cannot be sent to or 110 received from U-Boot. 111 112 Args: 113 None. 114 115 Returns: 116 Nothing. 117 """ 118 119 if self.p: 120 self.p.close() 121 self.logstream.close() 122 123 def run_command(self, cmd, wait_for_echo=True, send_nl=True, 124 wait_for_prompt=True): 125 """Execute a command via the U-Boot console. 126 127 The command is always sent to U-Boot. 128 129 U-Boot echoes any command back to its output, and this function 130 typically waits for that to occur. The wait can be disabled by setting 131 wait_for_echo=False, which is useful e.g. when sending CTRL-C to 132 interrupt a long-running command such as "ums". 133 134 Command execution is typically triggered by sending a newline 135 character. This can be disabled by setting send_nl=False, which is 136 also useful when sending CTRL-C. 137 138 This function typically waits for the command to finish executing, and 139 returns the console output that it generated. This can be disabled by 140 setting wait_for_prompt=False, which is useful when invoking a long- 141 running command such as "ums". 142 143 Args: 144 cmd: The command to send. 145 wait_for_each: Boolean indicating whether to wait for U-Boot to 146 echo the command text back to its output. 147 send_nl: Boolean indicating whether to send a newline character 148 after the command string. 149 wait_for_prompt: Boolean indicating whether to wait for the 150 command prompt to be sent by U-Boot. This typically occurs 151 immediately after the command has been executed. 152 153 Returns: 154 If wait_for_prompt == False: 155 Nothing. 156 Else: 157 The output from U-Boot during command execution. In other 158 words, the text U-Boot emitted between the point it echod the 159 command string and emitted the subsequent command prompts. 160 """ 161 162 if self.at_prompt and \ 163 self.at_prompt_logevt != self.logstream.logfile.cur_evt: 164 self.logstream.write(self.prompt, implicit=True) 165 166 try: 167 self.at_prompt = False 168 if send_nl: 169 cmd += '\n' 170 while cmd: 171 # Limit max outstanding data, so UART FIFOs don't overflow 172 chunk = cmd[:self.max_fifo_fill] 173 cmd = cmd[self.max_fifo_fill:] 174 self.p.send(chunk) 175 if not wait_for_echo: 176 continue 177 chunk = re.escape(chunk) 178 chunk = chunk.replace('\\\n', '[\r\n]') 179 m = self.p.expect([chunk] + self.bad_patterns) 180 if m != 0: 181 self.at_prompt = False 182 raise Exception('Bad pattern found on console: ' + 183 self.bad_pattern_ids[m - 1]) 184 if not wait_for_prompt: 185 return 186 m = self.p.expect([self.prompt_escaped] + self.bad_patterns) 187 if m != 0: 188 self.at_prompt = False 189 raise Exception('Bad pattern found on console: ' + 190 self.bad_pattern_ids[m - 1]) 191 self.at_prompt = True 192 self.at_prompt_logevt = self.logstream.logfile.cur_evt 193 # Only strip \r\n; space/TAB might be significant if testing 194 # indentation. 195 return self.p.before.strip('\r\n') 196 except Exception as ex: 197 self.log.error(str(ex)) 198 self.cleanup_spawn() 199 raise 200 201 def ctrlc(self): 202 """Send a CTRL-C character to U-Boot. 203 204 This is useful in order to stop execution of long-running synchronous 205 commands such as "ums". 206 207 Args: 208 None. 209 210 Returns: 211 Nothing. 212 """ 213 214 self.log.action('Sending Ctrl-C') 215 self.run_command(chr(3), wait_for_echo=False, send_nl=False) 216 217 def wait_for(self, text): 218 """Wait for a pattern to be emitted by U-Boot. 219 220 This is useful when a long-running command such as "dfu" is executing, 221 and it periodically emits some text that should show up at a specific 222 location in the log file. 223 224 Args: 225 text: The text to wait for; either a string (containing raw text, 226 not a regular expression) or an re object. 227 228 Returns: 229 Nothing. 230 """ 231 232 if type(text) == type(''): 233 text = re.escape(text) 234 m = self.p.expect([text] + self.bad_patterns) 235 if m != 0: 236 raise Exception('Bad pattern found on console: ' + 237 self.bad_pattern_ids[m - 1]) 238 239 def drain_console(self): 240 """Read from and log the U-Boot console for a short time. 241 242 U-Boot's console output is only logged when the test code actively 243 waits for U-Boot to emit specific data. There are cases where tests 244 can fail without doing this. For example, if a test asks U-Boot to 245 enable USB device mode, then polls until a host-side device node 246 exists. In such a case, it is useful to log U-Boot's console output 247 in case U-Boot printed clues as to why the host-side even did not 248 occur. This function will do that. 249 250 Args: 251 None. 252 253 Returns: 254 Nothing. 255 """ 256 257 # If we are already not connected to U-Boot, there's nothing to drain. 258 # This should only happen when a previous call to run_command() or 259 # wait_for() failed (and hence the output has already been logged), or 260 # the system is shutting down. 261 if not self.p: 262 return 263 264 orig_timeout = self.p.timeout 265 try: 266 # Drain the log for a relatively short time. 267 self.p.timeout = 1000 268 # Wait for something U-Boot will likely never send. This will 269 # cause the console output to be read and logged. 270 self.p.expect(['This should never match U-Boot output']) 271 except u_boot_spawn.Timeout: 272 pass 273 finally: 274 self.p.timeout = orig_timeout 275 276 def ensure_spawned(self): 277 """Ensure a connection to a correctly running U-Boot instance. 278 279 This may require spawning a new Sandbox process or resetting target 280 hardware, as defined by the implementation sub-class. 281 282 This is an internal function and should not be called directly. 283 284 Args: 285 None. 286 287 Returns: 288 Nothing. 289 """ 290 291 if self.p: 292 return 293 try: 294 self.at_prompt = False 295 self.log.action('Starting U-Boot') 296 self.p = self.get_spawn() 297 # Real targets can take a long time to scroll large amounts of 298 # text if LCD is enabled. This value may need tweaking in the 299 # future, possibly per-test to be optimal. This works for 'help' 300 # on board 'seaboard'. 301 self.p.timeout = 30000 302 self.p.logfile_read = self.logstream 303 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': 304 m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns) 305 if m != 0: 306 raise Exception('Bad pattern found on console: ' + 307 self.bad_pattern_ids[m - 1]) 308 m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns) 309 if m != 0: 310 raise Exception('Bad pattern found on console: ' + 311 self.bad_pattern_ids[m - 1]) 312 signon = self.p.after 313 build_idx = signon.find(', Build:') 314 if build_idx == -1: 315 self.u_boot_version_string = signon 316 else: 317 self.u_boot_version_string = signon[:build_idx] 318 while True: 319 m = self.p.expect([self.prompt_escaped, 320 pattern_stop_autoboot_prompt] + self.bad_patterns) 321 if m == 0: 322 break 323 if m == 1: 324 self.p.send(chr(3)) # CTRL-C 325 continue 326 raise Exception('Bad pattern found on console: ' + 327 self.bad_pattern_ids[m - 2]) 328 self.at_prompt = True 329 self.at_prompt_logevt = self.logstream.logfile.cur_evt 330 except Exception as ex: 331 self.log.error(str(ex)) 332 self.cleanup_spawn() 333 raise 334 335 def cleanup_spawn(self): 336 """Shut down all interaction with the U-Boot instance. 337 338 This is used when an error is detected prior to re-establishing a 339 connection with a fresh U-Boot instance. 340 341 This is an internal function and should not be called directly. 342 343 Args: 344 None. 345 346 Returns: 347 Nothing. 348 """ 349 350 try: 351 if self.p: 352 self.p.close() 353 except: 354 pass 355 self.p = None 356 357 def validate_version_string_in_text(self, text): 358 """Assert that a command's output includes the U-Boot signon message. 359 360 This is primarily useful for validating the "version" command without 361 duplicating the signon text regex in a test function. 362 363 Args: 364 text: The command output text to check. 365 366 Returns: 367 Nothing. An exception is raised if the validation fails. 368 """ 369 370 assert(self.u_boot_version_string in text) 371 372 def disable_check(self, check_type): 373 """Temporarily disable an error check of U-Boot's output. 374 375 Create a new context manager (for use with the "with" statement) which 376 temporarily disables a particular console output error check. 377 378 Args: 379 check_type: The type of error-check to disable. Valid values may 380 be found in self.disable_check_count above. 381 382 Returns: 383 A context manager object. 384 """ 385 386 return ConsoleDisableCheck(self, check_type) 387