Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 1 | # SPDX-License-Identifier: GPL-2.0 |
Tom Rini | 10e4779 | 2018-05-06 17:58:06 -0400 | [diff] [blame] | 2 | # Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 3 | |
| 4 | # Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB |
| 5 | # device enumeration on the host, executes dfu-util multiple times to test |
| 6 | # various transfer sizes, many of which trigger USB driver edge cases, and |
| 7 | # finally aborts the "dfu" command in U-Boot. |
| 8 | |
| 9 | import os |
| 10 | import os.path |
| 11 | import pytest |
| 12 | import u_boot_utils |
| 13 | |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 14 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 15 | Note: This test relies on: |
| 16 | |
| 17 | a) boardenv_* to contain configuration values to define which USB ports are |
| 18 | available for testing. Without this, this test will be automatically skipped. |
| 19 | For example: |
| 20 | |
| 21 | env__usb_dev_ports = ( |
| 22 | { |
Stephen Warren | 71a68fd | 2016-01-26 15:26:04 -0700 | [diff] [blame] | 23 | "fixture_id": "micro_b", |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 24 | "tgt_usb_ctlr": "0", |
| 25 | "host_usb_dev_node": "/dev/usbdev-p2371-2180", |
| 26 | # This parameter is optional /if/ you only have a single board |
| 27 | # attached to your host at a time. |
| 28 | "host_usb_port_path": "3-13", |
| 29 | }, |
| 30 | ) |
| 31 | |
Lukasz Majewski | 414ba5a | 2016-04-20 10:57:08 +0200 | [diff] [blame] | 32 | # Optional entries (required only when "alt_id_test_file" and |
| 33 | # "alt_id_dummy_file" are specified). |
| 34 | test_file_name = "/dfu_test.bin" |
| 35 | dummy_file_name = "/dfu_dummy.bin" |
| 36 | # Above files are used to generate proper "alt_info" entry |
| 37 | "alt_info": "/%s ext4 0 2;/%s ext4 0 2" % (test_file_name, dummy_file_name), |
| 38 | |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 39 | env__dfu_configs = ( |
| 40 | # eMMC, partition 1 |
| 41 | { |
Stephen Warren | 71a68fd | 2016-01-26 15:26:04 -0700 | [diff] [blame] | 42 | "fixture_id": "emmc", |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 43 | "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1", |
| 44 | "cmd_params": "mmc 0", |
Stephen Warren | 071bc84 | 2016-01-28 13:14:16 -0700 | [diff] [blame] | 45 | # This value is optional. |
| 46 | # If present, it specified the set of transfer sizes tested. |
| 47 | # If missing, a default list of sizes will be used, which covers |
| 48 | # various useful corner cases. |
| 49 | # Manually specifying test sizes is useful if you wish to test 4 DFU |
| 50 | # configurations, but don't want to test every single transfer size |
| 51 | # on each, to avoid bloating the overall time taken by testing. |
| 52 | "test_sizes": (63, 64, 65), |
Lukasz Majewski | 15608c8 | 2016-04-18 17:01:15 +0200 | [diff] [blame] | 53 | # This value is optional. |
| 54 | # The name of the environment variable that the the dfu command reads |
| 55 | # alt info from. If unspecified, this defaults to dfu_alt_info, which is |
| 56 | # valid for most systems. Some systems use a different variable name. |
| 57 | # One example is the Odroid XU3, which automatically generates |
| 58 | # $dfu_alt_info, each time the dfu command is run, by concatenating |
| 59 | # $dfu_alt_boot and $dfu_alt_system. |
| 60 | "alt_info_env_name": "dfu_alt_system", |
Lukasz Majewski | 414ba5a | 2016-04-20 10:57:08 +0200 | [diff] [blame] | 61 | # This value is optional. |
| 62 | # For boards which require the "test file" alt setting number other than |
| 63 | # default (0) it is possible to specify exact file name to be used as |
| 64 | # this parameter. |
| 65 | "alt_id_test_file": test_file_name, |
| 66 | # This value is optional. |
| 67 | # For boards which require the "dummy file" alt setting number other |
| 68 | # than default (1) it is possible to specify exact file name to be used |
| 69 | # as this parameter. |
| 70 | "alt_id_dummy_file": dummy_file_name, |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 71 | }, |
| 72 | ) |
Stephen Warren | 71a68fd | 2016-01-26 15:26:04 -0700 | [diff] [blame] | 73 | |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 74 | b) udev rules to set permissions on devices nodes, so that sudo is not |
| 75 | required. For example: |
| 76 | |
| 77 | ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666" |
| 78 | |
| 79 | (You may wish to change the group ID instead of setting the permissions wide |
| 80 | open. All that matters is that the user ID running the test can access the |
| 81 | device.) |
Tom Rini | 0294c2f | 2016-08-29 09:57:01 -0400 | [diff] [blame] | 82 | |
| 83 | c) An optional udev rule to give you a persistent value to use in |
| 84 | host_usb_dev_node. For example: |
| 85 | |
| 86 | IMPORT{builtin}="path_id" |
| 87 | ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="bus/usb/by-path/$env{ID_PATH}" |
| 88 | ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="bus/usb/by-path/$env{ID_PATH}-port$env{.ID_PORT}" |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 89 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 90 | |
| 91 | # The set of file sizes to test. These values trigger various edge-cases such |
| 92 | # as one less than, equal to, and one greater than typical USB max packet |
| 93 | # sizes, and similar boundary conditions. |
Stephen Warren | 071bc84 | 2016-01-28 13:14:16 -0700 | [diff] [blame] | 94 | test_sizes_default = ( |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 95 | 64 - 1, |
| 96 | 64, |
| 97 | 64 + 1, |
| 98 | 128 - 1, |
| 99 | 128, |
| 100 | 128 + 1, |
| 101 | 960 - 1, |
| 102 | 960, |
| 103 | 960 + 1, |
| 104 | 4096 - 1, |
| 105 | 4096, |
| 106 | 4096 + 1, |
| 107 | 1024 * 1024 - 1, |
| 108 | 1024 * 1024, |
| 109 | 8 * 1024 * 1024, |
| 110 | ) |
| 111 | |
| 112 | first_usb_dev_port = None |
| 113 | |
| 114 | @pytest.mark.buildconfigspec('cmd_dfu') |
Stephen Warren | 2079db3 | 2017-09-18 11:11:49 -0600 | [diff] [blame] | 115 | @pytest.mark.requiredtool('dfu-util') |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 116 | def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 117 | """Test the "dfu" command; the host system must be able to enumerate a USB |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 118 | device when "dfu" is running, various DFU transfers are tested, and the |
| 119 | USB device must disappear when "dfu" is aborted. |
| 120 | |
| 121 | Args: |
| 122 | u_boot_console: A U-Boot console connection. |
| 123 | env__usb_dev_port: The single USB device-mode port specification on |
| 124 | which to run the test. See the file-level comment above for |
| 125 | details of the format. |
| 126 | env__dfu_config: The single DFU (memory region) configuration on which |
| 127 | to run the test. See the file-level comment above for details |
| 128 | of the format. |
| 129 | |
| 130 | Returns: |
| 131 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 132 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 133 | |
| 134 | def start_dfu(): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 135 | """Start U-Boot's dfu shell command. |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 136 | |
| 137 | This also waits for the host-side USB enumeration process to complete. |
| 138 | |
| 139 | Args: |
| 140 | None. |
| 141 | |
| 142 | Returns: |
| 143 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 144 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 145 | |
Stephen Warren | ec15768 | 2016-05-05 17:02:06 -0600 | [diff] [blame] | 146 | u_boot_utils.wait_until_file_open_fails( |
| 147 | env__usb_dev_port['host_usb_dev_node'], True) |
Stephen Warren | 9d7e55d | 2016-01-26 10:59:43 -0700 | [diff] [blame] | 148 | fh = u_boot_utils.attempt_to_open_file( |
| 149 | env__usb_dev_port['host_usb_dev_node']) |
| 150 | if fh: |
| 151 | fh.close() |
| 152 | raise Exception('USB device present before dfu command invoked') |
| 153 | |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 154 | u_boot_console.log.action( |
| 155 | 'Starting long-running U-Boot dfu shell command') |
| 156 | |
Lukasz Majewski | 15608c8 | 2016-04-18 17:01:15 +0200 | [diff] [blame] | 157 | dfu_alt_info_env = env__dfu_config.get('alt_info_env_name', \ |
| 158 | 'dfu_alt_info') |
| 159 | |
| 160 | cmd = 'setenv "%s" "%s"' % (dfu_alt_info_env, |
| 161 | env__dfu_config['alt_info']) |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 162 | u_boot_console.run_command(cmd) |
| 163 | |
| 164 | cmd = 'dfu 0 ' + env__dfu_config['cmd_params'] |
| 165 | u_boot_console.run_command(cmd, wait_for_prompt=False) |
| 166 | u_boot_console.log.action('Waiting for DFU USB device to appear') |
| 167 | fh = u_boot_utils.wait_until_open_succeeds( |
| 168 | env__usb_dev_port['host_usb_dev_node']) |
| 169 | fh.close() |
| 170 | |
| 171 | def stop_dfu(ignore_errors): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 172 | """Stop U-Boot's dfu shell command from executing. |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 173 | |
| 174 | This also waits for the host-side USB de-enumeration process to |
| 175 | complete. |
| 176 | |
| 177 | Args: |
| 178 | ignore_errors: Ignore any errors. This is useful if an error has |
| 179 | already been detected, and the code is performing best-effort |
| 180 | cleanup. In this case, we do not want to mask the original |
| 181 | error by "honoring" any new errors. |
| 182 | |
| 183 | Returns: |
| 184 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 185 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 186 | |
| 187 | try: |
| 188 | u_boot_console.log.action( |
| 189 | 'Stopping long-running U-Boot dfu shell command') |
| 190 | u_boot_console.ctrlc() |
| 191 | u_boot_console.log.action( |
| 192 | 'Waiting for DFU USB device to disappear') |
| 193 | u_boot_utils.wait_until_file_open_fails( |
| 194 | env__usb_dev_port['host_usb_dev_node'], ignore_errors) |
| 195 | except: |
| 196 | if not ignore_errors: |
| 197 | raise |
| 198 | |
| 199 | def run_dfu_util(alt_setting, fn, up_dn_load_arg): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 200 | """Invoke dfu-util on the host. |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 201 | |
| 202 | Args: |
| 203 | alt_setting: The DFU "alternate setting" identifier to interact |
| 204 | with. |
| 205 | fn: The host-side file name to transfer. |
| 206 | up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or |
| 207 | download operation should be performed. |
| 208 | |
| 209 | Returns: |
| 210 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 211 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 212 | |
Lukasz Majewski | 414ba5a | 2016-04-20 10:57:08 +0200 | [diff] [blame] | 213 | cmd = ['dfu-util', '-a', alt_setting, up_dn_load_arg, fn] |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 214 | if 'host_usb_port_path' in env__usb_dev_port: |
| 215 | cmd += ['-p', env__usb_dev_port['host_usb_port_path']] |
| 216 | u_boot_utils.run_and_log(u_boot_console, cmd) |
| 217 | u_boot_console.wait_for('Ctrl+C to exit ...') |
| 218 | |
| 219 | def dfu_write(alt_setting, fn): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 220 | """Write a file to the target board using DFU. |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 221 | |
| 222 | Args: |
| 223 | alt_setting: The DFU "alternate setting" identifier to interact |
| 224 | with. |
| 225 | fn: The host-side file name to transfer. |
| 226 | |
| 227 | Returns: |
| 228 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 229 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 230 | |
| 231 | run_dfu_util(alt_setting, fn, '-D') |
| 232 | |
| 233 | def dfu_read(alt_setting, fn): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 234 | """Read a file from the target board using DFU. |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 235 | |
| 236 | Args: |
| 237 | alt_setting: The DFU "alternate setting" identifier to interact |
| 238 | with. |
| 239 | fn: The host-side file name to transfer. |
| 240 | |
| 241 | Returns: |
| 242 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 243 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 244 | |
| 245 | # dfu-util fails reads/uploads if the host file already exists |
| 246 | if os.path.exists(fn): |
| 247 | os.remove(fn) |
| 248 | run_dfu_util(alt_setting, fn, '-U') |
| 249 | |
| 250 | def dfu_write_read_check(size): |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 251 | """Test DFU transfers of a specific size of data |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 252 | |
| 253 | This function first writes data to the board then reads it back and |
| 254 | compares the written and read back data. Measures are taken to avoid |
| 255 | certain types of false positives. |
| 256 | |
| 257 | Args: |
| 258 | size: The data size to test. |
| 259 | |
| 260 | Returns: |
| 261 | Nothing. |
Stephen Warren | 75e731e | 2016-01-26 13:41:30 -0700 | [diff] [blame] | 262 | """ |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 263 | |
| 264 | test_f = u_boot_utils.PersistentRandomFile(u_boot_console, |
| 265 | 'dfu_%d.bin' % size, size) |
| 266 | readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin' |
| 267 | |
| 268 | u_boot_console.log.action('Writing test data to DFU primary ' + |
| 269 | 'altsetting') |
Lukasz Majewski | 245bfec | 2016-04-20 10:36:32 +0200 | [diff] [blame] | 270 | dfu_write(alt_setting_test_file, test_f.abs_fn) |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 271 | |
| 272 | u_boot_console.log.action('Writing dummy data to DFU secondary ' + |
| 273 | 'altsetting to clear DFU buffers') |
Lukasz Majewski | 245bfec | 2016-04-20 10:36:32 +0200 | [diff] [blame] | 274 | dfu_write(alt_setting_dummy_file, dummy_f.abs_fn) |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 275 | |
| 276 | u_boot_console.log.action('Reading DFU primary altsetting for ' + |
| 277 | 'comparison') |
Lukasz Majewski | 245bfec | 2016-04-20 10:36:32 +0200 | [diff] [blame] | 278 | dfu_read(alt_setting_test_file, readback_fn) |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 279 | |
| 280 | u_boot_console.log.action('Comparing written and read data') |
| 281 | written_hash = test_f.content_hash |
| 282 | read_back_hash = u_boot_utils.md5sum_file(readback_fn, size) |
| 283 | assert(written_hash == read_back_hash) |
| 284 | |
| 285 | # This test may be executed against multiple USB ports. The test takes a |
| 286 | # long time, so we don't want to do the whole thing each time. Instead, |
| 287 | # execute the full test on the first USB port, and perform a very limited |
| 288 | # test on other ports. In the limited case, we solely validate that the |
| 289 | # host PC can enumerate the U-Boot USB device. |
| 290 | global first_usb_dev_port |
| 291 | if not first_usb_dev_port: |
| 292 | first_usb_dev_port = env__usb_dev_port |
| 293 | if env__usb_dev_port == first_usb_dev_port: |
Stephen Warren | 071bc84 | 2016-01-28 13:14:16 -0700 | [diff] [blame] | 294 | sizes = env__dfu_config.get('test_sizes', test_sizes_default) |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 295 | else: |
| 296 | sizes = [] |
| 297 | |
| 298 | dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console, |
| 299 | 'dfu_dummy.bin', 1024) |
| 300 | |
Lukasz Majewski | 414ba5a | 2016-04-20 10:57:08 +0200 | [diff] [blame] | 301 | alt_setting_test_file = env__dfu_config.get('alt_id_test_file', '0') |
| 302 | alt_setting_dummy_file = env__dfu_config.get('alt_id_dummy_file', '1') |
| 303 | |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 304 | ignore_cleanup_errors = True |
| 305 | try: |
| 306 | start_dfu() |
| 307 | |
| 308 | u_boot_console.log.action( |
| 309 | 'Overwriting DFU primary altsetting with dummy data') |
Lukasz Majewski | 245bfec | 2016-04-20 10:36:32 +0200 | [diff] [blame] | 310 | dfu_write(alt_setting_test_file, dummy_f.abs_fn) |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 311 | |
| 312 | for size in sizes: |
Stephen Warren | 3deb896 | 2016-01-26 13:41:31 -0700 | [diff] [blame] | 313 | with u_boot_console.log.section('Data size %d' % size): |
Stephen Warren | b92b446 | 2016-01-22 12:30:14 -0700 | [diff] [blame] | 314 | dfu_write_read_check(size) |
| 315 | # Make the status of each sub-test obvious. If the test didn't |
| 316 | # pass, an exception was thrown so this code isn't executed. |
| 317 | u_boot_console.log.status_pass('OK') |
| 318 | ignore_cleanup_errors = False |
| 319 | finally: |
| 320 | stop_dfu(ignore_cleanup_errors) |