Skip to content

Commit 7ff689b

Browse files
Add USB mass storage test
1 parent 0cd9d24 commit 7ff689b

File tree

5 files changed

+854
-1
lines changed

5 files changed

+854
-1
lines changed

TESTS/host_tests/pyusb_msd.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
"""
2+
Copyright (c) 2019, Arm Limited and affiliates.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
"""
17+
18+
from mbed_host_tests import BaseHostTest
19+
import time
20+
import psutil
21+
import tempfile
22+
import uuid
23+
import os
24+
import platform
25+
import subprocess
26+
import sys
27+
system_name = platform.system()
28+
if system_name == "Windows":
29+
import wmi
30+
31+
32+
class PyusbMSDTest(BaseHostTest):
33+
"""Host side test for USB MSD class."""
34+
35+
__result = None
36+
MOUNT_WAIT_TIME = 25 # in [s]
37+
initial_disk_list = None
38+
msd_disk = None
39+
serial_number = None
40+
41+
def _callback_device_ready(self, key, value, timestamp):
42+
"""Send a unique USB SN to the device.
43+
DUT uses this SN every time it connects to host as a USB device.
44+
"""
45+
self.serial_number = uuid.uuid4().hex # 32 hex digit string
46+
self.send_kv("serial_number", self.serial_number)
47+
48+
def _callback_check_file_exist(self, key, value, timestamp):
49+
"""Check if file exist.
50+
51+
"""
52+
folder_name, file_name, file_content = value.split(' ')
53+
msd_disk = MSDUtils.disk_path(self.serial_number)
54+
file_path = os.path.join(msd_disk, folder_name, file_name)
55+
try:
56+
file = open(file_path, 'r')
57+
line = file.readline()
58+
file.close()
59+
time.sleep(2) # wait for msd communication done
60+
if line == file_content:
61+
self.send_kv("exist", "0")
62+
return
63+
self.report_error("file content invalid")
64+
except IOError as err:
65+
self.log('{} !!!'.format(err))
66+
self.send_kv("non-exist", "0")
67+
68+
def _callback_delete_files(self, key, value, timestamp):
69+
"""Delete test file.
70+
71+
"""
72+
dir_name, file_name = value.split(' ')
73+
msd_disk = MSDUtils.disk_path(self.serial_number)
74+
try:
75+
os.remove(os.path.join(msd_disk, dir_name, file_name))
76+
except:
77+
self.report_error("delete files")
78+
return
79+
time.sleep(2) # wait for msd communication done
80+
self.report_success()
81+
82+
def _callback_check_if_mounted(self, key, value, timestamp):
83+
"""Check if disk was mounted.
84+
85+
"""
86+
wait_time = self.MOUNT_WAIT_TIME
87+
while wait_time != 0:
88+
msd_disk = MSDUtils.disk_path(self.serial_number)
89+
if msd_disk is not None:
90+
# MSD disk found
91+
time.sleep(2) # wait for msd communication done
92+
self.report_success()
93+
return
94+
wait_time -= 1
95+
time.sleep(1) # wait 1s and try again
96+
self.report_error("mount check")
97+
98+
def _callback_check_if_not_mounted(self, key, value, timestamp):
99+
"""Check if disk was unmouted.
100+
101+
"""
102+
wait_time = self.MOUNT_WAIT_TIME
103+
while wait_time != 0:
104+
msd_disk = MSDUtils.disk_path(self.serial_number)
105+
if msd_disk is None:
106+
#self.msd_disk = None
107+
time.sleep(2) # wait for msd communication done
108+
self.report_success()
109+
return
110+
wait_time -= 1
111+
time.sleep(1) # wait 1s and try again
112+
self.report_error("unmount check")
113+
114+
def _callback_get_mounted_fs_size(self, key, value, timestamp):
115+
"""Record visible filesystem size.
116+
117+
"""
118+
stats = psutil.disk_usage(MSDUtils.disk_path(self.serial_number))
119+
self.send_kv("{}".format(stats.total), "0")
120+
121+
def _callback_unmount(self, key, value, timestamp):
122+
"""Disk unmount.
123+
124+
"""
125+
if MSDUtils.unmount(serial=self.serial_number):
126+
self.report_success()
127+
else:
128+
self.report_error("unmount")
129+
130+
def setup(self):
131+
self.register_callback("get_serial_number", self._callback_device_ready)
132+
self.register_callback('check_if_mounted', self._callback_check_if_mounted)
133+
self.register_callback('check_if_not_mounted', self._callback_check_if_not_mounted)
134+
self.register_callback('get_mounted_fs_size', self._callback_get_mounted_fs_size)
135+
self.register_callback('check_file_exist', self._callback_check_file_exist)
136+
self.register_callback('delete_files', self._callback_delete_files)
137+
self.register_callback('unmount', self._callback_unmount)
138+
139+
def report_success(self):
140+
self.send_kv("passed", "0")
141+
142+
def report_error(self, msg):
143+
self.log('{} failed !!!'.format(msg))
144+
self.send_kv("failed", "0")
145+
146+
def result(self):
147+
return self.__result
148+
149+
def teardown(self):
150+
pass
151+
152+
153+
class MSDUtils(object):
154+
155+
@staticmethod
156+
def disk_path(serial):
157+
system_name = platform.system()
158+
if system_name == "Windows":
159+
return MSDUtils._disk_path_windows(serial)
160+
elif system_name == "Linux":
161+
return MSDUtils._disk_path_linux(serial)
162+
elif system_name == "Darwin":
163+
return MSDUtils._disk_path_mac(serial)
164+
return None
165+
166+
@staticmethod
167+
def unmount(serial):
168+
system_name = platform.system()
169+
if system_name == "Windows":
170+
return MSDUtils._unmount_windows(serial)
171+
elif system_name == "Linux":
172+
return MSDUtils._unmount_linux(serial)
173+
elif system_name == "Darwin":
174+
return MSDUtils._unmount_mac(serial)
175+
return False
176+
177+
@staticmethod
178+
def _disk_path_windows(serial):
179+
serial_decoded = serial.encode("ascii")
180+
c = wmi.WMI()
181+
for physical_disk in c.Win32_DiskDrive():
182+
if serial_decoded == physical_disk.SerialNumber:
183+
for partition in physical_disk.associators("Win32_DiskDriveToDiskPartition"):
184+
for logical_disk in partition.associators("Win32_LogicalDiskToPartition"):
185+
return logical_disk.Caption
186+
return None
187+
188+
@staticmethod
189+
def _disk_path_linux(serial):
190+
output = subprocess.check_output(['lsblk', '-dnoserial,mountpoint']).split('\n')
191+
for line in output:
192+
serial_and_mount_point = line.split()
193+
if len(serial_and_mount_point) == 2:
194+
if serial_and_mount_point[0] == str(serial):
195+
return serial_and_mount_point[1]
196+
return None
197+
198+
@staticmethod
199+
def _disk_path_mac(serial):
200+
# TODO:
201+
# add implementation
202+
return None
203+
204+
@staticmethod
205+
def _unmount_windows(serial):
206+
disk_path = MSDUtils._disk_path_windows(serial)
207+
tmp_file = tempfile.NamedTemporaryFile(suffix='.ps1', delete=False)
208+
try:
209+
# create unmount script
210+
tmp_file.write('$disk_leter=$args[0]\n')
211+
tmp_file.write('$driveEject = New-Object -comObject Shell.Application\n')
212+
tmp_file.write('$driveEject.Namespace(17).ParseName($disk_leter).InvokeVerb("Eject")\n')
213+
# close to allow open by other process
214+
tmp_file.close()
215+
216+
try_count = 10
217+
while try_count:
218+
p = subprocess.Popen(["powershell.exe", tmp_file.name + " " + disk_path], stdout=sys.stdout)
219+
p.communicate()
220+
try_count -= 1
221+
if MSDUtils._disk_path_windows(serial) is None:
222+
return True
223+
time.sleep(1)
224+
finally:
225+
os.remove(tmp_file.name)
226+
227+
return False
228+
229+
@staticmethod
230+
def _unmount_linux(serial):
231+
disk_path = MSDUtils._disk_path_linux(serial)
232+
os.system("umount " + disk_path)
233+
return MSDUtils._disk_path_linux(serial) is None
234+
235+
@staticmethod
236+
def _unmount_mac(serial):
237+
disk_path = MSDUtils._disk_path_mac(serial)
238+
os.system("diskutil unmount " + disk_path)
239+
disks = set(MSDUtils._disks_mac())
240+
return MSDUtils._disk_path_mac(serial) is None

TESTS/usb_device/msd/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# USB mass storage test user guide
2+
3+
To run the tests-usb_device-msd test device with at least *70kB* of RAM is required.
4+
Test creates 64kB `HeapBlockDevice` as block device and mounts FAT32 filesystem on it.
5+
64kB block device is the smallest one that can mount FAT32 filesystem.
6+
7+
Test can be easily extended to use any block device available in Mbed
8+
9+
Test run command:
10+
```bash
11+
mbed test -t COMPILER -m TARGET -n tests-usb_device-msd
12+
```

TESTS/usb_device/msd/TestUSBMSD.h

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright (c) 2019, Arm Limited and affiliates.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#ifndef Test_USBMSD_H
18+
#define Test_USBMSD_H
19+
20+
#include "USBMSD.h"
21+
22+
23+
#define USB_DEV_SN_LEN (32) // 32 hex digit UUID
24+
#define USB_DEV_SN_DESC_SIZE (USB_DEV_SN_LEN * 2 + 2)
25+
26+
/**
27+
* Convert a C style ASCII to a USB string descriptor
28+
*
29+
* @param usb_desc output buffer for the USB string descriptor
30+
* @param str ASCII string
31+
* @param n size of usb_desc buffer, even number
32+
* @returns number of bytes returned in usb_desc or -1 on failure
33+
*/
34+
int ascii2usb_string_desc(uint8_t *usb_desc, const char *str, size_t n)
35+
{
36+
if (str == NULL || usb_desc == NULL || n < 4) {
37+
return -1;
38+
}
39+
if (n % 2 != 0) {
40+
return -1;
41+
}
42+
size_t s, d;
43+
// set bString (@ offset 2 onwards) as a UNICODE UTF-16LE string
44+
memset(usb_desc, 0, n);
45+
for (s = 0, d = 2; str[s] != '\0' && d < n; s++, d += 2) {
46+
usb_desc[d] = str[s];
47+
}
48+
// set bLength @ offset 0
49+
usb_desc[0] = d;
50+
// set bDescriptorType @ offset 1
51+
usb_desc[1] = STRING_DESCRIPTOR;
52+
return d;
53+
}
54+
55+
class TestUSBMSD: public USBMSD {
56+
public:
57+
TestUSBMSD(BlockDevice *bd, bool connect_blocking = true, uint16_t vendor_id = 0x0703, uint16_t product_id = 0x0104,
58+
uint16_t product_release = 0x0001)
59+
: USBMSD(bd, connect_blocking, vendor_id, product_id, product_release)
60+
{
61+
62+
}
63+
64+
virtual ~TestUSBMSD()
65+
{
66+
67+
}
68+
69+
uint32_t get_read_counter()
70+
{
71+
return read_counter;
72+
}
73+
74+
uint32_t get_program_counter()
75+
{
76+
return program_counter;
77+
}
78+
79+
void reset_counters()
80+
{
81+
read_counter = program_counter = erase_counter = 0;
82+
}
83+
84+
static void setup_serial_number()
85+
{
86+
char _key[128] = { 0 };
87+
char _value[128] = { 0 };
88+
89+
greentea_send_kv("get_serial_number", 0);
90+
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
91+
TEST_ASSERT_EQUAL_STRING("serial_number", _key);
92+
usb_dev_sn[USB_DEV_SN_LEN] = '\0';
93+
memcpy(usb_dev_sn, _value, USB_DEV_SN_LEN);
94+
ascii2usb_string_desc(_serial_num_descriptor, usb_dev_sn, USB_DEV_SN_DESC_SIZE);
95+
}
96+
97+
virtual const uint8_t *string_iserial_desc()
98+
{
99+
return (const uint8_t *)_serial_num_descriptor;
100+
}
101+
102+
static volatile uint32_t read_counter;
103+
static volatile uint32_t program_counter;
104+
static volatile uint32_t erase_counter;
105+
106+
protected:
107+
virtual int disk_read(uint8_t *data, uint64_t block, uint8_t count)
108+
{
109+
read_counter++;
110+
return USBMSD::disk_read(data, block, count);
111+
}
112+
113+
virtual int disk_write(const uint8_t *data, uint64_t block, uint8_t count)
114+
{
115+
erase_counter++;
116+
program_counter++;
117+
118+
return USBMSD::disk_write(data, block, count);
119+
}
120+
private:
121+
static uint8_t _serial_num_descriptor[USB_DEV_SN_DESC_SIZE];
122+
static char usb_dev_sn[USB_DEV_SN_LEN + 1];
123+
};
124+
125+
uint8_t TestUSBMSD::_serial_num_descriptor[USB_DEV_SN_DESC_SIZE] = { 0 };
126+
char TestUSBMSD::usb_dev_sn[USB_DEV_SN_LEN + 1] = { 0 };
127+
128+
129+
volatile uint32_t TestUSBMSD::read_counter = 0;
130+
volatile uint32_t TestUSBMSD::program_counter = 0;
131+
volatile uint32_t TestUSBMSD::erase_counter = 0;
132+
133+
#endif // Test_USBMSD_H

0 commit comments

Comments
 (0)