esp-idf/components/usb/test/hcd/test_hcd_common.c
Darian Leung 7f09fe1b23 usb: USB Host stack uses USB PHY driver
This commit updates the USB Host stack to use the USB PHY driver. The
USB PHY and the OTG Controller should now both be setup/deleted using
usb_new_phy() and usb_del_phy() respectively.

- The hcd_install() now expects the USB PHY and OTG Contorller to be
    already setup before it is called
- usb_host_install() now has an option to skip calling usb_del_phy() if
    the user wants to setup their own USB PHY (e.g., in the case of using
    and external PHY).
- CDC-ACM and MSC examples/test updated to use internal PHY

Closes https://github.com/espressif/esp-idf/issues/8061
2022-02-18 15:51:05 +08:00

318 lines
13 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2022 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 "test_utils.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 "hcd.h"
#include "usb_private.h"
#include "usb/usb_types_ch9.h"
#include "test_hcd_common.h"
#include "test_usb_common.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;
// ---------------------------------------------------- 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(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_EQUAL(NULL, port_evt_queue);
//Wait for port callback to send an event message
port_event_msg_t msg;
xQueueReceive(port_evt_queue, &msg, portMAX_DELAY);
//Check the contents of that event message
TEST_ASSERT_EQUAL(port_hdl, msg.port_hdl);
TEST_ASSERT_EQUAL(expected_event, msg.port_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_EQUAL(NULL, pipe_evt_queue);
//Wait for pipe callback to send an event message
pipe_event_msg_t msg;
xQueueReceive(pipe_evt_queue, &msg, portMAX_DELAY);
//Check the contents of that event message
TEST_ASSERT_EQUAL(pipe_hdl, msg.pipe_hdl);
TEST_ASSERT_EQUAL(expected_event, msg.pipe_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_EQUAL(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_EQUAL(NULL, pipe_evt_queue);
return EVENT_QUEUE_LEN - uxQueueSpacesAvailable(pipe_evt_queue);
}
// ----------------------------------------------- Driver/Port Related -------------------------------------------------
hcd_port_handle_t test_hcd_setup(void)
{
test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing
//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_EQUAL(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 = {
.fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED,
.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_EQUAL(NULL, port_hdl);
TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl));
test_usb_set_phy_state(false, 0); //Force disconnected state on PHY
return port_hdl;
}
void test_hcd_teardown(hcd_port_handle_t port_hdl)
{
//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_EQUAL(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);
test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing
}
usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl)
{
//Power ON the port
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_usb_set_phy_state(true, pdMS_TO_TICKS(100)); //Allow for connected state on PHY
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));
if (port_speed == USB_SPEED_FULL) {
printf("Full speed enabled\n");
} else {
printf("Low speed enabled\n");
}
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));
}
//Wait for a safe disconnect
printf("Waiting for disconnection\n");
test_usb_set_phy_state(false, pdMS_TO_TICKS(100)); //Force disconnected state on PHY
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_EQUAL(NULL, pipe_evt_queue);
printf("Creating pipe\n");
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_EQUAL(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_EQUAL(NULL, pipe_evt_queue);
//Free the pipe and queue
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_free(pipe_hdl));
vQueueDelete(pipe_evt_queue);
}
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) + (num_isoc_packets * sizeof(usb_isoc_packet_desc_t)), MALLOC_CAP_DEFAULT);
uint8_t *data_buffer = heap_caps_malloc(data_buffer_size, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_EQUAL(NULL, urb);
TEST_ASSERT_NOT_EQUAL(NULL, data_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->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(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//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(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Update address of default pipe
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(default_pipe, ENUM_ADDR));
//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(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Free URB
test_hcd_free_urb(urb);
return ENUM_ADDR;
}