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