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