1*4882a593Smuzhiyun# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0 4*4882a593Smuzhiyun 5*4882a593Smuzhiyun# Test U-Boot's "ums" command. The test starts UMS in U-Boot, waits for USB 6*4882a593Smuzhiyun# device enumeration on the host, reads a small block of data from the UMS 7*4882a593Smuzhiyun# block device, optionally mounts a partition and performs filesystem-based 8*4882a593Smuzhiyun# read/write tests, and finally aborts the "ums" command in U-Boot. 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport os 11*4882a593Smuzhiyunimport os.path 12*4882a593Smuzhiyunimport pytest 13*4882a593Smuzhiyunimport re 14*4882a593Smuzhiyunimport time 15*4882a593Smuzhiyunimport u_boot_utils 16*4882a593Smuzhiyun 17*4882a593Smuzhiyun""" 18*4882a593SmuzhiyunNote: This test relies on: 19*4882a593Smuzhiyun 20*4882a593Smuzhiyuna) boardenv_* to contain configuration values to define which USB ports are 21*4882a593Smuzhiyunavailable for testing. Without this, this test will be automatically skipped. 22*4882a593SmuzhiyunFor example: 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun# Leave this list empty if you have no block_devs below with writable 25*4882a593Smuzhiyun# partitions defined. 26*4882a593Smuzhiyunenv__mount_points = ( 27*4882a593Smuzhiyun "/mnt/ubtest-mnt-p2371-2180-na", 28*4882a593Smuzhiyun) 29*4882a593Smuzhiyun 30*4882a593Smuzhiyunenv__usb_dev_ports = ( 31*4882a593Smuzhiyun { 32*4882a593Smuzhiyun "fixture_id": "micro_b", 33*4882a593Smuzhiyun "tgt_usb_ctlr": "0", 34*4882a593Smuzhiyun "host_ums_dev_node": "/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0", 35*4882a593Smuzhiyun }, 36*4882a593Smuzhiyun) 37*4882a593Smuzhiyun 38*4882a593Smuzhiyunenv__block_devs = ( 39*4882a593Smuzhiyun # eMMC; always present 40*4882a593Smuzhiyun { 41*4882a593Smuzhiyun "fixture_id": "emmc", 42*4882a593Smuzhiyun "type": "mmc", 43*4882a593Smuzhiyun "id": "0", 44*4882a593Smuzhiyun # The following two properties are optional. 45*4882a593Smuzhiyun # If present, the partition will be mounted and a file written-to and 46*4882a593Smuzhiyun # read-from it. If missing, only a simple block read test will be 47*4882a593Smuzhiyun # performed. 48*4882a593Smuzhiyun "writable_fs_partition": 1, 49*4882a593Smuzhiyun "writable_fs_subdir": "tmp/", 50*4882a593Smuzhiyun }, 51*4882a593Smuzhiyun # SD card; present since I plugged one in 52*4882a593Smuzhiyun { 53*4882a593Smuzhiyun "fixture_id": "sd", 54*4882a593Smuzhiyun "type": "mmc", 55*4882a593Smuzhiyun "id": "1" 56*4882a593Smuzhiyun }, 57*4882a593Smuzhiyun) 58*4882a593Smuzhiyun 59*4882a593Smuzhiyunb) udev rules to set permissions on devices nodes, so that sudo is not 60*4882a593Smuzhiyunrequired. For example: 61*4882a593Smuzhiyun 62*4882a593SmuzhiyunACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666" 63*4882a593Smuzhiyun 64*4882a593Smuzhiyun(You may wish to change the group ID instead of setting the permissions wide 65*4882a593Smuzhiyunopen. All that matters is that the user ID running the test can access the 66*4882a593Smuzhiyundevice.) 67*4882a593Smuzhiyun 68*4882a593Smuzhiyunc) /etc/fstab entries to allow the block device to be mounted without requiring 69*4882a593Smuzhiyunroot permissions. For example: 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0-part1 /mnt/ubtest-mnt-p2371-2180-na ext4 noauto,user,nosuid,nodev 72*4882a593Smuzhiyun 73*4882a593SmuzhiyunThis entry is only needed if any block_devs above contain a 74*4882a593Smuzhiyunwritable_fs_partition value. 75*4882a593Smuzhiyun""" 76*4882a593Smuzhiyun 77*4882a593Smuzhiyun@pytest.mark.buildconfigspec('cmd_usb_mass_storage') 78*4882a593Smuzhiyundef test_ums(u_boot_console, env__usb_dev_port, env__block_devs): 79*4882a593Smuzhiyun """Test the "ums" command; the host system must be able to enumerate a UMS 80*4882a593Smuzhiyun device when "ums" is running, block and optionally file I/O are tested, 81*4882a593Smuzhiyun and this device must disappear when "ums" is aborted. 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun Args: 84*4882a593Smuzhiyun u_boot_console: A U-Boot console connection. 85*4882a593Smuzhiyun env__usb_dev_port: The single USB device-mode port specification on 86*4882a593Smuzhiyun which to run the test. See the file-level comment above for 87*4882a593Smuzhiyun details of the format. 88*4882a593Smuzhiyun env__block_devs: The list of block devices that the target U-Boot 89*4882a593Smuzhiyun device has attached. See the file-level comment above for details 90*4882a593Smuzhiyun of the format. 91*4882a593Smuzhiyun 92*4882a593Smuzhiyun Returns: 93*4882a593Smuzhiyun Nothing. 94*4882a593Smuzhiyun """ 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0] 97*4882a593Smuzhiyun if not have_writable_fs_partition: 98*4882a593Smuzhiyun # If 'writable_fs_subdir' is missing, we'll skip all parts of the 99*4882a593Smuzhiyun # testing which mount filesystems. 100*4882a593Smuzhiyun u_boot_console.log.warning( 101*4882a593Smuzhiyun 'boardenv missing "writable_fs_partition"; ' + 102*4882a593Smuzhiyun 'UMS testing will be limited.') 103*4882a593Smuzhiyun 104*4882a593Smuzhiyun tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr'] 105*4882a593Smuzhiyun host_ums_dev_node = env__usb_dev_port['host_ums_dev_node'] 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun # We're interested in testing USB device mode on each port, not the cross- 108*4882a593Smuzhiyun # product of that with each device. So, just pick the first entry in the 109*4882a593Smuzhiyun # device list here. We'll test each block device somewhere else. 110*4882a593Smuzhiyun tgt_dev_type = env__block_devs[0]['type'] 111*4882a593Smuzhiyun tgt_dev_id = env__block_devs[0]['id'] 112*4882a593Smuzhiyun if have_writable_fs_partition: 113*4882a593Smuzhiyun mount_point = u_boot_console.config.env['env__mount_points'][0] 114*4882a593Smuzhiyun mount_subdir = env__block_devs[0]['writable_fs_subdir'] 115*4882a593Smuzhiyun part_num = env__block_devs[0]['writable_fs_partition'] 116*4882a593Smuzhiyun host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num) 117*4882a593Smuzhiyun else: 118*4882a593Smuzhiyun host_ums_part_node = host_ums_dev_node 119*4882a593Smuzhiyun 120*4882a593Smuzhiyun test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin', 121*4882a593Smuzhiyun 1024 * 1024); 122*4882a593Smuzhiyun if have_writable_fs_partition: 123*4882a593Smuzhiyun mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun def start_ums(): 126*4882a593Smuzhiyun """Start U-Boot's ums shell command. 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun This also waits for the host-side USB enumeration process to complete. 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun Args: 131*4882a593Smuzhiyun None. 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun Returns: 134*4882a593Smuzhiyun Nothing. 135*4882a593Smuzhiyun """ 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun u_boot_console.log.action( 138*4882a593Smuzhiyun 'Starting long-running U-Boot ums shell command') 139*4882a593Smuzhiyun cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) 140*4882a593Smuzhiyun u_boot_console.run_command(cmd, wait_for_prompt=False) 141*4882a593Smuzhiyun u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]')) 142*4882a593Smuzhiyun fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node) 143*4882a593Smuzhiyun u_boot_console.log.action('Reading raw data from UMS device') 144*4882a593Smuzhiyun fh.read(4096) 145*4882a593Smuzhiyun fh.close() 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun def mount(): 148*4882a593Smuzhiyun """Mount the block device that U-Boot exports. 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun Args: 151*4882a593Smuzhiyun None. 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun Returns: 154*4882a593Smuzhiyun Nothing. 155*4882a593Smuzhiyun """ 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun u_boot_console.log.action('Mounting exported UMS device') 158*4882a593Smuzhiyun cmd = ('/bin/mount', host_ums_part_node) 159*4882a593Smuzhiyun u_boot_utils.run_and_log(u_boot_console, cmd) 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun def umount(ignore_errors): 162*4882a593Smuzhiyun """Unmount the block device that U-Boot exports. 163*4882a593Smuzhiyun 164*4882a593Smuzhiyun Args: 165*4882a593Smuzhiyun ignore_errors: Ignore any errors. This is useful if an error has 166*4882a593Smuzhiyun already been detected, and the code is performing best-effort 167*4882a593Smuzhiyun cleanup. In this case, we do not want to mask the original 168*4882a593Smuzhiyun error by "honoring" any new errors. 169*4882a593Smuzhiyun 170*4882a593Smuzhiyun Returns: 171*4882a593Smuzhiyun Nothing. 172*4882a593Smuzhiyun """ 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun u_boot_console.log.action('Unmounting UMS device') 175*4882a593Smuzhiyun cmd = ('/bin/umount', host_ums_part_node) 176*4882a593Smuzhiyun u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors) 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun def stop_ums(ignore_errors): 179*4882a593Smuzhiyun """Stop U-Boot's ums shell command from executing. 180*4882a593Smuzhiyun 181*4882a593Smuzhiyun This also waits for the host-side USB de-enumeration process to 182*4882a593Smuzhiyun complete. 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun Args: 185*4882a593Smuzhiyun ignore_errors: Ignore any errors. This is useful if an error has 186*4882a593Smuzhiyun already been detected, and the code is performing best-effort 187*4882a593Smuzhiyun cleanup. In this case, we do not want to mask the original 188*4882a593Smuzhiyun error by "honoring" any new errors. 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun Returns: 191*4882a593Smuzhiyun Nothing. 192*4882a593Smuzhiyun """ 193*4882a593Smuzhiyun 194*4882a593Smuzhiyun u_boot_console.log.action( 195*4882a593Smuzhiyun 'Stopping long-running U-Boot ums shell command') 196*4882a593Smuzhiyun u_boot_console.ctrlc() 197*4882a593Smuzhiyun u_boot_utils.wait_until_file_open_fails(host_ums_part_node, 198*4882a593Smuzhiyun ignore_errors) 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun ignore_cleanup_errors = True 201*4882a593Smuzhiyun try: 202*4882a593Smuzhiyun start_ums() 203*4882a593Smuzhiyun if not have_writable_fs_partition: 204*4882a593Smuzhiyun # Skip filesystem-based testing if not configured 205*4882a593Smuzhiyun return 206*4882a593Smuzhiyun try: 207*4882a593Smuzhiyun mount() 208*4882a593Smuzhiyun u_boot_console.log.action('Writing test file via UMS') 209*4882a593Smuzhiyun cmd = ('rm', '-f', mounted_test_fn) 210*4882a593Smuzhiyun u_boot_utils.run_and_log(u_boot_console, cmd) 211*4882a593Smuzhiyun if os.path.exists(mounted_test_fn): 212*4882a593Smuzhiyun raise Exception('Could not rm target UMS test file') 213*4882a593Smuzhiyun cmd = ('cp', test_f.abs_fn, mounted_test_fn) 214*4882a593Smuzhiyun u_boot_utils.run_and_log(u_boot_console, cmd) 215*4882a593Smuzhiyun ignore_cleanup_errors = False 216*4882a593Smuzhiyun finally: 217*4882a593Smuzhiyun umount(ignore_errors=ignore_cleanup_errors) 218*4882a593Smuzhiyun finally: 219*4882a593Smuzhiyun stop_ums(ignore_errors=ignore_cleanup_errors) 220*4882a593Smuzhiyun 221*4882a593Smuzhiyun ignore_cleanup_errors = True 222*4882a593Smuzhiyun try: 223*4882a593Smuzhiyun start_ums() 224*4882a593Smuzhiyun try: 225*4882a593Smuzhiyun mount() 226*4882a593Smuzhiyun u_boot_console.log.action('Reading test file back via UMS') 227*4882a593Smuzhiyun read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn) 228*4882a593Smuzhiyun cmd = ('rm', '-f', mounted_test_fn) 229*4882a593Smuzhiyun u_boot_utils.run_and_log(u_boot_console, cmd) 230*4882a593Smuzhiyun ignore_cleanup_errors = False 231*4882a593Smuzhiyun finally: 232*4882a593Smuzhiyun umount(ignore_errors=ignore_cleanup_errors) 233*4882a593Smuzhiyun finally: 234*4882a593Smuzhiyun stop_ums(ignore_errors=ignore_cleanup_errors) 235*4882a593Smuzhiyun 236*4882a593Smuzhiyun written_hash = test_f.content_hash 237*4882a593Smuzhiyun assert(written_hash == read_back_hash) 238