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