mirror of https://github.com/ARMmbed/mbed-os.git
393 lines
13 KiB
C++
393 lines
13 KiB
C++
/*
|
|
* 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)
|