blob: 520f9a9e9f311d1b66d022ae14016344ee4b0e0f [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
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