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 self.ram_base = None 90 91 def close(self): 92 '''Terminate the connection to the U-Boot console. 93 94 This function is only useful once all interaction with U-Boot is 95 complete. Once this function is called, data cannot be sent to or 96 received from U-Boot. 97 98 Args: 99 None. 100 101 Returns: 102 Nothing. 103 ''' 104 105 if self.p: 106 self.p.close() 107 self.logstream.close() 108 109 def run_command(self, cmd, wait_for_echo=True, send_nl=True, 110 wait_for_prompt=True): 111 '''Execute a command via the U-Boot console. 112 113 The command is always sent to U-Boot. 114 115 U-Boot echoes any command back to its output, and this function 116 typically waits for that to occur. The wait can be disabled by setting 117 wait_for_echo=False, which is useful e.g. when sending CTRL-C to 118 interrupt a long-running command such as "ums". 119 120 Command execution is typically triggered by sending a newline 121 character. This can be disabled by setting send_nl=False, which is 122 also useful when sending CTRL-C. 123 124 This function typically waits for the command to finish executing, and 125 returns the console output that it generated. This can be disabled by 126 setting wait_for_prompt=False, which is useful when invoking a long- 127 running command such as "ums". 128 129 Args: 130 cmd: The command to send. 131 wait_for_each: Boolean indicating whether to wait for U-Boot to 132 echo the command text back to its output. 133 send_nl: Boolean indicating whether to send a newline character 134 after the command string. 135 wait_for_prompt: Boolean indicating whether to wait for the 136 command prompt to be sent by U-Boot. This typically occurs 137 immediately after the command has been executed. 138 139 Returns: 140 If wait_for_prompt == False: 141 Nothing. 142 Else: 143 The output from U-Boot during command execution. In other 144 words, the text U-Boot emitted between the point it echod the 145 command string and emitted the subsequent command prompts. 146 ''' 147 148 if self.at_prompt and \ 149 self.at_prompt_logevt != self.logstream.logfile.cur_evt: 150 self.logstream.write(self.prompt, implicit=True) 151 152 bad_patterns = [] 153 bad_pattern_ids = [] 154 if (self.disable_check_count['spl_signon'] == 0 and 155 self.u_boot_spl_signon): 156 bad_patterns.append(self.u_boot_spl_signon_escaped) 157 bad_pattern_ids.append('SPL signon') 158 if self.disable_check_count['main_signon'] == 0: 159 bad_patterns.append(self.u_boot_main_signon_escaped) 160 bad_pattern_ids.append('U-Boot main signon') 161 if self.disable_check_count['unknown_command'] == 0: 162 bad_patterns.append(pattern_unknown_command) 163 bad_pattern_ids.append('Unknown command') 164 if self.disable_check_count['error_notification'] == 0: 165 bad_patterns.append(pattern_error_notification) 166 bad_pattern_ids.append('Error notification') 167 try: 168 self.at_prompt = False 169 if send_nl: 170 cmd += '\n' 171 while cmd: 172 # Limit max outstanding data, so UART FIFOs don't overflow 173 chunk = cmd[:self.max_fifo_fill] 174 cmd = cmd[self.max_fifo_fill:] 175 self.p.send(chunk) 176 if not wait_for_echo: 177 continue 178 chunk = re.escape(chunk) 179 chunk = chunk.replace('\\\n', '[\r\n]') 180 m = self.p.expect([chunk] + bad_patterns) 181 if m != 0: 182 self.at_prompt = False 183 raise Exception('Bad pattern found on console: ' + 184 bad_pattern_ids[m - 1]) 185 if not wait_for_prompt: 186 return 187 m = self.p.expect([self.prompt_escaped] + bad_patterns) 188 if m != 0: 189 self.at_prompt = False 190 raise Exception('Bad pattern found on console: ' + 191 bad_pattern_ids[m - 1]) 192 self.at_prompt = True 193 self.at_prompt_logevt = self.logstream.logfile.cur_evt 194 # Only strip \r\n; space/TAB might be significant if testing 195 # indentation. 196 return self.p.before.strip('\r\n') 197 except Exception as ex: 198 self.log.error(str(ex)) 199 self.cleanup_spawn() 200 raise 201 202 def ctrlc(self): 203 '''Send a CTRL-C character to U-Boot. 204 205 This is useful in order to stop execution of long-running synchronous 206 commands such as "ums". 207 208 Args: 209 None. 210 211 Returns: 212 Nothing. 213 ''' 214 215 self.log.action('Sending Ctrl-C') 216 self.run_command(chr(3), wait_for_echo=False, send_nl=False) 217 218 def wait_for(self, text): 219 '''Wait for a pattern to be emitted by U-Boot. 220 221 This is useful when a long-running command such as "dfu" is executing, 222 and it periodically emits some text that should show up at a specific 223 location in the log file. 224 225 Args: 226 text: The text to wait for; either a string (containing raw text, 227 not a regular expression) or an re object. 228 229 Returns: 230 Nothing. 231 ''' 232 233 if type(text) == type(''): 234 text = re.escape(text) 235 self.p.expect([text]) 236 237 def drain_console(self): 238 '''Read from and log the U-Boot console for a short time. 239 240 U-Boot's console output is only logged when the test code actively 241 waits for U-Boot to emit specific data. There are cases where tests 242 can fail without doing this. For example, if a test asks U-Boot to 243 enable USB device mode, then polls until a host-side device node 244 exists. In such a case, it is useful to log U-Boot's console output 245 in case U-Boot printed clues as to why the host-side even did not 246 occur. This function will do that. 247 248 Args: 249 None. 250 251 Returns: 252 Nothing. 253 ''' 254 255 # If we are already not connected to U-Boot, there's nothing to drain. 256 # This should only happen when a previous call to run_command() or 257 # wait_for() failed (and hence the output has already been logged), or 258 # the system is shutting down. 259 if not self.p: 260 return 261 262 orig_timeout = self.p.timeout 263 try: 264 # Drain the log for a relatively short time. 265 self.p.timeout = 1000 266 # Wait for something U-Boot will likely never send. This will 267 # cause the console output to be read and logged. 268 self.p.expect(['This should never match U-Boot output']) 269 except u_boot_spawn.Timeout: 270 pass 271 finally: 272 self.p.timeout = orig_timeout 273 274 def ensure_spawned(self): 275 '''Ensure a connection to a correctly running U-Boot instance. 276 277 This may require spawning a new Sandbox process or resetting target 278 hardware, as defined by the implementation sub-class. 279 280 This is an internal function and should not be called directly. 281 282 Args: 283 None. 284 285 Returns: 286 Nothing. 287 ''' 288 289 if self.p: 290 return 291 try: 292 self.at_prompt = False 293 self.log.action('Starting U-Boot') 294 self.p = self.get_spawn() 295 # Real targets can take a long time to scroll large amounts of 296 # text if LCD is enabled. This value may need tweaking in the 297 # future, possibly per-test to be optimal. This works for 'help' 298 # on board 'seaboard'. 299 self.p.timeout = 30000 300 self.p.logfile_read = self.logstream 301 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': 302 self.p.expect([pattern_u_boot_spl_signon]) 303 self.u_boot_spl_signon = self.p.after 304 self.u_boot_spl_signon_escaped = re.escape(self.p.after) 305 else: 306 self.u_boot_spl_signon = None 307 self.p.expect([pattern_u_boot_main_signon]) 308 self.u_boot_main_signon = self.p.after 309 self.u_boot_main_signon_escaped = re.escape(self.p.after) 310 build_idx = self.u_boot_main_signon.find(', Build:') 311 if build_idx == -1: 312 self.u_boot_version_string = self.u_boot_main_signon 313 else: 314 self.u_boot_version_string = self.u_boot_main_signon[:build_idx] 315 while True: 316 match = self.p.expect([self.prompt_escaped, 317 pattern_stop_autoboot_prompt]) 318 if match == 1: 319 self.p.send(chr(3)) # CTRL-C 320 continue 321 break 322 self.at_prompt = True 323 self.at_prompt_logevt = self.logstream.logfile.cur_evt 324 except Exception as ex: 325 self.log.error(str(ex)) 326 self.cleanup_spawn() 327 raise 328 329 def cleanup_spawn(self): 330 '''Shut down all interaction with the U-Boot instance. 331 332 This is used when an error is detected prior to re-establishing a 333 connection with a fresh U-Boot instance. 334 335 This is an internal function and should not be called directly. 336 337 Args: 338 None. 339 340 Returns: 341 Nothing. 342 ''' 343 344 try: 345 if self.p: 346 self.p.close() 347 except: 348 pass 349 self.p = None 350 351 def validate_version_string_in_text(self, text): 352 '''Assert that a command's output includes the U-Boot signon message. 353 354 This is primarily useful for validating the "version" command without 355 duplicating the signon text regex in a test function. 356 357 Args: 358 text: The command output text to check. 359 360 Returns: 361 Nothing. An exception is raised if the validation fails. 362 ''' 363 364 assert(self.u_boot_version_string in text) 365 366 def disable_check(self, check_type): 367 '''Temporarily disable an error check of U-Boot's output. 368 369 Create a new context manager (for use with the "with" statement) which 370 temporarily disables a particular console output error check. 371 372 Args: 373 check_type: The type of error-check to disable. Valid values may 374 be found in self.disable_check_count above. 375 376 Returns: 377 A context manager object. 378 ''' 379 380 return ConsoleDisableCheck(self, check_type) 381 382 def find_ram_base(self): 383 '''Find the running U-Boot's RAM location. 384 385 Probe the running U-Boot to determine the address of the first bank 386 of RAM. This is useful for tests that test reading/writing RAM, or 387 load/save files that aren't associated with some standard address 388 typically represented in an environment variable such as 389 ${kernel_addr_r}. The value is cached so that it only needs to be 390 actively read once. 391 392 Args: 393 None. 394 395 Returns: 396 The address of U-Boot's first RAM bank, as an integer. 397 ''' 398 399 if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': 400 pytest.skip('bdinfo command not supported') 401 if self.ram_base == -1: 402 pytest.skip('Previously failed to find RAM bank start') 403 if self.ram_base is not None: 404 return self.ram_base 405 406 with self.log.section('find_ram_base'): 407 response = self.run_command('bdinfo') 408 for l in response.split('\n'): 409 if '-> start' in l: 410 self.ram_base = int(l.split('=')[1].strip(), 16) 411 break 412 if self.ram_base is None: 413 self.ram_base = -1 414 raise Exception('Failed to find RAM bank start in `bdinfo`') 415 416 return self.ram_base 417