/* * Copyright (c) 2018-2019, 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 !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 #include "usb_phy_api.h" #include "USBCDC.h" #include "USBSerial.h" #include "hal/us_ticker_api.h" #define USB_CDC_VID 0x1f00 #define USB_CDC_PID 0x2013 #define USB_SERIAL_VID 0x1f00 #define USB_SERIAL_PID 0x2012 #define MSG_KEY_LEN 24 #define MSG_VALUE_DUMMY "0" #define MSG_KEY_DEVICE_READY "ready" #define MSG_KEY_SERIAL_NUMBER "usb_dev_sn" #define MSG_KEY_PORT_OPEN_WAIT "port_open_wait" #define MSG_KEY_PORT_OPEN_CLOSE "port_open_close" #define MSG_KEY_SEND_BYTES_SINGLE "send_single" #define MSG_KEY_SEND_BYTES_MULTIPLE "send_multiple" #define MSG_KEY_LOOPBACK "loopback" #define MSG_KEY_CHANGE_LINE_CODING "change_lc" #define TX_BUFF_SIZE 32 #define RX_BUFF_SIZE 32 // The size of every data chunk the host sends (for each value from a // known sequence) during 'CDC RX multiple' test cases is // HOST_RX_BUFF_SIZE_RATIO times the size of RX_BUFF_SIZE input buffer. // This way the device has to correctly handle data bigger that its buffer. #define HOST_RX_BUFF_SIZE_RATIO 64 // A DTR line is used to signal that the host has configured a terminal and // is ready to transmit and receive data from the USB CDC/Serial device. // When this test suite is run with the use of a Linux host, a workaround has // to be used to overcome some platform specific DTR line behavior. // Every time the serial port file descriptor is opened, the DTR line is // asserted until the terminal attributes are set. // As a consequence, the device receives a premature DTR signal with a // duration of 200-500 us before the correct, long-lasting DTR signal set by // the host-side test script. (tested on the Linux kernel 4.15.0) // // Online references: // https://github.com/pyserial/pyserial/issues/124#issuecomment-227235402 // // The solution is to wait for the first DTR spike, ignore it, and wait for // the correct DTR signal again. #define LINUX_HOST_DTR_FIX 1 #define LINUX_HOST_DTR_FIX_DELAY_MS 1 #define CDC_LOOPBACK_REPS 1200 #define SERIAL_LOOPBACK_REPS 100 #define USB_RECONNECT_DELAY_MS 1 #define LINE_CODING_STRLEN 13 // 6 + 2 + 1 + 1 + 3 * comma #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; typedef struct LineCoding { // bits per second int baud; // 5, 6, 7, 8 or 16 int bits; // 0 -- None, // 1 -- Odd, // 2 -- Even, // 3 -- Mark, // 4 -- Space int parity; // 0 -- 1 Stop bit, // 1 -- 1.5 Stop bits, // 2 -- 2 Stop bits int stop; int get_num_diffs(LineCoding const &other) const { int diffs = 0; if (baud != other.baud) { diffs++; } if (bits != other.bits) { diffs++; } if (parity != other.parity) { diffs++; } if (stop != other.stop) { diffs++; } return diffs; } } line_coding_t; line_coding_t default_lc = { 9600, 8, 0, 0 }; // There is no POSIX support for 1.5 stop bits. // Do not set stop bits to 1.5 to keep tests compatible with all supported // host systems. line_coding_t test_codings[] = { { 9600, 5, 0, 2 }, { 4800, 7, 2, 0 }, { 19200, 8, 0, 2 }, { 115200, 8, 0, 0 }, { 38400, 8, 1, 0 }, { 1200, 8, 0, 0 }, { 19200, 8, 0, 0 }, { 2400, 7, 2, 0 }, { 9600, 8, 0, 0 }, { 57600, 8, 0, 0 }, }; Mail lc_mail; #define EF_SEND (1ul << 0) EventFlags event_flags; /** * 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 TestUSBCDC: public USBCDC { private: uint8_t _serial_num_descriptor[USB_DEV_SN_DESC_SIZE]; public: TestUSBCDC(uint16_t vendor_id = 0x1f00, uint16_t product_id = 0x2012, uint16_t product_release = 0x0001, const char *serial_number = default_serial_num) : USBCDC(get_usb_phy(), vendor_id, product_id, product_release) { 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 ~TestUSBCDC() { deinit(); } virtual const uint8_t *string_iserial_desc() { return (const uint8_t *) _serial_num_descriptor; } }; class TestUSBSerial: public USBSerial { private: uint8_t _serial_num_descriptor[USB_DEV_SN_DESC_SIZE]; public: TestUSBSerial(uint16_t vendor_id = 0x1f00, uint16_t product_id = 0x2012, uint16_t product_release = 0x0001, const char *serial_number = default_serial_num) : USBSerial(get_usb_phy(), vendor_id, product_id, product_release) { 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 ~TestUSBSerial() { deinit(); } virtual const uint8_t *string_iserial_desc() { return (const uint8_t *) _serial_num_descriptor; } }; /** Test CDC USB reconnect * * Given the host has successfully opened the port of a USB CDC device * When the USB device disconnects and connects again * Then the host is able to successfully open the port again */ void test_cdc_usb_reconnect() { TestUSBCDC usb_cdc(USB_CDC_VID, USB_CDC_PID, 1, usb_dev_sn); TEST_ASSERT_FALSE(usb_cdc.configured()); TEST_ASSERT_FALSE(usb_cdc.ready()); // Connect the USB device. usb_cdc.connect(); // Wait for the USB enumeration to complete. while (!usb_cdc.configured()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_cdc.configured()); TEST_ASSERT_FALSE(usb_cdc.ready()); greentea_send_kv(MSG_KEY_PORT_OPEN_WAIT, MSG_VALUE_DUMMY); // Wait for the host to open the port. #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); TEST_ASSERT_TRUE(usb_cdc.configured()); TEST_ASSERT_TRUE(usb_cdc.ready()); // Disconnect the USB device. usb_cdc.disconnect(); TEST_ASSERT_FALSE(usb_cdc.configured()); TEST_ASSERT_FALSE(usb_cdc.ready()); wait_ms(USB_RECONNECT_DELAY_MS); // Connect the USB device again. usb_cdc.connect(); // Wait for the USB enumeration to complete. while (!usb_cdc.configured()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_cdc.configured()); TEST_ASSERT_FALSE(usb_cdc.ready()); greentea_send_kv(MSG_KEY_PORT_OPEN_WAIT, MSG_VALUE_DUMMY); // Wait for the host to open the port again. #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); TEST_ASSERT_TRUE(usb_cdc.configured()); TEST_ASSERT_TRUE(usb_cdc.ready()); // Disconnect the USB device again. usb_cdc.disconnect(); TEST_ASSERT_FALSE(usb_cdc.configured()); TEST_ASSERT_FALSE(usb_cdc.ready()); } /** Test CDC receive single bytes * * Given the USB CDC device connected to a host * When the host transmits a known sequence one byte at a time * Then every byte received by the device matches the sequence */ void test_cdc_rx_single_bytes() { TestUSBCDC usb_cdc(USB_CDC_VID, USB_CDC_PID, 1, usb_dev_sn); usb_cdc.connect(); greentea_send_kv(MSG_KEY_SEND_BYTES_SINGLE, MSG_VALUE_DUMMY); #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); uint8_t buff = 0x01; for (int expected = 0xff; expected >= 0; expected--) { TEST_ASSERT(usb_cdc.receive(&buff, 1, NULL)); TEST_ASSERT_EQUAL_UINT8(expected, buff); } for (int expected = 0; expected <= 0xff; expected++) { TEST_ASSERT(usb_cdc.receive(&buff, 1, NULL)); TEST_ASSERT_EQUAL_UINT8(expected, buff); } // Wait for the host to close its port. while (usb_cdc.ready()) { wait_ms(1); } usb_cdc.disconnect(); } void tx_thread_fun(USBCDC *usb_cdc) { uint8_t buff_val = 0; uint8_t buff[TX_BUFF_SIZE] = { 0 }; while (event_flags.get() & EF_SEND) { if (!usb_cdc->send(buff, TX_BUFF_SIZE)) { wait_ms(1); continue; } buff_val++; memset(buff, buff_val, TX_BUFF_SIZE); } } /** Test CDC receive single bytes concurrently * * Given the USB CDC device connected to a host * When the host transmits a known sequence one byte at a time * and at the same time the device transmits data to host * Then every byte received by the device matches the sequence */ void test_cdc_rx_single_bytes_concurrent() { TestUSBCDC usb_cdc(USB_CDC_VID, USB_CDC_PID, 1, usb_dev_sn); usb_cdc.connect(); greentea_send_kv(MSG_KEY_SEND_BYTES_SINGLE, MSG_VALUE_DUMMY); #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); Thread tx_thread; event_flags.set(EF_SEND); tx_thread.start(mbed::callback(tx_thread_fun, &usb_cdc)); uint8_t buff = 0x01; for (int expected = 0xff; expected >= 0; expected--) { TEST_ASSERT(usb_cdc.receive(&buff, 1, NULL)); TEST_ASSERT_EQUAL_UINT8(expected, buff); } for (int expected = 0; expected <= 0xff; expected++) { TEST_ASSERT(usb_cdc.receive(&buff, 1, NULL)); TEST_ASSERT_EQUAL_UINT8(expected, buff); } event_flags.clear(EF_SEND); tx_thread.join(); // Wait for the host to close its port. while (usb_cdc.ready()) { wait_ms(1); } usb_cdc.disconnect(); } /** Test CDC receive multiple bytes * * Given the USB CDC device connected to a host * When the host transmits chunks of data following a known sequence * Then every chunk received by the device matches the sequence */ void test_cdc_rx_multiple_bytes() { TestUSBCDC usb_cdc(USB_CDC_VID, USB_CDC_PID, 1, usb_dev_sn); usb_cdc.connect(); greentea_send_kv(MSG_KEY_SEND_BYTES_MULTIPLE, HOST_RX_BUFF_SIZE_RATIO); #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); uint8_t buff[RX_BUFF_SIZE] = { 0 }; uint8_t expected_buff[RX_BUFF_SIZE] = { 0 }; for (int expected = 0xff; expected >= 0; expected--) { for (int chunk = 0; chunk < HOST_RX_BUFF_SIZE_RATIO; chunk++) { memset(expected_buff, expected, RX_BUFF_SIZE); TEST_ASSERT(usb_cdc.receive(buff, RX_BUFF_SIZE, NULL)); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buff, buff, RX_BUFF_SIZE); } } for (int expected = 0; expected <= 0xff; expected++) { for (int chunk = 0; chunk < HOST_RX_BUFF_SIZE_RATIO; chunk++) { memset(expected_buff, expected, RX_BUFF_SIZE); TEST_ASSERT(usb_cdc.receive(buff, RX_BUFF_SIZE, NULL)); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buff, buff, RX_BUFF_SIZE); } } // Wait for the host to close its port. while (usb_cdc.ready()) { wait_ms(1); } usb_cdc.disconnect(); } /** Test CDC receive multiple bytes concurrently * * Given the USB CDC device connected to a host * When the host transmits chunks of data following a known sequence * and at the same time the device transmits data to host * Then every chunk received by the device matches the sequence */ void test_cdc_rx_multiple_bytes_concurrent() { TestUSBCDC usb_cdc(USB_CDC_VID, USB_CDC_PID, 1, usb_dev_sn); usb_cdc.connect(); greentea_send_kv(MSG_KEY_SEND_BYTES_MULTIPLE, HOST_RX_BUFF_SIZE_RATIO); #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); Thread tx_thread; event_flags.set(EF_SEND); tx_thread.start(mbed::callback(tx_thread_fun, &usb_cdc)); uint8_t buff[RX_BUFF_SIZE] = { 0 }; uint8_t expected_buff[RX_BUFF_SIZE] = { 0 }; for (int expected = 0xff; expected >= 0; expected--) { for (int chunk = 0; chunk < HOST_RX_BUFF_SIZE_RATIO; chunk++) { memset(expected_buff, expected, RX_BUFF_SIZE); TEST_ASSERT(usb_cdc.receive(buff, RX_BUFF_SIZE, NULL)); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buff, buff, RX_BUFF_SIZE); } } for (int expected = 0; expected <= 0xff; expected++) { for (int chunk = 0; chunk < HOST_RX_BUFF_SIZE_RATIO; chunk++) { memset(expected_buff, expected, RX_BUFF_SIZE); TEST_ASSERT(usb_cdc.receive(buff, RX_BUFF_SIZE, NULL)); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_buff, buff, RX_BUFF_SIZE); } } event_flags.clear(EF_SEND); tx_thread.join(); // Wait for the host to close its port. while (usb_cdc.ready()) { wait_ms(1); } usb_cdc.disconnect(); } /** Test CDC loopback * * Given the USB CDC device connected to a host * When the device transmits random bytes to host * and the host transmits them back to the device * Then every byte received by the device is equal to byte preciously sent */ void test_cdc_loopback() { TestUSBCDC usb_cdc(USB_CDC_VID, USB_CDC_PID, 1, usb_dev_sn); usb_cdc.connect(); greentea_send_kv(MSG_KEY_LOOPBACK, MSG_VALUE_DUMMY); #if LINUX_HOST_DTR_FIX usb_cdc.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_cdc.wait_ready(); uint8_t rx_buff, tx_buff; for (int i = 0; i < CDC_LOOPBACK_REPS; i++) { tx_buff = (uint8_t)(rand() % 0x100); rx_buff = (uint8_t)(tx_buff + 1); TEST_ASSERT(usb_cdc.send(&tx_buff, 1)); TEST_ASSERT(usb_cdc.receive(&rx_buff, 1, NULL)); TEST_ASSERT_EQUAL_UINT8(tx_buff, rx_buff); } // Wait for the host to close its port. while (usb_cdc.ready()) { wait_ms(1); } usb_cdc.disconnect(); } /** Test Serial USB reconnect * * Given the host has successfully opened the port of a USB Serial device * When the USB device disconnects and connects again * Then the host is able to successfully open the port again */ void test_serial_usb_reconnect() { TestUSBSerial usb_serial(USB_SERIAL_VID, USB_SERIAL_PID, 1, usb_dev_sn); TEST_ASSERT_FALSE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); // Connect the USB device. usb_serial.connect(); // Wait for the USB enumeration to complete. while (!usb_serial.configured()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); greentea_send_kv(MSG_KEY_PORT_OPEN_WAIT, MSG_VALUE_DUMMY); // Wait for the host to open the port. #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif while (!usb_serial.connected()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_TRUE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); // Disconnect the USB device. usb_serial.disconnect(); TEST_ASSERT_FALSE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); wait_ms(USB_RECONNECT_DELAY_MS); // Connect the USB device again. usb_serial.connect(); // Wait for the USB enumeration to complete. while (!usb_serial.configured()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); greentea_send_kv(MSG_KEY_PORT_OPEN_WAIT, MSG_VALUE_DUMMY); // Wait for the host to open the port again. #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif while (!usb_serial.connected()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_TRUE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); // Disconnect the USB device again. usb_serial.disconnect(); TEST_ASSERT_FALSE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); } /** Test Serial terminal reopen * * Given the host has successfully opened the port of a USB Serial device * When the host closes its port * Then the host is able to successfully open the port again */ void test_serial_term_reopen() { TestUSBSerial usb_serial(USB_SERIAL_VID, USB_SERIAL_PID, 1, usb_dev_sn); usb_serial.connect(); greentea_send_kv(MSG_KEY_PORT_OPEN_CLOSE, MSG_VALUE_DUMMY); // Wait for the host to open the terminal. #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif while (!usb_serial.connected()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_TRUE(usb_serial.ready()); TEST_ASSERT_TRUE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); // Wait for the host to close the terminal. while (usb_serial.ready()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.ready()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); greentea_send_kv(MSG_KEY_PORT_OPEN_CLOSE, MSG_VALUE_DUMMY); // Wait for the host to open the terminal again. #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif while (!usb_serial.connected()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_TRUE(usb_serial.ready()); TEST_ASSERT_TRUE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); // Wait for the host to close the terminal again. while (usb_serial.ready()) { wait_ms(1); } TEST_ASSERT_TRUE(usb_serial.configured()); TEST_ASSERT_FALSE(usb_serial.ready()); TEST_ASSERT_FALSE(usb_serial.connected()); TEST_ASSERT_EQUAL_INT(0, usb_serial.readable()); usb_serial.disconnect(); } /** Test Serial getc * * Given the USB Serial device connected to a host * When the host transmits a known sequence one byte at a time * Then every byte received by the device matches the sequence */ void test_serial_getc() { TestUSBSerial usb_serial(USB_SERIAL_VID, USB_SERIAL_PID, 1, usb_dev_sn); usb_serial.connect(); greentea_send_kv(MSG_KEY_SEND_BYTES_SINGLE, MSG_VALUE_DUMMY); #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_serial.wait_ready(); for (int expected = 0xff; expected >= 0; expected--) { TEST_ASSERT_EQUAL_INT(expected, usb_serial.getc()); } for (int expected = 0; expected <= 0xff; expected++) { TEST_ASSERT_EQUAL_INT(expected, usb_serial.getc()); } // Wait for the host to close its port. while (usb_serial.ready()) { wait_ms(1); } usb_serial.disconnect(); } /** Test Serial printf & scanf * * Given the USB Serial device connected to a host * When the device transmits a formatted string with a random value * using the printf method * and the host sends it back to the device * Then the device can successfully read the value using scanf method * and the value received is equal value sent */ void test_serial_printf_scanf() { TestUSBSerial usb_serial(USB_SERIAL_VID, USB_SERIAL_PID, 1, usb_dev_sn); usb_serial.connect(); greentea_send_kv(MSG_KEY_LOOPBACK, MSG_VALUE_DUMMY); #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_serial.wait_ready(); static const char fmt[] = "Formatted\nstring %i."; int tx_val, rx_val, rc; for (int i = 0; i < SERIAL_LOOPBACK_REPS; i++) { tx_val = rand(); rx_val = tx_val + 1; rc = usb_serial.printf(fmt, tx_val); TEST_ASSERT(rc > 0); rc = usb_serial.scanf(fmt, &rx_val); TEST_ASSERT(rc == 1); TEST_ASSERT_EQUAL_INT(tx_val, rx_val); } // Wait for the host to close its port. while (usb_serial.ready()) { wait_ms(1); } usb_serial.disconnect(); } void line_coding_changed_cb(int baud, int bits, int parity, int stop) { line_coding_t *lc = lc_mail.alloc(); lc->baud = baud; lc->bits = bits; lc->parity = parity; lc->stop = stop; lc_mail.put(lc); } /** Test Serial / CDC line coding change * * Given the device transmits a set of line coding params to host * When the host updates serial port settings * Then line_coding_changed() callback is called * and the line coding is set as expected */ void test_serial_line_coding_change() { TestUSBSerial usb_serial(USB_SERIAL_VID, USB_SERIAL_PID, 1, usb_dev_sn); usb_serial.connect(); greentea_send_kv(MSG_KEY_CHANGE_LINE_CODING, MSG_VALUE_DUMMY); #if LINUX_HOST_DTR_FIX usb_serial.wait_ready(); wait_ms(LINUX_HOST_DTR_FIX_DELAY_MS); #endif usb_serial.wait_ready(); usb_serial.attach(line_coding_changed_cb); size_t num_line_codings = sizeof test_codings / sizeof test_codings[0]; line_coding_t *lc_prev = &default_lc; line_coding_t *lc_expected = NULL; line_coding_t *lc_actual = NULL; int num_expected_callbacks, rc; for (size_t i = 0; i < num_line_codings; i++) { lc_expected = &(test_codings[i]); num_expected_callbacks = lc_prev->get_num_diffs(*lc_expected); rc = usb_serial.printf("%06i,%02i,%01i,%01i", lc_expected->baud, lc_expected->bits, lc_expected->parity, lc_expected->stop); TEST_ASSERT_EQUAL_INT(LINE_CODING_STRLEN, rc); // The pyserial Python module does not update all line coding params // at once. It updates params one by one instead, and since every // update is followed by port reconfiguration we get multiple // calls to line_coding_changed callback on the device. while (num_expected_callbacks > 0) { num_expected_callbacks--; osEvent event = lc_mail.get(); TEST_ASSERT_EQUAL_UINT32(osEventMail, event.status); lc_actual = (line_coding_t *) event.value.p; if (lc_expected->get_num_diffs(*lc_actual) == 0) { break; } else if (num_expected_callbacks > 0) { // Discard lc_actual only if there is still a chance to get new // set of params. lc_mail.free(lc_actual); } } TEST_ASSERT_EQUAL_INT(lc_expected->baud, lc_actual->baud); TEST_ASSERT_EQUAL_INT(lc_expected->bits, lc_actual->bits); TEST_ASSERT_EQUAL_INT(lc_expected->parity, lc_actual->parity); TEST_ASSERT_EQUAL_INT(lc_expected->stop, lc_actual->stop); lc_mail.free(lc_actual); lc_prev = lc_expected; } // Wait for the host to close its port. while (usb_serial.ready()) { wait_ms(1); } usb_serial.disconnect(); } utest::v1::status_t testsuite_setup(const size_t number_of_cases) { GREENTEA_SETUP(45, "usb_device_serial"); 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("CDC USB reconnect", test_cdc_usb_reconnect), Case("CDC RX single bytes", test_cdc_rx_single_bytes), Case("CDC RX single bytes concurrent", test_cdc_rx_single_bytes_concurrent), Case("CDC RX multiple bytes", test_cdc_rx_multiple_bytes), Case("CDC RX multiple bytes concurrent", test_cdc_rx_multiple_bytes_concurrent), Case("CDC loopback", test_cdc_loopback), Case("Serial USB reconnect", test_serial_usb_reconnect), Case("Serial terminal reopen", test_serial_term_reopen), Case("Serial getc", test_serial_getc), Case("Serial printf/scanf", test_serial_printf_scanf), Case("Serial line coding change", test_serial_line_coding_change), }; Specification specification((utest::v1::test_setup_handler_t) testsuite_setup, cases); int main() { return !Harness::run(specification); } #endif // !defined(DEVICE_USBDEVICE) || !DEVICE_USBDEVICE