/* * 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. */ #if !USB_DEVICE_TESTS #error [NOT_SUPPORTED] usb device tests not enabled #else #if !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE #error [NOT_SUPPORTED] USB Device not supported for this target #else #include "greentea-client/test_env.h" #include "utest/utest.h" #include "unity/unity.h" #include "mbed.h" #include <stdlib.h> #include "usb_phy_api.h" #include "USBHID.h" #include "USBMouse.h" #include "USBKeyboard.h" #include "hal/us_ticker_api.h" // Reuse the VID & PID from basic USB test. #define USB_HID_VID 0x0d28 #define USB_HID_PID_GENERIC 0x0206 #define USB_HID_PID_KEYBOARD 0x0206 #define USB_HID_PID_MOUSE 0x0206 #define USB_HID_PID_GENERIC2 0x0007 #define MSG_VALUE_LEN 24 #define MSG_KEY_LEN 24 #define MSG_KEY_DEVICE_READY "dev_ready" #define MSG_KEY_HOST_READY "host_ready" #define MSG_KEY_SERIAL_NUMBER "usb_dev_sn" #define MSG_KEY_TEST_GET_DESCRIPTOR_HID "test_get_desc_hid" #define MSG_KEY_TEST_GET_DESCRIPTOR_CFG "test_get_desc_cfg" #define MSG_KEY_TEST_REQUESTS "test_requests" #define MSG_KEY_TEST_RAW_IO "test_raw_io" #define MSG_KEY_TEST_CASE_FAILED "fail" #define MSG_KEY_TEST_CASE_PASSED "pass" #define MSG_VALUE_DUMMY "0" #define MSG_VALUE_NOT_SUPPORTED "not_supported" #define RAW_IO_REPS 16 #define USB_DEV_SN_LEN (32) // 32 hex digit UUID #define NONASCII_CHAR ('?') #define USB_DEV_SN_DESC_SIZE (USB_DEV_SN_LEN * 2 + 2) const char *default_serial_num = "0123456789"; char usb_dev_sn[USB_DEV_SN_LEN + 1]; using utest::v1::Case; using utest::v1::Specification; using utest::v1::Harness; /** * Convert a USB string descriptor to C style ASCII * * The string placed in str is always null-terminated which may cause the * loss of data if n is to small. If the length of descriptor string is less * than n, additional null bytes are written to str. * * @param str output buffer for the ASCII string * @param usb_desc USB string descriptor * @param n size of str buffer * @returns number of non-null bytes returned in str or -1 on failure */ int usb_string_desc2ascii(char *str, const uint8_t *usb_desc, size_t n) { if (str == NULL || usb_desc == NULL || n < 1) { return -1; } // bDescriptorType @ offset 1 if (usb_desc[1] != STRING_DESCRIPTOR) { return -1; } // bLength @ offset 0 const size_t bLength = usb_desc[0]; if (bLength % 2 != 0) { return -1; } size_t s, d; for (s = 0, d = 2; s < n - 1 && d < bLength; s++, d += 2) { // handle non-ASCII characters if (usb_desc[d] > 0x7f || usb_desc[d + 1] != 0) { str[s] = NONASCII_CHAR; } else { str[s] = usb_desc[d]; } } int str_len = s; for (; s < n; s++) { str[s] = '\0'; } return str_len; } /** * Convert a C style ASCII to a USB string descriptor * * @param usb_desc output buffer for the USB string descriptor * @param str ASCII string * @param n size of usb_desc buffer, even number * @returns number of bytes returned in usb_desc or -1 on failure */ int ascii2usb_string_desc(uint8_t *usb_desc, const char *str, size_t n) { if (str == NULL || usb_desc == NULL || n < 4) { return -1; } if (n % 2 != 0) { return -1; } size_t s, d; // set bString (@ offset 2 onwards) as a UNICODE UTF-16LE string memset(usb_desc, 0, n); for (s = 0, d = 2; str[s] != '\0' && d < n; s++, d += 2) { usb_desc[d] = str[s]; } // set bLength @ offset 0 usb_desc[0] = d; // set bDescriptorType @ offset 1 usb_desc[1] = STRING_DESCRIPTOR; return d; } class TestUSBHID: public USBHID { private: uint8_t _serial_num_descriptor[USB_DEV_SN_DESC_SIZE]; public: TestUSBHID(uint16_t vendor_id, uint16_t product_id, const char *serial_number = default_serial_num, uint8_t output_report_length = 64, uint8_t input_report_length = 64) : USBHID(get_usb_phy(), output_report_length, input_report_length, vendor_id, product_id, 0x01) { init(); int rc = ascii2usb_string_desc(_serial_num_descriptor, serial_number, USB_DEV_SN_DESC_SIZE); if (rc < 0) { ascii2usb_string_desc(_serial_num_descriptor, default_serial_num, USB_DEV_SN_DESC_SIZE); } } virtual ~TestUSBHID() { deinit(); } virtual const uint8_t *string_iserial_desc() { return (const uint8_t *) _serial_num_descriptor; } // Make this accessible for tests (public). using USBHID::report_desc_length; }; class TestUSBMouse: public USBMouse { private: uint8_t _serial_num_descriptor[USB_DEV_SN_DESC_SIZE]; public: TestUSBMouse(uint16_t vendor_id, uint16_t product_id, const char *serial_number = default_serial_num) : USBMouse(get_usb_phy(), REL_MOUSE, vendor_id, product_id, 0x01) { init(); int rc = ascii2usb_string_desc(_serial_num_descriptor, serial_number, USB_DEV_SN_DESC_SIZE); if (rc < 0) { ascii2usb_string_desc(_serial_num_descriptor, default_serial_num, USB_DEV_SN_DESC_SIZE); } } virtual ~TestUSBMouse() { deinit(); } virtual const uint8_t *string_iserial_desc() { return (const uint8_t *) _serial_num_descriptor; } // Make this accessible for tests (public). using USBHID::report_desc_length; }; class TestUSBKeyboard: public USBKeyboard { private: uint8_t _serial_num_descriptor[USB_DEV_SN_DESC_SIZE]; public: TestUSBKeyboard(uint16_t vendor_id, uint16_t product_id, const char *serial_number = default_serial_num) : USBKeyboard(get_usb_phy(), vendor_id, product_id, 0x01) { init(); int rc = ascii2usb_string_desc(_serial_num_descriptor, serial_number, USB_DEV_SN_DESC_SIZE); if (rc < 0) { ascii2usb_string_desc(_serial_num_descriptor, default_serial_num, USB_DEV_SN_DESC_SIZE); } } virtual ~TestUSBKeyboard() { deinit(); } virtual const uint8_t *string_iserial_desc() { return (const uint8_t *) _serial_num_descriptor; } // Make this accessible for tests (public). using USBHID::report_desc_length; }; /** Test Get_Descriptor request with the HID class descriptors * * Given a USB HID class device connected to a host, * when the host issues the Get_Descriptor(HID) request, * then the device returns the HID descriptor. * * When the host issues the Get_Descriptor(Report) request, * then the device returns the Report descriptor * and the size of the descriptor is equal to USBHID::report_desc_length(). * * Details in USB Device Class Definition for HID, v1.11, paragraph 7.1. */ template<typename T, uint16_t PID> void test_get_hid_class_desc() { T usb_hid(USB_HID_VID, PID, usb_dev_sn); usb_hid.connect(); greentea_send_kv(MSG_KEY_TEST_GET_DESCRIPTOR_HID, MSG_VALUE_DUMMY); char key[MSG_KEY_LEN + 1] = { }; char value[MSG_VALUE_LEN + 1] = { }; greentea_parse_kv(key, value, MSG_KEY_LEN, MSG_VALUE_LEN); TEST_ASSERT_EQUAL_STRING(MSG_KEY_TEST_CASE_PASSED, key); uint16_t host_report_desc_len; int num_args = sscanf(value, "%04hx", &host_report_desc_len); TEST_ASSERT_MESSAGE(num_args != 0 && num_args != EOF, "Invalid data received from host."); TEST_ASSERT_EQUAL_UINT16(usb_hid.report_desc_length(), host_report_desc_len); } /** Test Get_Descriptor request with the Configuration descriptor * * Given a USB HID class device connected to a host, * when the host issues the Get_Descriptor(Configuration) request, * then the device returns the Configuration descriptor and a HID * descriptor for each HID interface. * * Details in USB Device Class Definition for HID, v1.11, paragraph 7.1. */ template<typename T, uint16_t PID> void test_get_configuration_desc() { T usb_hid(USB_HID_VID, PID, usb_dev_sn); usb_hid.connect(); greentea_send_kv(MSG_KEY_TEST_GET_DESCRIPTOR_CFG, MSG_VALUE_DUMMY); char key[MSG_KEY_LEN + 1] = { }; char value[MSG_VALUE_LEN + 1] = { }; greentea_parse_kv(key, value, MSG_KEY_LEN, MSG_VALUE_LEN); TEST_ASSERT_EQUAL_STRING(MSG_KEY_TEST_CASE_PASSED, key); } /** Test HID class requests * * Given a USB HID class device connected to a host, * when the host issues a request specific to the HID class device type, * then the device returns valid data. * * Details in USB Device Class Definition for HID, v1.11, * paragraph 7.2 and Appendix G. */ template<typename T, uint16_t PID> void test_class_requests() { T usb_hid(USB_HID_VID, PID, usb_dev_sn); usb_hid.connect(); greentea_send_kv(MSG_KEY_TEST_REQUESTS, MSG_VALUE_DUMMY); char key[MSG_KEY_LEN + 1] = { }; char value[MSG_VALUE_LEN + 1] = { }; greentea_parse_kv(key, value, MSG_KEY_LEN, MSG_VALUE_LEN); TEST_ASSERT_EQUAL_STRING(MSG_KEY_TEST_CASE_PASSED, key); } /** Test send & read * * Given a USB HID class device connected to a host, * when the device sends input reports with a random data to the host * and the host sends them back to the device, * then received output report data is equal to the input report data. */ template<uint8_t REPORT_SIZE> // Range [1, MAX_HID_REPORT_SIZE]. void test_generic_raw_io() { TestUSBHID usb_hid(USB_HID_VID, USB_HID_PID_GENERIC2, usb_dev_sn, REPORT_SIZE, REPORT_SIZE); usb_hid.connect(); greentea_send_kv(MSG_KEY_TEST_RAW_IO, REPORT_SIZE); // Wait for the host HID driver to complete setup. char key[MSG_KEY_LEN + 1] = { }; char value[MSG_VALUE_LEN + 1] = { }; greentea_parse_kv(key, value, MSG_KEY_LEN, MSG_VALUE_LEN); TEST_ASSERT_EQUAL_STRING(MSG_KEY_HOST_READY, key); if (strcmp(value, MSG_VALUE_NOT_SUPPORTED) == 0) { TEST_IGNORE_MESSAGE("Test case not supported by host plarform."); return; } // Report ID omitted here. There are no Report ID tags in the Report descriptor. HID_REPORT input_report = {}; HID_REPORT output_report = {}; for (size_t r = 0; r < RAW_IO_REPS; r++) { for (size_t i = 0; i < REPORT_SIZE; i++) { input_report.data[i] = (uint8_t)(rand() % 0x100); } input_report.length = REPORT_SIZE; output_report.length = 0; TEST_ASSERT(usb_hid.send(&input_report)); TEST_ASSERT(usb_hid.read(&output_report)); TEST_ASSERT_EQUAL_UINT32(input_report.length, output_report.length); TEST_ASSERT_EQUAL_UINT8_ARRAY(input_report.data, output_report.data, REPORT_SIZE); } } utest::v1::status_t testsuite_setup(const size_t number_of_cases) { GREENTEA_SETUP(45, "usb_device_hid"); srand((unsigned) ticker_read_us(get_us_ticker_data())); utest::v1::status_t status = utest::v1::greentea_test_setup_handler(number_of_cases); if (status != utest::v1::STATUS_CONTINUE) { return status; } char key[MSG_KEY_LEN + 1] = { }; char usb_dev_uuid[USB_DEV_SN_LEN + 1] = { }; greentea_send_kv(MSG_KEY_DEVICE_READY, MSG_VALUE_DUMMY); greentea_parse_kv(key, usb_dev_uuid, MSG_KEY_LEN, USB_DEV_SN_LEN + 1); if (strcmp(key, MSG_KEY_SERIAL_NUMBER) != 0) { utest_printf("Invalid message key.\n"); return utest::v1::STATUS_ABORT; } strncpy(usb_dev_sn, usb_dev_uuid, USB_DEV_SN_LEN + 1); return status; } Case cases[] = { Case("Configuration descriptor, generic", test_get_configuration_desc<TestUSBHID, USB_HID_PID_GENERIC>), Case("Configuration descriptor, keyboard", test_get_configuration_desc<TestUSBKeyboard, USB_HID_PID_KEYBOARD>), Case("Configuration descriptor, mouse", test_get_configuration_desc<TestUSBMouse, USB_HID_PID_MOUSE>), Case("HID class descriptors, generic", test_get_hid_class_desc<TestUSBHID, USB_HID_PID_GENERIC>), Case("HID class descriptors, keyboard", test_get_hid_class_desc<TestUSBKeyboard, USB_HID_PID_KEYBOARD>), Case("HID class descriptors, mouse", test_get_hid_class_desc<TestUSBMouse, USB_HID_PID_MOUSE>), // HID class requests not supported by Mbed // Case("HID class requests, generic", test_class_requests<TestUSBHID, USB_HID_PID_GENERIC>), // Case("HID class requests, keyboard", test_class_requests<TestUSBKeyboard, USB_HID_PID_KEYBOARD>), // Case("HID class requests, mouse", test_class_requests<TestUSBMouse, USB_HID_PID_MOUSE>), Case("Raw input/output, 1-byte reports", test_generic_raw_io<1>), Case("Raw input/output, 20-byte reports", test_generic_raw_io<20>), Case("Raw input/output, 64-byte reports", test_generic_raw_io<64>), }; Specification specification((utest::v1::test_setup_handler_t) testsuite_setup, cases); int main() { return !Harness::run(specification); } #endif // !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE #endif // !defined(USB_DEVICE_TESTS)