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