diff --git a/test/py/tests/test_ums.py b/test/py/tests/test_ums.py index a137221c7a5b04d27a5c87f14795ee1177492306..f482cfeca1b144b4985745455fccd6aa69c7cf6c 100644 --- a/test/py/tests/test_ums.py +++ b/test/py/tests/test_ums.py @@ -2,13 +2,17 @@ # # SPDX-License-Identifier: GPL-2.0 -# Test U-Boot's "ums" command. At present, this test only ensures that a UMS -# device can be enumerated by the host/test machine. In the future, this test -# should be enhanced to validate disk IO. +# Test U-Boot's "ums" command. The test starts UMS in U-Boot, waits for USB +# device enumeration on the host, reads a small block of data from the UMS +# block device, optionally mounts a partition and performs filesystem-based +# read/write tests, and finally aborts the "ums" command in U-Boot. import os +import os.path import pytest +import re import time +import u_boot_utils ''' Note: This test relies on: @@ -17,13 +21,36 @@ a) boardenv_* to contain configuration values to define which USB ports are available for testing. Without this, this test will be automatically skipped. For example: +# Leave this list empty if you have no block_devs below with writable +# partitions defined. +env__mount_points = ( + "/mnt/ubtest-mnt-p2371-2180-na", +) + env__usb_dev_ports = ( - {'tgt_usb_ctlr': '0', 'host_ums_dev_node': '/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0'}, + { + "tgt_usb_ctlr": "0", + "host_ums_dev_node": "/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0", + }, ) env__block_devs = ( - {'type': 'mmc', 'id': '0'}, # eMMC; always present - {'type': 'mmc', 'id': '1'}, # SD card; present since I plugged one in + # eMMC; always present + { + "type": "mmc", + "id": "0", + # The following two properties are optional. + # If present, the partition will be mounted and a file written-to and + # read-from it. If missing, only a simple block read test will be + # performed. + "writable_fs_partition": 1, + "writable_fs_subdir": "tmp/", + }, + # SD card; present since I plugged one in + { + "type": "mmc", + "id": "1" + }, ) b) udev rules to set permissions on devices nodes, so that sudo is not @@ -34,47 +61,42 @@ ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="66 (You may wish to change the group ID instead of setting the permissions wide open. All that matters is that the user ID running the test can access the device.) -''' -def open_ums_device(host_ums_dev_node): - '''Attempt to open a device node, returning either the opened file handle, - or None on any error.''' +c) /etc/fstab entries to allow the block device to be mounted without requiring +root permissions. For example: - try: - return open(host_ums_dev_node, 'rb') - except: - return None - -def wait_for_ums_device(host_ums_dev_node): - '''Continually attempt to open the device node exported by the "ums" - command, and either return the opened file handle, or raise an exception - after a timeout.''' - - for i in xrange(100): - fh = open_ums_device(host_ums_dev_node) - if fh: - return fh - time.sleep(0.1) - raise Exception('UMS device did not appear') - -def wait_for_ums_device_gone(host_ums_dev_node): - '''Continually attempt to open the device node exported by the "ums" - command, and either return once the device has disappeared, or raise an - exception if it does not before a timeout occurs.''' - - for i in xrange(100): - fh = open_ums_device(host_ums_dev_node) - if not fh: - return - fh.close() - time.sleep(0.1) - raise Exception('UMS device did not disappear') +/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 + +This entry is only needed if any block_devs above contain a +writable_fs_partition value. +''' @pytest.mark.buildconfigspec('cmd_usb_mass_storage') def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): '''Test the "ums" command; the host system must be able to enumerate a UMS - device when "ums" is running, and this device must disappear when "ums" is - aborted.''' + device when "ums" is running, block and optionally file I/O are tested, + and this device must disappear when "ums" is aborted. + + Args: + u_boot_console: A U-Boot console connection. + env__usb_dev_port: The single USB device-mode port specification on + which to run the test. See the file-level comment above for + details of the format. + env__block_devs: The list of block devices that the target U-Boot + device has attached. See the file-level comment above for details + of the format. + + Returns: + Nothing. + ''' + + have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0] + if not have_writable_fs_partition: + # If 'writable_fs_subdir' is missing, we'll skip all parts of the + # testing which mount filesystems. + u_boot_console.log.warning( + 'boardenv missing "writable_fs_partition"; ' + + 'UMS testing will be limited.') tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr'] host_ums_dev_node = env__usb_dev_port['host_ums_dev_node'] @@ -84,11 +106,129 @@ def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): # device list here. We'll test each block device somewhere else. tgt_dev_type = env__block_devs[0]['type'] tgt_dev_id = env__block_devs[0]['id'] + if have_writable_fs_partition: + mount_point = u_boot_console.config.env['env__mount_points'][0] + mount_subdir = env__block_devs[0]['writable_fs_subdir'] + part_num = env__block_devs[0]['writable_fs_partition'] + host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num) + else: + host_ums_part_node = host_ums_dev_node + + test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin', + 1024 * 1024); + if have_writable_fs_partition: + mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn + + def start_ums(): + '''Start U-Boot's ums shell command. + + This also waits for the host-side USB enumeration process to complete. + + Args: + None. + + Returns: + Nothing. + ''' + + u_boot_console.log.action( + 'Starting long-running U-Boot ums shell command') + cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) + u_boot_console.run_command(cmd, wait_for_prompt=False) + u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]')) + fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node) + u_boot_console.log.action('Reading raw data from UMS device') + fh.read(4096) + fh.close() + + def mount(): + '''Mount the block device that U-Boot exports. + + Args: + None. + + Returns: + Nothing. + ''' + + u_boot_console.log.action('Mounting exported UMS device') + cmd = ('/bin/mount', host_ums_part_node) + u_boot_utils.run_and_log(u_boot_console, cmd) - cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) - u_boot_console.run_command('ums 0 mmc 0', wait_for_prompt=False) - fh = wait_for_ums_device(host_ums_dev_node) - fh.read(4096) - fh.close() - u_boot_console.ctrlc() - wait_for_ums_device_gone(host_ums_dev_node) + def umount(ignore_errors): + '''Unmount the block device that U-Boot exports. + + Args: + ignore_errors: Ignore any errors. This is useful if an error has + already been detected, and the code is performing best-effort + cleanup. In this case, we do not want to mask the original + error by "honoring" any new errors. + + Returns: + Nothing. + ''' + + u_boot_console.log.action('Unmounting UMS device') + cmd = ('/bin/umount', host_ums_part_node) + u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors) + + def stop_ums(ignore_errors): + '''Stop U-Boot's ums shell command from executing. + + This also waits for the host-side USB de-enumeration process to + complete. + + Args: + ignore_errors: Ignore any errors. This is useful if an error has + already been detected, and the code is performing best-effort + cleanup. In this case, we do not want to mask the original + error by "honoring" any new errors. + + Returns: + Nothing. + ''' + + u_boot_console.log.action( + 'Stopping long-running U-Boot ums shell command') + u_boot_console.ctrlc() + u_boot_utils.wait_until_file_open_fails(host_ums_part_node, + ignore_errors) + + ignore_cleanup_errors = True + try: + start_ums() + if not have_writable_fs_partition: + # Skip filesystem-based testing if not configured + return + try: + mount() + u_boot_console.log.action('Writing test file via UMS') + cmd = ('rm', '-f', mounted_test_fn) + u_boot_utils.run_and_log(u_boot_console, cmd) + if os.path.exists(mounted_test_fn): + raise Exception('Could not rm target UMS test file') + cmd = ('cp', test_f.abs_fn, mounted_test_fn) + u_boot_utils.run_and_log(u_boot_console, cmd) + ignore_cleanup_errors = False + finally: + umount(ignore_errors=ignore_cleanup_errors) + finally: + stop_ums(ignore_errors=ignore_cleanup_errors) + + ignore_cleanup_errors = True + try: + start_ums() + try: + mount() + u_boot_console.log.action('Reading test file back via UMS') + read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn) + cmd = ('rm', '-f', mounted_test_fn) + u_boot_utils.run_and_log(u_boot_console, cmd) + ignore_cleanup_errors = False + finally: + umount(ignore_errors=ignore_cleanup_errors) + finally: + stop_ums(ignore_errors=ignore_cleanup_errors) + + written_hash = test_f.content_hash + assert(written_hash == read_back_hash) diff --git a/test/ums/README b/test/ums/README deleted file mode 100644 index c80fbfefbf52f689e0ae0a3c4ef9877ba961f620..0000000000000000000000000000000000000000 --- a/test/ums/README +++ /dev/null @@ -1,30 +0,0 @@ -UMS test script. - -ums_gadget_test.sh -================== - -Example usage: -1. On the target: - create UMS exportable partitions (with e.g. gpt write), or specify a - partition number (PART_NUM) as "-" to use the entire device - ums 0 mmc 0 -2. On the host: - sudo test/ums/ums_gadget_test.sh VID PID PART_NUM [-f FILE_SYSTEM] [test_file] - e.g. sudo test/ums/ums_gadget_test.sh 0525 a4a5 6 -f vfat ./dat_14M.img - -... where: - VID - UMS device USB Vendor ID - PID - UMS device USB Product ID - PART_NUM - is the partition number on which UMS operates or "-" to use the - whole device - -Information about available partitions on the target one can read with using -the 'mmc part' or 'part list' commands. - -The partition num (PART_NUM) can be specified as '-' for using the whole device. - -The [-f FILE_SYSTEM] optional switch allows for formatting target partition to -FILE_SYSTEM. - -The last, optional [test_file] parameter is for specifying the exact test file -to use. diff --git a/test/ums/ums_gadget_test.sh b/test/ums/ums_gadget_test.sh deleted file mode 100755 index 9da486b266ce079cd0b8621c66a8baf4f6b20b7a..0000000000000000000000000000000000000000 --- a/test/ums/ums_gadget_test.sh +++ /dev/null @@ -1,183 +0,0 @@ -#! /bin/bash - -# Copyright (C) 2014 Samsung Electronics -# Lukasz Majewski -# -# UMS operation test script -# -# SPDX-License-Identifier: GPL-2.0+ - -clear - -COLOUR_RED="\33[31m" -COLOUR_GREEN="\33[32m" -COLOUR_ORANGE="\33[33m" -COLOUR_DEFAULT="\33[0m" - -DIR=./ -SUFFIX=img -RCV_DIR=rcv/ -LOG_FILE=./log/log-`date +%d-%m-%Y_%H-%M-%S` - -cd `dirname $0` -../dfu/dfu_gadget_test_init.sh 33M 97M - -cleanup () { - rm -rf $RCV_DIR $MNT_DIR -} - -control_c() -# run if user hits control-c -{ - echo -en "\n*** CTRL+C ***\n" - umount $MNT_DIR - cleanup - exit 0 -} - -# trap keyboard interrupt (control-c) -trap control_c SIGINT - -die () { - printf " $COLOUR_RED FAILED $COLOUR_DEFAULT \n" - cleanup - exit 1 -} - -calculate_md5sum () { - MD5SUM=`md5sum $1` - MD5SUM=`echo $MD5SUM | cut -d ' ' -f1` - echo "md5sum:"$MD5SUM -} - -ums_test_file () { - printf "$COLOUR_GREEN========================================================================================= $COLOUR_DEFAULT\n" - printf "File:$COLOUR_GREEN %s $COLOUR_DEFAULT\n" $1 - - mount /dev/$MEM_DEV $MNT_DIR - if [ -f $MNT_DIR/dat_* ]; then - rm $MNT_DIR/dat_* - fi - - cp ./$1 $MNT_DIR - - while true; do - umount $MNT_DIR > /dev/null 2>&1 - if [ $? -eq 0 ]; then - break - fi - printf "$COLOUR_ORANGE\tSleeping to wait for umount...$COLOUR_DEFAULT\n" - sleep 1 - done - - echo -n "TX: " - calculate_md5sum $1 - - MD5_TX=$MD5SUM - sleep 1 - N_FILE=$DIR$RCV_DIR${1:2}"_rcv" - - mount /dev/$MEM_DEV $MNT_DIR - cp $MNT_DIR/$1 $N_FILE || die $? - rm $MNT_DIR/$1 - umount $MNT_DIR - - echo -n "RX: " - calculate_md5sum $N_FILE - MD5_RX=$MD5SUM - - if [ "$MD5_TX" == "$MD5_RX" ]; then - printf " $COLOUR_GREEN -------> OK $COLOUR_DEFAULT \n" - else - printf " $COLOUR_RED -------> FAILED $COLOUR_DEFAULT \n" - cleanup - exit 1 - fi -} - -printf "$COLOUR_GREEN========================================================================================= $COLOUR_DEFAULT\n" -echo "U-boot UMS test program" - -if [ $EUID -ne 0 ]; then - echo "You must be root to do this." 1>&2 - exit 100 -fi - -if [ $# -lt 3 ]; then - echo "Wrong number of arguments" - echo "Example:" - echo "sudo ./ums_gadget_test.sh VID PID PART_NUM [-f ext4] [test_file]" - die -fi - -MNT_DIR="/mnt/tmp-ums-test" - -VID=$1; shift -PID=$1; shift -PART_NUM=$1; shift - -if [ "$1" == "-f" ]; then - shift - FS_TO_FORMAT=$1; shift -fi - -TEST_FILE=$1 - -for f in `find /sys -type f -name idProduct`; do - d=`dirname ${f}` - if [ `cat ${d}/idVendor` != "${VID}" ]; then - continue - fi - if [ `cat ${d}/idProduct` != "${PID}" ]; then - continue - fi - USB_DEV=${d} - break -done - -if [ -z "${USB_DEV}" ]; then - echo "Connect target" - echo "e.g. ums 0 mmc 0" - exit 1 -fi - -MEM_DEV=`find $USB_DEV -type d -name "sd[a-z]" | awk -F/ '{print $(NF)}' -` - -mkdir -p $RCV_DIR -if [ ! -d $MNT_DIR ]; then - mkdir -p $MNT_DIR -fi - -if [ "$PART_NUM" == "-" ]; then - PART_NUM="" -fi -MEM_DEV=$MEM_DEV$PART_NUM - -if [ -n "$FS_TO_FORMAT" ]; then - echo -n "Formatting partition /dev/$MEM_DEV to $FS_TO_FORMAT" - mkfs -t $FS_TO_FORMAT /dev/$MEM_DEV > /dev/null 2>&1 - if [ $? -eq 0 ]; then - printf " $COLOUR_GREEN DONE $COLOUR_DEFAULT \n" - else - die - fi -fi - -printf "Mount: /dev/$MEM_DEV \n" - -if [ -n "$TEST_FILE" ]; then - if [ ! -e $TEST_FILE ]; then - echo "No file: $TEST_FILE" - die - fi - ums_test_file $TEST_FILE -else - for file in $DIR*.$SUFFIX - do - ums_test_file $file - done -fi - -cleanup - -exit 0