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