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