From 8a6271bc32d7c3012f33411d061f18516de95357 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 12 Feb 2025 13:28:27 +0100 Subject: [PATCH] feat(test_ext_port): Added test cases for the external port driver testing --- components/usb/test_apps/common/hcd_common.c | 4 + .../usb/test_apps/ext_port/CMakeLists.txt | 11 + components/usb/test_apps/ext_port/README.md | 12 + .../test_apps/ext_port/main/CMakeLists.txt | 16 + .../test_apps/ext_port/main/Kconfig.projbuild | 23 + .../test_apps/ext_port/main/ext_port_common.c | 525 ++++++++++++++++++ .../test_apps/ext_port/main/ext_port_common.h | 107 ++++ .../usb/test_apps/ext_port/main/hub_common.c | 273 +++++++++ .../usb/test_apps/ext_port/main/hub_common.h | 164 ++++++ .../test_apps/ext_port/main/test_app_main.c | 61 ++ .../test_apps/ext_port/main/test_ext_port.c | 470 ++++++++++++++++ .../usb/test_apps/ext_port/pytest_ext_port.py | 14 + .../usb/test_apps/ext_port/sdkconfig.ci | 9 + .../usb/test_apps/ext_port/sdkconfig.defaults | 9 + 14 files changed, 1698 insertions(+) create mode 100644 components/usb/test_apps/ext_port/CMakeLists.txt create mode 100644 components/usb/test_apps/ext_port/README.md create mode 100644 components/usb/test_apps/ext_port/main/CMakeLists.txt create mode 100644 components/usb/test_apps/ext_port/main/Kconfig.projbuild create mode 100644 components/usb/test_apps/ext_port/main/ext_port_common.c create mode 100644 components/usb/test_apps/ext_port/main/ext_port_common.h create mode 100644 components/usb/test_apps/ext_port/main/hub_common.c create mode 100644 components/usb/test_apps/ext_port/main/hub_common.h create mode 100644 components/usb/test_apps/ext_port/main/test_app_main.c create mode 100644 components/usb/test_apps/ext_port/main/test_ext_port.c create mode 100644 components/usb/test_apps/ext_port/pytest_ext_port.py create mode 100644 components/usb/test_apps/ext_port/sdkconfig.ci create mode 100644 components/usb/test_apps/ext_port/sdkconfig.defaults diff --git a/components/usb/test_apps/common/hcd_common.c b/components/usb/test_apps/common/hcd_common.c index df4eca45ec..28f6852e00 100644 --- a/components/usb/test_apps/common/hcd_common.c +++ b/components/usb/test_apps/common/hcd_common.c @@ -313,6 +313,10 @@ uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe) // Update address of default pipe TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(default_pipe, ENUM_ADDR)); + // Some high-speed Hubs need some time before being able to process SetConfiguration request + // Full-speed devices doesn't require that time + vTaskDelay(pdMS_TO_TICKS(10)); + // Send a set configuration request USB_SETUP_PACKET_INIT_SET_CONFIG(setup_pkt, ENUM_CONFIG); urb->transfer.num_bytes = sizeof(usb_setup_packet_t); diff --git a/components/usb/test_apps/ext_port/CMakeLists.txt b/components/usb/test_apps/ext_port/CMakeLists.txt new file mode 100644 index 0000000000..e06ef3708b --- /dev/null +++ b/components/usb/test_apps/ext_port/CMakeLists.txt @@ -0,0 +1,11 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS "../common") + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) + +project(test_app_hub) diff --git a/components/usb/test_apps/ext_port/README.md b/components/usb/test_apps/ext_port/README.md new file mode 100644 index 0000000000..ecc16e60ee --- /dev/null +++ b/components/usb/test_apps/ext_port/README.md @@ -0,0 +1,12 @@ +| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | + +# USB: External Port test application + +Testing the External Port Driver logic. + +## Requirements + +USB external Hub should be attached to the root port (USB OTG peripheral). + +Ports could be configured via menuconfig. diff --git a/components/usb/test_apps/ext_port/main/CMakeLists.txt b/components/usb/test_apps/ext_port/main/CMakeLists.txt new file mode 100644 index 0000000000..95701b17ee --- /dev/null +++ b/components/usb/test_apps/ext_port/main/CMakeLists.txt @@ -0,0 +1,16 @@ +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +set(srcs) + +list(APPEND srcs "test_app_main.c" + "test_ext_port.c" + # Internal common test files + "hub_common.c" + "ext_port_common.c" +) + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS "." + PRIV_INCLUDE_DIRS "." + REQUIRES usb unity common + WHOLE_ARCHIVE) diff --git a/components/usb/test_apps/ext_port/main/Kconfig.projbuild b/components/usb/test_apps/ext_port/main/Kconfig.projbuild new file mode 100644 index 0000000000..a484064f9c --- /dev/null +++ b/components/usb/test_apps/ext_port/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +source "$IDF_PATH/components/usb/test_apps/common/Kconfig.common" + +menu "USB Host External Port test" + + config USB_HOST_TEST_HUB_PORT_NUM_DEVICE_FSHS + int "Port number with Full-/High-speed device" + default 1 + help + Configure the number of the port, with a Full-/High-speed USB device. + + config USB_HOST_TEST_HUB_PORT_NUM_DEVICE_LS + int "Port number with Low-speed device" + default 2 + help + Configure the number of the port, with a Low-speed USB device. + + config USB_HOST_TEST_HUB_PORT_NUM_EMPTY + int "Empty Port number" + default 3 + help + Configure the number of the port, without device. + +endmenu diff --git a/components/usb/test_apps/ext_port/main/ext_port_common.c b/components/usb/test_apps/ext_port/main/ext_port_common.c new file mode 100644 index 0000000000..eb4e87e6c7 --- /dev/null +++ b/components/usb/test_apps/ext_port/main/ext_port_common.c @@ -0,0 +1,525 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +// USB Host Lib +#include "ext_hub.h" // For port driver API, remove when IDF-12562 is closed +#include "ext_port.h" +#include "usb_private.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_ch11.h" +#include "unity.h" +#include "hub_common.h" +#include "ext_port_common.h" + +#define TEST_EXT_PORT_QUEUE_LEN 3 +#define EXT_PORT_PROC_CB_TIMEOUT_MS 50 +#define EXT_PORT_HUB_REQUEST_TIMEOUT_MS 100 +#define EXT_PORT_EVENT_TIMEOUT_MS 100 + +#define USB_B_REQUEST_ANY 0xFF + +const char *const hub_request_string[] = { + "Get Port Status", + "Clear Port Feature", + "Get Hub State", + "Set Feature", + "RESERVED", + "RESERVED", + "Get Hub Descriptor", + "Set Hub Descriptor", + "Clear TT Buffer", + "Reset TT", + "Get TT State", + "Stop TT", +}; + +const char *const hub_feature_string[] = { + "Port Connection", + "Port Enable", + "Port Suspend", + "Port Over Current", + "Port Reset", + "Feature N/A", + "Feature N/A", + "Feature N/A", + "Port Power", + "Port Low Speed", + "Feature N/A", + "Feature N/A", + "Feature N/A", + "Feature N/A", + "Feature N/A", + "Feature N/A", + "C Port Connection", + "C Port Enable", + "C Port Suspend", + "C Port Over Current", + "C Port Reset", + "Port Test", + "Port Indicator", +}; + +const char *const ext_port_event_string[] = { + "N/A", + "<- Connected", + "<> Reset Completed", + "-> Disconnected", +}; + +typedef struct { + ext_hub_handle_t hub_hdl; + ext_hub_request_data_t data; +} ext_port_hub_request_msg_t; + +static const ext_hub_port_driver_t* port_api; +SemaphoreHandle_t _process_cd_req = NULL; +QueueHandle_t _ext_port_hub_req_queue = NULL; +QueueHandle_t _ext_port_event_queue = NULL; + +static void test_ext_port_callback(void *user_arg) +{ + // We can call ext_port_process() right here, but use semaphore instead to have more linear logic in the test + SemaphoreHandle_t process_req_cb = (SemaphoreHandle_t)user_arg; + xSemaphoreGive(process_req_cb); +} + +static void test_ext_port_event_callback(ext_port_event_data_t *event_data, void *arg) +{ + QueueHandle_t ext_port_event_queue = (QueueHandle_t)arg; + ext_port_event_data_t msg = *event_data; + xQueueSend(ext_port_event_queue, &msg, portMAX_DELAY); +} + +static esp_err_t test_ext_port_hub_class_request(ext_hub_handle_t ext_hub_hdl, ext_hub_request_data_t *request_data, void *user_arg) +{ + QueueHandle_t hub_req_queue = (QueueHandle_t)user_arg; + ext_port_hub_request_msg_t msg = { + .hub_hdl = ext_hub_hdl, + .data = { + .request = request_data->request, + .port_num = request_data->port_num, + .feature = request_data->feature, + }, + }; + xQueueSend(hub_req_queue, &msg, portMAX_DELAY); + return ESP_OK; +} + +static void test_wait_ext_port_process_request(void) +{ + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(_process_cd_req, pdMS_TO_TICKS(EXT_PORT_PROC_CB_TIMEOUT_MS))); +} + +static void test_wait_ext_port_event(ext_port_event_t event) +{ + // Get the port event queue from the port's context variable + QueueHandle_t ext_port_evt_queue = _ext_port_event_queue; + TEST_ASSERT_NOT_NULL(ext_port_evt_queue); + // Wait for port callback to send an event message + ext_port_event_data_t msg; + BaseType_t ret = xQueueReceive(ext_port_evt_queue, &msg, pdMS_TO_TICKS(EXT_PORT_EVENT_TIMEOUT_MS)); + TEST_ASSERT_EQUAL_MESSAGE(pdPASS, ret, "External Hub port event not generated on time"); + // Check the contents of that event message + printf("\tHub port event: %s\n", ext_port_event_string[msg.event]); + TEST_ASSERT_EQUAL_MESSAGE(event, msg.event, "Unexpected External Hub port event"); +} + +static esp_err_t test_wait_ext_port_hub_request(void *ext_hub_hdl, uint8_t port_num, usb_hub_class_request_t request) +{ + // Get the hub request queue + TEST_ASSERT_NOT_NULL(_ext_port_hub_req_queue); + // Wait for port callback to send an event message + ext_port_hub_request_msg_t msg; + BaseType_t ret = xQueueReceive(_ext_port_hub_req_queue, &msg, pdMS_TO_TICKS(EXT_PORT_HUB_REQUEST_TIMEOUT_MS)); + if (ret != pdPASS) { + printf("Hub request callback not triggered on time \n"); + return ESP_ERR_TIMEOUT; + } + // Check the contents of that event message + // TEST_ASSERT_EQUAL(ext_hub_hdl, msg.hub_hdl); // TODO: Enable verification after closing IDF-10023 + TEST_ASSERT_EQUAL(port_num, msg.data.port_num); + printf("\tCallback for class request: %s %s\n", hub_request_string[msg.data.request], + (msg.data.request == USB_B_REQUEST_HUB_SET_PORT_FEATURE || + msg.data.request == USB_B_REQUEST_HUB_CLEAR_FEATURE) + ? hub_feature_string[msg.data.feature] + : " "); + // Verify the request if it is not USB_B_REQUEST_ANY + if (msg.data.request != USB_B_REQUEST_ANY) { + TEST_ASSERT_EQUAL_MESSAGE(request, msg.data.request, "Unexpected request"); + } + return ESP_OK; +} + +void test_ext_port_setup(void) +{ + // Create a semaphore to wait for the process request + _process_cd_req = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(_process_cd_req); + // Create a queue for ext port hub request + _ext_port_hub_req_queue = xQueueCreate(TEST_EXT_PORT_QUEUE_LEN, sizeof(ext_port_hub_request_msg_t)); + TEST_ASSERT_NOT_NULL(_ext_port_hub_req_queue); + // Create a queue for ext port event + _ext_port_event_queue = xQueueCreate(TEST_EXT_PORT_QUEUE_LEN, sizeof(ext_port_event_data_t)); + TEST_ASSERT_NOT_NULL(_ext_port_event_queue); + // Install External Port driver + ext_port_driver_config_t ext_port_config = { + .proc_req_cb = test_ext_port_callback, + .proc_req_cb_arg = (void*)_process_cd_req, + .event_cb = test_ext_port_event_callback, + .event_cb_arg = (void*)_ext_port_event_queue, + .hub_request_cb = test_ext_port_hub_class_request, + .hub_request_cb_arg = (void*)_ext_port_hub_req_queue, + }; + TEST_ASSERT_EQUAL(ESP_OK, ext_port_install(&ext_port_config)); + port_api = ext_port_get_driver(); + TEST_ASSERT_NOT_NULL(port_api); +} + +void test_ext_port_teardown(void) +{ + // Uninstall External Port driver + TEST_ASSERT_EQUAL(ESP_OK, ext_port_uninstall()); + vSemaphoreDelete(_process_cd_req); + vQueueDelete(_ext_port_hub_req_queue); + vQueueDelete(_ext_port_event_queue); +} + +ext_port_hdl_t test_ext_port_new(ext_port_config_t *config) +{ + ext_port_hdl_t port = NULL; + TEST_ASSERT_EQUAL(ESP_OK, port_api->new (config, (void**) &port)); + TEST_ASSERT_NOT_NULL(port); + // Adding the port should trigger the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Newly added port should trigger the Get Port Status request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + config->parent_port_num, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); // Process the port + return port; +} + +void test_ext_port_power_on(uint8_t port1, ext_port_hdl_t port_hdl) +{ + usb_port_status_t port_status; + hub_get_port_status(port1, &port_status); + // Verify actual port status + TEST_ASSERT_EQUAL(0, port_status.wPortStatus.val); // Port should be not powered + TEST_ASSERT_EQUAL(0, port_status.wPortChange.val); // No port Change + + // Set port status to the External Port Driver + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Setting the port status should trigger the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Wait Set Feature - Port Power + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_SET_PORT_FEATURE)); + // Power on the port + hub_port_power_on(port1); + vTaskDelay(pdMS_TO_TICKS(30)); // TODO: change to power power on delay + // After powering the port, trigger the port processing + TEST_ASSERT_EQUAL(ESP_OK, port_api->req_process(port_hdl)); + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Wait Get Port Status + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + // Get Port Status + hub_get_port_status(port1, &port_status); + // Verify the actual port status + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Port should be powered +} + +void test_ext_port_disconnected(uint8_t port1, ext_port_hdl_t port_hdl) +{ + usb_port_status_t port_status; + + // Get Actual Port Status + hub_get_port_status(port1, &port_status); + // Port should be powered, has no connection and no port change + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); + TEST_ASSERT_EQUAL(0, port_status.wPortStatus.PORT_CONNECTION); + TEST_ASSERT_EQUAL(0, port_status.wPortChange.val); + + // Set port status to the External Port Driver + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Setting the port status should trigger the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Disconnected port doesn't require any processing, so there should be no process request + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_ANY)); + // Port doesn't require any processing, so there should debug message "No more ports to handle" + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); // TODO: IDF-12562 replace with Enable INTR EP event await + // Port is idle +} + +usb_speed_t test_ext_port_connected(uint8_t port1, ext_port_hdl_t port_hdl) +{ + usb_port_status_t port_status; + // Get the port status + hub_get_port_status(port1, &port_status); + // Port should be powered, has a connection and a port change + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); + TEST_ASSERT_EQUAL(1, port_status.wPortChange.C_PORT_CONNECTION); + + // Set port status to the External Port Driver + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Setting the port status should trigger the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Setting the status of the port with C_CONNECTION should trigger the Clear Port Connection + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_CLEAR_FEATURE)); + + // Clear Port Connection + hub_port_clear_connection(port1); + // After clearing connection, trigger the port processing + TEST_ASSERT_EQUAL(ESP_OK, port_api->req_process(port_hdl)); + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Powered port with connection should trigger the status update + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + // Get Port Status + hub_get_port_status(port1, &port_status); + // Port should be powered, has a connection and no port change + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Powered + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); // Connection detected + TEST_ASSERT_EQUAL(0, port_status.wPortChange.C_PORT_CONNECTION); // Port does not need to clear connection + // Set port status to the External Port Driver + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Setting the port status should trigger the process request callback + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Powered port with connection should trigger the Port Reset request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_SET_PORT_FEATURE)); + // Port Reset + hub_port_reset(port1); + // Give port some time to process the reset + vTaskDelay(pdMS_TO_TICKS(30)); // TODO: Port Reset recovery + // After resetting the port, trigger the port processing + TEST_ASSERT_EQUAL(ESP_OK, port_api->req_process(port_hdl)); + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Powered port with connection should trigger the Get Port Status request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + // Get Port Status + hub_get_port_status(port1, &port_status); + // Port should be powered, has a connection and requires clear a port reset + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Powered + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); // Connection detected + TEST_ASSERT_EQUAL(0, port_status.wPortChange.C_PORT_CONNECTION); // Port does not need to clear connection + TEST_ASSERT_EQUAL(1, port_status.wPortChange.C_PORT_RESET); // Port does need to clear reset + // Set port status + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Powered port with connection after reset should trigger the Clear Port Reset request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_CLEAR_FEATURE)); + // Clear Port reset request + hub_port_clear_reset(port1); + // After clearing reset, trigger the port processing + TEST_ASSERT_EQUAL(ESP_OK, port_api->req_process(port_hdl)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Powered port with connection after reset should trigger the Get Port Status request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + + // Get Port Status + hub_get_port_status(port1, &port_status); + + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Powered + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); // Connection detected + TEST_ASSERT_EQUAL(0, port_status.wPortChange.C_PORT_CONNECTION); // Port does not need to clear connection + TEST_ASSERT_EQUAL(0, port_status.wPortChange.C_PORT_RESET); // Port does not need to clear reset + // Set port status to the External Port Driver + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Processing the port after clear reset should generate the connection event + test_wait_ext_port_event(EXT_PORT_CONNECTED); + // Get device speed + usb_speed_t dev_speed; + TEST_ASSERT_EQUAL(ESP_OK, port_api->get_speed(port_hdl, &dev_speed)); + return dev_speed; +} + +void test_ext_port_imitate_disconnection(uint8_t port1, ext_port_hdl_t port_hdl) +{ + usb_port_status_t port_status; + // Get Port Status + hub_get_port_status(port1, &port_status); + // Imitate disconnection possible only on the port, that has device and enabled + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Port powered + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_ENABLE); // Port enabled + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); // Port has connection + TEST_ASSERT_EQUAL(0, port_status.wPortChange.val); // No change to handle + + // + // EMULATE DISCONNECTION VIA PORT STATUS + // + // Change the port status: reset enable, reset the connection and set the clear connection bit + port_status.wPortStatus.PORT_ENABLE = 0; + port_status.wPortStatus.PORT_CONNECTION = 0; + port_status.wPortChange.C_PORT_CONNECTION = 1; + + // Set new port status + printf("set status %04X.%04X*\n", port_status.wPortStatus.val, port_status.wPortChange.val); + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Setting the port status should trigger the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Setting the status of the port with connection should trigger the Clear Port Connection + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_CLEAR_FEATURE)); + // Assume that clear connection done successfully, but we do not need actually change the port status + // Clear Port Feature: C_CONNECTION - assume we done it, ESP_OK + port_status.wPortChange.C_PORT_CONNECTION = 0; + // Request process + TEST_ASSERT_EQUAL(ESP_OK, port_api->req_process(port_hdl)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Cleared port connection should trigger the Get Port Status request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + + // We don't need to request actual port status, proceed with cached status + + // Set new port status + printf("set status %04X.%04X*\n", port_status.wPortStatus.val, port_status.wPortChange.val); + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Setting the port status should trigger the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // The External Port driver should indicate that device has been disconnected and port disconnected + test_wait_ext_port_event(EXT_PORT_DISCONNECTED); +} + +void test_ext_port_disable(uint8_t port1, ext_port_hdl_t port_hdl) +{ + usb_port_status_t port_status; + hub_get_port_status(port1, &port_status); + + // Disable port is possible only when it is enabled, powered and has a connection + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Port powered + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_ENABLE); // Port enabled + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); // Port has connection + TEST_ASSERT_EQUAL(0, port_status.wPortChange.val); // No change to handle + + TEST_ASSERT_EQUAL(ESP_OK, port_api->disable(port_hdl)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_CLEAR_FEATURE)); + // When port is not gone, it should create a DISCONNECTION event + test_wait_ext_port_event(EXT_PORT_DISCONNECTED); + // Disable the port + hub_port_disable(port1); + // After completion, trigger the port processing + TEST_ASSERT_EQUAL(ESP_OK, port_api->req_process(port_hdl)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Cleared port enable should trigger the Get Port Status request on the parent hub + TEST_ASSERT_EQUAL(ESP_OK, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + // Get Port Status + hub_get_port_status(port1, &port_status); + // Port should be powered, disabled and have a connection + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_POWER); // Powered + TEST_ASSERT_EQUAL(0, port_status.wPortStatus.PORT_ENABLE); // Disabled + TEST_ASSERT_EQUAL(1, port_status.wPortStatus.PORT_CONNECTION); // Connection detected + // Set port status + TEST_ASSERT_EQUAL(ESP_OK, port_api->set_status(port_hdl, &port_status)); + // Wait for the process request callback + test_wait_ext_port_process_request(); + // Port doesn't require any processing, so there should debug message "No more ports to handle" + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); // TODO: IDF-12562 replace with Enable INTR EP event await + // Disabled port doesn't require any processing, so there should be no process request + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, test_wait_ext_port_hub_request(NULL, + port1, + USB_B_REQUEST_HUB_GET_PORT_STATUS)); + // Port is disabled and wait device to be detached. + // Terminal state +} + +void test_ext_port_gone(ext_port_hdl_t port_hdl, bool has_device) +{ + if (has_device) { + // Port is enabled, port gone returns ESP_ERR_NOT_FINISHED + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, port_api->gone(port_hdl)); + // Wait for the process request callback + // test_ext_port_wait_process_request(); + // Port doesn't require any processing, so there should debug message "No more ports to handle" + // TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); // TODO: IDF-12562 replace with Enable INTR EP event await + // Get Disconnect event + test_wait_ext_port_event(EXT_PORT_DISCONNECTED); + return; + } + // Port doesn't have a device, port gone returns ESP_OK + TEST_ASSERT_EQUAL(ESP_OK, port_api->gone(port_hdl)); +} + +void test_ext_port_delete(ext_port_hdl_t port_hdl) +{ + TEST_ASSERT_EQUAL(ESP_OK, port_api->del(port_hdl)); +} + +void test_ext_port_recycle(ext_port_hdl_t port_hdl) +{ + TEST_ASSERT_EQUAL(ESP_OK, port_api->recycle(port_hdl)); + // Recycling the port for the process request callback + test_wait_ext_port_process_request(); + // Process the port + TEST_ASSERT_EQUAL(ESP_OK, ext_port_process()); + // Port gone + // ? Port delete + // : Port is IDLE +} diff --git a/components/usb/test_apps/ext_port/main/ext_port_common.h b/components/usb/test_apps/ext_port/main/ext_port_common.h new file mode 100644 index 0000000000..83e7f7b772 --- /dev/null +++ b/components/usb/test_apps/ext_port/main/ext_port_common.h @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "ext_port.h" + +/** + * @brief External Port setup test + * Create queues for events/callbacks and install External Port driver + */ +void test_ext_port_setup(void); + +/** + * @brief External Port teardown test + * Uninstall External Port driver and delete queues + */ +void test_ext_port_teardown(void); + +/** + * @brief Create External Port + * + * Creates the port. + * Verifies that creating new port triggers the process request callback. + * Verifies that processing newly added port requires the Get Port Status request on the parent hub. + * + * @param[in] config External Port configuration + * @return + * - External Port handle if port is created successfully + */ +ext_port_hdl_t test_ext_port_new(ext_port_config_t *config); + +/** + * @brief Test power on the Port + * + * Requests actual port status from parent hub. + * Set status to the External Port Driver. + * Verifies that setting the port status triggers the process request callback. + * Verifies that processing the port after setting the status triggers the Get Port Status request on the parent hub. + * + * @param[in] port1 Port number + * @param[in] port_hdl External Port handle + */ +void test_ext_port_power_on(uint8_t port1, ext_port_hdl_t port_hdl); + +/** + * @brief Test Port disconnected + * + * Handles the port that doesn't have a connection. + * + * @param[in] port1 Port number + * @param[in] port_hdl External Port handle + */ +void test_ext_port_disconnected(uint8_t port1, ext_port_hdl_t port_hdl); + +/** + * @brief Test External Port connected + * + * Handles the port that has a connection + * + * @param[in] port1 Port number + * @param[in] port_hdl External Port handle + * @return + * - usb_speed_t Port speed + */ +usb_speed_t test_ext_port_connected(uint8_t port1, ext_port_hdl_t port_hdl); + +/** + * @brief Test External Port imitate disconnection + * + * Imitates the port disconnection via sending changed status to the External Port Driver + * + * @param[in] port1 Port number + * @param[in] port_hdl External Port handle + */ +void test_ext_port_imitate_disconnection(uint8_t port1, ext_port_hdl_t port_hdl); + +/** + * @brief Test External Port disable + * + * @param[in] port1 Port number + * @param[in] port_hdl External Port handle + */ +void test_ext_port_disable(uint8_t port1, ext_port_hdl_t port_hdl); + +/** + * @brief Test External Port recycle + * + * @param[in] port_hdl External Port handle + * @param[in] has_device Device presence + */ +void test_ext_port_gone(ext_port_hdl_t port_hdl, bool has_device); + +/** + * @brief Test External Port delete + * + * @param[in] port_hdl External Port handle + */ +void test_ext_port_delete(ext_port_hdl_t port_hdl); + +/** + * @brief Test External Port recycle + * + * @param[in] port_hdl External Port handle + */ +void test_ext_port_recycle(ext_port_hdl_t port_hdl); diff --git a/components/usb/test_apps/ext_port/main/hub_common.c b/components/usb/test_apps/ext_port/main/hub_common.c new file mode 100644 index 0000000000..5ec4f5ec22 --- /dev/null +++ b/components/usb/test_apps/ext_port/main/hub_common.c @@ -0,0 +1,273 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "string.h" +// Test common +#include "hcd_common.h" +#include "hub_common.h" +// USB Host Lib +#include "usb_private.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_ch11.h" +#include "unity.h" + +typedef struct { + hcd_port_handle_t root_port_hdl; + hcd_pipe_handle_t ctrl_pipe_hdl; + urb_t *urb; + usb_speed_t speed; + uint8_t addr; + usb_hub_descriptor_t desc; +} hub_dev_t; + +static hub_dev_t hub; + +// ------------------------------------------------------------------------------------------------- +// --------------------------------------- Internal logic ----------------------------------------- +// ------------------------------------------------------------------------------------------------- +/** + * @brief Get Port status + * + * @param[in] port1 Port number + * @param[out] status Port status + */ +static void hub_class_request_get_port_status(uint8_t port1, usb_port_status_t *status) +{ + usb_port_status_t *port_status = (usb_port_status_t *)(hub.urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)hub.urb->transfer.data_buffer; + USB_SETUP_PACKET_INIT_GET_PORT_STATUS(setup_pkt, port1); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_port_status_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(hub.ctrl_pipe_hdl, hub.urb)); + test_hcd_expect_pipe_event(hub.ctrl_pipe_hdl, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(hub.ctrl_pipe_hdl)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Get Port Status: Transfer NOT completed"); + if (status) { + memcpy(status, port_status, sizeof(usb_port_status_t)); + } +} + +/** + * @brief Set Port Feature + * + * @param[in] port1 Port number + * @param[in] feature Feature to set + */ +static void hub_class_request_set_port_feature(uint8_t port1, usb_hub_port_feature_t feature) +{ + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)hub.urb->transfer.data_buffer; + USB_SETUP_PACKET_INIT_SET_PORT_FEATURE(setup_pkt, port1, feature); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(hub.ctrl_pipe_hdl, hub.urb)); + test_hcd_expect_pipe_event(hub.ctrl_pipe_hdl, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(hub.ctrl_pipe_hdl)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Set Port Feature: Transfer NOT completed"); +} + +/** + * @brief Clear Port Feature + * + * @param[in] port1 Port number + * @param[in] feature Feature to clear + */ +static void hub_class_request_clear_port_feature(uint8_t port1, usb_hub_port_feature_t feature) +{ + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)hub.urb->transfer.data_buffer; + USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE(setup_pkt, port1, feature); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(hub.ctrl_pipe_hdl, hub.urb)); + test_hcd_expect_pipe_event(hub.ctrl_pipe_hdl, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(hub.ctrl_pipe_hdl)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Clear Port Feature: Transfer NOT completed"); +} + +/** + * @brief Get Hub Descriptor + * + * Get Hub Descriptor and store it in the hub structure + */ +static void hub_get_descriptor(void) +{ + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)hub.urb->transfer.data_buffer; + USB_SETUP_PACKET_INIT_GET_HUB_DESCRIPTOR(setup_pkt); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_descriptor_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(hub.ctrl_pipe_hdl, hub.urb)); + test_hcd_expect_pipe_event(hub.ctrl_pipe_hdl, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(hub.ctrl_pipe_hdl)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Get Hub Descriptor: Transfer NOT completed"); + + // Extract Hub capabilities + const usb_hub_descriptor_t *hub_desc = (const usb_hub_descriptor_t *)(hub.urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + memcpy(&hub.desc, hub_desc, sizeof(usb_hub_descriptor_t)); +} + +// ------------------------------------------------------------------------------------------------- +// ----------------------------------------- Hub Functions ----------------------------------------- +// ------------------------------------------------------------------------------------------------- + +void hub_set_root_port(hcd_port_handle_t root_port_hdl) +{ + hub.root_port_hdl = root_port_hdl; +} + +void hub_attach(void) +{ + // Attach the root port + hub.speed = test_hcd_wait_for_conn(hub.root_port_hdl); + // Short delay send of SOF (for FS) or EOPs (for LS) + vTaskDelay(pdMS_TO_TICKS(100)); + // Create Control Pipe + hub.ctrl_pipe_hdl = test_hcd_pipe_alloc(hub.root_port_hdl, NULL, 0, hub.speed); + TEST_ASSERT_NOT_NULL(hub.ctrl_pipe_hdl); + // Enumerate Hub + hub.addr = test_hcd_enum_device(hub.ctrl_pipe_hdl); + // Hub should have an address + TEST_ASSERT_NOT_EQUAL(0, hub.addr); + // Create urb for CTRL transfers + urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_setup_packet_t) + 256); + TEST_ASSERT_NOT_NULL(urb); + hub.urb = urb; + // Get Hub Descriptor + hub_get_descriptor(); + printf("\tHub has %d ports\n", hub.desc.bNbrPorts); +} + +hcd_pipe_handle_t hub_child_create_pipe(usb_speed_t speed) +{ + // Downstream pipe has the same root port handle + hcd_pipe_handle_t ctrl_pipe = test_hcd_pipe_alloc(hub.root_port_hdl, NULL, 0, speed); + TEST_ASSERT_NOT_NULL(ctrl_pipe); + return ctrl_pipe; +} + +void hub_child_pipe_free(hcd_pipe_handle_t pipe_hdl) +{ + TEST_ASSERT_NOT_NULL(pipe_hdl); + test_hcd_pipe_free(pipe_hdl); +} + +void hub_child_quick_enum(hcd_pipe_handle_t ctrl_pipe, uint8_t dev_addr, uint8_t config_num) +{ + // TODO: test_hcd_enum_device() could be used instead, but it requires refactoring: assign the configurable dev address + usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)hub.urb->transfer.data_buffer; + // Get the device descriptor (note that device might only return 8 bytes) + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC(setup_pkt); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(ctrl_pipe, hub.urb)); + test_hcd_expect_pipe_event(ctrl_pipe, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(ctrl_pipe)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Transfer NOT completed"); + + // Update the MPS of the default pipe + usb_device_desc_t *device_desc = (usb_device_desc_t *)(hub.urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(ctrl_pipe, device_desc->bMaxPacketSize0)); + // + printf("\t\tEnumerated device: %04x:%04x, mps: %d\n", + device_desc->idVendor, + device_desc->idProduct, + device_desc->bMaxPacketSize0); + + // Send a set address request + USB_SETUP_PACKET_INIT_SET_ADDR(setup_pkt, dev_addr); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(ctrl_pipe, hub.urb)); + test_hcd_expect_pipe_event(ctrl_pipe, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(ctrl_pipe)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Transfer NOT completed"); + + // Update address of default pipe + TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(ctrl_pipe, dev_addr)); + + // Send a set configuration request + USB_SETUP_PACKET_INIT_SET_CONFIG(setup_pkt, config_num); + hub.urb->transfer.num_bytes = sizeof(usb_setup_packet_t); + TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(ctrl_pipe, hub.urb)); + test_hcd_expect_pipe_event(ctrl_pipe, HCD_PIPE_EVENT_URB_DONE); + TEST_ASSERT_EQUAL(hub.urb, hcd_urb_dequeue(ctrl_pipe)); + TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, hub.urb->transfer.status, "Transfer NOT completed"); +} + +void hub_detach(void) +{ + // Free urb + test_hcd_free_urb(hub.urb); + // Free Control Pipe + test_hcd_pipe_free(hub.ctrl_pipe_hdl); + // Detach the root port + test_hcd_wait_for_disconn(hub.root_port_hdl, true); +} + +void* hub_get_context(void) +{ + return (void*) &hub; +} + +uint8_t hub_get_port_num(void) +{ + return hub.desc.bNbrPorts; +} + +uint16_t hub_get_port_poweron_delay_ms(void) +{ + return hub.desc.bPwrOn2PwrGood * 2; +} + +void hub_get_port_status(uint8_t port1, usb_port_status_t *status) +{ + TEST_ASSERT_NOT_NULL(status); + hub_class_request_get_port_status(port1, status); + printf("\t\tport%d %04X.%04X\n", port1, + status->wPortStatus.val, + status->wPortChange.val); +} + +bool hub_get_port_connection(uint8_t port1) +{ + usb_port_status_t port_status; + hub_class_request_get_port_status(port1, &port_status); + return port_status.wPortStatus.PORT_CONNECTION; +} + +void hub_port_power_off(uint8_t port1) +{ + hub_class_request_clear_port_feature(port1, USB_FEATURE_PORT_POWER); +} + +void hub_port_power_on(uint8_t port1) +{ + hub_class_request_set_port_feature(port1, USB_FEATURE_PORT_POWER); + // Wait while port powered, crucial for High-speed ports + vTaskDelay(pdMS_TO_TICKS(hub.desc.bPwrOn2PwrGood)); +} + +void hub_port_reset(uint8_t port1) +{ + hub_class_request_set_port_feature(port1, USB_FEATURE_PORT_RESET); +} + +void hub_port_suspend(uint8_t port1) +{ + hub_class_request_set_port_feature(port1, USB_FEATURE_PORT_SUSPEND); +} + +void hub_port_resume(uint8_t port1) +{ + hub_class_request_clear_port_feature(port1, USB_FEATURE_C_PORT_SUSPEND); +} + +void hub_port_disable(uint8_t port1) +{ + hub_class_request_clear_port_feature(port1, USB_FEATURE_PORT_ENABLE); +} + +void hub_port_clear_connection(uint8_t port1) +{ + hub_class_request_clear_port_feature(port1, USB_FEATURE_C_PORT_CONNECTION); +} + +void hub_port_clear_reset(uint8_t port1) +{ + hub_class_request_clear_port_feature(port1, USB_FEATURE_C_PORT_RESET); +} diff --git a/components/usb/test_apps/ext_port/main/hub_common.h b/components/usb/test_apps/ext_port/main/hub_common.h new file mode 100644 index 0000000000..ffac58df2f --- /dev/null +++ b/components/usb/test_apps/ext_port/main/hub_common.h @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "stdint.h" +#include "hcd.h" +#include "usb/usb_types_ch11.h" + +/** + * @brief Hub set root port handle + * + * When external hub connected to the root hub port (USB OTG peripheral), one port handle shared between all downstream pipes. + * + * @param[in] root_port_hdl Root port handle + */ +void hub_set_root_port(hcd_port_handle_t root_port_hdl); + +/** + * @brief Hub attach + * + * Enables the root port, wait a connection and configure the external Hub + */ +void hub_attach(void); + +/** + * @brief Hub detach + * + * Disables the root port by turing the port off and disconnects the external Hub + */ +void hub_detach(void); + +// ================================================================================================= +// ================================== Hub Child API ================================================ +// ================================================================================================= +/** + * @brief Create pipe for the child device + * + * @param[in] speed Device speed + * @return hcd_pipe_handle_t Pipe handle + */ +hcd_pipe_handle_t hub_child_create_pipe(usb_speed_t speed); + +/** + * @brief Free pipe for the child device + * + * @param[in] pipe_hdl Pipe handle + */ +void hub_child_pipe_free(hcd_pipe_handle_t pipe_hdl); + +/** + * @brief Quick enumeration of the child device + * + * Performs quick enumeration of the device, using the ctrl_pipe handle. + * Quick enumeration includes: + * - Get Device Descriptor + * - Set Address + * - Get Configuration (1) + * + * @param[in] ctrl_pipe Control pipe handle + * @param[in] dev_addr Device address + * @param[in] config_num Configuration number + */ +void hub_child_quick_enum(hcd_pipe_handle_t ctrl_pipe, uint8_t dev_addr, uint8_t config_num); + +// ================================================================================================= +// =================================== Hub Getters ================================================ +// ================================================================================================= +/** + * @brief Get hub context + * + * @return void* pointer to a Hub context + */ +void* hub_get_context(void); + +/** + * @brief Get number of ports + * + * @return uint8_t Number of ports + */ +uint8_t hub_get_port_num(void); + +/** + * @brief Get port power on delay + * + * @return uint16_t Power on delay in milliseconds + */ +uint16_t hub_get_port_poweron_delay_ms(void); + +/** + * @brief Get port status + * + * @param[in] port1 Port number + * @param[out] status Port status + */ +void hub_get_port_status(uint8_t port1, usb_port_status_t *status); + +/** + * @brief Get port connection status + * + * @param[in] port1 Port number + * @return bool Port connection status + */ +bool hub_get_port_connection(uint8_t port1); + +// ================================================================================================= +// =================================== Hub Port Control ============================================ +// ================================================================================================= +/** + * @brief Power off the port + * + * @param[in] port1 Port number + */ +void hub_port_power_off(uint8_t port1); + +/** + * @brief Power on the port + * + * @param[in] port1 Port number + */ +void hub_port_power_on(uint8_t port1); + +/** + * @brief Reset the port + * + * @param[in] port1 Port number + */ +void hub_port_reset(uint8_t port1); + +/** + * @brief Suspend the port + * + * @param[in] port1 Port number + */ +void hub_port_suspend(uint8_t port1); + +/** + * @brief Resume the port + * + * @param[in] port1 Port number + */ +void hub_port_resume(uint8_t port1); + +/** + * @brief Disable the port + * + * @param[in] port1 Port number + */ +void hub_port_disable(uint8_t port1); + +/** + * @brief Clear the port connection + * + * @param[in] port1 Port number + */ +void hub_port_clear_connection(uint8_t port1); + +/** + * @brief Clear the port reset + * + * @param[in] port1 Port number + */ +void hub_port_clear_reset(uint8_t port1); diff --git a/components/usb/test_apps/ext_port/main/test_app_main.c b/components/usb/test_apps/ext_port/main/test_app_main.c new file mode 100644 index 0000000000..4a9c824bbd --- /dev/null +++ b/components/usb/test_apps/ext_port/main/test_app_main.c @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hcd_common.h" +#include "hub_common.h" +#include "ext_port_common.h" + +static hcd_port_handle_t _root_port_hdl; + +void setUp(void) +{ + unity_utils_record_free_mem(); + // Install HCD port + _root_port_hdl = test_hcd_setup(); + // Set root port handle to the hub + hub_set_root_port(_root_port_hdl); + // Setup the External Port Driver + test_ext_port_setup(); + printf("External Port Driver installed\n"); +} + +void tearDown(void) +{ + // Short delay to allow task to be cleaned up + vTaskDelay(10); + // Clean up USB Host + printf("External Port Driver uninstall\n"); + test_ext_port_teardown(); + test_hcd_teardown(_root_port_hdl); + // Short delay to allow task to be cleaned up after client uninstall + vTaskDelay(10); + unity_utils_evaluate_leaks(); +} + +void app_main(void) +{ + // ____ ___ ___________________ __ __ + // | | \/ _____/\______ \ _/ |_ ____ _______/ |_ + // | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\. + // | | / / \ | | \ | | \ ___/ \___ \ | | + // |______/ /_______ / |______ / |__| \___ >____ > |__| + // \/ \/ \/ \/ + printf(" ____ ___ ___________________ __ __ \r\n"); + printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n"); + printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n"); + printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n"); + printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n"); + printf(" \\/ \\/ \\/ \\/ \r\n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} diff --git a/components/usb/test_apps/ext_port/main/test_ext_port.c b/components/usb/test_apps/ext_port/main/test_ext_port.c new file mode 100644 index 0000000000..55c66d7289 --- /dev/null +++ b/components/usb/test_apps/ext_port/main/test_ext_port.c @@ -0,0 +1,470 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include "unity.h" +#include "sdkconfig.h" +// Internal common test files +#include "ext_port_common.h" +#include "hub_common.h" +// Cross-test common files +#include "hcd_common.h" + +#define TEST_PORT_NUM_DEVICE_FSHS CONFIG_USB_HOST_TEST_HUB_PORT_NUM_DEVICE_FSHS +#define TEST_PORT_NUM_DEVICE_LS CONFIG_USB_HOST_TEST_HUB_PORT_NUM_DEVICE_LS +#define TEST_PORT_NUM_EMPTY CONFIG_USB_HOST_TEST_HUB_PORT_NUM_EMPTY +#define TEST_CHILD_DEVICE_ADDR 2 +#define TEST_CHILD_DEVICE_CONFIG 1 + +/** + * @brief Test the Port rconnection based on the test configuration + * + * @param port1 Port number + */ +static inline void test_port_connection(uint8_t port1) +{ + switch (port1) { + case TEST_PORT_NUM_DEVICE_FSHS: + TEST_ASSERT_TRUE_MESSAGE(hub_get_port_connection(port1), "Configured port doesn't have a device. Reconfigure the port number via menuconfig and run the test again."); + break; + case TEST_PORT_NUM_EMPTY: + TEST_ASSERT_FALSE_MESSAGE(hub_get_port_connection(port1), "Configured port doesn't have a device. Reconfigure the port number via menuconfig and run the test again."); + break; + default: + // Nothing to verify + break; + } +} + +/* +Test the External Port reachability by getting the number of ports and checking the port status after powering the port on. + +Purpose: + - Verify the root hub port connection/disconnection + - Verify the port power on/off + - Verify the port status and print it out + +Procedure: + - Attach the hub + - Get the number of ports + - Power on all ports + - Get the port status + - Power off all ports + - Detach the hub +*/ +TEST_CASE("Port: all power on then off", "[ext_port][full_speed][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + usb_port_status_t port_status; + for (uint8_t i = 1; i <= port_num; i++) { + hub_port_power_on(i); + hub_get_port_status(i, &port_status); + test_port_connection(i); + hub_port_power_off(i); + } + hub_detach(); +} + +/* +Test the port in disconnected state. + +Purpose: + - Verify the process how the External Port Driver handles the port without a connection + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON + - Handling the port: POWERED_ON -> DISCONNECTED -> IDLE + - Detach the hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: disconnected", "[low_speed][full_speed][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_EMPTY <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_EMPTY, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_EMPTY, port_hdl); + printf("Handle disconnected port...\n"); + test_ext_port_disconnected(TEST_PORT_NUM_EMPTY, port_hdl); + // Detach parent hub + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); +} + +/* +Test the port with a connection and perform the enumeration of the child USB Low-speed device. + +To configure the port to which the device is connected, change the TEST_PORT_NUM_DEVICE_LS value in the menuconfig. + +Purpose: + - Verify the process how the External Port Driver handles the port with a connection + - Verify the connectivity with the Low-speed USB device, connected via hub + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON -> CONNECTED + - Enumerate the child device, speed: Low + - Handling the port: CONNECTED -> IDLE -> DISCONNECTED + - Recycle the port + - Detach the hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: enumerate child device Low-speed", "[ext_port][low_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_LS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_LS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_LS, port_hdl); + // Wait connection + usb_speed_t port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_LS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + // Check the device speed + TEST_ASSERT_EQUAL_MESSAGE(USB_SPEED_LOW, port_speed, "Invalid USB device speed"); + // Enumerate device + hcd_pipe_handle_t ctrl_pipe = hub_child_create_pipe(port_speed); + hub_child_quick_enum(ctrl_pipe, TEST_CHILD_DEVICE_ADDR, TEST_CHILD_DEVICE_CONFIG); + hub_child_pipe_free(ctrl_pipe); + // Wait disconnection + test_ext_port_imitate_disconnection(TEST_PORT_NUM_DEVICE_LS, port_hdl); + // Recycle the port + test_ext_port_recycle(port_hdl); + // Detach hub + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); +} + +/* +Test the port with a connection and perform the enumeration of the child USB Full-speed device. + +To configure the port to which the device is connected, change the TEST_PORT_NUM_DEVICE_FSHS value in the menuconfig. + +Purpose: + - Verify the process how the External Port Driver handles the port with a connection + - Verify the connectivity with the Low-speed USB device, connected via hub + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON -> CONNECTED + - Enumerate the child device, speed: Full + - Handling the port: CONNECTED -> IDLE -> DISCONNECTED + - Recycle the port + - Detach the hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: enumerate child device Full-speed", "[ext_port][full_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_FSHS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Wait connection + usb_speed_t port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + // Check the device speed + TEST_ASSERT_EQUAL_MESSAGE(USB_SPEED_FULL, port_speed, "Invalid USB device speed"); + // Enumerate device + hcd_pipe_handle_t ctrl_pipe = hub_child_create_pipe(port_speed); + hub_child_quick_enum(ctrl_pipe, TEST_CHILD_DEVICE_ADDR, TEST_CHILD_DEVICE_CONFIG); + hub_child_pipe_free(ctrl_pipe); + // Wait disconnection + test_ext_port_imitate_disconnection(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Recycle the port + test_ext_port_recycle(port_hdl); + // Detach hub + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); +} + +/* +Test the port with a connection and perform the enumeration of the child USB High-speed device. + +To configure the port to which the device is connected, change the TEST_PORT_NUM_DEVICE_FSHS value in the menuconfig. + +Purpose: + - Verify the process how the External Port Driver handles the port with a connection + - Verify the connectivity with the Low-speed USB device, connected via hub + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON -> CONNECTED + - Enumerate the child device, speed: High + - Handling the port: CONNECTED -> IDLE -> DISCONNECTED + - Recycle the port + - Detach the hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: enumerate child device High-speed", "[ext_port][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_FSHS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Wait connection + usb_speed_t port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + // Check the device speed + TEST_ASSERT_EQUAL_MESSAGE(USB_SPEED_HIGH, port_speed, "Invalid USB device speed"); + // Enumerate device + hcd_pipe_handle_t ctrl_pipe = hub_child_create_pipe(port_speed); + hub_child_quick_enum(ctrl_pipe, TEST_CHILD_DEVICE_ADDR, TEST_CHILD_DEVICE_CONFIG); + hub_child_pipe_free(ctrl_pipe); + // Wait disconnection + test_ext_port_imitate_disconnection(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Recycle the port + test_ext_port_recycle(port_hdl); + // Detach hub + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); +} + +/* +Test the port recycle procedure. + +Purpose: + - Verify the port recycle procedure + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON -> CONNECTED + - Wait disconnection + - Recycle the port + - Repower the port + - Verify the port is still available + - Wait disconnection + - Recycle the port + - Detach the hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: recycle", "[ext_port][full_speed][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_FSHS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Wait connection + usb_speed_t port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + // Wait disconnection + test_ext_port_imitate_disconnection(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Recylce the port + printf("Recycle the port...\n"); + test_ext_port_recycle(port_hdl); + // Repower the port as we use imitation for disconnection. Port state in the External Port Driver is not changed during that. + hub_port_power_off(TEST_PORT_NUM_DEVICE_FSHS); + hub_port_power_on(TEST_PORT_NUM_DEVICE_FSHS); + // Verify that port still available + printf("Verify the port is still available...\n"); + port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + // Wait disconnection + test_ext_port_imitate_disconnection(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Recylce the port + test_ext_port_recycle(port_hdl); + // Detach hub + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); +} + +/* +Test the port disable procedure. + +Purpose: + - Verify the port disable procedure + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON -> CONNECTED + - Disable the port + - Detach the parent hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: disable", "[ext_port][full_speed][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_FSHS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Wait connection + usb_speed_t port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + printf("Disable the port ...\n"); + test_ext_port_disable(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Detach hub + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); +} + +/* +Test the port gone procedure, when the port is in POWERED_ON state. + +Purpose: + - Verify the port gone procedure + +Procedure: + - Attach the hub + - Create External Port + - Power on the port + - Detach the parent hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: gone in state - powered on", "[ext_port][full_speed][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_FSHS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // + printf("Port Gone...\n"); + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, false); + test_ext_port_delete(port_hdl); + +} + +/* +Test the port gone procedure, when the port is in ENABLED state. + +Purpose: + - Verify the port gone procedure + +Procedure: + - Attach the hub + - Create External Port + - Handling the port: NOT_CONFIGURED -> POWERED_OFF -> POWERED_ON -> CONNECTED + - Detach the parent hub + - Notify the port + - Delete the port +*/ +TEST_CASE("Port: gone in state - enabled", "[ext_port][full_speed][high_speed]") +{ + hub_attach(); + uint8_t port_num = hub_get_port_num(); + TEST_ASSERT_TRUE(TEST_PORT_NUM_DEVICE_FSHS <= port_num); + // Create External Port + ext_port_config_t port_config = { + .ext_hub_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_dev_hdl = (void*) hub_get_context() /* use any before IDF-10023 */, + .parent_port_num = TEST_PORT_NUM_DEVICE_FSHS, + .port_power_delay_ms = hub_get_port_poweron_delay_ms(), + }; + ext_port_hdl_t port_hdl = test_ext_port_new(&port_config); + // After adding the port, it is in POWERED_OFF state + test_ext_port_power_on(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + // Wait connection + usb_speed_t port_speed = test_ext_port_connected(TEST_PORT_NUM_DEVICE_FSHS, port_hdl); + printf("Hub port: %s speed device \n", (char*[]) { + "Low", "Full", "High" + }[port_speed]); + // + printf("Port Gone...\n"); + hub_detach(); + // Notify port + test_ext_port_gone(port_hdl, true); + test_ext_port_delete(port_hdl); +} diff --git a/components/usb/test_apps/ext_port/pytest_ext_port.py b/components/usb/test_apps/ext_port/pytest_ext_port.py new file mode 100644 index 0000000000..78ec247cdc --- /dev/null +++ b/components/usb/test_apps/ext_port/pytest_ext_port.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.temp_skip_ci(targets=['esp32s2', 'esp32s3', 'esp32p4'], reason='no runner with external hub available') +@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +def test_usb_ext_port(dut: Dut) -> None: + if dut.target == 'esp32p4': + dut.run_all_single_board_cases(group='high_speed', reset=True) + else: + dut.run_all_single_board_cases(group='full_speed', reset=True) diff --git a/components/usb/test_apps/ext_port/sdkconfig.ci b/components/usb/test_apps/ext_port/sdkconfig.ci new file mode 100644 index 0000000000..057d76e808 --- /dev/null +++ b/components/usb/test_apps/ext_port/sdkconfig.ci @@ -0,0 +1,9 @@ +# Hub ports assignment +CONFIG_USB_HOST_TEST_HUB_PORT_NUM_DEVICE_FSHS=1 +CONFIG_USB_HOST_TEST_HUB_PORT_NUM_DEVICE_LS=2 +CONFIG_USB_HOST_TEST_HUB_PORT_NUM_EMPTY=3 + +# Use external power switch to control USB device's power +# switch is controlled by GPIO 21 +CONFIG_USB_PHY_TEST_OTG_DRVVBUS_ENABLE=y +CONFIG_USB_PHY_TEST_OTG_DRVVBUS_GPIO=21 diff --git a/components/usb/test_apps/ext_port/sdkconfig.defaults b/components/usb/test_apps/ext_port/sdkconfig.defaults new file mode 100644 index 0000000000..d433503fbf --- /dev/null +++ b/components/usb/test_apps/ext_port/sdkconfig.defaults @@ -0,0 +1,9 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +# CONFIG_ESP_TASK_WDT_INIT is not set +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +# CONFIG_UNITY_ENABLE_FLOAT is not set +# CONFIG_UNITY_ENABLE_DOUBLE is not set +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y +CONFIG_USB_HOST_HUBS_SUPPORTED=y