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