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 self.ensure_spawned() 148 149 if self.at_prompt and \ 150 self.at_prompt_logevt != self.logstream.logfile.cur_evt: 151 self.logstream.write(self.prompt, implicit=True) 152 153 bad_patterns = [] 154 bad_pattern_ids = [] 155 if (self.disable_check_count['spl_signon'] == 0 and 156 self.u_boot_spl_signon): 157 bad_patterns.append(self.u_boot_spl_signon_escaped) 158 bad_pattern_ids.append('SPL signon') 159 if self.disable_check_count['main_signon'] == 0: 160 bad_patterns.append(self.u_boot_main_signon_escaped) 161 bad_pattern_ids.append('U-Boot main signon') 162 if self.disable_check_count['unknown_command'] == 0: 163 bad_patterns.append(pattern_unknown_command) 164 bad_pattern_ids.append('Unknown command') 165 if self.disable_check_count['error_notification'] == 0: 166 bad_patterns.append(pattern_error_notification) 167 bad_pattern_ids.append('Error notification') 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] + bad_patterns) 182 if m != 0: 183 self.at_prompt = False 184 raise Exception('Bad pattern found on console: ' + 185 bad_pattern_ids[m - 1]) 186 if not wait_for_prompt: 187 return 188 m = self.p.expect([self.prompt_escaped] + bad_patterns) 189 if m != 0: 190 self.at_prompt = False 191 raise Exception('Bad pattern found on console: ' + 192 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.run_command(chr(3), wait_for_echo=False, send_nl=False) 217 218 def ensure_spawned(self): 219 '''Ensure a connection to a correctly running U-Boot instance. 220 221 This may require spawning a new Sandbox process or resetting target 222 hardware, as defined by the implementation sub-class. 223 224 This is an internal function and should not be called directly. 225 226 Args: 227 None. 228 229 Returns: 230 Nothing. 231 ''' 232 233 if self.p: 234 return 235 try: 236 self.at_prompt = False 237 self.log.action('Starting U-Boot') 238 self.p = self.get_spawn() 239 # Real targets can take a long time to scroll large amounts of 240 # text if LCD is enabled. This value may need tweaking in the 241 # future, possibly per-test to be optimal. This works for 'help' 242 # on board 'seaboard'. 243 self.p.timeout = 30000 244 self.p.logfile_read = self.logstream 245 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y': 246 self.p.expect([pattern_u_boot_spl_signon]) 247 self.u_boot_spl_signon = self.p.after 248 self.u_boot_spl_signon_escaped = re.escape(self.p.after) 249 else: 250 self.u_boot_spl_signon = None 251 self.p.expect([pattern_u_boot_main_signon]) 252 self.u_boot_main_signon = self.p.after 253 self.u_boot_main_signon_escaped = re.escape(self.p.after) 254 build_idx = self.u_boot_main_signon.find(', Build:') 255 if build_idx == -1: 256 self.u_boot_version_string = self.u_boot_main_signon 257 else: 258 self.u_boot_version_string = self.u_boot_main_signon[:build_idx] 259 while True: 260 match = self.p.expect([self.prompt_escaped, 261 pattern_stop_autoboot_prompt]) 262 if match == 1: 263 self.p.send(chr(3)) # CTRL-C 264 continue 265 break 266 self.at_prompt = True 267 self.at_prompt_logevt = self.logstream.logfile.cur_evt 268 except Exception as ex: 269 self.log.error(str(ex)) 270 self.cleanup_spawn() 271 raise 272 273 def cleanup_spawn(self): 274 '''Shut down all interaction with the U-Boot instance. 275 276 This is used when an error is detected prior to re-establishing a 277 connection with a fresh U-Boot instance. 278 279 This is an internal function and should not be called directly. 280 281 Args: 282 None. 283 284 Returns: 285 Nothing. 286 ''' 287 288 try: 289 if self.p: 290 self.p.close() 291 except: 292 pass 293 self.p = None 294 295 def validate_version_string_in_text(self, text): 296 '''Assert that a command's output includes the U-Boot signon message. 297 298 This is primarily useful for validating the "version" command without 299 duplicating the signon text regex in a test function. 300 301 Args: 302 text: The command output text to check. 303 304 Returns: 305 Nothing. An exception is raised if the validation fails. 306 ''' 307 308 assert(self.u_boot_version_string in text) 309 310 def disable_check(self, check_type): 311 '''Temporarily disable an error check of U-Boot's output. 312 313 Create a new context manager (for use with the "with" statement) which 314 temporarily disables a particular console output error check. 315 316 Args: 317 check_type: The type of error-check to disable. Valid values may 318 be found in self.disable_check_count above. 319 320 Returns: 321 A context manager object. 322 ''' 323 324 return ConsoleDisableCheck(self, check_type) 325 326 def find_ram_base(self): 327 '''Find the running U-Boot's RAM location. 328 329 Probe the running U-Boot to determine the address of the first bank 330 of RAM. This is useful for tests that test reading/writing RAM, or 331 load/save files that aren't associated with some standard address 332 typically represented in an environment variable such as 333 ${kernel_addr_r}. The value is cached so that it only needs to be 334 actively read once. 335 336 Args: 337 None. 338 339 Returns: 340 The address of U-Boot's first RAM bank, as an integer. 341 ''' 342 343 if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y': 344 pytest.skip('bdinfo command not supported') 345 if self.ram_base == -1: 346 pytest.skip('Previously failed to find RAM bank start') 347 if self.ram_base is not None: 348 return self.ram_base 349 350 with self.log.section('find_ram_base'): 351 response = self.run_command('bdinfo') 352 for l in response.split('\n'): 353 if '-> start' in l: 354 self.ram_base = int(l.split('=')[1].strip(), 16) 355 break 356 if self.ram_base is None: 357 self.ram_base = -1 358 raise Exception('Failed to find RAM bank start in `bdinfo`') 359 360 return self.ram_base 361