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