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 33f3a87f5bSLukasz Majewski# Optional entries (required only when "alt_id_test_file" and 34f3a87f5bSLukasz Majewski# "alt_id_dummy_file" are specified). 35f3a87f5bSLukasz Majewskitest_file_name = "/dfu_test.bin" 36f3a87f5bSLukasz Majewskidummy_file_name = "/dfu_dummy.bin" 37f3a87f5bSLukasz Majewski# Above files are used to generate proper "alt_info" entry 38f3a87f5bSLukasz Majewski"alt_info": "/%s ext4 0 2;/%s ext4 0 2" % (test_file_name, dummy_file_name), 39f3a87f5bSLukasz Majewski 40f5d196d0SStephen Warrenenv__dfu_configs = ( 41f5d196d0SStephen Warren # eMMC, partition 1 42f5d196d0SStephen Warren { 43d20e5e97SStephen Warren "fixture_id": "emmc", 44f5d196d0SStephen Warren "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1", 45f5d196d0SStephen Warren "cmd_params": "mmc 0", 4626db3a61SStephen Warren # This value is optional. 4726db3a61SStephen Warren # If present, it specified the set of transfer sizes tested. 4826db3a61SStephen Warren # If missing, a default list of sizes will be used, which covers 4926db3a61SStephen Warren # various useful corner cases. 5026db3a61SStephen Warren # Manually specifying test sizes is useful if you wish to test 4 DFU 5126db3a61SStephen Warren # configurations, but don't want to test every single transfer size 5226db3a61SStephen Warren # on each, to avoid bloating the overall time taken by testing. 5326db3a61SStephen Warren "test_sizes": (63, 64, 65), 548eb37524SLukasz Majewski # This value is optional. 558eb37524SLukasz Majewski # The name of the environment variable that the the dfu command reads 568eb37524SLukasz Majewski # alt info from. If unspecified, this defaults to dfu_alt_info, which is 578eb37524SLukasz Majewski # valid for most systems. Some systems use a different variable name. 588eb37524SLukasz Majewski # One example is the Odroid XU3, which automatically generates 598eb37524SLukasz Majewski # $dfu_alt_info, each time the dfu command is run, by concatenating 608eb37524SLukasz Majewski # $dfu_alt_boot and $dfu_alt_system. 618eb37524SLukasz Majewski "alt_info_env_name": "dfu_alt_system", 62f3a87f5bSLukasz Majewski # This value is optional. 63f3a87f5bSLukasz Majewski # For boards which require the "test file" alt setting number other than 64f3a87f5bSLukasz Majewski # default (0) it is possible to specify exact file name to be used as 65f3a87f5bSLukasz Majewski # this parameter. 66f3a87f5bSLukasz Majewski "alt_id_test_file": test_file_name, 67f3a87f5bSLukasz Majewski # This value is optional. 68f3a87f5bSLukasz Majewski # For boards which require the "dummy file" alt setting number other 69f3a87f5bSLukasz Majewski # than default (1) it is possible to specify exact file name to be used 70f3a87f5bSLukasz Majewski # as this parameter. 71f3a87f5bSLukasz Majewski "alt_id_dummy_file": dummy_file_name, 72f5d196d0SStephen Warren }, 73f5d196d0SStephen Warren) 74d20e5e97SStephen Warren 75f5d196d0SStephen Warrenb) udev rules to set permissions on devices nodes, so that sudo is not 76f5d196d0SStephen Warrenrequired. For example: 77f5d196d0SStephen Warren 78f5d196d0SStephen WarrenACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666" 79f5d196d0SStephen Warren 80f5d196d0SStephen Warren(You may wish to change the group ID instead of setting the permissions wide 81f5d196d0SStephen Warrenopen. All that matters is that the user ID running the test can access the 82f5d196d0SStephen Warrendevice.) 83e8debf39SStephen Warren""" 84f5d196d0SStephen Warren 85f5d196d0SStephen Warren# The set of file sizes to test. These values trigger various edge-cases such 86f5d196d0SStephen Warren# as one less than, equal to, and one greater than typical USB max packet 87f5d196d0SStephen Warren# sizes, and similar boundary conditions. 8826db3a61SStephen Warrentest_sizes_default = ( 89f5d196d0SStephen Warren 64 - 1, 90f5d196d0SStephen Warren 64, 91f5d196d0SStephen Warren 64 + 1, 92f5d196d0SStephen Warren 128 - 1, 93f5d196d0SStephen Warren 128, 94f5d196d0SStephen Warren 128 + 1, 95f5d196d0SStephen Warren 960 - 1, 96f5d196d0SStephen Warren 960, 97f5d196d0SStephen Warren 960 + 1, 98f5d196d0SStephen Warren 4096 - 1, 99f5d196d0SStephen Warren 4096, 100f5d196d0SStephen Warren 4096 + 1, 101f5d196d0SStephen Warren 1024 * 1024 - 1, 102f5d196d0SStephen Warren 1024 * 1024, 103f5d196d0SStephen Warren 8 * 1024 * 1024, 104f5d196d0SStephen Warren) 105f5d196d0SStephen Warren 106f5d196d0SStephen Warrenfirst_usb_dev_port = None 107f5d196d0SStephen Warren 108f5d196d0SStephen Warren@pytest.mark.buildconfigspec('cmd_dfu') 109f5d196d0SStephen Warrendef test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): 110e8debf39SStephen Warren """Test the "dfu" command; the host system must be able to enumerate a USB 111f5d196d0SStephen Warren device when "dfu" is running, various DFU transfers are tested, and the 112f5d196d0SStephen Warren USB device must disappear when "dfu" is aborted. 113f5d196d0SStephen Warren 114f5d196d0SStephen Warren Args: 115f5d196d0SStephen Warren u_boot_console: A U-Boot console connection. 116f5d196d0SStephen Warren env__usb_dev_port: The single USB device-mode port specification on 117f5d196d0SStephen Warren which to run the test. See the file-level comment above for 118f5d196d0SStephen Warren details of the format. 119f5d196d0SStephen Warren env__dfu_config: The single DFU (memory region) configuration on which 120f5d196d0SStephen Warren to run the test. See the file-level comment above for details 121f5d196d0SStephen Warren of the format. 122f5d196d0SStephen Warren 123f5d196d0SStephen Warren Returns: 124f5d196d0SStephen Warren Nothing. 125e8debf39SStephen Warren """ 126f5d196d0SStephen Warren 127f5d196d0SStephen Warren def start_dfu(): 128e8debf39SStephen Warren """Start U-Boot's dfu shell command. 129f5d196d0SStephen Warren 130f5d196d0SStephen Warren This also waits for the host-side USB enumeration process to complete. 131f5d196d0SStephen Warren 132f5d196d0SStephen Warren Args: 133f5d196d0SStephen Warren None. 134f5d196d0SStephen Warren 135f5d196d0SStephen Warren Returns: 136f5d196d0SStephen Warren Nothing. 137e8debf39SStephen Warren """ 138f5d196d0SStephen Warren 139*daa69f5fSStephen Warren u_boot_utils.wait_until_file_open_fails( 140*daa69f5fSStephen Warren env__usb_dev_port['host_usb_dev_node'], True) 141be1df826SStephen Warren fh = u_boot_utils.attempt_to_open_file( 142be1df826SStephen Warren env__usb_dev_port['host_usb_dev_node']) 143be1df826SStephen Warren if fh: 144be1df826SStephen Warren fh.close() 145be1df826SStephen Warren raise Exception('USB device present before dfu command invoked') 146be1df826SStephen Warren 147f5d196d0SStephen Warren u_boot_console.log.action( 148f5d196d0SStephen Warren 'Starting long-running U-Boot dfu shell command') 149f5d196d0SStephen Warren 1508eb37524SLukasz Majewski dfu_alt_info_env = env__dfu_config.get('alt_info_env_name', \ 1518eb37524SLukasz Majewski 'dfu_alt_info') 1528eb37524SLukasz Majewski 1538eb37524SLukasz Majewski cmd = 'setenv "%s" "%s"' % (dfu_alt_info_env, 1548eb37524SLukasz Majewski env__dfu_config['alt_info']) 155f5d196d0SStephen Warren u_boot_console.run_command(cmd) 156f5d196d0SStephen Warren 157f5d196d0SStephen Warren cmd = 'dfu 0 ' + env__dfu_config['cmd_params'] 158f5d196d0SStephen Warren u_boot_console.run_command(cmd, wait_for_prompt=False) 159f5d196d0SStephen Warren u_boot_console.log.action('Waiting for DFU USB device to appear') 160f5d196d0SStephen Warren fh = u_boot_utils.wait_until_open_succeeds( 161f5d196d0SStephen Warren env__usb_dev_port['host_usb_dev_node']) 162f5d196d0SStephen Warren fh.close() 163f5d196d0SStephen Warren 164f5d196d0SStephen Warren def stop_dfu(ignore_errors): 165e8debf39SStephen Warren """Stop U-Boot's dfu shell command from executing. 166f5d196d0SStephen Warren 167f5d196d0SStephen Warren This also waits for the host-side USB de-enumeration process to 168f5d196d0SStephen Warren complete. 169f5d196d0SStephen Warren 170f5d196d0SStephen Warren Args: 171f5d196d0SStephen Warren ignore_errors: Ignore any errors. This is useful if an error has 172f5d196d0SStephen Warren already been detected, and the code is performing best-effort 173f5d196d0SStephen Warren cleanup. In this case, we do not want to mask the original 174f5d196d0SStephen Warren error by "honoring" any new errors. 175f5d196d0SStephen Warren 176f5d196d0SStephen Warren Returns: 177f5d196d0SStephen Warren Nothing. 178e8debf39SStephen Warren """ 179f5d196d0SStephen Warren 180f5d196d0SStephen Warren try: 181f5d196d0SStephen Warren u_boot_console.log.action( 182f5d196d0SStephen Warren 'Stopping long-running U-Boot dfu shell command') 183f5d196d0SStephen Warren u_boot_console.ctrlc() 184f5d196d0SStephen Warren u_boot_console.log.action( 185f5d196d0SStephen Warren 'Waiting for DFU USB device to disappear') 186f5d196d0SStephen Warren u_boot_utils.wait_until_file_open_fails( 187f5d196d0SStephen Warren env__usb_dev_port['host_usb_dev_node'], ignore_errors) 188f5d196d0SStephen Warren except: 189f5d196d0SStephen Warren if not ignore_errors: 190f5d196d0SStephen Warren raise 191f5d196d0SStephen Warren 192f5d196d0SStephen Warren def run_dfu_util(alt_setting, fn, up_dn_load_arg): 193e8debf39SStephen Warren """Invoke dfu-util on the host. 194f5d196d0SStephen Warren 195f5d196d0SStephen Warren Args: 196f5d196d0SStephen Warren alt_setting: The DFU "alternate setting" identifier to interact 197f5d196d0SStephen Warren with. 198f5d196d0SStephen Warren fn: The host-side file name to transfer. 199f5d196d0SStephen Warren up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or 200f5d196d0SStephen Warren download operation should be performed. 201f5d196d0SStephen Warren 202f5d196d0SStephen Warren Returns: 203f5d196d0SStephen Warren Nothing. 204e8debf39SStephen Warren """ 205f5d196d0SStephen Warren 206f3a87f5bSLukasz Majewski cmd = ['dfu-util', '-a', alt_setting, up_dn_load_arg, fn] 207f5d196d0SStephen Warren if 'host_usb_port_path' in env__usb_dev_port: 208f5d196d0SStephen Warren cmd += ['-p', env__usb_dev_port['host_usb_port_path']] 209f5d196d0SStephen Warren u_boot_utils.run_and_log(u_boot_console, cmd) 210f5d196d0SStephen Warren u_boot_console.wait_for('Ctrl+C to exit ...') 211f5d196d0SStephen Warren 212f5d196d0SStephen Warren def dfu_write(alt_setting, fn): 213e8debf39SStephen Warren """Write a file to the target board using DFU. 214f5d196d0SStephen Warren 215f5d196d0SStephen Warren Args: 216f5d196d0SStephen Warren alt_setting: The DFU "alternate setting" identifier to interact 217f5d196d0SStephen Warren with. 218f5d196d0SStephen Warren fn: The host-side file name to transfer. 219f5d196d0SStephen Warren 220f5d196d0SStephen Warren Returns: 221f5d196d0SStephen Warren Nothing. 222e8debf39SStephen Warren """ 223f5d196d0SStephen Warren 224f5d196d0SStephen Warren run_dfu_util(alt_setting, fn, '-D') 225f5d196d0SStephen Warren 226f5d196d0SStephen Warren def dfu_read(alt_setting, fn): 227e8debf39SStephen Warren """Read a file from the target board using DFU. 228f5d196d0SStephen Warren 229f5d196d0SStephen Warren Args: 230f5d196d0SStephen Warren alt_setting: The DFU "alternate setting" identifier to interact 231f5d196d0SStephen Warren with. 232f5d196d0SStephen Warren fn: The host-side file name to transfer. 233f5d196d0SStephen Warren 234f5d196d0SStephen Warren Returns: 235f5d196d0SStephen Warren Nothing. 236e8debf39SStephen Warren """ 237f5d196d0SStephen Warren 238f5d196d0SStephen Warren # dfu-util fails reads/uploads if the host file already exists 239f5d196d0SStephen Warren if os.path.exists(fn): 240f5d196d0SStephen Warren os.remove(fn) 241f5d196d0SStephen Warren run_dfu_util(alt_setting, fn, '-U') 242f5d196d0SStephen Warren 243f5d196d0SStephen Warren def dfu_write_read_check(size): 244e8debf39SStephen Warren """Test DFU transfers of a specific size of data 245f5d196d0SStephen Warren 246f5d196d0SStephen Warren This function first writes data to the board then reads it back and 247f5d196d0SStephen Warren compares the written and read back data. Measures are taken to avoid 248f5d196d0SStephen Warren certain types of false positives. 249f5d196d0SStephen Warren 250f5d196d0SStephen Warren Args: 251f5d196d0SStephen Warren size: The data size to test. 252f5d196d0SStephen Warren 253f5d196d0SStephen Warren Returns: 254f5d196d0SStephen Warren Nothing. 255e8debf39SStephen Warren """ 256f5d196d0SStephen Warren 257f5d196d0SStephen Warren test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 258f5d196d0SStephen Warren 'dfu_%d.bin' % size, size) 259f5d196d0SStephen Warren readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin' 260f5d196d0SStephen Warren 261f5d196d0SStephen Warren u_boot_console.log.action('Writing test data to DFU primary ' + 262f5d196d0SStephen Warren 'altsetting') 263c6eb899cSLukasz Majewski dfu_write(alt_setting_test_file, test_f.abs_fn) 264f5d196d0SStephen Warren 265f5d196d0SStephen Warren u_boot_console.log.action('Writing dummy data to DFU secondary ' + 266f5d196d0SStephen Warren 'altsetting to clear DFU buffers') 267c6eb899cSLukasz Majewski dfu_write(alt_setting_dummy_file, dummy_f.abs_fn) 268f5d196d0SStephen Warren 269f5d196d0SStephen Warren u_boot_console.log.action('Reading DFU primary altsetting for ' + 270f5d196d0SStephen Warren 'comparison') 271c6eb899cSLukasz Majewski dfu_read(alt_setting_test_file, readback_fn) 272f5d196d0SStephen Warren 273f5d196d0SStephen Warren u_boot_console.log.action('Comparing written and read data') 274f5d196d0SStephen Warren written_hash = test_f.content_hash 275f5d196d0SStephen Warren read_back_hash = u_boot_utils.md5sum_file(readback_fn, size) 276f5d196d0SStephen Warren assert(written_hash == read_back_hash) 277f5d196d0SStephen Warren 278f5d196d0SStephen Warren # This test may be executed against multiple USB ports. The test takes a 279f5d196d0SStephen Warren # long time, so we don't want to do the whole thing each time. Instead, 280f5d196d0SStephen Warren # execute the full test on the first USB port, and perform a very limited 281f5d196d0SStephen Warren # test on other ports. In the limited case, we solely validate that the 282f5d196d0SStephen Warren # host PC can enumerate the U-Boot USB device. 283f5d196d0SStephen Warren global first_usb_dev_port 284f5d196d0SStephen Warren if not first_usb_dev_port: 285f5d196d0SStephen Warren first_usb_dev_port = env__usb_dev_port 286f5d196d0SStephen Warren if env__usb_dev_port == first_usb_dev_port: 28726db3a61SStephen Warren sizes = env__dfu_config.get('test_sizes', test_sizes_default) 288f5d196d0SStephen Warren else: 289f5d196d0SStephen Warren sizes = [] 290f5d196d0SStephen Warren 291f5d196d0SStephen Warren dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console, 292f5d196d0SStephen Warren 'dfu_dummy.bin', 1024) 293f5d196d0SStephen Warren 294f3a87f5bSLukasz Majewski alt_setting_test_file = env__dfu_config.get('alt_id_test_file', '0') 295f3a87f5bSLukasz Majewski alt_setting_dummy_file = env__dfu_config.get('alt_id_dummy_file', '1') 296f3a87f5bSLukasz Majewski 297f5d196d0SStephen Warren ignore_cleanup_errors = True 298f5d196d0SStephen Warren try: 299f5d196d0SStephen Warren start_dfu() 300f5d196d0SStephen Warren 301f5d196d0SStephen Warren u_boot_console.log.action( 302f5d196d0SStephen Warren 'Overwriting DFU primary altsetting with dummy data') 303c6eb899cSLukasz Majewski dfu_write(alt_setting_test_file, dummy_f.abs_fn) 304f5d196d0SStephen Warren 305f5d196d0SStephen Warren for size in sizes: 306a2ec5606SStephen Warren with u_boot_console.log.section('Data size %d' % size): 307f5d196d0SStephen Warren dfu_write_read_check(size) 308f5d196d0SStephen Warren # Make the status of each sub-test obvious. If the test didn't 309f5d196d0SStephen Warren # pass, an exception was thrown so this code isn't executed. 310f5d196d0SStephen Warren u_boot_console.log.status_pass('OK') 311f5d196d0SStephen Warren ignore_cleanup_errors = False 312f5d196d0SStephen Warren finally: 313f5d196d0SStephen Warren stop_dfu(ignore_cleanup_errors) 314