xref: /openbmc/u-boot/test/py/u_boot_utils.py (revision dffceb4b)
1# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
2#
3# SPDX-License-Identifier: GPL-2.0
4
5# Utility code shared across multiple tests.
6
7import hashlib
8import os
9import os.path
10import sys
11import time
12
13def md5sum_data(data):
14    """Calculate the MD5 hash of some data.
15
16    Args:
17        data: The data to hash.
18
19    Returns:
20        The hash of the data, as a binary string.
21    """
22
23    h = hashlib.md5()
24    h.update(data)
25    return h.digest()
26
27def md5sum_file(fn, max_length=None):
28    """Calculate the MD5 hash of the contents of a file.
29
30    Args:
31        fn: The filename of the file to hash.
32        max_length: The number of bytes to hash. If the file has more
33            bytes than this, they will be ignored. If None or omitted, the
34            entire file will be hashed.
35
36    Returns:
37        The hash of the file content, as a binary string.
38    """
39
40    with open(fn, 'rb') as fh:
41        if max_length:
42            params = [max_length]
43        else:
44            params = []
45        data = fh.read(*params)
46    return md5sum_data(data)
47
48class PersistentRandomFile(object):
49    """Generate and store information about a persistent file containing
50    random data."""
51
52    def __init__(self, u_boot_console, fn, size):
53        """Create or process the persistent file.
54
55        If the file does not exist, it is generated.
56
57        If the file does exist, its content is hashed for later comparison.
58
59        These files are always located in the "persistent data directory" of
60        the current test run.
61
62        Args:
63            u_boot_console: A console connection to U-Boot.
64            fn: The filename (without path) to create.
65            size: The desired size of the file in bytes.
66
67        Returns:
68            Nothing.
69        """
70
71        self.fn = fn
72
73        self.abs_fn = u_boot_console.config.persistent_data_dir + '/' + fn
74
75        if os.path.exists(self.abs_fn):
76            u_boot_console.log.action('Persistent data file ' + self.abs_fn +
77                ' already exists')
78            self.content_hash = md5sum_file(self.abs_fn)
79        else:
80            u_boot_console.log.action('Generating ' + self.abs_fn +
81                ' (random, persistent, %d bytes)' % size)
82            data = os.urandom(size)
83            with open(self.abs_fn, 'wb') as fh:
84                fh.write(data)
85            self.content_hash = md5sum_data(data)
86
87def attempt_to_open_file(fn):
88    """Attempt to open a file, without throwing exceptions.
89
90    Any errors (exceptions) that occur during the attempt to open the file
91    are ignored. This is useful in order to test whether a file (in
92    particular, a device node) exists and can be successfully opened, in order
93    to poll for e.g. USB enumeration completion.
94
95    Args:
96        fn: The filename to attempt to open.
97
98    Returns:
99        An open file handle to the file, or None if the file could not be
100            opened.
101    """
102
103    try:
104        return open(fn, 'rb')
105    except:
106        return None
107
108def wait_until_open_succeeds(fn):
109    """Poll until a file can be opened, or a timeout occurs.
110
111    Continually attempt to open a file, and return when this succeeds, or
112    raise an exception after a timeout.
113
114    Args:
115        fn: The filename to attempt to open.
116
117    Returns:
118        An open file handle to the file.
119    """
120
121    for i in xrange(100):
122        fh = attempt_to_open_file(fn)
123        if fh:
124            return fh
125        time.sleep(0.1)
126    raise Exception('File could not be opened')
127
128def wait_until_file_open_fails(fn, ignore_errors):
129    """Poll until a file cannot be opened, or a timeout occurs.
130
131    Continually attempt to open a file, and return when this fails, or
132    raise an exception after a timeout.
133
134    Args:
135        fn: The filename to attempt to open.
136        ignore_errors: Indicate whether to ignore timeout errors. If True, the
137            function will simply return if a timeout occurs, otherwise an
138            exception will be raised.
139
140    Returns:
141        Nothing.
142    """
143
144    for i in xrange(100):
145        fh = attempt_to_open_file(fn)
146        if not fh:
147            return
148        fh.close()
149        time.sleep(0.1)
150    if ignore_errors:
151        return
152    raise Exception('File can still be opened')
153
154def run_and_log(u_boot_console, cmd, ignore_errors=False):
155    """Run a command and log its output.
156
157    Args:
158        u_boot_console: A console connection to U-Boot.
159        cmd: The command to run, as an array of argv[].
160        ignore_errors: Indicate whether to ignore errors. If True, the function
161            will simply return if the command cannot be executed or exits with
162            an error code, otherwise an exception will be raised if such
163            problems occur.
164
165    Returns:
166        Nothing.
167    """
168
169    runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
170    runner.run(cmd, ignore_errors=ignore_errors)
171    runner.close()
172
173ram_base = None
174def find_ram_base(u_boot_console):
175    """Find the running U-Boot's RAM location.
176
177    Probe the running U-Boot to determine the address of the first bank
178    of RAM. This is useful for tests that test reading/writing RAM, or
179    load/save files that aren't associated with some standard address
180    typically represented in an environment variable such as
181    ${kernel_addr_r}. The value is cached so that it only needs to be
182    actively read once.
183
184    Args:
185        u_boot_console: A console connection to U-Boot.
186
187    Returns:
188        The address of U-Boot's first RAM bank, as an integer.
189    """
190
191    global ram_base
192    if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
193        pytest.skip('bdinfo command not supported')
194    if ram_base == -1:
195        pytest.skip('Previously failed to find RAM bank start')
196    if ram_base is not None:
197        return ram_base
198
199    with u_boot_console.log.section('find_ram_base'):
200        response = u_boot_console.run_command('bdinfo')
201        for l in response.split('\n'):
202            if '-> start' in l:
203                ram_base = int(l.split('=')[1].strip(), 16)
204                break
205        if ram_base is None:
206            ram_base = -1
207            raise Exception('Failed to find RAM bank start in `bdinfo`')
208
209    return ram_base
210