mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
accbaee57c
This commit adds the preliminary version of the USB Host Library. This commit contains: - USBH (USB Host Driver) - Hub Driver that only supports a single device and device enumeration - USB Host Library (asychronous API) - Test cases for USB Host Library asychronous API The following changes were made to the existing HCD: - Removed HCD_PIPE_STATE_INVALID. Pipes are no longer invalidated - Changed pipe commands to halt, flush, and clear. Pipes need to be manually halted, flush, and cleared. - Halting and flushing a pipe will execute the pipe callback if it causes a HCD_PIPE_EVENT_URB_DONE event
343 lines
16 KiB
C
343 lines
16 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/semphr.h"
|
|
#include "unity.h"
|
|
#include "esp_rom_sys.h"
|
|
#include "test_utils.h"
|
|
#include "test_hcd_common.h"
|
|
|
|
#define TEST_DEV_ADDR 0
|
|
#define NUM_URBS 3
|
|
#define TRANSFER_MAX_BYTES 256
|
|
#define URB_DATA_BUFF_SIZE (sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
|
|
#define POST_ENQUEUE_DELAY_US 10
|
|
|
|
/*
|
|
Test a port sudden disconnect and port recovery
|
|
|
|
Purpose: Test that when sudden disconnection happens on an HCD port, the port will
|
|
- Generate the HCD_PORT_EVENT_SUDDEN_DISCONN and be put into the HCD_PORT_STATE_RECOVERY state
|
|
- Pipes can be halted and flushed after a port error
|
|
|
|
Procedure:
|
|
- Setup the HCD and a port
|
|
- Trigger a port connection
|
|
- Create a default pipe
|
|
- Start transfers but trigger a disconnect after a short delay
|
|
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event.
|
|
- Check that the default pipe remains in the HCD_PIPE_STATE_ACTIVE after the port error.
|
|
- Check that the default pipe can be halted, a HCD_PIPE_EVENT_URB_DONE event should be generated
|
|
- Check that the default pipe can be flushed, a HCD_PIPE_EVENT_URB_DONE event should be generated
|
|
- Check that all URBs can be dequeued.
|
|
- Free default pipe
|
|
- Recover the port
|
|
- Trigger connection and disconnection again (to make sure the port works post recovery)
|
|
- Teardown port and HCD
|
|
*/
|
|
|
|
TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
|
|
{
|
|
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
|
|
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
|
|
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
|
|
|
|
//Allocate some URBs and initialize their data buffers with control transfers
|
|
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
|
|
urb_t *urb_list[NUM_URBS];
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
|
|
//Initialize with a "Get Config Descriptor request"
|
|
urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES;
|
|
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
|
|
urb_list[i]->transfer.context = (void *)0xDEADBEEF;
|
|
}
|
|
|
|
//Enqueue URBs but immediately trigger a disconnect
|
|
printf("Enqueuing URBs\n");
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
|
|
}
|
|
//Add a short delay to let the transfers run for a bit
|
|
esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
|
|
test_hcd_force_conn_state(false, 0);
|
|
//Disconnect event should have occurred. Handle the 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));
|
|
printf("Sudden disconnect\n");
|
|
|
|
//We should be able to halt then flush the pipe
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
|
|
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
|
|
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
|
|
printf("Pipe halted and flushed\n");
|
|
|
|
//Dequeue URBs
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
urb_t *urb = hcd_urb_dequeue(default_pipe);
|
|
TEST_ASSERT_EQUAL(urb_list[i], urb);
|
|
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
|
|
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
|
|
//We must have transmitted at least the setup packet, but device may return less than bytes requested
|
|
TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);
|
|
TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes);
|
|
} else {
|
|
//A failed transfer should 0 actual number of bytes transmitted
|
|
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
|
|
}
|
|
TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
|
|
}
|
|
//Free URB list and pipe
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
test_hcd_free_urb(urb_list[i]);
|
|
}
|
|
test_hcd_pipe_free(default_pipe);
|
|
|
|
//Recover the port should return to the to NOT POWERED state
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_recover(port_hdl));
|
|
TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl));
|
|
|
|
//Recovered port should be able to connect and disconnect again
|
|
test_hcd_wait_for_conn(port_hdl);
|
|
test_hcd_wait_for_disconn(port_hdl, false);
|
|
test_hcd_teardown(port_hdl);
|
|
}
|
|
|
|
/*
|
|
Test port suspend and resume with active pipes
|
|
|
|
Purpose:
|
|
- Test port suspend and resume procedure
|
|
- When suspending, the pipes should be halted before suspending the port. Any pending transfers should remain pending
|
|
- When resuming, the pipes should remain in the halted state
|
|
- Pipes on being cleared of the halt should resume transferring the pending transfers
|
|
|
|
Procedure:
|
|
- Setup the HCD and a port
|
|
- Trigger a port connection
|
|
- Create a default pipe
|
|
- Start transfers
|
|
- Halt the default pipe after a short delay
|
|
- Suspend the port
|
|
- Resume the port
|
|
- Check that all the URBs have either completed successfully or been canceled by the pipe halt
|
|
- Cleanup URBs and default pipe
|
|
- Trigger disconnection and teardown
|
|
*/
|
|
TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
|
|
{
|
|
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
|
|
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
|
|
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
|
|
|
|
//Allocate some URBs and initialize their data buffers with control transfers
|
|
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
|
|
urb_t *urb_list[NUM_URBS];
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
|
|
//Initialize with a "Get Config Descriptor request"
|
|
urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES;
|
|
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
|
|
urb_list[i]->transfer.context = (void *)0xDEADBEEF;
|
|
}
|
|
|
|
//Enqueue URBs but immediately suspend the port
|
|
printf("Enqueuing URBs\n");
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
|
|
}
|
|
//Add a short delay to let the transfers run for a bit
|
|
esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
|
|
//Halt the default pipe before suspending
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
|
|
//Suspend the port
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND));
|
|
TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl));
|
|
printf("Suspended\n");
|
|
vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for bus to remain suspended
|
|
|
|
//Resume the port
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME));
|
|
TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl));
|
|
printf("Resumed\n");
|
|
//Clear the default pipe's halt
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR));
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
|
|
vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed URBs to complete
|
|
//Dequeue URBs
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
urb_t *urb = hcd_urb_dequeue(default_pipe);
|
|
TEST_ASSERT_EQUAL(urb_list[i], urb);
|
|
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
|
|
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
|
|
//We must have transmitted at least the setup packet, but device may return less than bytes requested
|
|
TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);
|
|
TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes);
|
|
} else {
|
|
//A failed transfer should 0 actual number of bytes transmitted
|
|
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
|
|
}
|
|
TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
|
|
}
|
|
|
|
//Free URB list and pipe
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
test_hcd_free_urb(urb_list[i]);
|
|
}
|
|
test_hcd_pipe_free(default_pipe);
|
|
//Cleanup
|
|
test_hcd_wait_for_disconn(port_hdl, false);
|
|
test_hcd_teardown(port_hdl);
|
|
}
|
|
|
|
/*
|
|
Test HCD port disable and disconnection
|
|
|
|
Purpose:
|
|
- Test that the port disable command works correctly
|
|
- Check that port can only be disabled when pipes have been halted
|
|
- Check that a disconnection after port disable still triggers a HCD_PORT_EVENT_DISCONNECTION event
|
|
|
|
Procedure:
|
|
- Setup HCD, a default pipe, and multiple URBs
|
|
- Start transfers
|
|
- Halt the default pipe after a short delay
|
|
- Check that port can be disabled
|
|
- Flush the default pipe and cleanup the default pipe
|
|
- Check that a disconnection still works after disable
|
|
- Teardown
|
|
*/
|
|
TEST_CASE("Test HCD port disable", "[hcd][ignore]")
|
|
{
|
|
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
|
|
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
|
|
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
|
|
|
|
//Allocate some URBs and initialize their data buffers with control transfers
|
|
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor)
|
|
urb_t *urb_list[NUM_URBS];
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
|
|
//Initialize with a "Get Config Descriptor request"
|
|
urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES;
|
|
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
|
|
urb_list[i]->transfer.context = (void *)0xDEADBEEF;
|
|
}
|
|
|
|
//Enqueue URBs but immediately disable the port
|
|
printf("Enqueuing URBs\n");
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
|
|
}
|
|
//Add a short delay to let the transfers run for a bit
|
|
esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
|
|
//Halt the default pipe before suspending
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
|
|
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
|
|
|
|
|
|
//Check that port can be disabled
|
|
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("Disabled\n");
|
|
|
|
//Flush pipe
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
|
|
//Dequeue URBs
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
urb_t *urb = hcd_urb_dequeue(default_pipe);
|
|
TEST_ASSERT_EQUAL(urb_list[i], urb);
|
|
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
|
|
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
|
|
//We must have transmitted at least the setup packet, but device may return less than bytes requested
|
|
TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);
|
|
TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes);
|
|
} else {
|
|
//A failed transfer should 0 actual number of bytes transmitted
|
|
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
|
|
}
|
|
TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
|
|
}
|
|
|
|
//Free URB list and pipe
|
|
for (int i = 0; i < NUM_URBS; i++) {
|
|
test_hcd_free_urb(urb_list[i]);
|
|
}
|
|
test_hcd_pipe_free(default_pipe);
|
|
|
|
//Trigger a disconnection and cleanup
|
|
test_hcd_wait_for_disconn(port_hdl, true);
|
|
test_hcd_teardown(port_hdl);
|
|
}
|
|
|
|
/*
|
|
Test HCD port command bailout
|
|
|
|
Purpose:
|
|
- Test that if the a port's state changes whilst a command is being executed, the port command should return
|
|
ESP_ERR_INVALID_RESPONSE
|
|
|
|
Procedure:
|
|
- Setup HCD and wait for connection
|
|
- Suspend the port
|
|
- Resume the port but trigger a disconnect from another thread during the resume command
|
|
- Check that port command returns ESP_ERR_INVALID_RESPONSE
|
|
*/
|
|
|
|
static void concurrent_task(void *arg)
|
|
{
|
|
SemaphoreHandle_t sync_sem = (SemaphoreHandle_t) arg;
|
|
xSemaphoreTake(sync_sem, portMAX_DELAY);
|
|
vTaskDelay(pdMS_TO_TICKS(10)); //Give a short delay let reset command start in main thread
|
|
//Force a disconnection
|
|
test_hcd_force_conn_state(false, 0);
|
|
vTaskDelay(portMAX_DELAY); //Block forever and wait to be deleted
|
|
}
|
|
|
|
TEST_CASE("Test HCD port command bailout", "[hcd][ignore]")
|
|
{
|
|
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
|
|
test_hcd_wait_for_conn(port_hdl); //Trigger a connection
|
|
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
|
|
|
|
//Create task to run port commands concurrently
|
|
SemaphoreHandle_t sync_sem = xSemaphoreCreateBinary();
|
|
TaskHandle_t task_handle;
|
|
TEST_ASSERT_NOT_EQUAL(NULL, sync_sem);
|
|
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(concurrent_task, "tsk", 4096, (void *) sync_sem, UNITY_FREERTOS_PRIORITY + 1, &task_handle, 0));
|
|
|
|
//Suspend the device
|
|
printf("Suspending\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND));
|
|
vTaskDelay(pdMS_TO_TICKS(20)); //Short delay for device to enter suspend state
|
|
|
|
//Attempt to resume the port. But the concurrent task should override this with a disconnection event
|
|
printf("Attempting to resume\n");
|
|
xSemaphoreGive(sync_sem); //Trigger concurrent task
|
|
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_RESPONSE, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME));
|
|
|
|
//Check that concurrent task triggered a sudden disconnection
|
|
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));
|
|
|
|
//Cleanup task and semaphore
|
|
vTaskDelay(pdMS_TO_TICKS(10)); //Short delay for concurrent task finish running
|
|
vTaskDelete(task_handle);
|
|
vSemaphoreDelete(sync_sem);
|
|
|
|
test_hcd_teardown(port_hdl);
|
|
}
|