blob: cc4b8d8e04e615734a71fe7444c1fc5488615da3 [file] [log] [blame]
Stephen Warrenf5d196d2016-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
15'''
16Note: 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 {
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
32env__dfu_configs = (
33 # eMMC, partition 1
34 {
35 "alt_info": "/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1",
36 "cmd_params": "mmc 0",
37 },
38)
39b) udev rules to set permissions on devices nodes, so that sudo is not
40required. For example:
41
42ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
43
44(You may wish to change the group ID instead of setting the permissions wide
45open. All that matters is that the user ID running the test can access the
46device.)
47'''
48
49# The set of file sizes to test. These values trigger various edge-cases such
50# as one less than, equal to, and one greater than typical USB max packet
51# sizes, and similar boundary conditions.
52test_sizes = (
53 64 - 1,
54 64,
55 64 + 1,
56 128 - 1,
57 128,
58 128 + 1,
59 960 - 1,
60 960,
61 960 + 1,
62 4096 - 1,
63 4096,
64 4096 + 1,
65 1024 * 1024 - 1,
66 1024 * 1024,
67 8 * 1024 * 1024,
68)
69
70first_usb_dev_port = None
71
72@pytest.mark.buildconfigspec('cmd_dfu')
73def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
74 '''Test the "dfu" command; the host system must be able to enumerate a USB
75 device when "dfu" is running, various DFU transfers are tested, and the
76 USB device must disappear when "dfu" is aborted.
77
78 Args:
79 u_boot_console: A U-Boot console connection.
80 env__usb_dev_port: The single USB device-mode port specification on
81 which to run the test. See the file-level comment above for
82 details of the format.
83 env__dfu_config: The single DFU (memory region) configuration on which
84 to run the test. See the file-level comment above for details
85 of the format.
86
87 Returns:
88 Nothing.
89 '''
90
91 def start_dfu():
92 '''Start U-Boot's dfu shell command.
93
94 This also waits for the host-side USB enumeration process to complete.
95
96 Args:
97 None.
98
99 Returns:
100 Nothing.
101 '''
102
103 u_boot_console.log.action(
104 'Starting long-running U-Boot dfu shell command')
105
106 cmd = 'setenv dfu_alt_info "%s"' % env__dfu_config['alt_info']
107 u_boot_console.run_command(cmd)
108
109 cmd = 'dfu 0 ' + env__dfu_config['cmd_params']
110 u_boot_console.run_command(cmd, wait_for_prompt=False)
111 u_boot_console.log.action('Waiting for DFU USB device to appear')
112 fh = u_boot_utils.wait_until_open_succeeds(
113 env__usb_dev_port['host_usb_dev_node'])
114 fh.close()
115
116 def stop_dfu(ignore_errors):
117 '''Stop U-Boot's dfu shell command from executing.
118
119 This also waits for the host-side USB de-enumeration process to
120 complete.
121
122 Args:
123 ignore_errors: Ignore any errors. This is useful if an error has
124 already been detected, and the code is performing best-effort
125 cleanup. In this case, we do not want to mask the original
126 error by "honoring" any new errors.
127
128 Returns:
129 Nothing.
130 '''
131
132 try:
133 u_boot_console.log.action(
134 'Stopping long-running U-Boot dfu shell command')
135 u_boot_console.ctrlc()
136 u_boot_console.log.action(
137 'Waiting for DFU USB device to disappear')
138 u_boot_utils.wait_until_file_open_fails(
139 env__usb_dev_port['host_usb_dev_node'], ignore_errors)
140 except:
141 if not ignore_errors:
142 raise
143
144 def run_dfu_util(alt_setting, fn, up_dn_load_arg):
145 '''Invoke dfu-util on the host.
146
147 Args:
148 alt_setting: The DFU "alternate setting" identifier to interact
149 with.
150 fn: The host-side file name to transfer.
151 up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or
152 download operation should be performed.
153
154 Returns:
155 Nothing.
156 '''
157
158 cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn]
159 if 'host_usb_port_path' in env__usb_dev_port:
160 cmd += ['-p', env__usb_dev_port['host_usb_port_path']]
161 u_boot_utils.run_and_log(u_boot_console, cmd)
162 u_boot_console.wait_for('Ctrl+C to exit ...')
163
164 def dfu_write(alt_setting, fn):
165 '''Write a file to the target board using DFU.
166
167 Args:
168 alt_setting: The DFU "alternate setting" identifier to interact
169 with.
170 fn: The host-side file name to transfer.
171
172 Returns:
173 Nothing.
174 '''
175
176 run_dfu_util(alt_setting, fn, '-D')
177
178 def dfu_read(alt_setting, fn):
179 '''Read a file from the target board using DFU.
180
181 Args:
182 alt_setting: The DFU "alternate setting" identifier to interact
183 with.
184 fn: The host-side file name to transfer.
185
186 Returns:
187 Nothing.
188 '''
189
190 # dfu-util fails reads/uploads if the host file already exists
191 if os.path.exists(fn):
192 os.remove(fn)
193 run_dfu_util(alt_setting, fn, '-U')
194
195 def dfu_write_read_check(size):
196 '''Test DFU transfers of a specific size of data
197
198 This function first writes data to the board then reads it back and
199 compares the written and read back data. Measures are taken to avoid
200 certain types of false positives.
201
202 Args:
203 size: The data size to test.
204
205 Returns:
206 Nothing.
207 '''
208
209 test_f = u_boot_utils.PersistentRandomFile(u_boot_console,
210 'dfu_%d.bin' % size, size)
211 readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin'
212
213 u_boot_console.log.action('Writing test data to DFU primary ' +
214 'altsetting')
215 dfu_write(0, test_f.abs_fn)
216
217 u_boot_console.log.action('Writing dummy data to DFU secondary ' +
218 'altsetting to clear DFU buffers')
219 dfu_write(1, dummy_f.abs_fn)
220
221 u_boot_console.log.action('Reading DFU primary altsetting for ' +
222 'comparison')
223 dfu_read(0, readback_fn)
224
225 u_boot_console.log.action('Comparing written and read data')
226 written_hash = test_f.content_hash
227 read_back_hash = u_boot_utils.md5sum_file(readback_fn, size)
228 assert(written_hash == read_back_hash)
229
230 # This test may be executed against multiple USB ports. The test takes a
231 # long time, so we don't want to do the whole thing each time. Instead,
232 # execute the full test on the first USB port, and perform a very limited
233 # test on other ports. In the limited case, we solely validate that the
234 # host PC can enumerate the U-Boot USB device.
235 global first_usb_dev_port
236 if not first_usb_dev_port:
237 first_usb_dev_port = env__usb_dev_port
238 if env__usb_dev_port == first_usb_dev_port:
239 sizes = test_sizes
240 else:
241 sizes = []
242
243 dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console,
244 'dfu_dummy.bin', 1024)
245
246 ignore_cleanup_errors = True
247 try:
248 start_dfu()
249
250 u_boot_console.log.action(
251 'Overwriting DFU primary altsetting with dummy data')
252 dfu_write(0, dummy_f.abs_fn)
253
254 for size in sizes:
255 with u_boot_console.log.section("Data size %d" % size):
256 dfu_write_read_check(size)
257 # Make the status of each sub-test obvious. If the test didn't
258 # pass, an exception was thrown so this code isn't executed.
259 u_boot_console.log.status_pass('OK')
260 ignore_cleanup_errors = False
261 finally:
262 stop_dfu(ignore_cleanup_errors)