1 # Copyright (c) 2015 Stephen Warren
2 # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
4 # SPDX-License-Identifier: GPL-2.0
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.
12 import multiplexed_log
18 # Regexes for text we expect U-Boot to send to the console.
19 pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)')
20 pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
21 pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
22 pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
23 pattern_error_notification = re.compile('## Error: ')
25 class 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
33 def __init__(self, console, check_type):
34 self.console = console
35 self.check_type = check_type
38 self.console.disable_check_count[self.check_type] += 1
40 def __exit__(self, extype, value, traceback):
41 self.console.disable_check_count[self.check_type] -= 1
43 class 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.'''
49 def __init__(self, log, config, max_fifo_fill):
50 '''Initialize a U-Boot console connection.
52 Can only usefully be called by sub-classes.
55 log: A mulptiplex_log.Logfile object, to which the U-Boot output
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.
71 self.max_fifo_fill = max_fifo_fill
73 self.logstream = self.log.get_stream('console', sys.stdout)
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)
79 self.disable_check_count = {
83 'error_notification': 0,
86 self.at_prompt = False
87 self.at_prompt_logevt = None
91 '''Terminate the connection to the U-Boot console.
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
106 self.logstream.close()
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.
112 The command is always sent to U-Boot.
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".
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.
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".
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.
139 If wait_for_prompt == False:
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.
147 self.ensure_spawned()
149 if self.at_prompt and \
150 self.at_prompt_logevt != self.logstream.logfile.cur_evt:
151 self.logstream.write(self.prompt, implicit=True)
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')
169 self.at_prompt = False
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:]
177 if not wait_for_echo:
179 chunk = re.escape(chunk)
180 chunk = chunk.replace('\\\n', '[\r\n]')
181 m = self.p.expect([chunk] + bad_patterns)
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:
188 m = self.p.expect([self.prompt_escaped] + bad_patterns)
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
197 return self.p.before.strip('\r\n')
198 except Exception as ex:
199 self.log.error(str(ex))
204 '''Send a CTRL-C character to U-Boot.
206 This is useful in order to stop execution of long-running synchronous
207 commands such as "ums".
216 self.run_command(chr(3), wait_for_echo=False, send_nl=False)
218 def ensure_spawned(self):
219 '''Ensure a connection to a correctly running U-Boot instance.
221 This may require spawning a new Sandbox process or resetting target
222 hardware, as defined by the implementation sub-class.
224 This is an internal function and should not be called directly.
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)
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:')
256 self.u_boot_version_string = self.u_boot_main_signon
258 self.u_boot_version_string = self.u_boot_main_signon[:build_idx]
260 match = self.p.expect([self.prompt_escaped,
261 pattern_stop_autoboot_prompt])
263 self.p.send(chr(3)) # CTRL-C
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))
273 def cleanup_spawn(self):
274 '''Shut down all interaction with the U-Boot instance.
276 This is used when an error is detected prior to re-establishing a
277 connection with a fresh U-Boot instance.
279 This is an internal function and should not be called directly.
295 def validate_version_string_in_text(self, text):
296 '''Assert that a command's output includes the U-Boot signon message.
298 This is primarily useful for validating the "version" command without
299 duplicating the signon text regex in a test function.
302 text: The command output text to check.
305 Nothing. An exception is raised if the validation fails.
308 assert(self.u_boot_version_string in text)
310 def disable_check(self, check_type):
311 '''Temporarily disable an error check of U-Boot's output.
313 Create a new context manager (for use with the "with" statement) which
314 temporarily disables a particular console output error check.
317 check_type: The type of error-check to disable. Valid values may
318 be found in self.disable_check_count above.
321 A context manager object.
324 return ConsoleDisableCheck(self, check_type)
326 def find_ram_base(self):
327 '''Find the running U-Boot's RAM location.
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
340 The address of U-Boot's first RAM bank, as an integer.
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:
350 with self.log.section('find_ram_base'):
351 response = self.run_command('bdinfo')
352 for l in response.split('\n'):
354 self.ram_base = int(l.split('=')[1].strip(), 16)
356 if self.ram_base is None:
358 raise Exception('Failed to find RAM bank start in `bdinfo`')