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