mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-26 17:48:26 +00:00 
			
		
		
		
	Some tests have ended up using double quotes where single quotes could be used. Adjust this for consistency with the rest of U-Boot's Python code. Signed-off-by: Simon Glass <sjg@chromium.org> Reviewed-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
		
			
				
	
	
		
			321 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # SPDX-License-Identifier: GPL-2.0
 | |
| # Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
 | |
| 
 | |
| # 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.)
 | |
| 
 | |
| c) An optional udev rule to give you a persistent value to use in
 | |
| host_usb_dev_node. For example:
 | |
| 
 | |
| IMPORT{builtin}="path_id"
 | |
| ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="bus/usb/by-path/$env{ID_PATH}"
 | |
| ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="bus/usb/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
 | |
| """
 | |
| 
 | |
| # 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')
 | |
| @pytest.mark.requiredtool('dfu-util')
 | |
| 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)
 |