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