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
19 # Regexes for text we expect U-Boot to send to the console.
20 pattern_u_boot_spl_signon
= re
.compile('(U-Boot SPL \\d{4}\\.\\d{2}-[^\r\n]*)')
21 pattern_u_boot_main_signon
= re
.compile('(U-Boot \\d{4}\\.\\d{2}-[^\r\n]*)')
22 pattern_stop_autoboot_prompt
= re
.compile('Hit any key to stop autoboot: ')
23 pattern_unknown_command
= re
.compile('Unknown command \'.*\' - try \'help\'')
24 pattern_error_notification
= re
.compile('## Error: ')
30 ('spl_signon', pattern_u_boot_spl_signon
),
31 ('main_signon', pattern_u_boot_main_signon
),
32 ('stop_autoboot_prompt', pattern_stop_autoboot_prompt
),
33 ('unknown_command', pattern_unknown_command
),
34 ('error_notification', pattern_error_notification
),
37 class ConsoleDisableCheck(object):
38 """Context manager (for Python's with statement) that temporarily disables
39 the specified console output error check. This is useful when deliberately
40 executing a command that is known to trigger one of the error checks, in
41 order to test that the error condition is actually raised. This class is
42 used internally by ConsoleBase::disable_check(); it is not intended for
45 def __init__(self
, console
, check_type
):
46 self
.console
= console
47 self
.check_type
= check_type
50 self
.console
.disable_check_count
[self
.check_type
] += 1
51 self
.console
.eval_bad_patterns()
53 def __exit__(self
, extype
, value
, traceback
):
54 self
.console
.disable_check_count
[self
.check_type
] -= 1
55 self
.console
.eval_bad_patterns()
57 class ConsoleBase(object):
58 """The interface through which test functions interact with the U-Boot
59 console. This primarily involves executing shell commands, capturing their
60 results, and checking for common error conditions. Some common utilities
61 are also provided too."""
63 def __init__(self
, log
, config
, max_fifo_fill
):
64 """Initialize a U-Boot console connection.
66 Can only usefully be called by sub-classes.
69 log: A mulptiplex_log.Logfile object, to which the U-Boot output
71 config: A configuration data structure, as built by conftest.py.
72 max_fifo_fill: The maximum number of characters to send to U-Boot
73 command-line before waiting for U-Boot to echo the characters
74 back. For UART-based HW without HW flow control, this value
75 should be set less than the UART RX FIFO size to avoid
76 overflow, assuming that U-Boot can't keep up with full-rate
77 traffic at the baud rate.
85 self
.max_fifo_fill
= max_fifo_fill
87 self
.logstream
= self
.log
.get_stream('console', sys
.stdout
)
89 # Array slice removes leading/trailing quotes
90 self
.prompt
= self
.config
.buildconfig
['config_sys_prompt'][1:-1]
91 self
.prompt_escaped
= re
.escape(self
.prompt
)
93 self
.disable_check_count
= {pat
[PAT_ID
]: 0 for pat
in bad_pattern_defs
}
94 self
.eval_bad_patterns()
96 self
.at_prompt
= False
97 self
.at_prompt_logevt
= None
99 def eval_bad_patterns(self
):
100 self
.bad_patterns
= [pat
[PAT_RE
] for pat
in bad_pattern_defs \
101 if self
.disable_check_count
[pat
[PAT_ID
]] == 0]
102 self
.bad_pattern_ids
= [pat
[PAT_ID
] for pat
in bad_pattern_defs \
103 if self
.disable_check_count
[pat
[PAT_ID
]] == 0]
106 """Terminate the connection to the U-Boot console.
108 This function is only useful once all interaction with U-Boot is
109 complete. Once this function is called, data cannot be sent to or
110 received from U-Boot.
121 self
.logstream
.close()
123 def run_command(self
, cmd
, wait_for_echo
=True, send_nl
=True,
124 wait_for_prompt
=True):
125 """Execute a command via the U-Boot console.
127 The command is always sent to U-Boot.
129 U-Boot echoes any command back to its output, and this function
130 typically waits for that to occur. The wait can be disabled by setting
131 wait_for_echo=False, which is useful e.g. when sending CTRL-C to
132 interrupt a long-running command such as "ums".
134 Command execution is typically triggered by sending a newline
135 character. This can be disabled by setting send_nl=False, which is
136 also useful when sending CTRL-C.
138 This function typically waits for the command to finish executing, and
139 returns the console output that it generated. This can be disabled by
140 setting wait_for_prompt=False, which is useful when invoking a long-
141 running command such as "ums".
144 cmd: The command to send.
145 wait_for_each: Boolean indicating whether to wait for U-Boot to
146 echo the command text back to its output.
147 send_nl: Boolean indicating whether to send a newline character
148 after the command string.
149 wait_for_prompt: Boolean indicating whether to wait for the
150 command prompt to be sent by U-Boot. This typically occurs
151 immediately after the command has been executed.
154 If wait_for_prompt == False:
157 The output from U-Boot during command execution. In other
158 words, the text U-Boot emitted between the point it echod the
159 command string and emitted the subsequent command prompts.
162 if self
.at_prompt
and \
163 self
.at_prompt_logevt
!= self
.logstream
.logfile
.cur_evt
:
164 self
.logstream
.write(self
.prompt
, implicit
=True)
167 self
.at_prompt
= False
171 # Limit max outstanding data, so UART FIFOs don't overflow
172 chunk
= cmd
[:self
.max_fifo_fill
]
173 cmd
= cmd
[self
.max_fifo_fill
:]
175 if not wait_for_echo
:
177 chunk
= re
.escape(chunk
)
178 chunk
= chunk
.replace('\\\n', '[\r\n]')
179 m
= self
.p
.expect([chunk
] + self
.bad_patterns
)
181 self
.at_prompt
= False
182 raise Exception('Bad pattern found on console: ' +
183 self
.bad_pattern_ids
[m
- 1])
184 if not wait_for_prompt
:
186 m
= self
.p
.expect([self
.prompt_escaped
] + self
.bad_patterns
)
188 self
.at_prompt
= False
189 raise Exception('Bad pattern found on console: ' +
190 self
.bad_pattern_ids
[m
- 1])
191 self
.at_prompt
= True
192 self
.at_prompt_logevt
= self
.logstream
.logfile
.cur_evt
193 # Only strip \r\n; space/TAB might be significant if testing
195 return self
.p
.before
.strip('\r\n')
196 except Exception as ex
:
197 self
.log
.error(str(ex
))
202 """Send a CTRL-C character to U-Boot.
204 This is useful in order to stop execution of long-running synchronous
205 commands such as "ums".
214 self
.log
.action('Sending Ctrl-C')
215 self
.run_command(chr(3), wait_for_echo
=False, send_nl
=False)
217 def wait_for(self
, text
):
218 """Wait for a pattern to be emitted by U-Boot.
220 This is useful when a long-running command such as "dfu" is executing,
221 and it periodically emits some text that should show up at a specific
222 location in the log file.
225 text: The text to wait for; either a string (containing raw text,
226 not a regular expression) or an re object.
232 if type(text
) == type(''):
233 text
= re
.escape(text
)
234 self
.p
.expect([text
])
236 def drain_console(self
):
237 """Read from and log the U-Boot console for a short time.
239 U-Boot's console output is only logged when the test code actively
240 waits for U-Boot to emit specific data. There are cases where tests
241 can fail without doing this. For example, if a test asks U-Boot to
242 enable USB device mode, then polls until a host-side device node
243 exists. In such a case, it is useful to log U-Boot's console output
244 in case U-Boot printed clues as to why the host-side even did not
245 occur. This function will do that.
254 # If we are already not connected to U-Boot, there's nothing to drain.
255 # This should only happen when a previous call to run_command() or
256 # wait_for() failed (and hence the output has already been logged), or
257 # the system is shutting down.
261 orig_timeout
= self
.p
.timeout
263 # Drain the log for a relatively short time.
264 self
.p
.timeout
= 1000
265 # Wait for something U-Boot will likely never send. This will
266 # cause the console output to be read and logged.
267 self
.p
.expect(['This should never match U-Boot output'])
268 except u_boot_spawn
.Timeout
:
271 self
.p
.timeout
= orig_timeout
273 def ensure_spawned(self
):
274 """Ensure a connection to a correctly running U-Boot instance.
276 This may require spawning a new Sandbox process or resetting target
277 hardware, as defined by the implementation sub-class.
279 This is an internal function and should not be called directly.
291 self
.at_prompt
= False
292 self
.log
.action('Starting U-Boot')
293 self
.p
= self
.get_spawn()
294 # Real targets can take a long time to scroll large amounts of
295 # text if LCD is enabled. This value may need tweaking in the
296 # future, possibly per-test to be optimal. This works for 'help'
297 # on board 'seaboard'.
298 self
.p
.timeout
= 30000
299 self
.p
.logfile_read
= self
.logstream
300 if self
.config
.buildconfig
.get('CONFIG_SPL', False) == 'y':
301 self
.p
.expect([pattern_u_boot_spl_signon
])
302 self
.p
.expect([pattern_u_boot_main_signon
])
303 signon
= self
.p
.after
304 build_idx
= signon
.find(', Build:')
306 self
.u_boot_version_string
= signon
308 self
.u_boot_version_string
= signon
[:build_idx
]
310 match
= self
.p
.expect([self
.prompt_escaped
,
311 pattern_stop_autoboot_prompt
])
313 self
.p
.send(chr(3)) # CTRL-C
316 self
.at_prompt
= True
317 self
.at_prompt_logevt
= self
.logstream
.logfile
.cur_evt
318 except Exception as ex
:
319 self
.log
.error(str(ex
))
323 def cleanup_spawn(self
):
324 """Shut down all interaction with the U-Boot instance.
326 This is used when an error is detected prior to re-establishing a
327 connection with a fresh U-Boot instance.
329 This is an internal function and should not be called directly.
345 def validate_version_string_in_text(self
, text
):
346 """Assert that a command's output includes the U-Boot signon message.
348 This is primarily useful for validating the "version" command without
349 duplicating the signon text regex in a test function.
352 text: The command output text to check.
355 Nothing. An exception is raised if the validation fails.
358 assert(self
.u_boot_version_string
in text
)
360 def disable_check(self
, check_type
):
361 """Temporarily disable an error check of U-Boot's output.
363 Create a new context manager (for use with the "with" statement) which
364 temporarily disables a particular console output error check.
367 check_type: The type of error-check to disable. Valid values may
368 be found in self.disable_check_count above.
371 A context manager object.
374 return ConsoleDisableCheck(self
, check_type
)