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 17 18# Regexes for text we expect U-Boot to send to the console. 19pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)') 20pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)') 21pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ') 22pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'') 23pattern_error_notification = re.compile('## Error: ') 24 25class ConsoleDisableCheck(object): 26 '''Context manager (for Python's with statement) that temporarily disables 27 the specified console output error check. This is useful when deliberately 28 executing a command that is known to trigger one of the error checks, in 29 order to test that the error condition is actually raised. This class is 30 used internally by ConsoleBase::disable_check(); it is not intended for 31 direct usage.''' 32 33 def __init__(self, console, check_type): 34 self.console = console 35 self.check_type = check_type 36 37 def __enter__(self): 38 self.console.disable_check_count[self.check_type] += 1 39 40 def __exit__(self, extype, value, traceback): 41 self.console.disable_check_count[self.check_type] -= 1 42 43class ConsoleBase(object): 44 '''The interface through which test functions interact with the U-Boot 45 console. This primarily involves executing shell commands, capturing their 46 results, and checking for common error conditions. Some common utilities 47 are also provided too.''' 48 49 def __init__(self, log, config, max_fifo_fill): 50 '''Initialize a U-Boot console connection. 51 52 Can only usefully be called by sub-classes. 53 54 Args: 55 log: A mulptiplex_log.Logfile object, to which the U-Boot output 56 will be logged. 57 config: A configuration data structure, as built by conftest.py. 58 max_fifo_fill: The maximum number of characters to send to U-Boot 59 command-line before waiting for U-Boot to echo the characters 60 back. For UART-based HW without HW flow control, this value 61 should be set less than the UART RX FIFO size to avoid 62 overflow, assuming that U-Boot can't keep up with full-rate 63 traffic at the baud rate. 64 65 Returns: 66 Nothing. 67 ''' 68 69 self.log = log 70 self.config = config 71 self.max_fifo_fill = max_fifo_fill 72 73 self.logstream = self.log.get_stream('console', sys.stdout) 74 75 # Array slice removes leading/trailing quotes 76 self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1] 77 self.prompt_escaped = re.escape(self.prompt) 78 self.p = None 79 self.disable_check_count = { 80 'spl_signon': 0, 81 'main_signon': 0, 82 'unknown_command': 0, 83 'error_notification': 0, 84 } 85 86 self.at_prompt = False 87 self.at_prompt_logevt = None 88 self.ram_base = 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 and 154 self.u_boot_spl_signon): 155 bad_patterns.append(self.u_boot_spl_signon_escaped) 156 bad_pattern_ids.append('SPL signon') 157 if self.disable_check_count['main_signon'] == 0: 158 bad_patterns.append(self.u_boot_main_signon_escaped) 159 bad_pattern_ids.append('U-Boot main signon') 160 if self.disable_check_count['unknown_command'] == 0: 161 bad_patterns.append(pattern_unknown_command) 162 bad_pattern_ids.append('Unknown command') 163 if self.disable_check_count['error_notification'] == 0: 164 bad_patterns.append(pattern_error_notification) 165 bad_pattern_ids.append('Error notification') 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] + bad_patterns) 180 if m != 0: 181 self.at_prompt = False 182 raise Exception('Bad pattern found on console: ' + 183 bad_pattern_ids[m - 1]) 184 if not wait_for_prompt: 185 return 186 m = self.p.expect([self.prompt_escaped] + bad_patterns) 187 if m != 0: 188 self.at_prompt = False 189 raise Exception('Bad pattern found on console: ' + 190 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.run_command(chr(3), wait_for_echo=False, send_nl=False) 215 216 def ensure_spawned(self): 217 '''Ensure a connection to a correctly running U-Boot instance. 218 219 This may require spawning a new Sandbox process or resetting target 220 hardware, as defined by the implementation sub-class. 221 222 This is an internal function and should not be called directly. 223 224 Args: 225 None. 226 227 Returns: 228 Nothing. 229 ''' 230 231 if self.p: 232 return 233 try: 234 self.at_prompt = False 235 self.log.action('Starting U-Boot') 236 self.p = self.get_spawn() 237 # Real targets can take a long time to scroll large amounts of 238 # text if LCD is enabled. This value may need tweaking in the 239 # future, possibly per-test to be optimal. This works for 'help' 240 # on board 'seaboard'. 241 self.p.timeout = 30000 242 self.p.logfile_read = self.logstream 243 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': 244 self.p.expect([pattern_u_boot_spl_signon]) 245 self.u_boot_spl_signon = self.p.after 246 self.u_boot_spl_signon_escaped = re.escape(self.p.after) 247 else: 248 self.u_boot_spl_signon = None 249 self.p.expect([pattern_u_boot_main_signon]) 250 self.u_boot_main_signon = self.p.after 251 self.u_boot_main_signon_escaped = re.escape(self.p.after) 252 build_idx = self.u_boot_main_signon.find(', Build:') 253 if build_idx == -1: 254 self.u_boot_version_string = self.u_boot_main_signon 255 else: 256 self.u_boot_version_string = self.u_boot_main_signon[:build_idx] 257 while True: 258 match = self.p.expect([self.prompt_escaped, 259 pattern_stop_autoboot_prompt]) 260 if match == 1: 261 self.p.send(chr(3)) # CTRL-C 262 continue 263 break 264 self.at_prompt = True 265 self.at_prompt_logevt = self.logstream.logfile.cur_evt 266 except Exception as ex: 267 self.log.error(str(ex)) 268 self.cleanup_spawn() 269 raise 270 271 def cleanup_spawn(self): 272 '''Shut down all interaction with the U-Boot instance. 273 274 This is used when an error is detected prior to re-establishing a 275 connection with a fresh U-Boot instance. 276 277 This is an internal function and should not be called directly. 278 279 Args: 280 None. 281 282 Returns: 283 Nothing. 284 ''' 285 286 try: 287 if self.p: 288 self.p.close() 289 except: 290 pass 291 self.p = None 292 293 def validate_version_string_in_text(self, text): 294 '''Assert that a command's output includes the U-Boot signon message. 295 296 This is primarily useful for validating the "version" command without 297 duplicating the signon text regex in a test function. 298 299 Args: 300 text: The command output text to check. 301 302 Returns: 303 Nothing. An exception is raised if the validation fails. 304 ''' 305 306 assert(self.u_boot_version_string in text) 307 308 def disable_check(self, check_type): 309 '''Temporarily disable an error check of U-Boot's output. 310 311 Create a new context manager (for use with the "with" statement) which 312 temporarily disables a particular console output error check. 313 314 Args: 315 check_type: The type of error-check to disable. Valid values may 316 be found in self.disable_check_count above. 317 318 Returns: 319 A context manager object. 320 ''' 321 322 return ConsoleDisableCheck(self, check_type) 323 324 def find_ram_base(self): 325 '''Find the running U-Boot's RAM location. 326 327 Probe the running U-Boot to determine the address of the first bank 328 of RAM. This is useful for tests that test reading/writing RAM, or 329 load/save files that aren't associated with some standard address 330 typically represented in an environment variable such as 331 ${kernel_addr_r}. The value is cached so that it only needs to be 332 actively read once. 333 334 Args: 335 None. 336 337 Returns: 338 The address of U-Boot's first RAM bank, as an integer. 339 ''' 340 341 if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': 342 pytest.skip('bdinfo command not supported') 343 if self.ram_base == -1: 344 pytest.skip('Previously failed to find RAM bank start') 345 if self.ram_base is not None: 346 return self.ram_base 347 348 with self.log.section('find_ram_base'): 349 response = self.run_command('bdinfo') 350 for l in response.split('\n'): 351 if '-> start' in l: 352 self.ram_base = int(l.split('=')[1].strip(), 16) 353 break 354 if self.ram_base is None: 355 self.ram_base = -1 356 raise Exception('Failed to find RAM bank start in `bdinfo`') 357 358 return self.ram_base 359