Files
esp-idf/components/usb/test_apps/common/hcd_common.c
igor.masar cb4d90186f feat(usb/hal): Add support for explicit FIFO configuration
Introduce a new HAL API `usb_dwc_hal_set_fifo_config()` that allows advanced users
to manually configure RX, Non-Periodic TX, and Periodic TX FIFO sizes. This offers
fine-grained control beyond the previous bias-based sizing approach.

The HAL function no longer returns `esp_err_t`, and internal validations are enforced
via `HAL_ASSERT()`. Responsibility for input validation has been moved to the HCD layer.

FIFO configuration must be applied before any USB pipes are created or activated.
This feature is intended for use during `usb_host_install()`.

If no custom FIFO configuration is provided (i.e., all values are zero),
the driver falls back to a bias-based default layout based on Kconfig settings
(`CONFIG_USB_HOST_HW_BUFFER_BIAS_*`). Bias resolution is done inside `hcd_port_init()`.

The `port_obj_t` structure has been extended with a `fifo_config` field, which stores
the configuration to allow re-application after a USB port reset.

Obsolete FIFO bias enums (`usb_hal_fifo_bias_t`, `hcd_port_fifo_bias_t`) and related
APIs (`hcd_port_set_fifo_bias()`) have been removed in favor of the new structure-based mechanism.

The HCD initialization and port reset flow has been updated to use the explicit
FIFO configuration.

USB Host maintainer documentation (`maintainers.md`) has been updated accordingly.
Test cases were updated to remove the usage of removed bias API and now rely on default
or custom FIFO configuration.
2025-05-09 15:03:52 +02:00

340 lines
14 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "soc/usb_wrap_struct.h"
#include "esp_intr_alloc.h"
#include "esp_err.h"
#include "esp_attr.h"
#include "esp_rom_gpio.h"
#include "usb_private.h"
#include "usb/usb_types_ch9.h"
#include "esp_private/usb_phy.h"
#include "hcd_common.h"
#include "mock_msc.h"
#include "phy_common.h"
#include "unity.h"
#include "sdkconfig.h"
#define PORT_NUM 1
#define EVENT_QUEUE_LEN 5
#define ENUM_ADDR 1 // Device address to use for tests that enumerate the device
#define ENUM_CONFIG 1 // Device configuration number to use for tests that enumerate the device
typedef struct {
hcd_port_handle_t port_hdl;
hcd_port_event_t port_event;
} port_event_msg_t;
typedef struct {
hcd_pipe_handle_t pipe_hdl;
hcd_pipe_event_t pipe_event;
} pipe_event_msg_t;
hcd_port_handle_t port_hdl = NULL;
// ---------------------------------------------------- Private --------------------------------------------------------
/**
* @brief HCD port callback. Registered when initializing an HCD port
*
* @param port_hdl Port handle
* @param port_event Port event that triggered the callback
* @param user_arg User argument
* @param in_isr Whether callback was called in an ISR context
* @return true ISR should yield after this callback returns
* @return false No yield required (non-ISR context calls should always return false)
*/
static bool port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr)
{
// We store the port's queue handle in the port's context variable
void *port_ctx = hcd_port_get_context(port_hdl);
QueueHandle_t port_evt_queue = (QueueHandle_t)port_ctx;
TEST_ASSERT_TRUE(in_isr); // Current HCD implementation should never call a port callback in a task context
port_event_msg_t msg = {
.port_hdl = port_hdl,
.port_event = port_event,
};
BaseType_t xTaskWoken = pdFALSE;
xQueueSendFromISR(port_evt_queue, &msg, &xTaskWoken);
return (xTaskWoken == pdTRUE);
}
/**
* @brief HCD pipe callback. Registered when allocating a HCD pipe
*
* @param pipe_hdl Pipe handle
* @param pipe_event Pipe event that triggered the callback
* @param user_arg User argument
* @param in_isr Whether the callback was called in an ISR context
* @return true ISR should yield after this callback returns
* @return false No yield required (non-ISR context calls should always return false)
*/
static bool pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr)
{
QueueHandle_t pipe_evt_queue = (QueueHandle_t)user_arg;
pipe_event_msg_t msg = {
.pipe_hdl = pipe_hdl,
.pipe_event = pipe_event,
};
if (in_isr) {
BaseType_t xTaskWoken = pdFALSE;
xQueueSendFromISR(pipe_evt_queue, &msg, &xTaskWoken);
return (xTaskWoken == pdTRUE);
} else {
xQueueSend(pipe_evt_queue, &msg, portMAX_DELAY);
return false;
}
}
// ------------------------------------------------- HCD Event Test ----------------------------------------------------
void test_hcd_expect_port_event(hcd_port_handle_t port_hdl, hcd_port_event_t expected_event)
{
// Get the port event queue from the port's context variable
QueueHandle_t port_evt_queue = (QueueHandle_t)hcd_port_get_context(port_hdl);
TEST_ASSERT_NOT_NULL(port_evt_queue);
// Wait for port callback to send an event message
port_event_msg_t msg;
BaseType_t ret = xQueueReceive(port_evt_queue, &msg, pdMS_TO_TICKS(5000));
TEST_ASSERT_EQUAL_MESSAGE(pdPASS, ret, "Port event not generated on time");
// Check the contents of that event message
TEST_ASSERT_EQUAL(port_hdl, msg.port_hdl);
TEST_ASSERT_EQUAL_MESSAGE(expected_event, msg.port_event, "Unexpected event");
printf("\t-> Port event\n");
}
void test_hcd_expect_pipe_event(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t expected_event)
{
// Get the pipe's event queue from the pipe's context variable
QueueHandle_t pipe_evt_queue = (QueueHandle_t)hcd_pipe_get_context(pipe_hdl);
TEST_ASSERT_NOT_NULL(pipe_evt_queue);
// Wait for pipe callback to send an event message
pipe_event_msg_t msg;
BaseType_t ret = xQueueReceive(pipe_evt_queue, &msg, pdMS_TO_TICKS(5000));
TEST_ASSERT_EQUAL_MESSAGE(pdPASS, ret, "Pipe event not generated on time");
// Check the contents of that event message
TEST_ASSERT_EQUAL(pipe_hdl, msg.pipe_hdl);
TEST_ASSERT_EQUAL_MESSAGE(expected_event, msg.pipe_event, "Unexpected event");
}
int test_hcd_get_num_port_events(hcd_port_handle_t port_hdl)
{
// Get the port event queue from the port's context variable
QueueHandle_t port_evt_queue = (QueueHandle_t)hcd_port_get_context(port_hdl);
TEST_ASSERT_NOT_NULL(port_evt_queue);
return EVENT_QUEUE_LEN - uxQueueSpacesAvailable(port_evt_queue);
}
int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl)
{
// Get the pipe's event queue from the pipe's context variable
QueueHandle_t pipe_evt_queue = (QueueHandle_t)hcd_pipe_get_context(pipe_hdl);
TEST_ASSERT_NOT_NULL(pipe_evt_queue);
return EVENT_QUEUE_LEN - uxQueueSpacesAvailable(pipe_evt_queue);
}
// ----------------------------------------------- Driver/Port Related -------------------------------------------------
hcd_port_handle_t test_hcd_setup(void)
{
test_setup_usb_phy();
// Create a queue for port callback to queue up port events
QueueHandle_t port_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(port_event_msg_t));
TEST_ASSERT_NOT_NULL(port_evt_queue);
// Install HCD
hcd_config_t hcd_config = {
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
TEST_ASSERT_EQUAL(ESP_OK, hcd_install(&hcd_config));
// Initialize a port
hcd_port_config_t port_config = {
.callback = port_callback,
.callback_arg = (void *)port_evt_queue,
.context = (void *)port_evt_queue,
};
hcd_port_handle_t port_hdl;
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_init(PORT_NUM, &port_config, &port_hdl));
TEST_ASSERT_NOT_NULL(port_hdl);
return port_hdl;
}
void test_hcd_teardown(hcd_port_handle_t port_hdl)
{
if (!port_hdl) {
return; // In case of setup stage failure, don't run tear-down stage
}
// Get the queue handle from the port's context variable
QueueHandle_t port_evt_queue = (QueueHandle_t)hcd_port_get_context(port_hdl);
TEST_ASSERT_NOT_NULL(port_evt_queue);
// Deinitialize a port
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_deinit(port_hdl));
// Uninstall the HCD
TEST_ASSERT_EQUAL(ESP_OK, hcd_uninstall());
vQueueDelete(port_evt_queue);
// Deinitialize the internal USB PHY
test_delete_usb_phy();
}
usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl)
{
// Power ON the port. This should allow for connections to occur
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_ON));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISCONNECTED, hcd_port_get_state(port_hdl));
// Wait for connection event
printf("Waiting for connection\n");
test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_CONNECTION);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_CONNECTION, hcd_port_handle_event(port_hdl));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl));
// Reset newly connected device
printf("Resetting\n");
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESET));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl));
// Get speed of connected
usb_speed_t port_speed;
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_get_speed(port_hdl, &port_speed));
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(USB_SPEED_HIGH, port_speed, "Invalid port speed");
printf("%s speed enabled\n", (char *[]) {
"Low", "Full", "High"
}[port_speed]);
return port_speed;
}
void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled)
{
if (!already_disabled) {
// Disable the device
printf("Disabling\n");
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl));
}
printf("Waiting for disconnection\n");
// Power-off the port to trigger a disconnection
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_OFF));
// Wait for the port disconnection event
test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
// Power down the port
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_OFF));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl));
}
// ---------------------------------------------- Pipe Setup/Tear-down -------------------------------------------------
hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed)
{
// Create a queue for pipe callback to queue up pipe events
QueueHandle_t pipe_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(pipe_event_msg_t));
TEST_ASSERT_NOT_NULL(pipe_evt_queue);
hcd_pipe_config_t pipe_config = {
.callback = pipe_callback,
.callback_arg = (void *)pipe_evt_queue,
.context = (void *)pipe_evt_queue,
.ep_desc = ep_desc,
.dev_addr = dev_addr,
.dev_speed = dev_speed,
};
hcd_pipe_handle_t pipe_hdl;
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_alloc(port_hdl, &pipe_config, &pipe_hdl));
TEST_ASSERT_NOT_NULL(pipe_hdl);
return pipe_hdl;
}
void test_hcd_pipe_free(hcd_pipe_handle_t pipe_hdl)
{
// Get the pipe's event queue from its context variable
QueueHandle_t pipe_evt_queue = (QueueHandle_t)hcd_pipe_get_context(pipe_hdl);
TEST_ASSERT_NOT_NULL(pipe_evt_queue);
// Free the pipe and queue
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_free(pipe_hdl));
vQueueDelete(pipe_evt_queue);
}
#include "esp_private/esp_cache_private.h"
#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED)
#define ALIGN_UP(num, align) ((align) == 0 ? (num) : (((num) + ((align) - 1)) & ~((align) - 1)))
urb_t *test_hcd_alloc_urb(int num_isoc_packets, size_t data_buffer_size)
{
// Allocate a URB and data buffer
urb_t *urb = heap_caps_calloc(1, sizeof(urb_t) + (sizeof(usb_isoc_packet_desc_t) * num_isoc_packets), MALLOC_CAP_DEFAULT);
size_t cache_align = 0;
esp_cache_get_alignment(DATA_BUFFER_CAPS, &cache_align);
data_buffer_size = ALIGN_UP(data_buffer_size, cache_align);
void *data_buffer = heap_caps_malloc(data_buffer_size, DATA_BUFFER_CAPS);
TEST_ASSERT_NOT_NULL_MESSAGE(urb, "Failed to allocate URB");
TEST_ASSERT_NOT_NULL_MESSAGE(data_buffer, "Failed to allocate transfer buffer");
// Initialize URB and underlying transfer structure. Need to cast to dummy due to const fields
usb_transfer_dummy_t *transfer_dummy = (usb_transfer_dummy_t *)&urb->transfer;
transfer_dummy->data_buffer = data_buffer;
transfer_dummy->data_buffer_size = data_buffer_size;
transfer_dummy->num_isoc_packets = num_isoc_packets;
return urb;
}
void test_hcd_free_urb(urb_t *urb)
{
// Free data buffer of the transfer
heap_caps_free(urb->transfer.data_buffer);
// Free the URB
heap_caps_free(urb);
}
uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe)
{
// We need to create a URB for the enumeration control transfers
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_setup_packet_t) + 256);
usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)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);
urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status, "Transfer NOT completed");
// Update the MPS of the default pipe
usb_device_desc_t *device_desc = (usb_device_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(default_pipe, device_desc->bMaxPacketSize0));
// Send a set address request
USB_SETUP_PACKET_INIT_SET_ADDR(setup_pkt, ENUM_ADDR); // We only support one device for now so use address 1
urb->transfer.num_bytes = sizeof(usb_setup_packet_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status, "Transfer NOT completed");
// 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);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL_MESSAGE(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status, "Transfer NOT completed");
// Free URB
test_hcd_free_urb(urb);
return ENUM_ADDR;
}