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