123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- # 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 = (
- {
- "fixture_id": "micro_b",
- "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",
- },
- )
- # Optional entries (required only when "alt_id_test_file" and
- # "alt_id_dummy_file" are specified).
- test_file_name = "/dfu_test.bin"
- dummy_file_name = "/dfu_dummy.bin"
- # Above files are used to generate proper "alt_info" entry
- "alt_info": "/%s ext4 0 2;/%s ext4 0 2" % (test_file_name, dummy_file_name),
- env__dfu_configs = (
- # eMMC, partition 1
- {
- "fixture_id": "emmc",
- "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1",
- "cmd_params": "mmc 0",
- # This value is optional.
- # If present, it specified the set of transfer sizes tested.
- # If missing, a default list of sizes will be used, which covers
- # various useful corner cases.
- # Manually specifying test sizes is useful if you wish to test 4 DFU
- # configurations, but don't want to test every single transfer size
- # on each, to avoid bloating the overall time taken by testing.
- "test_sizes": (63, 64, 65),
- # This value is optional.
- # The name of the environment variable that the the dfu command reads
- # alt info from. If unspecified, this defaults to dfu_alt_info, which is
- # valid for most systems. Some systems use a different variable name.
- # One example is the Odroid XU3, which automatically generates
- # $dfu_alt_info, each time the dfu command is run, by concatenating
- # $dfu_alt_boot and $dfu_alt_system.
- "alt_info_env_name": "dfu_alt_system",
- # This value is optional.
- # For boards which require the "test file" alt setting number other than
- # default (0) it is possible to specify exact file name to be used as
- # this parameter.
- "alt_id_test_file": test_file_name,
- # This value is optional.
- # For boards which require the "dummy file" alt setting number other
- # than default (1) it is possible to specify exact file name to be used
- # as this parameter.
- "alt_id_dummy_file": dummy_file_name,
- },
- )
- 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_default = (
- 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_utils.wait_until_file_open_fails(
- env__usb_dev_port['host_usb_dev_node'], True)
- fh = u_boot_utils.attempt_to_open_file(
- env__usb_dev_port['host_usb_dev_node'])
- if fh:
- fh.close()
- raise Exception('USB device present before dfu command invoked')
- u_boot_console.log.action(
- 'Starting long-running U-Boot dfu shell command')
- dfu_alt_info_env = env__dfu_config.get('alt_info_env_name', \
- 'dfu_alt_info')
- cmd = 'setenv "%s" "%s"' % (dfu_alt_info_env,
- 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', 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(alt_setting_test_file, test_f.abs_fn)
- u_boot_console.log.action('Writing dummy data to DFU secondary ' +
- 'altsetting to clear DFU buffers')
- dfu_write(alt_setting_dummy_file, dummy_f.abs_fn)
- u_boot_console.log.action('Reading DFU primary altsetting for ' +
- 'comparison')
- dfu_read(alt_setting_test_file, 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 = env__dfu_config.get('test_sizes', test_sizes_default)
- else:
- sizes = []
- dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console,
- 'dfu_dummy.bin', 1024)
- alt_setting_test_file = env__dfu_config.get('alt_id_test_file', '0')
- alt_setting_dummy_file = env__dfu_config.get('alt_id_dummy_file', '1')
- ignore_cleanup_errors = True
- try:
- start_dfu()
- u_boot_console.log.action(
- 'Overwriting DFU primary altsetting with dummy data')
- dfu_write(alt_setting_test_file, 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)
|