Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(esp_tinyusb): Added tusb_teardown() call while tinyusb_driver_uninstall() #39

Merged
merged 2 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build_and_run_test_app_usb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: usb_test_app_bin_${{ matrix.idf_ver }}
- name: ⚙️ Install System tools
run: |
apt update
apt install -y usbutils
- name: Install Python packages
env:
PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/"
Expand Down
10 changes: 10 additions & 0 deletions device/esp_tinyusb/include/tinyusb.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ typedef struct {
*/
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config);

/**
* @brief This is an all-in-one helper function, including:
* 1. Stops the task to handle usb events
* 2. TinyUSB stack tearing down
* 2. Freeing resources after descriptors preparation
* 3. Deletes USB PHY
*
* @retval ESP_FAIL Uninstall driver or tinyusb stack failed because of internal error
* @retval ESP_OK Uninstall driver, tinyusb stack and USB PHY successfully
*/
esp_err_t tinyusb_driver_uninstall(void);

#ifdef __cplusplus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,5 @@ TEST_CASE("bvalid_signal", "[esp_tinyusb][usb_device]")

// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
}
#endif // SOC_USB_OTG_SUPPORTED
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ TEST_CASE("descriptors_config_all_default", "[esp_tinyusb][usb_device]")
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
__test_free();
}

Expand All @@ -163,7 +162,6 @@ TEST_CASE("descriptors_config_device", "[esp_tinyusb][usb_device]")
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
__test_free();
}

Expand All @@ -186,7 +184,6 @@ TEST_CASE("descriptors_config_device_and_config", "[esp_tinyusb][usb_device]")
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
__test_free();
}

Expand All @@ -208,7 +205,6 @@ TEST_CASE("descriptors_config_device_and_fs_config_only", "[esp_tinyusb][usb_dev
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
__test_free();
}

Expand All @@ -229,7 +225,6 @@ TEST_CASE("descriptors_config_device_and_hs_config_only", "[esp_tinyusb][usb_dev
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
__test_free();
}

Expand All @@ -250,7 +245,6 @@ TEST_CASE("descriptors_config_all_configured", "[esp_tinyusb][usb_device]")
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, tusb_stop_task());
__test_free();
}
#endif // TUD_OPT_HIGH_SPEED
Expand Down
9 changes: 9 additions & 0 deletions device/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)

project(test_app_teardown_device)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"
62 changes: 62 additions & 0 deletions device/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"

void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/

printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");

unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}

/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}

/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}
142 changes: 142 additions & 0 deletions device/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED

//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"

static const char *TAG = "teardown";

SemaphoreHandle_t wait_mount = NULL;

#define TEARDOWN_DEVICE_INIT_DELAY_MS 1000
#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000
#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000

#define TEARDOWN_AMOUNT 10

#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};

static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};

#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED

// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}

/**
* @brief TinyUSB Teardown specific testcase
*
* Scenario:
* 1. Install TinyUSB device without any class
* 2. Wait SetConfiguration() (tud_mount_cb)
* 3. If attempts == 0 goto step 8
* 4. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
* 5. Uninstall TinyUSB device
* 6. Wait TEARDOWN_DEVICE_INIT_DELAY_MS
* 7. Decrease attempts by 1, goto step 3
* 8. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
* 9. Uninstall TinyUSB device
*/
TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);

// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};

TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
ESP_LOGD(TAG, "mounted");

// Teardown routine
int attempts = TEARDOWN_AMOUNT;
while (attempts--) {
// Keep device attached
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_INIT_DELAY_MS));
// Reconnect
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
ESP_LOGD(TAG, "mounted");
}

// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

import pytest
from pytest_embedded_idf.dut import IdfDut
import subprocess
from time import sleep, time

class DeviceNotFoundError(Exception):
"""Custom exception for device not found within the timeout period."""
pass

def tusb_dev_in_list(vid, pid):
try:
output = subprocess.check_output(["lsusb"], text=True)
search_string = f"{vid}:{pid}"
return search_string in output
except Exception as e:
print(f"Error while executing lsusb: {e}")
raise

def wait_tusb_dev_appeared(vid, pid, timeout):
start_time = time()
while True:
if tusb_dev_in_list(vid, pid):
return True
if time() - start_time > timeout:
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} not found within {timeout} seconds.")
sleep(0.5)

def wait_tusb_dev_removed(vid, pid, timeout):
start_time = time()
while True:
if not tusb_dev_in_list(vid, pid):
return True
if time() - start_time > timeout:
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} wasn't removed within {timeout} seconds.")
sleep(0.5)

def tusb_device_teardown(iterations, timeout):
TUSB_VID = "303a" # Espressif TinyUSB VID
TUSB_PID = "4002" # Espressif TinyUSB VID

for i in range(iterations):
# Wait until the device is present
print(f"Waiting for device ...")
wait_tusb_dev_appeared(TUSB_VID, TUSB_PID, timeout)
print("Device detected.")

# Wait until the device is removed
print("Waiting for the device to be removed...")
wait_tusb_dev_removed(TUSB_VID, TUSB_PID, timeout)
print("Device removed.")
print("Monitoring completed.")

@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_teardown_device(dut: IdfDut) -> None:
dut.expect_exact('Press ENTER to see the list of tests.')
dut.write('[teardown]')
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
sleep(2) # Some time for the OS to enumerate our USB device

try:
tusb_device_teardown(10, 10) # Teardown tusb device: amount, timeout

except DeviceNotFoundError as e:
print(f"Error: {e}")
raise

except Exception as e:
print(f"An unexpected error occurred: {e}")
raise
Loading
Loading