]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
test/py: add DFU test
authorStephen Warren <swarren@nvidia.com>
Fri, 22 Jan 2016 19:30:14 +0000 (12:30 -0700)
committerSimon Glass <sjg@chromium.org>
Fri, 29 Jan 2016 04:01:23 +0000 (21:01 -0700)
Add a test of DFU functionality to the Python test suite. The test
starts DFU in U-Boot, waits for USB device enumeration on the host,
executes dfu-util multiple times to test various transfer sizes, many
of which trigger USB driver edge cases, and finally aborts the DFU
command in U-Boot.

This test mirrors the functionality previously available via the shell
scripts in test/dfu, and hence those are removed too.

Signed-off-by: Stephen Warren <swarren@nvidia.com>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Acked-by: Simon Glass <sjg@chromium.org>
test/dfu/README [deleted file]
test/dfu/dfu_gadget_test.sh [deleted file]
test/dfu/dfu_gadget_test_init.sh [deleted file]
test/py/tests/test_dfu.py [new file with mode: 0644]

diff --git a/test/dfu/README b/test/dfu/README
deleted file mode 100644 (file)
index 408d559..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-DFU TEST CASE DESCRIPTION:
-
-The prerequisites for running this script are assured by
-dfu_gadget_test_init.sh, which is automatically invoked by dfu_gadget_test.sh.
-In this file user is able to generate their own set of test files by altering
-the default set of TEST_FILES_SIZES variable.
-The dfu_gadget_test_init.sh would generate test images only if they are not
-already generated.
-
-On the target device, environment variable "dfu_alt_info" must contain at
-least:
-
-    dfu_test.bin fat 0 6;dfudummy.bin fat 0 6
-
-Depending on your device, you may need to replace "fat" with
-"ext4", and "6" with the relevant partition number. For reference please
-consult the config file for TRATS/TRATS2 devices
-(../../include/configs/trats{2}.h)
-
-One can use fat, ext4 or any other supported file system supported by U-Boot.
-These can be created by exporting storage devices via UMS (ums 0 mmc 0) and
-using standard tools on host (like mkfs.ext4).
-
-Example usage:
-1. On the target:
-   setenv dfu_alt_info dfu_test.bin fat 0 6\;dfudummy.bin fat 0 6
-   dfu 0 mmc 0
-2. On the host:
-   test/dfu/dfu_gadget_test.sh X Y [test file name] [usb device vendor:product]
-   e.g. test/dfu/dfu_gadget_test.sh 0 1
-   or
-   e.g. test/dfu/dfu_gadget_test.sh 0 1 ./dat_960.img
-   or
-   e.g. test/dfu/dfu_gadget_test.sh 0 1 0451:d022
-   or
-   e.g. test/dfu/dfu_gadget_test.sh 0 1 ./dat_960.img 0451:d022
-
-... where X and Y are dfu_test.bin's and dfudummy.bin's alt setting numbers.
-They can be obtained from dfu-util -l or $dfu_alt_info.
-It is also possible to pass optional [test file name] to force the script to
-test one particular file.
-If many DFU devices are connected, it may be useful to filter on USB
-vendor/product ID (0451:d022).
-One can get them by running "lsusb" command on a host PC.
diff --git a/test/dfu/dfu_gadget_test.sh b/test/dfu/dfu_gadget_test.sh
deleted file mode 100755 (executable)
index 9c79422..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-#! /bin/bash
-
-# Copyright (C) 2014 Samsung Electronics
-# Lukasz Majewski <l.majewski@samsung.com>
-#
-# Script fixes, enhancements and testing:
-# Stephen Warren <swarren@nvidia.com>
-#
-# DFU operation test script
-#
-# SPDX-License-Identifier:     GPL-2.0+
-
-set -e # any command return if not equal to zero
-clear
-
-COLOUR_RED="\33[31m"
-COLOUR_GREEN="\33[32m"
-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_gadget_test_init.sh
-
-cleanup () {
-    rm -rf $DIR$RCV_DIR
-}
-
-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
-}
-
-dfu_test_file () {
-    printf "$COLOUR_GREEN ========================================================================================= $COLOUR_DEFAULT\n"
-    printf "File:$COLOUR_GREEN %s $COLOUR_DEFAULT\n" $1
-
-    dfu-util $USB_DEV -D $1 -a $TARGET_ALT_SETTING >> $LOG_FILE 2>&1 || die $?
-
-    echo -n "TX: "
-    calculate_md5sum $1
-
-    MD5_TX=$MD5SUM
-
-    dfu-util $USB_DEV -D ${DIR}/dfudummy.bin -a $TARGET_ALT_SETTING_B >> $LOG_FILE 2>&1 || die $?
-
-    N_FILE=$DIR$RCV_DIR${1:2}"_rcv"
-
-    dfu-util $USB_DEV -U $N_FILE -a $TARGET_ALT_SETTING >> $LOG_FILE 2>&1 || die $?
-
-    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 "DFU EP0 transmission test program"
-echo "Trouble shoot -> disable DBG (even the KERN_DEBUG) in the UDC driver"
-echo "@ -> TRATS2 # dfu 0 mmc 0"
-cleanup
-mkdir -p $DIR$RCV_DIR
-touch $LOG_FILE
-
-if [ $# -eq 0 ]
-then
-       printf "   $COLOUR_RED Please pass alt setting number!!  $COLOUR_DEFAULT \n"
-       exit 0
-fi
-
-TARGET_ALT_SETTING=$1
-TARGET_ALT_SETTING_B=$2
-
-file=$3
-[[ $3 == *':'* ]] && USB_DEV="-d $3" && file=""
-[ $# -eq 4 ] && USB_DEV="-d $4"
-
-if [ -n "$file" ]
-then
-       dfu_test_file $file
-else
-       for f in $DIR*.$SUFFIX
-       do
-           dfu_test_file $f
-       done
-fi
-
-cleanup
-
-exit 0
diff --git a/test/dfu/dfu_gadget_test_init.sh b/test/dfu/dfu_gadget_test_init.sh
deleted file mode 100755 (executable)
index 640628e..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#! /bin/bash
-
-# Copyright (C) 2014 Samsung Electronics
-# Lukasz Majewski <l.majewski@samsung.com>
-#
-# Script fixes, enhancements and testing:
-# Stephen Warren <swarren@nvidia.com>
-#
-# Script for test files generation
-#
-# SPDX-License-Identifier:     GPL-2.0+
-
-set -e # any command return if not equal to zero
-clear
-
-COLOUR_RED="\33[31m"
-COLOUR_GREEN="\33[32m"
-COLOUR_DEFAULT="\33[0m"
-
-LOG_DIR="./log"
-
-if [ $# -eq 0 ]; then
-    TEST_FILES_SIZES="63 64 65 127 128 129 4095 4096 4097 959 960 961 1048575 1048576 8M"
-else
-    TEST_FILES_SIZES=$@
-fi
-
-printf "Init script for generating data necessary for DFU test script"
-
-if [ ! -d $LOG_DIR ]; then
-    `mkdir $LOG_DIR`
-fi
-
-for size in $TEST_FILES_SIZES
-do
-    FILE="./dat_$size.img"
-    if [ ! -f $FILE ]; then
-       dd if=/dev/urandom of="./dat_$size.img" bs=$size count=1 > /dev/null 2>&1 || exit $?
-    fi
-done
-dd if=/dev/urandom of="./dfudummy.bin" bs=1024 count=1 > /dev/null 2>&1 || exit $?
-
-printf "$COLOUR_GREEN OK $COLOUR_DEFAULT \n"
-
-exit 0
diff --git a/test/py/tests/test_dfu.py b/test/py/tests/test_dfu.py
new file mode 100644 (file)
index 0000000..cc4b8d8
--- /dev/null
@@ -0,0 +1,262 @@
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+#
+# SPDX-License-Identifier: GPL-2.0
+
+# Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB
+# device enumeration on the host, executes dfu-util multiple times to test
+# various transfer sizes, many of which trigger USB driver edge cases, and
+# finally aborts the "dfu" command in U-Boot.
+
+import os
+import os.path
+import pytest
+import u_boot_utils
+
+'''
+Note: This test relies on:
+
+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:
+
+env__usb_dev_ports = (
+    {
+        "tgt_usb_ctlr": "0",
+        "host_usb_dev_node": "/dev/usbdev-p2371-2180",
+        # This parameter is optional /if/ you only have a single board
+        # attached to your host at a time.
+        "host_usb_port_path": "3-13",
+    },
+)
+
+env__dfu_configs = (
+    # eMMC, partition 1
+    {
+        "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1",
+        "cmd_params": "mmc 0",
+    },
+)
+b) udev rules to set permissions on devices nodes, so that sudo is not
+required. For example:
+
+ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
+
+(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.)
+'''
+
+# The set of file sizes to test. These values trigger various edge-cases such
+# as one less than, equal to, and one greater than typical USB max packet
+# sizes, and similar boundary conditions.
+test_sizes = (
+    64 - 1,
+    64,
+    64 + 1,
+    128 - 1,
+    128,
+    128 + 1,
+    960 - 1,
+    960,
+    960 + 1,
+    4096 - 1,
+    4096,
+    4096 + 1,
+    1024 * 1024 - 1,
+    1024 * 1024,
+    8 * 1024 * 1024,
+)
+
+first_usb_dev_port = None
+
+@pytest.mark.buildconfigspec('cmd_dfu')
+def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
+    '''Test the "dfu" command; the host system must be able to enumerate a USB
+    device when "dfu" is running, various DFU transfers are tested, and the
+    USB device must disappear when "dfu" 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__dfu_config: The single DFU (memory region) configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    '''
+
+    def start_dfu():
+        '''Start U-Boot's dfu 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 dfu shell command')
+
+        cmd = 'setenv dfu_alt_info "%s"' % env__dfu_config['alt_info']
+        u_boot_console.run_command(cmd)
+
+        cmd = 'dfu 0 ' + env__dfu_config['cmd_params']
+        u_boot_console.run_command(cmd, wait_for_prompt=False)
+        u_boot_console.log.action('Waiting for DFU USB device to appear')
+        fh = u_boot_utils.wait_until_open_succeeds(
+            env__usb_dev_port['host_usb_dev_node'])
+        fh.close()
+
+    def stop_dfu(ignore_errors):
+        '''Stop U-Boot's dfu 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.
+        '''
+
+        try:
+            u_boot_console.log.action(
+                'Stopping long-running U-Boot dfu shell command')
+            u_boot_console.ctrlc()
+            u_boot_console.log.action(
+                'Waiting for DFU USB device to disappear')
+            u_boot_utils.wait_until_file_open_fails(
+                env__usb_dev_port['host_usb_dev_node'], ignore_errors)
+        except:
+            if not ignore_errors:
+                raise
+
+    def run_dfu_util(alt_setting, fn, up_dn_load_arg):
+        '''Invoke dfu-util on the host.
+
+        Args:
+            alt_setting: The DFU "alternate setting" identifier to interact
+                with.
+            fn: The host-side file name to transfer.
+            up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or
+                download operation should be performed.
+
+        Returns:
+            Nothing.
+        '''
+
+        cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn]
+        if 'host_usb_port_path' in env__usb_dev_port:
+            cmd += ['-p', env__usb_dev_port['host_usb_port_path']]
+        u_boot_utils.run_and_log(u_boot_console, cmd)
+        u_boot_console.wait_for('Ctrl+C to exit ...')
+
+    def dfu_write(alt_setting, fn):
+        '''Write a file to the target board using DFU.
+
+        Args:
+            alt_setting: The DFU "alternate setting" identifier to interact
+                with.
+            fn: The host-side file name to transfer.
+
+        Returns:
+            Nothing.
+        '''
+
+        run_dfu_util(alt_setting, fn, '-D')
+
+    def dfu_read(alt_setting, fn):
+        '''Read a file from the target board using DFU.
+
+        Args:
+            alt_setting: The DFU "alternate setting" identifier to interact
+                with.
+            fn: The host-side file name to transfer.
+
+        Returns:
+            Nothing.
+        '''
+
+        # dfu-util fails reads/uploads if the host file already exists
+        if os.path.exists(fn):
+            os.remove(fn)
+        run_dfu_util(alt_setting, fn, '-U')
+
+    def dfu_write_read_check(size):
+        '''Test DFU transfers of a specific size of data
+
+        This function first writes data to the board then reads it back and
+        compares the written and read back data. Measures are taken to avoid
+        certain types of false positives.
+
+        Args:
+            size: The data size to test.
+
+        Returns:
+            Nothing.
+        '''
+
+        test_f = u_boot_utils.PersistentRandomFile(u_boot_console,
+            'dfu_%d.bin' % size, size)
+        readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin'
+
+        u_boot_console.log.action('Writing test data to DFU primary ' +
+            'altsetting')
+        dfu_write(0, test_f.abs_fn)
+
+        u_boot_console.log.action('Writing dummy data to DFU secondary ' +
+            'altsetting to clear DFU buffers')
+        dfu_write(1, dummy_f.abs_fn)
+
+        u_boot_console.log.action('Reading DFU primary altsetting for ' +
+            'comparison')
+        dfu_read(0, readback_fn)
+
+        u_boot_console.log.action('Comparing written and read data')
+        written_hash = test_f.content_hash
+        read_back_hash = u_boot_utils.md5sum_file(readback_fn, size)
+        assert(written_hash == read_back_hash)
+
+    # This test may be executed against multiple USB ports. The test takes a
+    # long time, so we don't want to do the whole thing each time. Instead,
+    # execute the full test on the first USB port, and perform a very limited
+    # test on other ports. In the limited case, we solely validate that the
+    # host PC can enumerate the U-Boot USB device.
+    global first_usb_dev_port
+    if not first_usb_dev_port:
+        first_usb_dev_port = env__usb_dev_port
+    if env__usb_dev_port == first_usb_dev_port:
+        sizes = test_sizes
+    else:
+        sizes = []
+
+    dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console,
+        'dfu_dummy.bin', 1024)
+
+    ignore_cleanup_errors = True
+    try:
+        start_dfu()
+
+        u_boot_console.log.action(
+            'Overwriting DFU primary altsetting with dummy data')
+        dfu_write(0, dummy_f.abs_fn)
+
+        for size in sizes:
+            with u_boot_console.log.section("Data size %d" % size):
+                dfu_write_read_check(size)
+                # Make the status of each sub-test obvious. If the test didn't
+                # pass, an exception was thrown so this code isn't executed.
+                u_boot_console.log.status_pass('OK')
+        ignore_cleanup_errors = False
+    finally:
+        stop_dfu(ignore_cleanup_errors)