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