mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
18f9dabce1
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
310 lines
13 KiB
C
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);
|
|
}
|