mirror of https://github.com/ARMmbed/mbed-os.git
496 lines
15 KiB
C++
496 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2019-2020, Arm Limited and affiliates.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#if !USB_DEVICE_TESTS
|
|
#error [NOT_SUPPORTED] usb device tests not enabled
|
|
#else
|
|
|
|
#if !defined(MBED_CONF_RTOS_PRESENT)
|
|
#error [NOT_SUPPORTED] USB stack and test cases require RTOS to run.
|
|
#else
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h> /* srand, rand */
|
|
#include "greentea-client/test_env.h"
|
|
#include "unity/unity.h"
|
|
#include "utest/utest.h"
|
|
|
|
#include "mbed.h"
|
|
#include "USBMSD.h"
|
|
#include "TestUSBMSD.h"
|
|
|
|
#include "HeapBlockDevice.h"
|
|
#include "FATFileSystem.h"
|
|
|
|
// TARGET_NANO100 SRAM 16KB can't afford mass-storage-disk test, so skip usb_msd_test.
|
|
#if !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE || TARGET_NANO100
|
|
#error [NOT_SUPPORTED] USB Device not supported for this target
|
|
#else
|
|
|
|
|
|
#define OS_WINDOWS 1
|
|
#define OS_LINUX 2
|
|
#define OS_MAC 3
|
|
|
|
// Host side unmount was disabled for windows machines.
|
|
// PowerShell execution policies/restrictions cause that
|
|
// on some windows machines unmount is failing
|
|
// To re-enable it comment out below line.
|
|
#define DISABLE_HOST_SIDE_UMOUNT
|
|
|
|
#ifdef MIN
|
|
#undef MIN
|
|
#endif
|
|
#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
|
|
|
|
#define DEFAULT_BLOCK_SIZE 512
|
|
#define HEAP_BLOCK_DEVICE_SIZE (128 * DEFAULT_BLOCK_SIZE)
|
|
#define MIN_HEAP_SIZE (HEAP_BLOCK_DEVICE_SIZE + 6144)
|
|
|
|
|
|
/* TODO:
|
|
*
|
|
* Test if slave(DUT) can force host to refresh mounted fs. (not supported in USBMSD yet)
|
|
*
|
|
*/
|
|
|
|
#define TEST_DIR "usb_msd_test_data"
|
|
#define TEST_FILE "usb_msd_test_file"
|
|
#define TEST_STRING "usb_msd_test_string"
|
|
|
|
using namespace utest::v1;
|
|
|
|
uint32_t prev_read_counter = 0;
|
|
uint32_t prev_program_counter = 0;
|
|
extern uint32_t mbed_heap_size;
|
|
|
|
static char _key[256] = { 0 };
|
|
static char _value[128] = { 0 };
|
|
|
|
static volatile bool msd_process_done = false;
|
|
|
|
|
|
FATFileSystem heap_fs("heap_fs");
|
|
|
|
Semaphore media_remove_event(0, 1);
|
|
|
|
|
|
/** Creates heap block device
|
|
*
|
|
*/
|
|
BlockDevice *get_heap_block_device()
|
|
{
|
|
// create 64kB heap block device
|
|
if (mbed_heap_size >= MIN_HEAP_SIZE) {
|
|
static HeapBlockDevice bd(128 * DEFAULT_BLOCK_SIZE, DEFAULT_BLOCK_SIZE);
|
|
bd.init();
|
|
return &bd;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
uint64_t get_fs_mount_size(FileSystem *fs)
|
|
{
|
|
struct statvfs stat;
|
|
fs->statvfs(fs->getName(), &stat);
|
|
uint64_t size = stat.f_bsize * stat.f_blocks;
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Create test data
|
|
*
|
|
* @param fs_root filesystem path
|
|
*/
|
|
static bool test_files_create(const char *fs_root, const char *test_file = TEST_FILE, const char *test_string = TEST_STRING)
|
|
{
|
|
char path[128];
|
|
sprintf(path, "/%s/%s", fs_root, TEST_DIR);
|
|
int ret = mkdir(path, 0777);
|
|
if (ret != 0 && errno != EEXIST) {
|
|
utest_printf("mkdir failed!!! errno: %d\n", errno);
|
|
return false;
|
|
}
|
|
sprintf(path, "/%s/%s/%s", fs_root, TEST_DIR, test_file);
|
|
FILE *f = fopen(path, "w");
|
|
if (f == NULL) {
|
|
utest_printf("fopen failed!!! errno: %d\n", errno);
|
|
return false;
|
|
}
|
|
fprintf(f, test_string);
|
|
fflush(f);
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Remove test data
|
|
*
|
|
* @param fs_root filesystem path
|
|
*/
|
|
static void test_files_remove(const char *fs_root)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *dp;
|
|
char path[512];
|
|
|
|
sprintf(path, "/%s/%s", fs_root, TEST_DIR);
|
|
dir = opendir(path);
|
|
if (dir == NULL) {
|
|
return;
|
|
}
|
|
while ((dp = readdir(dir)) != NULL) {
|
|
sprintf(path, "/%s/%s/%s", fs_root, TEST_DIR, dp->d_name);
|
|
remove(path);
|
|
}
|
|
sprintf(path, "/%s/%s", fs_root, TEST_DIR);
|
|
remove(path);
|
|
}
|
|
|
|
/**
|
|
* Check if test data exist
|
|
*
|
|
* @param fs_root filesystem path
|
|
* @return true if data exist
|
|
*/
|
|
static bool test_files_exist(const char *fs_root, const char *test_file = TEST_FILE, const char *test_string = TEST_STRING)
|
|
{
|
|
char path[128];
|
|
char str[512] = { 0 };
|
|
sprintf(path, "/%s/%s/%s", fs_root, TEST_DIR, test_file);
|
|
FILE *f = fopen(path, "r");
|
|
if (f != NULL) {
|
|
fscanf(f, "%s", str);
|
|
if (strcmp(test_string, str) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Mounts a filesystem to a block device
|
|
*
|
|
* @param bd block device
|
|
* @param fs filesystem
|
|
* @return true if success, false otherwise
|
|
*/
|
|
static bool prepare_storage(BlockDevice *bd, FileSystem *fs)
|
|
{
|
|
const char *fs_root = fs->getName();
|
|
int err = fs->mount(bd);
|
|
if (err) {
|
|
utest_printf("%s filesystem mount failed\ntry to reformat device... ", fs->getName());
|
|
err = fs->reformat(bd);
|
|
if (err) {
|
|
utest_printf("failed !!!\n");
|
|
return false;
|
|
} else {
|
|
utest_printf("succeed\n");
|
|
}
|
|
}
|
|
// remove old test data
|
|
test_files_remove(fs_root);
|
|
|
|
return true;
|
|
}
|
|
|
|
void run_processing(Semaphore *sem)
|
|
{
|
|
sem->release();
|
|
}
|
|
|
|
void msd_process(USBMSD *msd)
|
|
{
|
|
Semaphore proc;
|
|
msd->attach(callback(run_processing, &proc));
|
|
while (!msd_process_done) {
|
|
proc.wait(100);
|
|
msd->process();
|
|
if (msd->media_removed()) {
|
|
media_remove_event.release();
|
|
}
|
|
}
|
|
msd->attach(NULL);
|
|
}
|
|
|
|
|
|
// wait until msd negotiation is done (no r/w disk operation for at least 1s)
|
|
// max wait time is 15s
|
|
#define WAIT_MSD_COMMUNICATION_DONE() \
|
|
for (int x = 0; x < 15; x++) { \
|
|
prev_read_counter = usb.get_read_counter();\
|
|
prev_program_counter = usb.get_program_counter();\
|
|
ThisThread::sleep_for(1000);\
|
|
if ((usb.get_read_counter() == prev_read_counter) && \
|
|
(usb.get_program_counter() == prev_program_counter)) {\
|
|
break;\
|
|
}\
|
|
}
|
|
|
|
|
|
#define TEST_ASSERT_EQUAL_STRING_LOOP(expected, actual, loop_index) \
|
|
if (strcmp(expected, actual) != 0) { \
|
|
char str[128]; \
|
|
sprintf(str, "expected %s was %s (loop index: %lu)", expected, actual, loop_index); \
|
|
TEST_ASSERT_MESSAGE(false, str); \
|
|
}
|
|
|
|
#define TEST_ASSERT_EQUAL_LOOP(expected, actual, loop_index) \
|
|
if (expected != actual) { \
|
|
char str[128]; \
|
|
sprintf(str, "expected %d was %d (loop index: %lu)", expected, actual, loop_index); \
|
|
TEST_ASSERT_MESSAGE(false, str); \
|
|
}
|
|
|
|
|
|
/** Initialize storages
|
|
*
|
|
* Given the DUT USB mass storage device
|
|
* When DUT has enought heap memory
|
|
* Then initialize heap block device for tests
|
|
* When DUT has any falsh block device
|
|
* Then initialize it for tests
|
|
*/
|
|
void storage_init()
|
|
{
|
|
TEST_ASSERT_MESSAGE(mbed_heap_size >= MIN_HEAP_SIZE, "Not enough heap memory for HeapBlockDevice creation");
|
|
FATFileSystem::format(get_heap_block_device());
|
|
bool result = prepare_storage(get_heap_block_device(), &heap_fs);
|
|
TEST_ASSERT_MESSAGE(result, "heap storage initialisation failed");
|
|
}
|
|
|
|
/** Test mass storage device mount and unmount
|
|
*
|
|
* Given the DUT USB mass storage device connected to the host
|
|
* When DUT call @USBMSD::connect
|
|
* Then host detects mass storage device is mounted as removable disk drive and it reports valid filesystem size
|
|
* When DUT call @USBMSD::disconnect
|
|
* Then host detects mass storage device is unmounted (ejected)
|
|
*
|
|
* Given the DUT USB mass storage device connected to the host and mounted
|
|
* When host unmounts (ejects) mass storage device
|
|
* Then DUT detects media remove event
|
|
*/
|
|
template <uint32_t N>
|
|
void mount_unmount_test(BlockDevice *bd, FileSystem *fs)
|
|
{
|
|
Thread msd_thread(osPriorityHigh);
|
|
TestUSBMSD usb(bd, false);
|
|
msd_process_done = false;
|
|
msd_thread.start(callback(msd_process, &usb));
|
|
|
|
for (uint32_t i = 1; i <= N; i++) {
|
|
// mount
|
|
usb.connect();
|
|
WAIT_MSD_COMMUNICATION_DONE();
|
|
// check if device is mounted on host side
|
|
greentea_send_kv("check_if_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
|
|
TEST_ASSERT_EQUAL_STRING_LOOP("passed", _key, i);
|
|
|
|
greentea_send_kv("get_mounted_fs_size", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
uint64_t ret_size = atoll(_key);
|
|
TEST_ASSERT_EQUAL_UINT64(get_fs_mount_size(fs), ret_size);
|
|
|
|
// unmount
|
|
usb.disconnect();
|
|
// check if device is detached on host side
|
|
greentea_send_kv("check_if_not_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING_LOOP("passed", _key, i);
|
|
}
|
|
|
|
for (uint32_t i = 1; i <= N; i++) {
|
|
// mount
|
|
usb.connect();
|
|
WAIT_MSD_COMMUNICATION_DONE();
|
|
// check if device is mounted on host side
|
|
greentea_send_kv("check_if_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING_LOOP("passed", _key, i);
|
|
|
|
greentea_send_kv("get_mounted_fs_size", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
uint64_t ret_size = atoll(_key);
|
|
TEST_ASSERT_EQUAL_UINT64(get_fs_mount_size(fs), ret_size);
|
|
|
|
#ifdef DISABLE_HOST_SIDE_UMOUNT
|
|
greentea_send_kv("get_os_type", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
int32_t os_type = atoi(_value);
|
|
if (os_type != OS_WINDOWS) {
|
|
#endif
|
|
// unmount msd device on host side
|
|
greentea_send_kv("unmount", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING_LOOP("passed", _key, i);
|
|
|
|
// wait for unmount event (set 10s timeout)
|
|
media_remove_event.wait(10000);
|
|
if (!usb.media_removed()) {
|
|
TEST_ASSERT_EQUAL_LOOP(true, usb.media_removed(), i);
|
|
}
|
|
|
|
// unmount since media_removed doesn't disconnects device side
|
|
usb.disconnect();
|
|
#ifdef DISABLE_HOST_SIDE_UMOUNT
|
|
} else {
|
|
// unmount
|
|
usb.disconnect();
|
|
}
|
|
#endif
|
|
// check if device is detached on host side
|
|
greentea_send_kv("check_if_not_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING_LOOP("passed", _key, i);
|
|
}
|
|
|
|
// mount
|
|
usb.connect();
|
|
WAIT_MSD_COMMUNICATION_DONE();
|
|
// check if device is mounted on host side
|
|
greentea_send_kv("check_if_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
// unmount
|
|
usb.disconnect();
|
|
// check if device is detached on host side
|
|
greentea_send_kv("check_if_not_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
msd_process_done = true; // terminate msd_thread
|
|
msd_thread.join();
|
|
}
|
|
|
|
/** Test mass storage device mount and unmount together with underlying file system operations
|
|
*
|
|
* Given the DUT USB mass storage device connected to the host
|
|
* When DUT call @USBMSD::connect
|
|
* Then host detects that mass storage device is mounted as removable disk drive and test files are present
|
|
* When DUT call @USBMSD::disconnect
|
|
* Then host detects mass storage device is unmounted (ejected)
|
|
*
|
|
* Given the DUT USB mass storage device connected to the host and already mounted
|
|
* When host unmounts (ejects) mass storage device
|
|
* Then DUT detects media remove event
|
|
*/
|
|
void mount_unmount_and_data_test(BlockDevice *bd, FileSystem *fs)
|
|
{
|
|
const char *fs_root = fs->getName();
|
|
Thread msd_thread(osPriorityHigh);
|
|
TestUSBMSD usb(bd, false);
|
|
msd_process_done = false;
|
|
msd_thread.start(callback(msd_process, &usb));
|
|
|
|
// mount
|
|
usb.connect();
|
|
WAIT_MSD_COMMUNICATION_DONE();
|
|
// check if device is mounted on host side
|
|
greentea_send_kv("check_if_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
greentea_send_kv("check_file_exist", TEST_DIR " " TEST_FILE " " TEST_STRING);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("non-exist", _key);
|
|
|
|
usb.disconnect();
|
|
// check if device is detached on host side
|
|
greentea_send_kv("check_if_not_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
test_files_create(fs_root);
|
|
TEST_ASSERT(test_files_exist(fs_root));
|
|
|
|
usb.connect();
|
|
WAIT_MSD_COMMUNICATION_DONE();
|
|
// check if device is mounted on host side
|
|
greentea_send_kv("check_if_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
greentea_send_kv("check_file_exist", TEST_DIR " " TEST_FILE " " TEST_STRING);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("exist", _key);
|
|
|
|
greentea_send_kv("delete_files", TEST_DIR " " TEST_FILE);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
do {
|
|
wait_ms(1);
|
|
} while (test_files_exist(fs_root));
|
|
TEST_ASSERT_EQUAL(false, test_files_exist(fs_root));
|
|
|
|
usb.disconnect();
|
|
// check if device is detached on host side
|
|
greentea_send_kv("check_if_not_mounted", 0);
|
|
greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value));
|
|
TEST_ASSERT_EQUAL_STRING("passed", _key);
|
|
|
|
msd_process_done = true; // terminate msd_thread
|
|
msd_thread.join();
|
|
test_files_remove(fs_root);
|
|
}
|
|
|
|
void heap_block_device_mount_unmount_test()
|
|
{
|
|
TEST_ASSERT_MESSAGE(mbed_heap_size >= MIN_HEAP_SIZE, "Not enough heap memory for HeapBlockDevice creation");
|
|
mount_unmount_test<3>(get_heap_block_device(), &heap_fs);
|
|
}
|
|
|
|
void heap_block_device_mount_unmount_and_data_test()
|
|
{
|
|
TEST_ASSERT_MESSAGE(mbed_heap_size >= MIN_HEAP_SIZE, "Not enough heap memory for HeapBlockDevice creation");
|
|
mount_unmount_and_data_test(get_heap_block_device(), &heap_fs);
|
|
}
|
|
|
|
|
|
Case cases[] = {
|
|
Case("storage initialization", storage_init),
|
|
|
|
Case("mount/unmount test - Heap block device", heap_block_device_mount_unmount_test),
|
|
Case("mount/unmount and data test - Heap block device", heap_block_device_mount_unmount_and_data_test),
|
|
};
|
|
|
|
utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
|
|
{
|
|
GREENTEA_SETUP(300, "pyusb_msd");
|
|
utest::v1::status_t status = greentea_test_setup_handler(number_of_cases);
|
|
TestUSBMSD::setup_serial_number();
|
|
return status;
|
|
}
|
|
|
|
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
|
|
|
|
int main()
|
|
{
|
|
Harness::run(specification);
|
|
}
|
|
|
|
#endif // !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE
|
|
#endif // !defined(MBED_CONF_RTOS_PRESENT)
|
|
#endif // !defined(USB_DEVICE_TESTS)
|