mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
7f42104893
This commit adds support for interrupt and isochronous pipes to the HCD: - HCD now internally uses double buffering - Added test cases for interrupt and isochronous transfers - Reorganized test cases for each transfer type - Updated API comments and maintainer's notes Some minor bugs were also fixed
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_IRPS 3
|
|
#define TRANSFER_MAX_BYTES 256
|
|
#define IRP_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 IRPs 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 IRPs
|
|
- 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 IRPs 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)
|
|
usb_irp_t *irp_list[NUM_IRPS];
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
|
|
//Initialize with a "Get Config Descriptor request"
|
|
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
|
|
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
|
|
irp_list[i]->context = (void *)0xDEADBEEF;
|
|
}
|
|
|
|
//Enqueue IRPs but immediately trigger a disconnect
|
|
printf("Enqueuing IRPs\n");
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_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_IRP_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_IRP_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 IRPs
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
|
|
TEST_ASSERT_EQUAL(irp_list[i], irp);
|
|
TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_NO_DEVICE);
|
|
if (irp->status == USB_TRANSFER_STATUS_COMPLETED) {
|
|
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
|
|
} else {
|
|
TEST_ASSERT_EQUAL(0, irp->actual_num_bytes);
|
|
}
|
|
TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context);
|
|
}
|
|
//Free IRP list and pipe
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
test_hcd_free_irp(irp_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 IRPs have completed successfully
|
|
- Cleanup IRPs 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 IRPs 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)
|
|
usb_irp_t *irp_list[NUM_IRPS];
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
|
|
//Initialize with a "Get Config Descriptor request"
|
|
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
|
|
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
|
|
irp_list[i]->context = (void *)0xDEADBEEF;
|
|
}
|
|
|
|
//Enqueue IRPs but immediately suspend the port
|
|
printf("Enqueuing IRPs\n");
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_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 IRPs to complete
|
|
//Dequeue IRPs
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
|
|
TEST_ASSERT_EQUAL(irp_list[i], irp);
|
|
TEST_ASSERT_EQUAL(irp->status, USB_TRANSFER_STATUS_COMPLETED);
|
|
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
|
|
TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context);
|
|
}
|
|
|
|
//Free IRP list and pipe
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
test_hcd_free_irp(irp_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 IRPs
|
|
- 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 IRPs 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)
|
|
usb_irp_t *irp_list[NUM_IRPS];
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
irp_list[i] = test_hcd_alloc_irp(0, IRP_DATA_BUFF_SIZE);
|
|
//Initialize with a "Get Config Descriptor request"
|
|
irp_list[i]->num_bytes = TRANSFER_MAX_BYTES;
|
|
USB_CTRL_REQ_INIT_GET_CFG_DESC((usb_ctrl_req_t *)irp_list[i]->data_buffer, 0, TRANSFER_MAX_BYTES);
|
|
irp_list[i]->context = (void *)0xDEADBEEF;
|
|
}
|
|
|
|
//Enqueue IRPs but immediately disable the port
|
|
printf("Enqueuing IRPs\n");
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
TEST_ASSERT_EQUAL(ESP_OK, hcd_irp_enqueue(default_pipe, irp_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_IRP_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_IRP_DONE);
|
|
}
|
|
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
|
|
|
|
//Dequeue IRPs
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
usb_irp_t *irp = hcd_irp_dequeue(default_pipe);
|
|
TEST_ASSERT_EQUAL(irp_list[i], irp);
|
|
TEST_ASSERT(irp->status == USB_TRANSFER_STATUS_COMPLETED || irp->status == USB_TRANSFER_STATUS_NO_DEVICE);
|
|
if (irp->status == USB_TRANSFER_STATUS_COMPLETED) {
|
|
TEST_ASSERT_GREATER_THAN(0, irp->actual_num_bytes);
|
|
} else {
|
|
TEST_ASSERT_EQUAL(0, irp->actual_num_bytes);
|
|
}
|
|
TEST_ASSERT_EQUAL(0xDEADBEEF, irp->context);
|
|
}
|
|
|
|
//Free IRP list and pipe
|
|
for (int i = 0; i < NUM_IRPS; i++) {
|
|
test_hcd_free_irp(irp_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);
|
|
}
|