esp-idf/components/usb/test/hcd/test_hcd_port.c
Darian Leung 18f9dabce1 USB: Change IRP to URBs
This commit changes the IRP (I/O Request Packet) structure to
the URB (USB Request Block) in order to represent USB transfers
in the host stack. The new URB strcuture:

- Add extra safety with const fields
- Allows each layer of the stack to add their own overhead variables

The HCD layer API and tests have been updated to use this new URB structure
2021-06-09 22:09:46 +08:00

310 lines
13 KiB
C

// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "unity.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_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
/*
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
- Ongoing URBs and pipes are handled correctly
Procedure:
- Setup the HCD and a port
- Trigger a port connection
- Create a default pipe
- Start transfers but immediately trigger a disconnect
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle the event.
- Check that default pipe received a HCD_PIPE_EVENT_INVALID event. Pipe state should be invalid. Dequeue URBs
- 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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_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]));
}
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_SUDDEN_DISCONN);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
printf("Sudden disconnect\n");
//Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe);
for (int i = 0; i < num_pipe_events - 1; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
TEST_ASSERT_EQUAL(hcd_pipe_get_state(default_pipe), HCD_PIPE_STATE_INVALID);
//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_NO_DEVICE);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
} else {
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 commands work correctly whilst there are active pipes with ongoing transfers
- When suspending, the pipes should be allowed to finish their current ongoing transfer before the bus is suspended.
- When resuming, pipes with pending transfer should be started after the bus is resumed.
Procedure:
- Setup the HCD and a port
- Trigger a port connection
- Create a default pipe
- Start transfers but suspend the port immediately
- Resume the port
- Check that all the URBs have completed successfully
- 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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_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]));
}
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");
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_EQUAL(urb->transfer.status, USB_TRANSFER_STATUS_COMPLETED);
TEST_ASSERT_GREATER_THAN(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 with active pipes
Purpose:
- Test that the port disable command works correctly with active pipes
- Pipes should be to finish their current ongoing transfer before port is disabled
- After disabling the port, all pipes should become invalid.
Procedure:
- Setup HCD, a default pipe, and multiple URBs
- Start transfers but immediately disable the port
- Check pipe received invalid event
- Check that transfer are either done or not executed
- 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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_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]));
}
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");
//Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe);
for (int i = 0; i < num_pipe_events - 1; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
//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_NO_DEVICE);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
} else {
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, 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_SUDDEN_DISCONN);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, 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);
}