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