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