xref: /openbmc/u-boot/test/py/tests/test_ums.py (revision d054f4c2)
1# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
2#
3# SPDX-License-Identifier: GPL-2.0
4
5# Test U-Boot's "ums" command. The test starts UMS in U-Boot, waits for USB
6# device enumeration on the host, reads a small block of data from the UMS
7# block device, optionally mounts a partition and performs filesystem-based
8# read/write tests, and finally aborts the "ums" command in U-Boot.
9
10import os
11import os.path
12import pytest
13import re
14import time
15import u_boot_utils
16
17'''
18Note: This test relies on:
19
20a) boardenv_* to contain configuration values to define which USB ports are
21available for testing. Without this, this test will be automatically skipped.
22For example:
23
24# Leave this list empty if you have no block_devs below with writable
25# partitions defined.
26env__mount_points = (
27    "/mnt/ubtest-mnt-p2371-2180-na",
28)
29
30env__usb_dev_ports = (
31    {
32        "tgt_usb_ctlr": "0",
33        "host_ums_dev_node": "/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0",
34    },
35)
36
37env__block_devs = (
38    # eMMC; always present
39    {
40        "type": "mmc",
41        "id": "0",
42        # The following two properties are optional.
43        # If present, the partition will be mounted and a file written-to and
44        # read-from it. If missing, only a simple block read test will be
45        # performed.
46        "writable_fs_partition": 1,
47        "writable_fs_subdir": "tmp/",
48    },
49    # SD card; present since I plugged one in
50    {
51        "type": "mmc",
52        "id": "1"
53    },
54)
55
56b) udev rules to set permissions on devices nodes, so that sudo is not
57required. For example:
58
59ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
60
61(You may wish to change the group ID instead of setting the permissions wide
62open. All that matters is that the user ID running the test can access the
63device.)
64
65c) /etc/fstab entries to allow the block device to be mounted without requiring
66root permissions. For example:
67
68/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0-part1 /mnt/ubtest-mnt-p2371-2180-na ext4 noauto,user,nosuid,nodev
69
70This entry is only needed if any block_devs above contain a
71writable_fs_partition value.
72'''
73
74@pytest.mark.buildconfigspec('cmd_usb_mass_storage')
75def test_ums(u_boot_console, env__usb_dev_port, env__block_devs):
76    '''Test the "ums" command; the host system must be able to enumerate a UMS
77    device when "ums" is running, block and optionally file I/O are tested,
78    and this device must disappear when "ums" is aborted.
79
80    Args:
81        u_boot_console: A U-Boot console connection.
82        env__usb_dev_port: The single USB device-mode port specification on
83            which to run the test. See the file-level comment above for
84            details of the format.
85        env__block_devs: The list of block devices that the target U-Boot
86            device has attached. See the file-level comment above for details
87            of the format.
88
89    Returns:
90        Nothing.
91    '''
92
93    have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0]
94    if not have_writable_fs_partition:
95        # If 'writable_fs_subdir' is missing, we'll skip all parts of the
96        # testing which mount filesystems.
97        u_boot_console.log.warning(
98            'boardenv missing "writable_fs_partition"; ' +
99            'UMS testing will be limited.')
100
101    tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr']
102    host_ums_dev_node = env__usb_dev_port['host_ums_dev_node']
103
104    # We're interested in testing USB device mode on each port, not the cross-
105    # product of that with each device. So, just pick the first entry in the
106    # device list here. We'll test each block device somewhere else.
107    tgt_dev_type = env__block_devs[0]['type']
108    tgt_dev_id = env__block_devs[0]['id']
109    if have_writable_fs_partition:
110        mount_point = u_boot_console.config.env['env__mount_points'][0]
111        mount_subdir = env__block_devs[0]['writable_fs_subdir']
112        part_num = env__block_devs[0]['writable_fs_partition']
113        host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num)
114    else:
115        host_ums_part_node = host_ums_dev_node
116
117    test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin',
118        1024 * 1024);
119    if have_writable_fs_partition:
120        mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn
121
122    def start_ums():
123        '''Start U-Boot's ums shell command.
124
125        This also waits for the host-side USB enumeration process to complete.
126
127        Args:
128            None.
129
130        Returns:
131            Nothing.
132        '''
133
134        u_boot_console.log.action(
135            'Starting long-running U-Boot ums shell command')
136        cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id)
137        u_boot_console.run_command(cmd, wait_for_prompt=False)
138        u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]'))
139        fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node)
140        u_boot_console.log.action('Reading raw data from UMS device')
141        fh.read(4096)
142        fh.close()
143
144    def mount():
145        '''Mount the block device that U-Boot exports.
146
147        Args:
148            None.
149
150        Returns:
151            Nothing.
152        '''
153
154        u_boot_console.log.action('Mounting exported UMS device')
155        cmd = ('/bin/mount', host_ums_part_node)
156        u_boot_utils.run_and_log(u_boot_console, cmd)
157
158    def umount(ignore_errors):
159        '''Unmount the block device that U-Boot exports.
160
161        Args:
162            ignore_errors: Ignore any errors. This is useful if an error has
163                already been detected, and the code is performing best-effort
164                cleanup. In this case, we do not want to mask the original
165                error by "honoring" any new errors.
166
167        Returns:
168            Nothing.
169        '''
170
171        u_boot_console.log.action('Unmounting UMS device')
172        cmd = ('/bin/umount', host_ums_part_node)
173        u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors)
174
175    def stop_ums(ignore_errors):
176        '''Stop U-Boot's ums shell command from executing.
177
178        This also waits for the host-side USB de-enumeration process to
179        complete.
180
181        Args:
182            ignore_errors: Ignore any errors. This is useful if an error has
183                already been detected, and the code is performing best-effort
184                cleanup. In this case, we do not want to mask the original
185                error by "honoring" any new errors.
186
187        Returns:
188            Nothing.
189        '''
190
191        u_boot_console.log.action(
192            'Stopping long-running U-Boot ums shell command')
193        u_boot_console.ctrlc()
194        u_boot_utils.wait_until_file_open_fails(host_ums_part_node,
195            ignore_errors)
196
197    ignore_cleanup_errors = True
198    try:
199        start_ums()
200        if not have_writable_fs_partition:
201            # Skip filesystem-based testing if not configured
202            return
203        try:
204            mount()
205            u_boot_console.log.action('Writing test file via UMS')
206            cmd = ('rm', '-f', mounted_test_fn)
207            u_boot_utils.run_and_log(u_boot_console, cmd)
208            if os.path.exists(mounted_test_fn):
209                raise Exception('Could not rm target UMS test file')
210            cmd = ('cp', test_f.abs_fn, mounted_test_fn)
211            u_boot_utils.run_and_log(u_boot_console, cmd)
212            ignore_cleanup_errors = False
213        finally:
214            umount(ignore_errors=ignore_cleanup_errors)
215    finally:
216        stop_ums(ignore_errors=ignore_cleanup_errors)
217
218    ignore_cleanup_errors = True
219    try:
220        start_ums()
221        try:
222            mount()
223            u_boot_console.log.action('Reading test file back via UMS')
224            read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn)
225            cmd = ('rm', '-f', mounted_test_fn)
226            u_boot_utils.run_and_log(u_boot_console, cmd)
227            ignore_cleanup_errors = False
228        finally:
229            umount(ignore_errors=ignore_cleanup_errors)
230    finally:
231        stop_ums(ignore_errors=ignore_cleanup_errors)
232
233    written_hash = test_f.content_hash
234    assert(written_hash == read_back_hash)
235