test/py: add DFU test

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>
master
Stephen Warren 9 years ago committed by Simon Glass
parent d054f4c2cb
commit f5d196d03e
  1. 44
      test/dfu/README
  2. 108
      test/dfu/dfu_gadget_test.sh
  3. 45
      test/dfu/dfu_gadget_test_init.sh
  4. 262
      test/py/tests/test_dfu.py

@ -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.

@ -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

@ -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

@ -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)
Loading…
Cancel
Save