xref: /rk3399_rockchip-uboot/test/py/u_boot_console_base.py (revision 9129d9f5fd73a8bcdca7489b3ed2418a8b1416e2)
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: ')
25*9129d9f5SStephen Warrenpattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
26d201506cSStephen Warren
27e4119ebbSStephen WarrenPAT_ID = 0
28e4119ebbSStephen WarrenPAT_RE = 1
29e4119ebbSStephen Warren
30e4119ebbSStephen Warrenbad_pattern_defs = (
31e4119ebbSStephen Warren    ('spl_signon', pattern_u_boot_spl_signon),
32e4119ebbSStephen Warren    ('main_signon', pattern_u_boot_main_signon),
33e4119ebbSStephen Warren    ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
34e4119ebbSStephen Warren    ('unknown_command', pattern_unknown_command),
35e4119ebbSStephen Warren    ('error_notification', pattern_error_notification),
36*9129d9f5SStephen Warren    ('error_please_reset', pattern_error_please_reset),
37e4119ebbSStephen Warren)
38e4119ebbSStephen Warren
39d201506cSStephen Warrenclass ConsoleDisableCheck(object):
40e8debf39SStephen Warren    """Context manager (for Python's with statement) that temporarily disables
41d201506cSStephen Warren    the specified console output error check. This is useful when deliberately
42d201506cSStephen Warren    executing a command that is known to trigger one of the error checks, in
43d201506cSStephen Warren    order to test that the error condition is actually raised. This class is
44d201506cSStephen Warren    used internally by ConsoleBase::disable_check(); it is not intended for
45e8debf39SStephen Warren    direct usage."""
46d201506cSStephen Warren
47d201506cSStephen Warren    def __init__(self, console, check_type):
48d201506cSStephen Warren        self.console = console
49d201506cSStephen Warren        self.check_type = check_type
50d201506cSStephen Warren
51d201506cSStephen Warren    def __enter__(self):
52d201506cSStephen Warren        self.console.disable_check_count[self.check_type] += 1
53e4119ebbSStephen Warren        self.console.eval_bad_patterns()
54d201506cSStephen Warren
55d201506cSStephen Warren    def __exit__(self, extype, value, traceback):
56d201506cSStephen Warren        self.console.disable_check_count[self.check_type] -= 1
57e4119ebbSStephen Warren        self.console.eval_bad_patterns()
58d201506cSStephen Warren
59d201506cSStephen Warrenclass ConsoleBase(object):
60e8debf39SStephen Warren    """The interface through which test functions interact with the U-Boot
61d201506cSStephen Warren    console. This primarily involves executing shell commands, capturing their
62d201506cSStephen Warren    results, and checking for common error conditions. Some common utilities
63e8debf39SStephen Warren    are also provided too."""
64d201506cSStephen Warren
65d201506cSStephen Warren    def __init__(self, log, config, max_fifo_fill):
66e8debf39SStephen Warren        """Initialize a U-Boot console connection.
67d201506cSStephen Warren
68d201506cSStephen Warren        Can only usefully be called by sub-classes.
69d201506cSStephen Warren
70d201506cSStephen Warren        Args:
71d201506cSStephen Warren            log: A mulptiplex_log.Logfile object, to which the U-Boot output
72d201506cSStephen Warren                will be logged.
73d201506cSStephen Warren            config: A configuration data structure, as built by conftest.py.
74d201506cSStephen Warren            max_fifo_fill: The maximum number of characters to send to U-Boot
75d201506cSStephen Warren                command-line before waiting for U-Boot to echo the characters
76d201506cSStephen Warren                back. For UART-based HW without HW flow control, this value
77d201506cSStephen Warren                should be set less than the UART RX FIFO size to avoid
78d201506cSStephen Warren                overflow, assuming that U-Boot can't keep up with full-rate
79d201506cSStephen Warren                traffic at the baud rate.
80d201506cSStephen Warren
81d201506cSStephen Warren        Returns:
82d201506cSStephen Warren            Nothing.
83e8debf39SStephen Warren        """
84d201506cSStephen Warren
85d201506cSStephen Warren        self.log = log
86d201506cSStephen Warren        self.config = config
87d201506cSStephen Warren        self.max_fifo_fill = max_fifo_fill
88d201506cSStephen Warren
89d201506cSStephen Warren        self.logstream = self.log.get_stream('console', sys.stdout)
90d201506cSStephen Warren
91d201506cSStephen Warren        # Array slice removes leading/trailing quotes
92d201506cSStephen Warren        self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
93d201506cSStephen Warren        self.prompt_escaped = re.escape(self.prompt)
94d201506cSStephen Warren        self.p = None
95e4119ebbSStephen Warren        self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
96e4119ebbSStephen Warren        self.eval_bad_patterns()
97d201506cSStephen Warren
98d201506cSStephen Warren        self.at_prompt = False
99d201506cSStephen Warren        self.at_prompt_logevt = None
100d201506cSStephen Warren
101e4119ebbSStephen Warren    def eval_bad_patterns(self):
102e4119ebbSStephen Warren        self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
103e4119ebbSStephen Warren            if self.disable_check_count[pat[PAT_ID]] == 0]
104e4119ebbSStephen Warren        self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
105e4119ebbSStephen Warren            if self.disable_check_count[pat[PAT_ID]] == 0]
106e4119ebbSStephen Warren
107d201506cSStephen Warren    def close(self):
108e8debf39SStephen Warren        """Terminate the connection to the U-Boot console.
109d201506cSStephen Warren
110d201506cSStephen Warren        This function is only useful once all interaction with U-Boot is
111d201506cSStephen Warren        complete. Once this function is called, data cannot be sent to or
112d201506cSStephen Warren        received from U-Boot.
113d201506cSStephen Warren
114d201506cSStephen Warren        Args:
115d201506cSStephen Warren            None.
116d201506cSStephen Warren
117d201506cSStephen Warren        Returns:
118d201506cSStephen Warren            Nothing.
119e8debf39SStephen Warren        """
120d201506cSStephen Warren
121d201506cSStephen Warren        if self.p:
122d201506cSStephen Warren            self.p.close()
123d201506cSStephen Warren        self.logstream.close()
124d201506cSStephen Warren
125d201506cSStephen Warren    def run_command(self, cmd, wait_for_echo=True, send_nl=True,
126d201506cSStephen Warren            wait_for_prompt=True):
127e8debf39SStephen Warren        """Execute a command via the U-Boot console.
128d201506cSStephen Warren
129d201506cSStephen Warren        The command is always sent to U-Boot.
130d201506cSStephen Warren
131d201506cSStephen Warren        U-Boot echoes any command back to its output, and this function
132d201506cSStephen Warren        typically waits for that to occur. The wait can be disabled by setting
133d201506cSStephen Warren        wait_for_echo=False, which is useful e.g. when sending CTRL-C to
134d201506cSStephen Warren        interrupt a long-running command such as "ums".
135d201506cSStephen Warren
136d201506cSStephen Warren        Command execution is typically triggered by sending a newline
137d201506cSStephen Warren        character. This can be disabled by setting send_nl=False, which is
138d201506cSStephen Warren        also useful when sending CTRL-C.
139d201506cSStephen Warren
140d201506cSStephen Warren        This function typically waits for the command to finish executing, and
141d201506cSStephen Warren        returns the console output that it generated. This can be disabled by
142d201506cSStephen Warren        setting wait_for_prompt=False, which is useful when invoking a long-
143d201506cSStephen Warren        running command such as "ums".
144d201506cSStephen Warren
145d201506cSStephen Warren        Args:
146d201506cSStephen Warren            cmd: The command to send.
147d201506cSStephen Warren            wait_for_each: Boolean indicating whether to wait for U-Boot to
148d201506cSStephen Warren                echo the command text back to its output.
149d201506cSStephen Warren            send_nl: Boolean indicating whether to send a newline character
150d201506cSStephen Warren                after the command string.
151d201506cSStephen Warren            wait_for_prompt: Boolean indicating whether to wait for the
152d201506cSStephen Warren                command prompt to be sent by U-Boot. This typically occurs
153d201506cSStephen Warren                immediately after the command has been executed.
154d201506cSStephen Warren
155d201506cSStephen Warren        Returns:
156d201506cSStephen Warren            If wait_for_prompt == False:
157d201506cSStephen Warren                Nothing.
158d201506cSStephen Warren            Else:
159d201506cSStephen Warren                The output from U-Boot during command execution. In other
160d201506cSStephen Warren                words, the text U-Boot emitted between the point it echod the
161d201506cSStephen Warren                command string and emitted the subsequent command prompts.
162e8debf39SStephen Warren        """
163d201506cSStephen Warren
164d201506cSStephen Warren        if self.at_prompt and \
165d201506cSStephen Warren                self.at_prompt_logevt != self.logstream.logfile.cur_evt:
166d201506cSStephen Warren            self.logstream.write(self.prompt, implicit=True)
167d201506cSStephen Warren
168d201506cSStephen Warren        try:
169d201506cSStephen Warren            self.at_prompt = False
170d201506cSStephen Warren            if send_nl:
171d201506cSStephen Warren                cmd += '\n'
172d201506cSStephen Warren            while cmd:
173d201506cSStephen Warren                # Limit max outstanding data, so UART FIFOs don't overflow
174d201506cSStephen Warren                chunk = cmd[:self.max_fifo_fill]
175d201506cSStephen Warren                cmd = cmd[self.max_fifo_fill:]
176d201506cSStephen Warren                self.p.send(chunk)
177d201506cSStephen Warren                if not wait_for_echo:
178d201506cSStephen Warren                    continue
179d201506cSStephen Warren                chunk = re.escape(chunk)
180d201506cSStephen Warren                chunk = chunk.replace('\\\n', '[\r\n]')
181e4119ebbSStephen Warren                m = self.p.expect([chunk] + self.bad_patterns)
182d201506cSStephen Warren                if m != 0:
183d201506cSStephen Warren                    self.at_prompt = False
184d201506cSStephen Warren                    raise Exception('Bad pattern found on console: ' +
185e4119ebbSStephen Warren                                    self.bad_pattern_ids[m - 1])
186d201506cSStephen Warren            if not wait_for_prompt:
187d201506cSStephen Warren                return
188e4119ebbSStephen Warren            m = self.p.expect([self.prompt_escaped] + self.bad_patterns)
189d201506cSStephen Warren            if m != 0:
190d201506cSStephen Warren                self.at_prompt = False
191d201506cSStephen Warren                raise Exception('Bad pattern found on console: ' +
192e4119ebbSStephen Warren                                self.bad_pattern_ids[m - 1])
193d201506cSStephen Warren            self.at_prompt = True
194d201506cSStephen Warren            self.at_prompt_logevt = self.logstream.logfile.cur_evt
195d201506cSStephen Warren            # Only strip \r\n; space/TAB might be significant if testing
196d201506cSStephen Warren            # indentation.
197d201506cSStephen Warren            return self.p.before.strip('\r\n')
198d201506cSStephen Warren        except Exception as ex:
199d201506cSStephen Warren            self.log.error(str(ex))
200d201506cSStephen Warren            self.cleanup_spawn()
201d201506cSStephen Warren            raise
202d201506cSStephen Warren
203d201506cSStephen Warren    def ctrlc(self):
204e8debf39SStephen Warren        """Send a CTRL-C character to U-Boot.
205d201506cSStephen Warren
206d201506cSStephen Warren        This is useful in order to stop execution of long-running synchronous
207d201506cSStephen Warren        commands such as "ums".
208d201506cSStephen Warren
209d201506cSStephen Warren        Args:
210d201506cSStephen Warren            None.
211d201506cSStephen Warren
212d201506cSStephen Warren        Returns:
213d201506cSStephen Warren            Nothing.
214e8debf39SStephen Warren        """
215d201506cSStephen Warren
216783cbcd3SStephen Warren        self.log.action('Sending Ctrl-C')
217d201506cSStephen Warren        self.run_command(chr(3), wait_for_echo=False, send_nl=False)
218d201506cSStephen Warren
21976b46939SStephen Warren    def wait_for(self, text):
220e8debf39SStephen Warren        """Wait for a pattern to be emitted by U-Boot.
22176b46939SStephen Warren
22276b46939SStephen Warren        This is useful when a long-running command such as "dfu" is executing,
22376b46939SStephen Warren        and it periodically emits some text that should show up at a specific
22476b46939SStephen Warren        location in the log file.
22576b46939SStephen Warren
22676b46939SStephen Warren        Args:
22776b46939SStephen Warren            text: The text to wait for; either a string (containing raw text,
22876b46939SStephen Warren                not a regular expression) or an re object.
22976b46939SStephen Warren
23076b46939SStephen Warren        Returns:
23176b46939SStephen Warren            Nothing.
232e8debf39SStephen Warren        """
23376b46939SStephen Warren
23476b46939SStephen Warren        if type(text) == type(''):
23576b46939SStephen Warren            text = re.escape(text)
2360c6189b5SStephen Warren        m = self.p.expect([text] + self.bad_patterns)
2370c6189b5SStephen Warren        if m != 0:
2380c6189b5SStephen Warren            raise Exception('Bad pattern found on console: ' +
2390c6189b5SStephen Warren                            self.bad_pattern_ids[m - 1])
24076b46939SStephen Warren
241c10eb9d3SStephen Warren    def drain_console(self):
242e8debf39SStephen Warren        """Read from and log the U-Boot console for a short time.
243c10eb9d3SStephen Warren
244c10eb9d3SStephen Warren        U-Boot's console output is only logged when the test code actively
245c10eb9d3SStephen Warren        waits for U-Boot to emit specific data. There are cases where tests
246c10eb9d3SStephen Warren        can fail without doing this. For example, if a test asks U-Boot to
247c10eb9d3SStephen Warren        enable USB device mode, then polls until a host-side device node
248c10eb9d3SStephen Warren        exists. In such a case, it is useful to log U-Boot's console output
249c10eb9d3SStephen Warren        in case U-Boot printed clues as to why the host-side even did not
250c10eb9d3SStephen Warren        occur. This function will do that.
251c10eb9d3SStephen Warren
252c10eb9d3SStephen Warren        Args:
253c10eb9d3SStephen Warren            None.
254c10eb9d3SStephen Warren
255c10eb9d3SStephen Warren        Returns:
256c10eb9d3SStephen Warren            Nothing.
257e8debf39SStephen Warren        """
258c10eb9d3SStephen Warren
259c10eb9d3SStephen Warren        # If we are already not connected to U-Boot, there's nothing to drain.
260c10eb9d3SStephen Warren        # This should only happen when a previous call to run_command() or
261c10eb9d3SStephen Warren        # wait_for() failed (and hence the output has already been logged), or
262c10eb9d3SStephen Warren        # the system is shutting down.
263c10eb9d3SStephen Warren        if not self.p:
264c10eb9d3SStephen Warren            return
265c10eb9d3SStephen Warren
266c10eb9d3SStephen Warren        orig_timeout = self.p.timeout
267c10eb9d3SStephen Warren        try:
268c10eb9d3SStephen Warren            # Drain the log for a relatively short time.
269c10eb9d3SStephen Warren            self.p.timeout = 1000
270c10eb9d3SStephen Warren            # Wait for something U-Boot will likely never send. This will
271c10eb9d3SStephen Warren            # cause the console output to be read and logged.
272c10eb9d3SStephen Warren            self.p.expect(['This should never match U-Boot output'])
273c10eb9d3SStephen Warren        except u_boot_spawn.Timeout:
274c10eb9d3SStephen Warren            pass
275c10eb9d3SStephen Warren        finally:
276c10eb9d3SStephen Warren            self.p.timeout = orig_timeout
277c10eb9d3SStephen Warren
278d201506cSStephen Warren    def ensure_spawned(self):
279e8debf39SStephen Warren        """Ensure a connection to a correctly running U-Boot instance.
280d201506cSStephen Warren
281d201506cSStephen Warren        This may require spawning a new Sandbox process or resetting target
282d201506cSStephen Warren        hardware, as defined by the implementation sub-class.
283d201506cSStephen Warren
284d201506cSStephen Warren        This is an internal function and should not be called directly.
285d201506cSStephen Warren
286d201506cSStephen Warren        Args:
287d201506cSStephen Warren            None.
288d201506cSStephen Warren
289d201506cSStephen Warren        Returns:
290d201506cSStephen Warren            Nothing.
291e8debf39SStephen Warren        """
292d201506cSStephen Warren
293d201506cSStephen Warren        if self.p:
294d201506cSStephen Warren            return
295d201506cSStephen Warren        try:
296d201506cSStephen Warren            self.at_prompt = False
297d201506cSStephen Warren            self.log.action('Starting U-Boot')
298d201506cSStephen Warren            self.p = self.get_spawn()
299d201506cSStephen Warren            # Real targets can take a long time to scroll large amounts of
300d201506cSStephen Warren            # text if LCD is enabled. This value may need tweaking in the
301d201506cSStephen Warren            # future, possibly per-test to be optimal. This works for 'help'
302d201506cSStephen Warren            # on board 'seaboard'.
303d201506cSStephen Warren            self.p.timeout = 30000
304d201506cSStephen Warren            self.p.logfile_read = self.logstream
305d201506cSStephen Warren            if self.config.buildconfig.get('CONFIG_SPL', False) == 'y':
3060c6189b5SStephen Warren                m = self.p.expect([pattern_u_boot_spl_signon] + self.bad_patterns)
3070c6189b5SStephen Warren                if m != 0:
3080c6189b5SStephen Warren                    raise Exception('Bad pattern found on console: ' +
3090c6189b5SStephen Warren                                    self.bad_pattern_ids[m - 1])
3100c6189b5SStephen Warren            m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
3110c6189b5SStephen Warren            if m != 0:
3120c6189b5SStephen Warren                raise Exception('Bad pattern found on console: ' +
3130c6189b5SStephen Warren                                self.bad_pattern_ids[m - 1])
314e787a58fSStephen Warren            signon = self.p.after
315e787a58fSStephen Warren            build_idx = signon.find(', Build:')
316d201506cSStephen Warren            if build_idx == -1:
317e787a58fSStephen Warren                self.u_boot_version_string = signon
318d201506cSStephen Warren            else:
319e787a58fSStephen Warren                self.u_boot_version_string = signon[:build_idx]
320d201506cSStephen Warren            while True:
3210c6189b5SStephen Warren                m = self.p.expect([self.prompt_escaped,
3220c6189b5SStephen Warren                    pattern_stop_autoboot_prompt] + self.bad_patterns)
3230c6189b5SStephen Warren                if m == 0:
3240c6189b5SStephen Warren                    break
3250c6189b5SStephen Warren                if m == 1:
326d201506cSStephen Warren                    self.p.send(chr(3)) # CTRL-C
327d201506cSStephen Warren                    continue
3280c6189b5SStephen Warren                raise Exception('Bad pattern found on console: ' +
3290c6189b5SStephen Warren                                self.bad_pattern_ids[m - 2])
330d201506cSStephen Warren            self.at_prompt = True
331d201506cSStephen Warren            self.at_prompt_logevt = self.logstream.logfile.cur_evt
332d201506cSStephen Warren        except Exception as ex:
333d201506cSStephen Warren            self.log.error(str(ex))
334d201506cSStephen Warren            self.cleanup_spawn()
335d201506cSStephen Warren            raise
336d201506cSStephen Warren
337d201506cSStephen Warren    def cleanup_spawn(self):
338e8debf39SStephen Warren        """Shut down all interaction with the U-Boot instance.
339d201506cSStephen Warren
340d201506cSStephen Warren        This is used when an error is detected prior to re-establishing a
341d201506cSStephen Warren        connection with a fresh U-Boot instance.
342d201506cSStephen Warren
343d201506cSStephen Warren        This is an internal function and should not be called directly.
344d201506cSStephen Warren
345d201506cSStephen Warren        Args:
346d201506cSStephen Warren            None.
347d201506cSStephen Warren
348d201506cSStephen Warren        Returns:
349d201506cSStephen Warren            Nothing.
350e8debf39SStephen Warren        """
351d201506cSStephen Warren
352d201506cSStephen Warren        try:
353d201506cSStephen Warren            if self.p:
354d201506cSStephen Warren                self.p.close()
355d201506cSStephen Warren        except:
356d201506cSStephen Warren            pass
357d201506cSStephen Warren        self.p = None
358d201506cSStephen Warren
359d201506cSStephen Warren    def validate_version_string_in_text(self, text):
360e8debf39SStephen Warren        """Assert that a command's output includes the U-Boot signon message.
361d201506cSStephen Warren
362d201506cSStephen Warren        This is primarily useful for validating the "version" command without
363d201506cSStephen Warren        duplicating the signon text regex in a test function.
364d201506cSStephen Warren
365d201506cSStephen Warren        Args:
366d201506cSStephen Warren            text: The command output text to check.
367d201506cSStephen Warren
368d201506cSStephen Warren        Returns:
369d201506cSStephen Warren            Nothing. An exception is raised if the validation fails.
370e8debf39SStephen Warren        """
371d201506cSStephen Warren
372d201506cSStephen Warren        assert(self.u_boot_version_string in text)
373d201506cSStephen Warren
374d201506cSStephen Warren    def disable_check(self, check_type):
375e8debf39SStephen Warren        """Temporarily disable an error check of U-Boot's output.
376d201506cSStephen Warren
377d201506cSStephen Warren        Create a new context manager (for use with the "with" statement) which
378d201506cSStephen Warren        temporarily disables a particular console output error check.
379d201506cSStephen Warren
380d201506cSStephen Warren        Args:
381d201506cSStephen Warren            check_type: The type of error-check to disable. Valid values may
382d201506cSStephen Warren            be found in self.disable_check_count above.
383d201506cSStephen Warren
384d201506cSStephen Warren        Returns:
385d201506cSStephen Warren            A context manager object.
386e8debf39SStephen Warren        """
387d201506cSStephen Warren
388d201506cSStephen Warren        return ConsoleDisableCheck(self, check_type)
389