Skip to content

Commit fae1686

Browse files
committed
feature(teardown_test): Added the test_app for teardowning the cdc device
1 parent a1832e0 commit fae1686

File tree

7 files changed

+403
-0
lines changed

7 files changed

+403
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.16)
4+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
5+
6+
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
7+
set(COMPONENTS main)
8+
9+
project(test_app_teardown_device)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
idf_component_register(SRC_DIRS .
2+
INCLUDE_DIRS .
3+
REQUIRES unity
4+
WHOLE_ARCHIVE)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## IDF Component Manager Manifest File
2+
dependencies:
3+
espressif/esp_tinyusb:
4+
version: "*"
5+
override_path: "../../../"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <string.h>
9+
#include "unity.h"
10+
#include "unity_test_runner.h"
11+
#include "unity_test_utils_memory.h"
12+
13+
void app_main(void)
14+
{
15+
/*
16+
_ _ _
17+
| | (_) | |
18+
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
19+
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
20+
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
21+
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
22+
| |______ __/ |
23+
|_|______| |___/
24+
_____ _____ _____ _____
25+
|_ _| ___/ ___|_ _|
26+
| | | |__ \ `--. | |
27+
| | | __| `--. \ | |
28+
| | | |___/\__/ / | |
29+
\_/ \____/\____/ \_/
30+
*/
31+
32+
printf(" _ _ _ \n");
33+
printf(" | | (_) | | \n");
34+
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
35+
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
36+
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
37+
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
38+
printf(" | |______ __/ | \n");
39+
printf(" |_|______| |___/ \n");
40+
printf(" _____ _____ _____ _____ \n");
41+
printf("|_ _| ___/ ___|_ _| \n");
42+
printf(" | | | |__ \\ `--. | | \n");
43+
printf(" | | | __| `--. \\ | | \n");
44+
printf(" | | | |___/\\__/ / | | \n");
45+
printf(" \\_/ \\____/\\____/ \\_/ \n");
46+
47+
unity_utils_setup_heap_record(80);
48+
unity_utils_set_leak_level(128);
49+
unity_run_menu();
50+
}
51+
52+
/* setUp runs before every test */
53+
void setUp(void)
54+
{
55+
unity_utils_record_free_mem();
56+
}
57+
58+
/* tearDown runs after every test */
59+
void tearDown(void)
60+
{
61+
unity_utils_evaluate_leaks();
62+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "soc/soc_caps.h"
8+
#if SOC_USB_OTG_SUPPORTED
9+
10+
//
11+
#include <stdio.h>
12+
#include <string.h>
13+
//
14+
#include "freertos/FreeRTOS.h"
15+
#include "freertos/task.h"
16+
#include "freertos/semphr.h"
17+
//
18+
#include "esp_system.h"
19+
#include "esp_log.h"
20+
#include "esp_err.h"
21+
//
22+
#include "unity.h"
23+
#include "tinyusb.h"
24+
#include "tusb_cdc_acm.h"
25+
26+
static const char *TAG = "teardown";
27+
28+
SemaphoreHandle_t wait_mount = NULL;
29+
SemaphoreHandle_t wait_terminal = NULL;
30+
SemaphoreHandle_t wait_command = NULL;
31+
32+
static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1];
33+
static uint8_t tx_buf[CONFIG_TINYUSB_CDC_TX_BUFSIZE + 1] = { 0 };
34+
35+
#define TEARDOWN_CMD_KEY 0xAA
36+
#define TEARDOWN_RPL_KEY 0x55
37+
#define TEARDOWN_CMD_RPL_SIZE ((TUD_OPT_HIGH_SPEED ? 512 : 64))
38+
#define TEARDOWN_ATTACH_TIMEOUT_MS 2000
39+
#define TEARDOWN_COMMAND_TIMEOUT_MS 3000
40+
#define TEARDOWN_AMOUNT 4
41+
42+
static const tusb_desc_device_t cdc_device_descriptor = {
43+
.bLength = sizeof(cdc_device_descriptor),
44+
.bDescriptorType = TUSB_DESC_DEVICE,
45+
.bcdUSB = 0x0200,
46+
.bDeviceClass = TUSB_CLASS_MISC,
47+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
48+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
49+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
50+
.idVendor = USB_ESPRESSIF_VID,
51+
.idProduct = 0x4002,
52+
.bcdDevice = 0x0100,
53+
.iManufacturer = 0x01,
54+
.iProduct = 0x02,
55+
.iSerialNumber = 0x03,
56+
.bNumConfigurations = 0x01
57+
};
58+
59+
static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN;
60+
static const uint8_t cdc_desc_configuration[] = {
61+
TUD_CONFIG_DESCRIPTOR(1, 2, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
62+
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)),
63+
};
64+
65+
#if (TUD_OPT_HIGH_SPEED)
66+
static const tusb_desc_device_qualifier_t device_qualifier = {
67+
.bLength = sizeof(tusb_desc_device_qualifier_t),
68+
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
69+
.bcdUSB = 0x0200,
70+
.bDeviceClass = TUSB_CLASS_MISC,
71+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
72+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
73+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
74+
.bNumConfigurations = 0x01,
75+
.bReserved = 0
76+
};
77+
#endif // TUD_OPT_HIGH_SPEED
78+
79+
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event)
80+
{
81+
// Something was received
82+
xSemaphoreGive(wait_command);
83+
}
84+
85+
/**
86+
* @brief CDC device line change callback
87+
*
88+
* CDC device signals, that the DTR, RTS states changed
89+
*
90+
* @param[in] itf CDC device index
91+
* @param[in] event CDC event type
92+
*/
93+
void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
94+
{
95+
int dtr = event->line_state_changed_data.dtr;
96+
int rts = event->line_state_changed_data.rts;
97+
ESP_LOGD(TAG, "Line state changed on channel %d: DTR:%d, RTS:%d", itf, dtr, rts);
98+
99+
// Terminal:
100+
// dtr==1 && rts==1 - connected
101+
// dtr==0 && rts==0 - disconnected
102+
xSemaphoreGive(wait_terminal);
103+
}
104+
105+
// Invoked when device is mounted
106+
void tud_mount_cb(void)
107+
{
108+
xSemaphoreGive(wait_mount);
109+
}
110+
111+
/**
112+
* @brief TinyUSB Teardown specific testcase
113+
*
114+
* Scenario:
115+
* - Installs the tinyUSB driver via esp-tinyusb wrapper with 1xCDC class device
116+
* - Awaits device configuration be the Host (TEARDOWN_ATTACH_TIMEOUT_MS)
117+
* - Awaits the terminal connection (TEARDOWN_ATTACH_TIMEOUT_MS)
118+
* - Expects the command sequence from the Host (TEARDOWN_COMMAND_TIMEOUT_MS)
119+
* - Replies with the response sequence to the Host
120+
* - Awaits terminal disconnection (TEARDOWN_ATTACH_TIMEOUT_MS)
121+
* - Teardowns the tinyUSB driver via esp-tinyusb wrapper
122+
* - Repeats the steps from the step.1 N times (where N = TEARDOWN_AMOUNT)
123+
* - Verifies amount of attempts and memory leakage (attempts should be 0)
124+
*
125+
* command sequence[] = ep_size * 0xAA
126+
* response sequence[] = ep_size * 0x55
127+
*
128+
* Hint: Values 0xAA and 0x55 were selected to verify the buffer memory integrity,
129+
* as the 0xAA and 0x55 are the inversion of each other and the data bits in the same position changes from 1 to 0 in every transaction.
130+
*/
131+
TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]")
132+
{
133+
size_t rx_size = 0;
134+
135+
wait_mount = xSemaphoreCreateBinary();
136+
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
137+
wait_command = xSemaphoreCreateBinary();
138+
TEST_ASSERT_NOT_EQUAL(NULL, wait_command);
139+
wait_terminal = xSemaphoreCreateBinary();
140+
TEST_ASSERT_NOT_EQUAL(NULL, wait_terminal);
141+
142+
// Prep reply
143+
for (int i = 0; i < TEARDOWN_CMD_RPL_SIZE; i++) {
144+
tx_buf[i] = TEARDOWN_RPL_KEY;
145+
}
146+
147+
// TinyUSB driver configuration
148+
const tinyusb_config_t tusb_cfg = {
149+
.device_descriptor = &cdc_device_descriptor,
150+
.string_descriptor = NULL,
151+
.string_descriptor_count = 0,
152+
.external_phy = false,
153+
#if (TUD_OPT_HIGH_SPEED)
154+
.fs_configuration_descriptor = cdc_desc_configuration,
155+
.hs_configuration_descriptor = cdc_desc_configuration,
156+
.qualifier_descriptor = &device_qualifier,
157+
#else
158+
.configuration_descriptor = cdc_desc_configuration,
159+
#endif // TUD_OPT_HIGH_SPEED
160+
};
161+
162+
// TinyUSB ACM Driver configuration
163+
const tinyusb_config_cdcacm_t acm_cfg = {
164+
.usb_dev = TINYUSB_USBDEV_0,
165+
.cdc_port = TINYUSB_CDC_ACM_0,
166+
.rx_unread_buf_sz = 64,
167+
.callback_rx = &tinyusb_cdc_rx_callback,
168+
.callback_rx_wanted_char = NULL,
169+
.callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
170+
.callback_line_coding_changed = NULL
171+
};
172+
173+
int attempts = TEARDOWN_AMOUNT;
174+
while (attempts) {
175+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
176+
// Init CDC 0
177+
TEST_ASSERT_FALSE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0));
178+
TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_init(&acm_cfg));
179+
TEST_ASSERT_TRUE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0));
180+
// Wait for the usb event
181+
ESP_LOGD(TAG, "wait dev mounted...");
182+
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_ATTACH_TIMEOUT_MS)));
183+
ESP_LOGD(TAG, "wait terminal connection...");
184+
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_terminal, pdMS_TO_TICKS(TEARDOWN_ATTACH_TIMEOUT_MS)));
185+
// Wait for the command
186+
ESP_LOGD(TAG, "wait command...");
187+
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_command, pdMS_TO_TICKS(TEARDOWN_COMMAND_TIMEOUT_MS)));
188+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_read(TINYUSB_CDC_ACM_0, rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size));
189+
for (int i = 0; i < TEARDOWN_CMD_RPL_SIZE; i++) {
190+
TEST_ASSERT_EQUAL(TEARDOWN_CMD_KEY, rx_buf[i]);
191+
}
192+
ESP_LOGD(TAG, "command received");
193+
// Reply the response sequence
194+
ESP_LOGD(TAG, "send response...");
195+
TEST_ASSERT_EQUAL(TEARDOWN_CMD_RPL_SIZE, tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, tx_buf, TEARDOWN_CMD_RPL_SIZE));
196+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, pdMS_TO_TICKS(1000)));
197+
ESP_LOGD(TAG, "wait for terminal disconnection");
198+
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_terminal, pdMS_TO_TICKS(TEARDOWN_ATTACH_TIMEOUT_MS)));
199+
// Teardown
200+
attempts--;
201+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_unregister_callback(TINYUSB_CDC_ACM_0, CDC_EVENT_RX));
202+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_unregister_callback(TINYUSB_CDC_ACM_0, CDC_EVENT_LINE_STATE_CHANGED));
203+
TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_deinit(TINYUSB_CDC_ACM_0));
204+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
205+
}
206+
// Remove primitives
207+
vSemaphoreDelete(wait_mount);
208+
vSemaphoreDelete(wait_command);
209+
// All attempts should be completed
210+
TEST_ASSERT_EQUAL(0, attempts);
211+
}
212+
213+
#endif
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import pytest
5+
from pytest_embedded_idf.dut import IdfDut
6+
import serial
7+
from serial.tools.list_ports import comports
8+
from time import sleep, time
9+
10+
def get_tusb_cdc_device(vid, pid):
11+
ports = comports()
12+
for cdc in ports:
13+
if cdc.vid == vid and cdc.pid == pid:
14+
return cdc.device
15+
return None
16+
17+
def wait_for_tusb_cdc(vid, pid, timeout=30):
18+
start_time = time()
19+
while time() - start_time < timeout:
20+
sleep(0.5) # Check every 0.5 seconds
21+
tusb_cdc = get_tusb_cdc_device(vid, pid)
22+
if tusb_cdc:
23+
return tusb_cdc
24+
return None
25+
26+
def teardown_device(key_len, amount):
27+
TUSB_VID = 0x303A # Espressif TinyUSB VID
28+
TUSB_PID = 0x4002 # Espressif TinyUSB VID
29+
30+
# Command to send and expected response
31+
COMMAND = b'\xAA' * key_len
32+
EXPECTED_RESPONSE = b'\x55' * key_len
33+
34+
# Number of iterations, must be equal to ITERATIONS in the test application
35+
ITERATIONS = amount
36+
37+
for i in range(ITERATIONS):
38+
print(f"Iteration {i+1} of {ITERATIONS}")
39+
40+
# Wait for the device to appear
41+
print("Waiting for the device to connect...")
42+
tusb_cdc = wait_for_tusb_cdc(TUSB_VID, TUSB_PID)
43+
if not tusb_cdc:
44+
print("Error: Device did not appear within the timeout period.")
45+
assert True
46+
47+
try:
48+
# Open the serial port
49+
with serial.Serial(port=tusb_cdc, baudrate=9600, timeout=1) as cdc:
50+
print(f"Opened port: {tusb_cdc}")
51+
# Send the key command
52+
res = cdc.write(COMMAND)
53+
assert res == key_len
54+
# Get the response
55+
res = cdc.readline()
56+
assert len(res) == key_len
57+
# Explicitly close the cdc
58+
cdc.close()
59+
# Check if the response matches the expected response
60+
if res == EXPECTED_RESPONSE:
61+
print("Response matches expected value.")
62+
else:
63+
print(f"Sent {len(COMMAND)}: {COMMAND.hex().upper()}")
64+
print(f"Received {len(res)}: {res.hex().upper()}")
65+
raise Exception("Error: Response does not match expected value.")
66+
67+
except serial.SerialException as e:
68+
print(f"Error communicating with the serial port: {e}")
69+
raise
70+
71+
# Wait for the device to disconnect
72+
print("Waiting for the device to disconnect...")
73+
while get_tusb_cdc_device(TUSB_VID, TUSB_PID):
74+
sleep(0.1) # Poll every 0.1 second while tinyusb cdc dev still in list
75+
76+
print("Finished all iterations.")
77+
78+
@pytest.mark.esp32s2
79+
@pytest.mark.esp32s3
80+
@pytest.mark.esp32p4
81+
@pytest.mark.usb_device
82+
def test_usb_teardown_device(dut) -> None:
83+
dut.expect_exact('Press ENTER to see the list of tests.')
84+
dut.write('[teardown]')
85+
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
86+
sleep(2) # Some time for the OS to enumerate our USB device
87+
if dut.target == 'esp32p4':
88+
MPS = 512
89+
else:
90+
MPS = 64
91+
# On Linux, the serial port kept opened, while len==MPS, https://github.com/pyserial/pyserial/issues/753
92+
# that can be seen via Beagle: CDC OUT transaction appears only when terminal is closed.
93+
teardown_device(MPS, 4) # Teardown tusb device
94+

0 commit comments

Comments
 (0)