blob: 093e8d0678710304659a59663e07cfc32d46fe0e [file] [log] [blame]
Stephen Warrenb92b4462016-01-22 12:30:14 -07001# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
2#
3# SPDX-License-Identifier: GPL-2.0
4
5# Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB
6# device enumeration on the host, executes dfu-util multiple times to test
7# various transfer sizes, many of which trigger USB driver edge cases, and
8# finally aborts the "dfu" command in U-Boot.
9
10import os
11import os.path
12import pytest
13import u_boot_utils
14
Stephen Warren75e731e2016-01-26 13:41:30 -070015"""
Stephen Warrenb92b4462016-01-22 12:30:14 -070016Note: This test relies on:
17
18a) boardenv_* to contain configuration values to define which USB ports are
19available for testing. Without this, this test will be automatically skipped.
20For example:
21
22env__usb_dev_ports = (
23 {
Stephen Warren71a68fd2016-01-26 15:26:04 -070024 "fixture_id": "micro_b",
Stephen Warrenb92b4462016-01-22 12:30:14 -070025 "tgt_usb_ctlr": "0",
26 "host_usb_dev_node": "/dev/usbdev-p2371-2180",
27 # This parameter is optional /if/ you only have a single board
28 # attached to your host at a time.
29 "host_usb_port_path": "3-13",
30 },
31)
32
33env__dfu_configs = (
34 # eMMC, partition 1
35 {
Stephen Warren71a68fd2016-01-26 15:26:04 -070036 "fixture_id": "emmc",
Stephen Warrenb92b4462016-01-22 12:30:14 -070037 "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1",
38 "cmd_params": "mmc 0",
Stephen Warren071bc842016-01-28 13:14:16 -070039 # This value is optional.
40 # If present, it specified the set of transfer sizes tested.
41 # If missing, a default list of sizes will be used, which covers
42 # various useful corner cases.
43 # Manually specifying test sizes is useful if you wish to test 4 DFU
44 # configurations, but don't want to test every single transfer size
45 # on each, to avoid bloating the overall time taken by testing.
46 "test_sizes": (63, 64, 65),
Stephen Warrenb92b4462016-01-22 12:30:14 -070047 },
48)
Stephen Warren71a68fd2016-01-26 15:26:04 -070049
Stephen Warrenb92b4462016-01-22 12:30:14 -070050b) udev rules to set permissions on devices nodes, so that sudo is not
51required. For example:
52
53ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
54
55(You may wish to change the group ID instead of setting the permissions wide
56open. All that matters is that the user ID running the test can access the
57device.)
Stephen Warren75e731e2016-01-26 13:41:30 -070058"""
Stephen Warrenb92b4462016-01-22 12:30:14 -070059
60# The set of file sizes to test. These values trigger various edge-cases such
61# as one less than, equal to, and one greater than typical USB max packet
62# sizes, and similar boundary conditions.
Stephen Warren071bc842016-01-28 13:14:16 -070063test_sizes_default = (
Stephen Warrenb92b4462016-01-22 12:30:14 -070064 64 - 1,
65 64,
66 64 + 1,
67 128 - 1,
68 128,
69 128 + 1,
70 960 - 1,
71 960,
72 960 + 1,
73 4096 - 1,
74 4096,
75 4096 + 1,
76 1024 * 1024 - 1,
77 1024 * 1024,
78 8 * 1024 * 1024,
79)
80
81first_usb_dev_port = None
82
83@pytest.mark.buildconfigspec('cmd_dfu')
84def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
Stephen Warren75e731e2016-01-26 13:41:30 -070085 """Test the "dfu" command; the host system must be able to enumerate a USB
Stephen Warrenb92b4462016-01-22 12:30:14 -070086 device when "dfu" is running, various DFU transfers are tested, and the
87 USB device must disappear when "dfu" is aborted.
88
89 Args:
90 u_boot_console: A U-Boot console connection.
91 env__usb_dev_port: The single USB device-mode port specification on
92 which to run the test. See the file-level comment above for
93 details of the format.
94 env__dfu_config: The single DFU (memory region) configuration on which
95 to run the test. See the file-level comment above for details
96 of the format.
97
98 Returns:
99 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700100 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700101
102 def start_dfu():
Stephen Warren75e731e2016-01-26 13:41:30 -0700103 """Start U-Boot's dfu shell command.
Stephen Warrenb92b4462016-01-22 12:30:14 -0700104
105 This also waits for the host-side USB enumeration process to complete.
106
107 Args:
108 None.
109
110 Returns:
111 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700112 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700113
Stephen Warren9d7e55d2016-01-26 10:59:43 -0700114 fh = u_boot_utils.attempt_to_open_file(
115 env__usb_dev_port['host_usb_dev_node'])
116 if fh:
117 fh.close()
118 raise Exception('USB device present before dfu command invoked')
119
Stephen Warrenb92b4462016-01-22 12:30:14 -0700120 u_boot_console.log.action(
121 'Starting long-running U-Boot dfu shell command')
122
123 cmd = 'setenv dfu_alt_info "%s"' % env__dfu_config['alt_info']
124 u_boot_console.run_command(cmd)
125
126 cmd = 'dfu 0 ' + env__dfu_config['cmd_params']
127 u_boot_console.run_command(cmd, wait_for_prompt=False)
128 u_boot_console.log.action('Waiting for DFU USB device to appear')
129 fh = u_boot_utils.wait_until_open_succeeds(
130 env__usb_dev_port['host_usb_dev_node'])
131 fh.close()
132
133 def stop_dfu(ignore_errors):
Stephen Warren75e731e2016-01-26 13:41:30 -0700134 """Stop U-Boot's dfu shell command from executing.
Stephen Warrenb92b4462016-01-22 12:30:14 -0700135
136 This also waits for the host-side USB de-enumeration process to
137 complete.
138
139 Args:
140 ignore_errors: Ignore any errors. This is useful if an error has
141 already been detected, and the code is performing best-effort
142 cleanup. In this case, we do not want to mask the original
143 error by "honoring" any new errors.
144
145 Returns:
146 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700147 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700148
149 try:
150 u_boot_console.log.action(
151 'Stopping long-running U-Boot dfu shell command')
152 u_boot_console.ctrlc()
153 u_boot_console.log.action(
154 'Waiting for DFU USB device to disappear')
155 u_boot_utils.wait_until_file_open_fails(
156 env__usb_dev_port['host_usb_dev_node'], ignore_errors)
157 except:
158 if not ignore_errors:
159 raise
160
161 def run_dfu_util(alt_setting, fn, up_dn_load_arg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700162 """Invoke dfu-util on the host.
Stephen Warrenb92b4462016-01-22 12:30:14 -0700163
164 Args:
165 alt_setting: The DFU "alternate setting" identifier to interact
166 with.
167 fn: The host-side file name to transfer.
168 up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or
169 download operation should be performed.
170
171 Returns:
172 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700173 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700174
175 cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn]
176 if 'host_usb_port_path' in env__usb_dev_port:
177 cmd += ['-p', env__usb_dev_port['host_usb_port_path']]
178 u_boot_utils.run_and_log(u_boot_console, cmd)
179 u_boot_console.wait_for('Ctrl+C to exit ...')
180
181 def dfu_write(alt_setting, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700182 """Write a file to the target board using DFU.
Stephen Warrenb92b4462016-01-22 12:30:14 -0700183
184 Args:
185 alt_setting: The DFU "alternate setting" identifier to interact
186 with.
187 fn: The host-side file name to transfer.
188
189 Returns:
190 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700191 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700192
193 run_dfu_util(alt_setting, fn, '-D')
194
195 def dfu_read(alt_setting, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700196 """Read a file from the target board using DFU.
Stephen Warrenb92b4462016-01-22 12:30:14 -0700197
198 Args:
199 alt_setting: The DFU "alternate setting" identifier to interact
200 with.
201 fn: The host-side file name to transfer.
202
203 Returns:
204 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700205 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700206
207 # dfu-util fails reads/uploads if the host file already exists
208 if os.path.exists(fn):
209 os.remove(fn)
210 run_dfu_util(alt_setting, fn, '-U')
211
212 def dfu_write_read_check(size):
Stephen Warren75e731e2016-01-26 13:41:30 -0700213 """Test DFU transfers of a specific size of data
Stephen Warrenb92b4462016-01-22 12:30:14 -0700214
215 This function first writes data to the board then reads it back and
216 compares the written and read back data. Measures are taken to avoid
217 certain types of false positives.
218
219 Args:
220 size: The data size to test.
221
222 Returns:
223 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700224 """
Stephen Warrenb92b4462016-01-22 12:30:14 -0700225
226 test_f = u_boot_utils.PersistentRandomFile(u_boot_console,
227 'dfu_%d.bin' % size, size)
228 readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin'
229
230 u_boot_console.log.action('Writing test data to DFU primary ' +
231 'altsetting')
232 dfu_write(0, test_f.abs_fn)
233
234 u_boot_console.log.action('Writing dummy data to DFU secondary ' +
235 'altsetting to clear DFU buffers')
236 dfu_write(1, dummy_f.abs_fn)
237
238 u_boot_console.log.action('Reading DFU primary altsetting for ' +
239 'comparison')
240 dfu_read(0, readback_fn)
241
242 u_boot_console.log.action('Comparing written and read data')
243 written_hash = test_f.content_hash
244 read_back_hash = u_boot_utils.md5sum_file(readback_fn, size)
245 assert(written_hash == read_back_hash)
246
247 # This test may be executed against multiple USB ports. The test takes a
248 # long time, so we don't want to do the whole thing each time. Instead,
249 # execute the full test on the first USB port, and perform a very limited
250 # test on other ports. In the limited case, we solely validate that the
251 # host PC can enumerate the U-Boot USB device.
252 global first_usb_dev_port
253 if not first_usb_dev_port:
254 first_usb_dev_port = env__usb_dev_port
255 if env__usb_dev_port == first_usb_dev_port:
Stephen Warren071bc842016-01-28 13:14:16 -0700256 sizes = env__dfu_config.get('test_sizes', test_sizes_default)
Stephen Warrenb92b4462016-01-22 12:30:14 -0700257 else:
258 sizes = []
259
260 dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console,
261 'dfu_dummy.bin', 1024)
262
263 ignore_cleanup_errors = True
264 try:
265 start_dfu()
266
267 u_boot_console.log.action(
268 'Overwriting DFU primary altsetting with dummy data')
269 dfu_write(0, dummy_f.abs_fn)
270
271 for size in sizes:
Stephen Warren3deb8962016-01-26 13:41:31 -0700272 with u_boot_console.log.section('Data size %d' % size):
Stephen Warrenb92b4462016-01-22 12:30:14 -0700273 dfu_write_read_check(size)
274 # Make the status of each sub-test obvious. If the test didn't
275 # pass, an exception was thrown so this code isn't executed.
276 u_boot_console.log.status_pass('OK')
277 ignore_cleanup_errors = False
278 finally:
279 stop_dfu(ignore_cleanup_errors)