1# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. 2# 3# SPDX-License-Identifier: GPL-2.0 4 5# Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB 6# device enumeration on the host, executes dfu-util multiple times to test 7# various transfer sizes, many of which trigger USB driver edge cases, and 8# finally aborts the "dfu" command in U-Boot. 9 10import os 11import os.path 12import pytest 13import u_boot_utils 14 15''' 16Note: This test relies on: 17 18a) boardenv_* to contain configuration values to define which USB ports are 19available for testing. Without this, this test will be automatically skipped. 20For example: 21 22env__usb_dev_ports = ( 23 { 24 "tgt_usb_ctlr": "0", 25 "host_usb_dev_node": "/dev/usbdev-p2371-2180", 26 # This parameter is optional /if/ you only have a single board 27 # attached to your host at a time. 28 "host_usb_port_path": "3-13", 29 }, 30) 31 32env__dfu_configs = ( 33 # eMMC, partition 1 34 { 35 "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1", 36 "cmd_params": "mmc 0", 37 }, 38) 39b) udev rules to set permissions on devices nodes, so that sudo is not 40required. For example: 41 42ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666" 43 44(You may wish to change the group ID instead of setting the permissions wide 45open. All that matters is that the user ID running the test can access the 46device.) 47''' 48 49# The set of file sizes to test. These values trigger various edge-cases such 50# as one less than, equal to, and one greater than typical USB max packet 51# sizes, and similar boundary conditions. 52test_sizes = ( 53 64 - 1, 54 64, 55 64 + 1, 56 128 - 1, 57 128, 58 128 + 1, 59 960 - 1, 60 960, 61 960 + 1, 62 4096 - 1, 63 4096, 64 4096 + 1, 65 1024 * 1024 - 1, 66 1024 * 1024, 67 8 * 1024 * 1024, 68) 69 70first_usb_dev_port = None 71 72@pytest.mark.buildconfigspec('cmd_dfu') 73def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): 74 '''Test the "dfu" command; the host system must be able to enumerate a USB 75 device when "dfu" is running, various DFU transfers are tested, and the 76 USB device must disappear when "dfu" is aborted. 77 78 Args: 79 u_boot_console: A U-Boot console connection. 80 env__usb_dev_port: The single USB device-mode port specification on 81 which to run the test. See the file-level comment above for 82 details of the format. 83 env__dfu_config: The single DFU (memory region) configuration on which 84 to run the test. See the file-level comment above for details 85 of the format. 86 87 Returns: 88 Nothing. 89 ''' 90 91 def start_dfu(): 92 '''Start U-Boot's dfu shell command. 93 94 This also waits for the host-side USB enumeration process to complete. 95 96 Args: 97 None. 98 99 Returns: 100 Nothing. 101 ''' 102 103 u_boot_console.log.action( 104 'Starting long-running U-Boot dfu shell command') 105 106 cmd = 'setenv dfu_alt_info "%s"' % env__dfu_config['alt_info'] 107 u_boot_console.run_command(cmd) 108 109 cmd = 'dfu 0 ' + env__dfu_config['cmd_params'] 110 u_boot_console.run_command(cmd, wait_for_prompt=False) 111 u_boot_console.log.action('Waiting for DFU USB device to appear') 112 fh = u_boot_utils.wait_until_open_succeeds( 113 env__usb_dev_port['host_usb_dev_node']) 114 fh.close() 115 116 def stop_dfu(ignore_errors): 117 '''Stop U-Boot's dfu shell command from executing. 118 119 This also waits for the host-side USB de-enumeration process to 120 complete. 121 122 Args: 123 ignore_errors: Ignore any errors. This is useful if an error has 124 already been detected, and the code is performing best-effort 125 cleanup. In this case, we do not want to mask the original 126 error by "honoring" any new errors. 127 128 Returns: 129 Nothing. 130 ''' 131 132 try: 133 u_boot_console.log.action( 134 'Stopping long-running U-Boot dfu shell command') 135 u_boot_console.ctrlc() 136 u_boot_console.log.action( 137 'Waiting for DFU USB device to disappear') 138 u_boot_utils.wait_until_file_open_fails( 139 env__usb_dev_port['host_usb_dev_node'], ignore_errors) 140 except: 141 if not ignore_errors: 142 raise 143 144 def run_dfu_util(alt_setting, fn, up_dn_load_arg): 145 '''Invoke dfu-util on the host. 146 147 Args: 148 alt_setting: The DFU "alternate setting" identifier to interact 149 with. 150 fn: The host-side file name to transfer. 151 up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or 152 download operation should be performed. 153 154 Returns: 155 Nothing. 156 ''' 157 158 cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn] 159 if 'host_usb_port_path' in env__usb_dev_port: 160 cmd += ['-p', env__usb_dev_port['host_usb_port_path']] 161 u_boot_utils.run_and_log(u_boot_console, cmd) 162 u_boot_console.wait_for('Ctrl+C to exit ...') 163 164 def dfu_write(alt_setting, fn): 165 '''Write a file to the target board using DFU. 166 167 Args: 168 alt_setting: The DFU "alternate setting" identifier to interact 169 with. 170 fn: The host-side file name to transfer. 171 172 Returns: 173 Nothing. 174 ''' 175 176 run_dfu_util(alt_setting, fn, '-D') 177 178 def dfu_read(alt_setting, fn): 179 '''Read a file from the target board using DFU. 180 181 Args: 182 alt_setting: The DFU "alternate setting" identifier to interact 183 with. 184 fn: The host-side file name to transfer. 185 186 Returns: 187 Nothing. 188 ''' 189 190 # dfu-util fails reads/uploads if the host file already exists 191 if os.path.exists(fn): 192 os.remove(fn) 193 run_dfu_util(alt_setting, fn, '-U') 194 195 def dfu_write_read_check(size): 196 '''Test DFU transfers of a specific size of data 197 198 This function first writes data to the board then reads it back and 199 compares the written and read back data. Measures are taken to avoid 200 certain types of false positives. 201 202 Args: 203 size: The data size to test. 204 205 Returns: 206 Nothing. 207 ''' 208 209 test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 210 'dfu_%d.bin' % size, size) 211 readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin' 212 213 u_boot_console.log.action('Writing test data to DFU primary ' + 214 'altsetting') 215 dfu_write(0, test_f.abs_fn) 216 217 u_boot_console.log.action('Writing dummy data to DFU secondary ' + 218 'altsetting to clear DFU buffers') 219 dfu_write(1, dummy_f.abs_fn) 220 221 u_boot_console.log.action('Reading DFU primary altsetting for ' + 222 'comparison') 223 dfu_read(0, readback_fn) 224 225 u_boot_console.log.action('Comparing written and read data') 226 written_hash = test_f.content_hash 227 read_back_hash = u_boot_utils.md5sum_file(readback_fn, size) 228 assert(written_hash == read_back_hash) 229 230 # This test may be executed against multiple USB ports. The test takes a 231 # long time, so we don't want to do the whole thing each time. Instead, 232 # execute the full test on the first USB port, and perform a very limited 233 # test on other ports. In the limited case, we solely validate that the 234 # host PC can enumerate the U-Boot USB device. 235 global first_usb_dev_port 236 if not first_usb_dev_port: 237 first_usb_dev_port = env__usb_dev_port 238 if env__usb_dev_port == first_usb_dev_port: 239 sizes = test_sizes 240 else: 241 sizes = [] 242 243 dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console, 244 'dfu_dummy.bin', 1024) 245 246 ignore_cleanup_errors = True 247 try: 248 start_dfu() 249 250 u_boot_console.log.action( 251 'Overwriting DFU primary altsetting with dummy data') 252 dfu_write(0, dummy_f.abs_fn) 253 254 for size in sizes: 255 with u_boot_console.log.section("Data size %d" % size): 256 dfu_write_read_check(size) 257 # Make the status of each sub-test obvious. If the test didn't 258 # pass, an exception was thrown so this code isn't executed. 259 u_boot_console.log.status_pass('OK') 260 ignore_cleanup_errors = False 261 finally: 262 stop_dfu(ignore_cleanup_errors) 263