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