/* * Copyright (c) 2018-2020, ARM Limited, All Rights Reserved * 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. */ #include <stdio.h> #include <string.h> #include "mbed.h" #include "greentea-client/test_env.h" #include "unity/unity.h" #include "utest/utest.h" #include "USBTester.h" #include "USBEndpointTester.h" #include "usb_phy_api.h" // TODO // suspend resume test: implement host side USB suspend/resume // sync frame test: add test on isochronous endpoint // Uncomment/remove this when host suspend_resume_test part will be implemented //#define SUSPEND_RESUME_TEST_SUPPORTED // If disconnect() + connect() occur too fast the reset event will be dropped. // At a minimum there should be a 200us delay between disconnect and connect. // To be on the safe side I would recommend a 1ms delay, so the host controller // has an entire USB frame to detect the disconnect. #define MIN_DISCONNECT_TIME_US 1000 #if !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE #error [NOT_SUPPORTED] USB Device not supported for this target #else using namespace utest::v1; // Print callback counters for endpoint tests #define EP_DBG 0 static USBPhy *get_phy() { return get_usb_phy(); } /** Control basic tests Test device configuration/deconfiguration Given an initialized USB (HOST <---> DUT connection established) When device configuration is checked just after initialization Then get_configuration returns 1 (default configuration is set) When device is deconfigured Then get_configuration returns 0 (no configuration is set) When each from supported configurations is set Then the configuration is set correctly Test device interface setting Given an initialized USB (HOST <---> DUT connection established) When each altsetting from every supported configuration is set Then the interface altsetting is set correctly Test device/interface/endpoint status Given an initialized USB (HOST <---> DUT connection established) When device status is checked Then status is within allowed values (see status bits description below) When control endpoint status is checked Then control endpoint status is 0 When status of each interface from every supported configuration is checked Then interface status is 0 When status of each endpoint in every allowed device interface/configuration combination is checked Then endpoint status is 0 (not halted) Test set/clear feature on device/interface/endpoint Given an initialized USB (HOST <---> DUT connection established) When for each endpoint in every allowed interface/configuration combination the feature is set and then cleared Then selected feature is set/cleared accordingly Test device/configuration/interface/endpoint descriptors Given an initialized USB (HOST <---> DUT connection established) When device descriptor is read Then the descriptor content is valid When configuration descriptor is read Then the descriptor content is valid When interface descriptor is read Then the error is thrown since it is not directly accessible When endpoint descriptor is read Then the error is thrown since it is not directly accessible Test descriptor setting Given an initialized USB (HOST <---> DUT connection established) When device descriptor is to be set Then error is thrown since descriptor setting command is not supported by Mbed */ void control_basic_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; char str[128] = {}; { USBTester serial(get_phy(), vendor_id, product_id, product_release); sprintf(str, "%s %d %d", serial.get_serial_desc_string(), vendor_id, product_id); greentea_send_kv("control_basic_test", str); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test control endpoint stall on invalid request Given an initialized USB (HOST <---> DUT connection established) When unsupported request to control endpoint is to be sent Then the endpoint is stalled and error is thrown */ void control_stall_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; { USBTester serial(get_phy(), vendor_id, product_id, product_release); greentea_send_kv("control_stall_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test various data sizes in control transfer Given an initialized USB (HOST <---> DUT connection established) When control data in each tested size is sent Then read data should match sent data */ void control_sizes_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; { USBTester serial(get_phy(), vendor_id, product_id, product_release); greentea_send_kv("control_sizes_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test various patterns of control transfers Given an initialized USB (HOST <---> DUT connection established) When stress control transfer with a data in stage is performed Then transfer ends with success When stress control transfer with a data out stage followed by a control transfer with a data in stage is performed Then transfer ends with success When stress control transfer with a data out stage is performed Then transfer ends with success */ void control_stress_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; { USBTester serial(get_phy(), vendor_id, product_id, product_release); greentea_send_kv("control_stress_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test data correctness for every OUT/IN endpoint pair * * Given a USB device with multiple OUT/IN endpoint pairs * When the host sends random payloads up to wMaxPacketSize in size * to an OUT endpoint of the device, * and then the device sends data back to host using an IN endpoint * Then data sent and received by host is equal for every endpoint pair */ void ep_test_data_correctness() { uint16_t vendor_id = 0x0d28; // Use a product ID different than that used in other tests, // to help Windows hosts use the correct configuration descriptor. uint16_t product_id = 0x0206; uint16_t product_release = 0x0001; char _key[11] = { }; char _value[128] = { }; { USBEndpointTester serial(get_phy(), vendor_id, product_id, product_release, false); greentea_send_kv("ep_test_data_correctness", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); #if EP_DBG ThisThread::sleep_for(100); printf("cnt_cb_set_conf = %lu\r\n", serial.get_cnt_cb_set_conf()); printf("cnt_cb_set_intf = %lu\r\n", serial.get_cnt_cb_set_intf()); printf("cnt_cb_bulk_out = %lu\r\n", serial.get_cnt_cb_bulk_out()); printf("cnt_cb_bulk_in = %lu\r\n", serial.get_cnt_cb_bulk_in()); printf("cnt_cb_int_out = %lu\r\n", serial.get_cnt_cb_int_out()); printf("cnt_cb_int_in = %lu\r\n", serial.get_cnt_cb_int_in()); printf("cnt_cb_iso_out = %lu\r\n", serial.get_cnt_cb_iso_out()); printf("cnt_cb_iso_in = %lu\r\n", serial.get_cnt_cb_iso_in()); #endif TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test endpoint halt for every OUT/IN endpoint pair * * Given a USB device with multiple OUT/IN endpoint pairs * When the host issues an endpoint halt control request at a random point * of OUT or IN transfer * Then the endpoint is stalled and all further transfers fail */ void ep_test_halt() { uint16_t vendor_id = 0x0d28; // Use a product ID different than that used in other tests, // to help Windows hosts use the correct configuration descriptor. uint16_t product_id = 0x0206; uint16_t product_release = 0x0001; char _key[11] = { }; char _value[128] = { }; { USBEndpointTester serial(get_phy(), vendor_id, product_id, product_release, false); greentea_send_kv("ep_test_halt", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); #if EP_DBG ThisThread::sleep_for(100); printf("cnt_cb_set_conf = %lu\r\n", serial.get_cnt_cb_set_conf()); printf("cnt_cb_set_intf = %lu\r\n", serial.get_cnt_cb_set_intf()); printf("cnt_cb_bulk_out = %lu\r\n", serial.get_cnt_cb_bulk_out()); printf("cnt_cb_bulk_in = %lu\r\n", serial.get_cnt_cb_bulk_in()); printf("cnt_cb_int_out = %lu\r\n", serial.get_cnt_cb_int_out()); printf("cnt_cb_int_in = %lu\r\n", serial.get_cnt_cb_int_in()); printf("cnt_cb_iso_out = %lu\r\n", serial.get_cnt_cb_iso_out()); printf("cnt_cb_iso_in = %lu\r\n", serial.get_cnt_cb_iso_in()); #endif TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test simultaneous data transfers for multiple OUT/IN endpoint pairs * * Given a USB device with multiple OUT/IN endpoint pairs * When multiple OUT and IN endpoints are used to transfer random data in parallel * Then all transfers succeed * and for every endpoint pair, data received by host equals data sent by host */ void ep_test_parallel_transfers() { uint16_t vendor_id = 0x0d28; // Use a product ID different than that used in other tests, // to help Windows hosts use the correct configuration descriptor. uint16_t product_id = 0x0206; uint16_t product_release = 0x0001; char _key[11] = { }; char _value[128] = { }; { USBEndpointTester serial(get_phy(), vendor_id, product_id, product_release, false); greentea_send_kv("ep_test_parallel_transfers", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); #if EP_DBG ThisThread::sleep_for(100); printf("cnt_cb_set_conf = %lu\r\n", serial.get_cnt_cb_set_conf()); printf("cnt_cb_set_intf = %lu\r\n", serial.get_cnt_cb_set_intf()); printf("cnt_cb_bulk_out = %lu\r\n", serial.get_cnt_cb_bulk_out()); printf("cnt_cb_bulk_in = %lu\r\n", serial.get_cnt_cb_bulk_in()); printf("cnt_cb_int_out = %lu\r\n", serial.get_cnt_cb_int_out()); printf("cnt_cb_int_in = %lu\r\n", serial.get_cnt_cb_int_in()); printf("cnt_cb_iso_out = %lu\r\n", serial.get_cnt_cb_iso_out()); printf("cnt_cb_iso_in = %lu\r\n", serial.get_cnt_cb_iso_in()); #endif TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test simultaneous data transfers in parallel with control transfers * * Given a USB device with multiple OUT/IN endpoint pairs * When multiple OUT and IN endpoints are used to transfer random data * and control requests are processed in parallel * Then all transfers succeed * and for every endpoint pair, data received by host equals data sent by host */ void ep_test_parallel_transfers_ctrl() { uint16_t vendor_id = 0x0d28; // Use a product ID different than that used in other tests, // to help Windows hosts use the correct configuration descriptor. uint16_t product_id = 0x0206; uint16_t product_release = 0x0001; char _key[11] = { }; char _value[128] = { }; { USBEndpointTester serial(get_phy(), vendor_id, product_id, product_release, false); greentea_send_kv("ep_test_parallel_transfers_ctrl", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); #if EP_DBG ThisThread::sleep_for(100); printf("cnt_cb_set_conf = %lu\r\n", serial.get_cnt_cb_set_conf()); printf("cnt_cb_set_intf = %lu\r\n", serial.get_cnt_cb_set_intf()); printf("cnt_cb_bulk_out = %lu\r\n", serial.get_cnt_cb_bulk_out()); printf("cnt_cb_bulk_in = %lu\r\n", serial.get_cnt_cb_bulk_in()); printf("cnt_cb_int_out = %lu\r\n", serial.get_cnt_cb_int_out()); printf("cnt_cb_int_in = %lu\r\n", serial.get_cnt_cb_int_in()); printf("cnt_cb_iso_out = %lu\r\n", serial.get_cnt_cb_iso_out()); printf("cnt_cb_iso_in = %lu\r\n", serial.get_cnt_cb_iso_in()); #endif TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test aborting data transfer for every OUT/IN endpoint pair * * Given a USB device with multiple OUT/IN endpoint pairs * When a device aborts an in progress data transfer * Then no more data is transmitted * and endpoint buffer is correctly released on the device end */ void ep_test_abort() { uint16_t vendor_id = 0x0d28; // Use a product ID different than that used in other tests, // to help Windows hosts use the correct configuration descriptor. uint16_t product_id = 0x0206; uint16_t product_release = 0x0001; char _key[11] = { }; char _value[128] = { }; { USBEndpointTester serial(get_phy(), vendor_id, product_id, product_release, true); greentea_send_kv("ep_test_abort", serial.get_serial_desc_string()); greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); #if EP_DBG ThisThread::sleep_for(100); printf("cnt_cb_set_conf = %lu\r\n", serial.get_cnt_cb_set_conf()); printf("cnt_cb_set_intf = %lu\r\n", serial.get_cnt_cb_set_intf()); printf("cnt_cb_bulk_out = %lu\r\n", serial.get_cnt_cb_bulk_out()); printf("cnt_cb_bulk_in = %lu\r\n", serial.get_cnt_cb_bulk_in()); printf("cnt_cb_int_out = %lu\r\n", serial.get_cnt_cb_int_out()); printf("cnt_cb_int_in = %lu\r\n", serial.get_cnt_cb_int_in()); printf("cnt_cb_iso_out = %lu\r\n", serial.get_cnt_cb_iso_out()); printf("cnt_cb_iso_in = %lu\r\n", serial.get_cnt_cb_iso_in()); #endif TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test data toggle reset for bulk OUT/IN endpoint pairs * * Given a USB device * When an interface is set * Then the data toggle bits for all endpoints are reset to DATA0 * When clear feature is called for an endpoint that *IS NOT* stalled * Then the data toggle is reset to DATA0 for that endpoint * When clear halt is called for an endpoint that *IS* stalled * Then the data toggle is reset to DATA0 for that endpoint */ void ep_test_data_toggle() { uint16_t vendor_id = 0x0d28; // Use a product ID different than that used in other tests, // to help Windows hosts use the correct configuration descriptor. uint16_t product_id = 0x0206; uint16_t product_release = 0x0001; char _key[11] = { }; char _value[128] = { }; { USBEndpointTester serial(get_phy(), vendor_id, product_id, product_release, false); greentea_send_kv("ep_test_data_toggle", serial.get_serial_desc_string()); greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); #if EP_DBG ThisThread::sleep_for(100); printf("cnt_cb_set_conf = %lu\r\n", serial.get_cnt_cb_set_conf()); printf("cnt_cb_set_intf = %lu\r\n", serial.get_cnt_cb_set_intf()); printf("cnt_cb_bulk_out = %lu\r\n", serial.get_cnt_cb_bulk_out()); printf("cnt_cb_bulk_in = %lu\r\n", serial.get_cnt_cb_bulk_in()); printf("cnt_cb_int_out = %lu\r\n", serial.get_cnt_cb_int_out()); printf("cnt_cb_int_in = %lu\r\n", serial.get_cnt_cb_int_in()); printf("cnt_cb_iso_out = %lu\r\n", serial.get_cnt_cb_iso_out()); printf("cnt_cb_iso_in = %lu\r\n", serial.get_cnt_cb_iso_in()); #endif TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test USB implementation against repeated reset Given an initialized USB (HOST <---> DUT connection established) When USB device is reset repeatedly Then the USB is operational with no errors */ void device_reset_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; greentea_send_kv("reset_support", 0); greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); if (strcmp(_value, "false") != 0) { USBTester serial(get_phy(), vendor_id, product_id, product_release); serial.clear_reset_count(); greentea_send_kv("device_reset_test", serial.get_serial_desc_string()); while (serial.get_reset_count() == 0); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); while (!serial.configured()); serial.clear_reset_count(); greentea_send_kv("device_reset_test", serial.get_serial_desc_string()); while (serial.get_reset_count() == 0); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); while (!serial.configured()); serial.clear_reset_count(); greentea_send_kv("device_reset_test", serial.get_serial_desc_string()); while (serial.get_reset_count() == 0); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); while (!serial.configured()); greentea_send_kv("device_reset_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } /** Test USB implementation against repeated reconnection Given an initialized USB (HOST <---> DUT connection established) When USB device is disconnected and then connected repeatedly Then the USB is operational with no errors */ void device_soft_reconnection_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; const uint32_t reconnect_try_count = 3; { USBTester serial(get_phy(), vendor_id, product_id, product_release); greentea_send_kv("device_soft_reconnection_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); for (int i = 0; i < reconnect_try_count; i++) { serial.disconnect(); wait_us(MIN_DISCONNECT_TIME_US); serial.connect(); greentea_send_kv("device_soft_reconnection_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } serial.disconnect(); wait_us(MIN_DISCONNECT_TIME_US); serial.connect(); serial.disconnect(); wait_us(MIN_DISCONNECT_TIME_US); serial.connect(); serial.disconnect(); wait_us(MIN_DISCONNECT_TIME_US); serial.connect(); greentea_send_kv("device_soft_reconnection_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } #if SUSPEND_RESUME_TEST_SUPPORTED /** Test USB implementation against repeated suspend and resume Given an initialized USB (HOST <---> DUT connection established) When USB device is suspended and then resumed repeatedly Then the USB is operational with no errors */ void device_suspend_resume_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; { USBTester serial(get_phy(), vendor_id, product_id, product_release); greentea_send_kv("device_suspend_resume_test", serial.get_serial_desc_string()); printf("[1] suspend_count: %d resume_count: %d\n", serial.get_suspend_count(), serial.get_resume_count()); serial.clear_suspend_count(); serial.clear_resume_count(); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); printf("[2] suspend_count: %d resume_count: %d\n", serial.get_suspend_count(), serial.get_resume_count()); TEST_ASSERT_EQUAL_STRING("pass", _key); ThisThread::sleep_for(5000); printf("[3] suspend_count: %d resume_count: %d\n", serial.get_suspend_count(), serial.get_resume_count()); } } #endif /** Test USB implementation against repeated initialization and deinitialization Given an initialized USB (HOST <---> DUT connection established) When USB device is deinitialized and then initialized repeatedly Then the USB is operational with no errors */ void repeated_construction_destruction_test() { uint16_t vendor_id = 0x0d28; uint16_t product_id = 0x0205; uint16_t product_release = 0x0001; char _key[11] = {}; char _value[128] = {}; { USBTester serial(get_phy(), vendor_id, product_id, product_release); TEST_ASSERT_EQUAL(true, serial.configured()); } wait_us(MIN_DISCONNECT_TIME_US); { USBTester serial(get_phy(), vendor_id, product_id, product_release); TEST_ASSERT_EQUAL(true, serial.configured()); } wait_us(MIN_DISCONNECT_TIME_US); { USBTester serial(get_phy(), vendor_id, product_id, product_release); TEST_ASSERT_EQUAL(true, serial.configured()); } wait_us(MIN_DISCONNECT_TIME_US); { USBTester serial(get_phy(), vendor_id, product_id, product_release); TEST_ASSERT_EQUAL(true, serial.configured()); greentea_send_kv("repeated_construction_destruction_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } wait_us(MIN_DISCONNECT_TIME_US); { USBTester serial(get_phy(), vendor_id, product_id, product_release); TEST_ASSERT_EQUAL(true, serial.configured()); greentea_send_kv("repeated_construction_destruction_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } wait_us(MIN_DISCONNECT_TIME_US); { USBTester serial(get_phy(), vendor_id, product_id, product_release); TEST_ASSERT_EQUAL(true, serial.configured()); greentea_send_kv("repeated_construction_destruction_test", serial.get_serial_desc_string()); // Wait for host before terminating greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); TEST_ASSERT_EQUAL_STRING("pass", _key); } } Case cases[] = { Case("usb control basic test", control_basic_test), Case("usb control stall test", control_stall_test), Case("usb control sizes test", control_sizes_test), Case("usb control stress test", control_stress_test), Case("usb device reset test", device_reset_test), Case("usb soft reconnection test", device_soft_reconnection_test), #if SUSPEND_RESUME_TEST_SUPPORTED Case("usb device suspend/resume test", device_suspend_resume_test), #endif Case("usb repeated construction destruction test", repeated_construction_destruction_test), Case("endpoint test data correctness", ep_test_data_correctness), Case("endpoint test halt", ep_test_halt), Case("endpoint test parallel transfers", ep_test_parallel_transfers), Case("endpoint test parallel transfers ctrl", ep_test_parallel_transfers_ctrl), Case("endpoint test abort", ep_test_abort), Case("endpoint test data toggle reset", ep_test_data_toggle) }; utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { GREENTEA_SETUP(180, "pyusb_basic"); return greentea_test_setup_handler(number_of_cases); } Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); int main() { Harness::run(specification); } #endif // !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE