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