blob: 418a26bb8e40d561d2b62948cd8962f114c935a2 [file] [log] [blame]
Stephen Warren10e50632016-01-15 11:15:24 -07001# 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
Stephen Warren97a54662016-01-22 12:30:09 -070017import u_boot_spawn
Stephen Warren10e50632016-01-15 11:15:24 -070018
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
Stephen Warren10e50632016-01-15 11:15:24 -0700148 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.run_command(chr(3), wait_for_echo=False, send_nl=False)
216
Stephen Warren97a54662016-01-22 12:30:09 -0700217 def drain_console(self):
218 '''Read from and log the U-Boot console for a short time.
219
220 U-Boot's console output is only logged when the test code actively
221 waits for U-Boot to emit specific data. There are cases where tests
222 can fail without doing this. For example, if a test asks U-Boot to
223 enable USB device mode, then polls until a host-side device node
224 exists. In such a case, it is useful to log U-Boot's console output
225 in case U-Boot printed clues as to why the host-side even did not
226 occur. This function will do that.
227
228 Args:
229 None.
230
231 Returns:
232 Nothing.
233 '''
234
235 # If we are already not connected to U-Boot, there's nothing to drain.
236 # This should only happen when a previous call to run_command() or
237 # wait_for() failed (and hence the output has already been logged), or
238 # the system is shutting down.
239 if not self.p:
240 return
241
242 orig_timeout = self.p.timeout
243 try:
244 # Drain the log for a relatively short time.
245 self.p.timeout = 1000
246 # Wait for something U-Boot will likely never send. This will
247 # cause the console output to be read and logged.
248 self.p.expect(['This should never match U-Boot output'])
249 except u_boot_spawn.Timeout:
250 pass
251 finally:
252 self.p.timeout = orig_timeout
253
Stephen Warren10e50632016-01-15 11:15:24 -0700254 def ensure_spawned(self):
255 '''Ensure a connection to a correctly running U-Boot instance.
256
257 This may require spawning a new Sandbox process or resetting target
258 hardware, as defined by the implementation sub-class.
259
260 This is an internal function and should not be called directly.
261
262 Args:
263 None.
264
265 Returns:
266 Nothing.
267 '''
268
269 if self.p:
270 return
271 try:
272 self.at_prompt = False
273 self.log.action('Starting U-Boot')
274 self.p = self.get_spawn()
275 # Real targets can take a long time to scroll large amounts of
276 # text if LCD is enabled. This value may need tweaking in the
277 # future, possibly per-test to be optimal. This works for 'help'
278 # on board 'seaboard'.
279 self.p.timeout = 30000
280 self.p.logfile_read = self.logstream
281 if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
282 self.p.expect([pattern_u_boot_spl_signon])
283 self.u_boot_spl_signon = self.p.after
284 self.u_boot_spl_signon_escaped = re.escape(self.p.after)
285 else:
286 self.u_boot_spl_signon = None
287 self.p.expect([pattern_u_boot_main_signon])
288 self.u_boot_main_signon = self.p.after
289 self.u_boot_main_signon_escaped = re.escape(self.p.after)
290 build_idx = self.u_boot_main_signon.find(', Build:')
291 if build_idx == -1:
292 self.u_boot_version_string = self.u_boot_main_signon
293 else:
294 self.u_boot_version_string = self.u_boot_main_signon[:build_idx]
295 while True:
296 match = self.p.expect([self.prompt_escaped,
297 pattern_stop_autoboot_prompt])
298 if match == 1:
299 self.p.send(chr(3)) # CTRL-C
300 continue
301 break
302 self.at_prompt = True
303 self.at_prompt_logevt = self.logstream.logfile.cur_evt
304 except Exception as ex:
305 self.log.error(str(ex))
306 self.cleanup_spawn()
307 raise
308
309 def cleanup_spawn(self):
310 '''Shut down all interaction with the U-Boot instance.
311
312 This is used when an error is detected prior to re-establishing a
313 connection with a fresh U-Boot instance.
314
315 This is an internal function and should not be called directly.
316
317 Args:
318 None.
319
320 Returns:
321 Nothing.
322 '''
323
324 try:
325 if self.p:
326 self.p.close()
327 except:
328 pass
329 self.p = None
330
331 def validate_version_string_in_text(self, text):
332 '''Assert that a command's output includes the U-Boot signon message.
333
334 This is primarily useful for validating the "version" command without
335 duplicating the signon text regex in a test function.
336
337 Args:
338 text: The command output text to check.
339
340 Returns:
341 Nothing. An exception is raised if the validation fails.
342 '''
343
344 assert(self.u_boot_version_string in text)
345
346 def disable_check(self, check_type):
347 '''Temporarily disable an error check of U-Boot's output.
348
349 Create a new context manager (for use with the "with" statement) which
350 temporarily disables a particular console output error check.
351
352 Args:
353 check_type: The type of error-check to disable. Valid values may
354 be found in self.disable_check_count above.
355
356 Returns:
357 A context manager object.
358 '''
359
360 return ConsoleDisableCheck(self, check_type)
361
362 def find_ram_base(self):
363 '''Find the running U-Boot's RAM location.
364
365 Probe the running U-Boot to determine the address of the first bank
366 of RAM. This is useful for tests that test reading/writing RAM, or
367 load/save files that aren't associated with some standard address
368 typically represented in an environment variable such as
369 ${kernel_addr_r}. The value is cached so that it only needs to be
370 actively read once.
371
372 Args:
373 None.
374
375 Returns:
376 The address of U-Boot's first RAM bank, as an integer.
377 '''
378
379 if self.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
380 pytest.skip('bdinfo command not supported')
381 if self.ram_base == -1:
382 pytest.skip('Previously failed to find RAM bank start')
383 if self.ram_base is not None:
384 return self.ram_base
385
386 with self.log.section('find_ram_base'):
387 response = self.run_command('bdinfo')
388 for l in response.split('\n'):
389 if '-> start' in l:
390 self.ram_base = int(l.split('=')[1].strip(), 16)
391 break
392 if self.ram_base is None:
393 self.ram_base = -1
394 raise Exception('Failed to find RAM bank start in `bdinfo`')
395
396 return self.ram_base