f5d196d03e
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>
263 lines
8.3 KiB
Python
263 lines
8.3 KiB
Python
# 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)
|