Merge branch 'feature/usb_host_ext_port_testing' into 'master'

feature(ci): The External Port Driver test app

Closes IDF-10473

See merge request espressif/esp-idf!37275
This commit is contained in:
Roman Leonov
2025-04-29 23:49:26 +08:00
14 changed files with 1698 additions and 0 deletions

View File

@@ -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);

View File

@@ -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)

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,525 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#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
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -0,0 +1,470 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#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);
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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