From 891c979c640b7059c21be6df228ae10a951aded6 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Wed, 27 Oct 2021 15:50:21 +0800 Subject: [PATCH] usb: Add USB Host Library sudden disconnect tests --- components/usb/test/common/test_usb_common.c | 33 +++ components/usb/test/common/test_usb_common.h | 16 ++ .../usb/test/common/test_usb_mock_classes.c | 20 +- .../usb/test/common/test_usb_mock_classes.h | 20 +- components/usb/test/hcd/test_hcd_common.c | 29 +-- components/usb/test/hcd/test_hcd_common.h | 12 +- components/usb/test/hcd/test_hcd_isoc.c | 3 +- components/usb/test/hcd/test_hcd_port.c | 5 +- components/usb/test/usb_host/msc_client.h | 2 + .../test/usb_host/msc_client_async_dconn.c | 246 ++++++++++++++++++ .../usb/test/usb_host/msc_client_async_seq.c | 5 +- .../usb/test/usb_host/test_usb_host_async.c | 1 + .../test/usb_host/test_usb_host_plugging.c | 111 ++++++++ 13 files changed, 435 insertions(+), 68 deletions(-) create mode 100644 components/usb/test/common/test_usb_common.c create mode 100644 components/usb/test/common/test_usb_common.h create mode 100644 components/usb/test/usb_host/msc_client_async_dconn.c create mode 100644 components/usb/test/usb_host/test_usb_host_plugging.c diff --git a/components/usb/test/common/test_usb_common.c b/components/usb/test/common/test_usb_common.c new file mode 100644 index 0000000000..61f997a98a --- /dev/null +++ b/components/usb/test/common/test_usb_common.c @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "soc/usb_wrap_struct.h" +#include "test_usb_common.h" + +void test_usb_force_conn_state(bool connected, TickType_t delay_ticks) +{ + if (delay_ticks > 0) { + //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. + vTaskDelay(delay_ticks); + } + usb_wrap_dev_t *wrap = &USB_WRAP; + if (connected) { + //Disable test mode to return to previous internal PHY configuration + wrap->test_conf.test_enable = 0; + } else { + /* + Mimic a disconnection by using the internal PHY's test mode. + Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0, + this will look like a disconnection. + */ + wrap->test_conf.val = 0; + wrap->test_conf.test_usb_wrap_oe = 1; + wrap->test_conf.test_enable = 1; + } +} diff --git a/components/usb/test/common/test_usb_common.h b/components/usb/test/common/test_usb_common.h new file mode 100644 index 0000000000..fc8a8379d3 --- /dev/null +++ b/components/usb/test/common/test_usb_common.h @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" + +/** + * @brief For the USB PHY into the connected or disconnected state + * + * @param connected For into connected state if true, disconnected if false + * @param delay_ticks Delay in ticks before forcing state + */ +void test_usb_force_conn_state(bool connected, TickType_t delay_ticks); diff --git a/components/usb/test/common/test_usb_mock_classes.c b/components/usb/test/common/test_usb_mock_classes.c index f3d7d42d8e..0e819281de 100644 --- a/components/usb/test/common/test_usb_mock_classes.c +++ b/components/usb/test/common/test_usb_mock_classes.c @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include @@ -20,6 +12,8 @@ // ---------------------------------------------------- MSC SCSI ------------------------------------------------------- +const char *MSC_CLIENT_TAG = "MSC Client"; + void mock_msc_scsi_init_cbw(mock_msc_bulk_cbw_t *cbw, bool is_read, int offset, int num_sectors, uint32_t tag) { cbw->dCBWSignature = 0x43425355; //Fixed value diff --git a/components/usb/test/common/test_usb_mock_classes.h b/components/usb/test/common/test_usb_mock_classes.h index 6c80b076c1..ef39aff127 100644 --- a/components/usb/test/common/test_usb_mock_classes.h +++ b/components/usb/test/common/test_usb_mock_classes.h @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ /* This header contains bare-bone mock implementations of some device classes in order to test various layers of the USB @@ -29,6 +21,8 @@ extern "C" { // ---------------------------------------------------- MSC SCSI ------------------------------------------------------- +const char *MSC_CLIENT_TAG; + /* Note: The mock MSC SCSI tests requires that USB flash drive be connected. The flash drive should... diff --git a/components/usb/test/hcd/test_hcd_common.c b/components/usb/test/hcd/test_hcd_common.c index 03217d83cf..9116ffa72d 100644 --- a/components/usb/test/hcd/test_hcd_common.c +++ b/components/usb/test/hcd/test_hcd_common.c @@ -18,6 +18,7 @@ #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 @@ -135,28 +136,6 @@ int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl) // ----------------------------------------------- Driver/Port Related ------------------------------------------------- -void test_hcd_force_conn_state(bool connected, TickType_t delay_ticks) -{ - if (delay_ticks > 0) { - //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. - vTaskDelay(delay_ticks); - } - usb_wrap_dev_t *wrap = &USB_WRAP; - if (connected) { - //Disable test mode to return to previous internal PHY configuration - wrap->test_conf.test_enable = 0; - } else { - /* - Mimic a disconnection by using the internal PHY's test mode. - Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0, - this will look like a disconnection. - */ - wrap->test_conf.val = 0; - wrap->test_conf.test_usb_wrap_oe = 1; - wrap->test_conf.test_enable = 1; - } -} - hcd_port_handle_t test_hcd_setup(void) { //Create a queue for port callback to queue up port events @@ -178,7 +157,7 @@ hcd_port_handle_t test_hcd_setup(void) 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_hcd_force_conn_state(false, 0); //Force disconnected state on PHY + test_usb_force_conn_state(false, 0); //Force disconnected state on PHY return port_hdl; } @@ -201,7 +180,7 @@ usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl) TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISCONNECTED, hcd_port_get_state(port_hdl)); //Wait for connection event printf("Waiting for connection\n"); - test_hcd_force_conn_state(true, pdMS_TO_TICKS(100)); //Allow for connected state on PHY + test_usb_force_conn_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)); @@ -230,7 +209,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled } //Wait for a safe disconnect printf("Waiting for disconnection\n"); - test_hcd_force_conn_state(false, pdMS_TO_TICKS(100)); //Force disconnected state on PHY + test_usb_force_conn_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)); diff --git a/components/usb/test/hcd/test_hcd_common.h b/components/usb/test/hcd/test_hcd_common.h index 493b95dc24..a41c9fe451 100644 --- a/components/usb/test/hcd/test_hcd_common.h +++ b/components/usb/test/hcd/test_hcd_common.h @@ -48,14 +48,6 @@ int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl); // ----------------------------------------------- Driver/Port Related ------------------------------------------------- -/** - * @brief For the USB PHY into the connected or disconnected state - * - * @param connected For into connected state if true, disconnected if false - * @param delay_ticks Delay in ticks before forcing state - */ -void test_hcd_force_conn_state(bool connected, TickType_t delay_ticks); - /** * @brief Sets up the HCD and initializes an HCD port. * @@ -73,7 +65,7 @@ void test_hcd_teardown(hcd_port_handle_t port_hdl); /** * @brief Wait for a connection on an HCD port * - * @note This function will internally call test_hcd_force_conn_state() to allow for a connection + * @note This function will internally call test_usb_force_conn_state() to allow for a connection * * @param port_hdl Port handle * @return usb_speed_t Speed of the connected device @@ -83,7 +75,7 @@ usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl); /** * @brief Wait for a disconnection on an HCD port * - * @note This fucntion will internally call test_hcd_force_conn_state() to force a disconnection + * @note This fucntion will internally call test_usb_force_conn_state() to force a disconnection * * @param port_hdl Port handle * @param already_disabled Whether the HCD port is already in the disabled state diff --git a/components/usb/test/hcd/test_hcd_isoc.c b/components/usb/test/hcd/test_hcd_isoc.c index 2258a89cf5..7358ff979e 100644 --- a/components/usb/test/hcd/test_hcd_isoc.c +++ b/components/usb/test/hcd/test_hcd_isoc.c @@ -11,6 +11,7 @@ #include "unity.h" #include "test_utils.h" #include "test_usb_mock_classes.h" +#include "test_usb_common.h" #include "test_hcd_common.h" #define NUM_URBS 3 @@ -154,7 +155,7 @@ TEST_CASE("Test HCD isochronous pipe sudden disconnect", "[hcd][ignore]") } //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); + test_usb_force_conn_state(false, 0); //Disconnect event should have occurred. Handle the port 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)); diff --git a/components/usb/test/hcd/test_hcd_port.c b/components/usb/test/hcd/test_hcd_port.c index 05ba751994..d3e2678160 100644 --- a/components/usb/test/hcd/test_hcd_port.c +++ b/components/usb/test/hcd/test_hcd_port.c @@ -10,6 +10,7 @@ #include "unity.h" #include "esp_rom_sys.h" #include "test_utils.h" +#include "test_usb_common.h" #include "test_hcd_common.h" #define TEST_DEV_ADDR 0 @@ -65,7 +66,7 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]") } //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); + test_usb_force_conn_state(false, 0); //Disconnect event should have occurred. Handle the port 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)); @@ -304,7 +305,7 @@ static void concurrent_task(void *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); + test_usb_force_conn_state(false, 0); vTaskDelay(portMAX_DELAY); //Block forever and wait to be deleted } diff --git a/components/usb/test/usb_host/msc_client.h b/components/usb/test/usb_host/msc_client.h index b1809b949c..90850701d8 100644 --- a/components/usb/test/usb_host/msc_client.h +++ b/components/usb/test/usb_host/msc_client.h @@ -15,3 +15,5 @@ typedef struct { } msc_client_test_param_t; void msc_client_async_seq_task(void *arg); + +void msc_client_async_dconn_task(void *arg); diff --git a/components/usb/test/usb_host/msc_client_async_dconn.c b/components/usb/test/usb_host/msc_client_async_dconn.c new file mode 100644 index 0000000000..67cd134edd --- /dev/null +++ b/components/usb/test/usb_host/msc_client_async_dconn.c @@ -0,0 +1,246 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "test_usb_mock_classes.h" +#include "test_usb_common.h" +#include "msc_client.h" +#include "usb/usb_host.h" +#include "unity.h" +#include "test_utils.h" + +/* +Implementation of an asynchronous MSC client used for USB Host disconnection test. + +- The MSC client will: + - Register itself as a client + - Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device + - Allocate IN and OUT transfer objects for MSC SCSI transfers + - Trigger a single MSC SCSI transfer + - Split the data stage into multiple transfers (so that the endpoint multiple queued up transfers) + - Cause a disconnection mid-way through the data stage + - All of the transfers should be automatically deqeueud + - Then a USB_HOST_CLIENT_EVENT_DEV_GONE event should occur afterwards + - Free transfer objects + - Close device + - Deregister MSC client +*/ + +#define MAX(x,y) (((x) >= (y)) ? (x) : (y)) +#define MSC_CLIENT_MAX_EVENT_MSGS 5 + +typedef enum { + TEST_STAGE_WAIT_CONN, + TEST_STAGE_DEV_OPEN, + TEST_STAGE_MSC_RESET, + TEST_STAGE_MSC_CBW, + TEST_STAGE_MSC_DATA_DCONN, + TEST_STAGE_DEV_CLOSE, +} test_stage_t; + +typedef struct { + msc_client_test_param_t test_param; + test_stage_t cur_stage; + test_stage_t next_stage; + uint8_t dev_addr_to_open; + usb_host_client_handle_t client_hdl; + usb_device_handle_t dev_hdl; + int num_data_transfers; + int event_count; +} msc_client_obj_t; + +static void msc_reset_cbw_transfer_cb(usb_transfer_t *transfer) +{ + msc_client_obj_t *msc_obj = (msc_client_obj_t *)transfer->context; + //We expect the reset and CBW transfers to complete with no issues + TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status); + TEST_ASSERT_EQUAL(transfer->num_bytes, transfer->actual_num_bytes); + switch (msc_obj->cur_stage) { + case TEST_STAGE_MSC_RESET: + msc_obj->next_stage = TEST_STAGE_MSC_CBW; + break; + case TEST_STAGE_MSC_CBW: + msc_obj->next_stage = TEST_STAGE_MSC_DATA_DCONN; + break; + default: + abort(); + break; + } +} + +static void msc_data_transfer_cb(usb_transfer_t *transfer) +{ + //The data stage should have either completed, or failed due to the disconnection. + TEST_ASSERT(transfer->status == USB_TRANSFER_STATUS_COMPLETED || transfer->status == USB_TRANSFER_STATUS_NO_DEVICE); + if (transfer->status == USB_TRANSFER_STATUS_COMPLETED) { + TEST_ASSERT_EQUAL(transfer->num_bytes, transfer->actual_num_bytes); + } else { + TEST_ASSERT_EQUAL(0, transfer->actual_num_bytes); + } + msc_client_obj_t *msc_obj = (msc_client_obj_t *)transfer->context; + msc_obj->event_count++; + //If all transfers dequeued and device gone event occurred. Go to next stage + if (msc_obj->event_count >= msc_obj->num_data_transfers + 1) { + msc_obj->next_stage = TEST_STAGE_DEV_CLOSE; + } +} + +static void msc_client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + msc_client_obj_t *msc_obj = (msc_client_obj_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + TEST_ASSERT_EQUAL(TEST_STAGE_WAIT_CONN, msc_obj->cur_stage); + msc_obj->next_stage = TEST_STAGE_DEV_OPEN; + msc_obj->dev_addr_to_open = event_msg->new_dev.address; + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + msc_obj->event_count++; + //If all transfers dequeued and device gone event occurred. Go to next stage + if (msc_obj->event_count >= msc_obj->num_data_transfers + 1) { + msc_obj->next_stage = TEST_STAGE_DEV_CLOSE; + } + break; + default: + abort(); //Should never occur in this test + break; + } +} + +void msc_client_async_dconn_task(void *arg) +{ + msc_client_obj_t msc_obj; + memcpy(&msc_obj.test_param, arg, sizeof(msc_client_test_param_t)); + msc_obj.cur_stage = TEST_STAGE_WAIT_CONN; + msc_obj.next_stage = TEST_STAGE_WAIT_CONN; + msc_obj.dev_addr_to_open = 0; + msc_obj.client_hdl = NULL; + msc_obj.dev_hdl = NULL; + msc_obj.num_data_transfers = msc_obj.test_param.num_sectors_per_xfer / MOCK_MSC_SCSI_SECTOR_SIZE; + msc_obj.event_count = 0; + + //Register client + usb_host_client_config_t client_config = { + .client_event_callback = msc_client_event_cb, + .callback_arg = (void *)&msc_obj, + .max_num_event_msg = MSC_CLIENT_MAX_EVENT_MSGS, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &msc_obj.client_hdl)); + + //Allocate transfers + usb_transfer_t *xfer_out; //Must be large enough to contain CBW and MSC reset control transfer + usb_transfer_t *xfer_in[msc_obj.num_data_transfers]; //We manually split the data stage into multiple transfers + size_t xfer_out_size = MAX(sizeof(mock_msc_bulk_cbw_t), sizeof(usb_setup_packet_t)); + size_t xfer_in_size = MOCK_MSC_SCSI_SECTOR_SIZE; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(xfer_out_size, 0, &xfer_out)); + xfer_out->context = (void *)&msc_obj; + for (int i = 0; i < msc_obj.num_data_transfers; i++) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(xfer_in_size, 0, &xfer_in[i])); + xfer_in[i]->context = (void *)&msc_obj; + } + + //Wait to be started by main thread + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ESP_LOGD(MSC_CLIENT_TAG, "Starting"); + + bool exit_loop = false; + bool skip_event_handling = false; + while (!exit_loop) { + if (!skip_event_handling) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(msc_obj.client_hdl, portMAX_DELAY)); + } + skip_event_handling = false; + if (msc_obj.cur_stage == msc_obj.next_stage) { + continue; + } + msc_obj.cur_stage = msc_obj.next_stage; + + switch (msc_obj.cur_stage) { + case TEST_STAGE_DEV_OPEN: { + ESP_LOGD(MSC_CLIENT_TAG, "Open"); + //Open the device + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(msc_obj.client_hdl, msc_obj.dev_addr_to_open, &msc_obj.dev_hdl)); + //Target our transfers to the device + xfer_out->device_handle = msc_obj.dev_hdl; + xfer_out->callback = msc_reset_cbw_transfer_cb; + for (int i = 0; i < msc_obj.num_data_transfers; i++) { + xfer_in[i]->device_handle = msc_obj.dev_hdl; + xfer_in[i]->callback = msc_data_transfer_cb; + } + //Check the VID/PID of the opened device + const usb_device_desc_t *device_desc; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(msc_obj.dev_hdl, &device_desc)); + TEST_ASSERT_EQUAL(msc_obj.test_param.idVendor, device_desc->idVendor); + TEST_ASSERT_EQUAL(msc_obj.test_param.idProduct, device_desc->idProduct); + //Claim the MSC interface + TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_claim(msc_obj.client_hdl, msc_obj.dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING)); + msc_obj.next_stage = TEST_STAGE_MSC_RESET; + skip_event_handling = true; //Need to execute TEST_STAGE_MSC_RESET + break; + } + case TEST_STAGE_MSC_RESET: { + ESP_LOGD(MSC_CLIENT_TAG, "MSC Reset"); + //Send an MSC SCSI interface reset + MOCK_MSC_SCSI_REQ_INIT_RESET((usb_setup_packet_t *)xfer_out->data_buffer, MOCK_MSC_SCSI_INTF_NUMBER); + xfer_out->num_bytes = sizeof(usb_setup_packet_t); + xfer_out->bEndpointAddress = 0; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit_control(msc_obj.client_hdl, xfer_out)); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_MSC_CBW: { + ESP_LOGD(MSC_CLIENT_TAG, "CBW"); + mock_msc_scsi_init_cbw((mock_msc_bulk_cbw_t *)xfer_out->data_buffer, true, 0, msc_obj.test_param.num_sectors_per_xfer, msc_obj.test_param.msc_scsi_xfer_tag); + xfer_out->num_bytes = sizeof(mock_msc_bulk_cbw_t); + xfer_out->bEndpointAddress = MOCK_MSC_SCSI_BULK_OUT_EP_ADDR; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_out)); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_MSC_DATA_DCONN: { + ESP_LOGD(MSC_CLIENT_TAG, "Data and disconnect"); + //Setup the Data IN transfers + for (int i = 0; i < msc_obj.num_data_transfers; i++) { + xfer_in[i]->num_bytes = usb_round_up_to_mps(MOCK_MSC_SCSI_SECTOR_SIZE, MOCK_MSC_SCSI_BULK_EP_MPS); + xfer_in[i]->bEndpointAddress = MOCK_MSC_SCSI_BULK_IN_EP_ADDR; + } + //Submit those transfers + for (int i = 0; i < msc_obj.num_data_transfers; i++) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_in[i])); + } + //Trigger a disconnect + test_usb_force_conn_state(false, 0); + //Next stage set from transfer callback + break; + } + case TEST_STAGE_DEV_CLOSE: { + ESP_LOGD(MSC_CLIENT_TAG, "Close"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_release(msc_obj.client_hdl, msc_obj.dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(msc_obj.client_hdl, msc_obj.dev_hdl)); + exit_loop = true; + break; + } + default: + abort(); + break; + } + } + //Free transfers + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(xfer_out)); + for (int i = 0; i < msc_obj.num_data_transfers; i++) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(xfer_in[i])); + } + //Deregister the client + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(msc_obj.client_hdl)); + ESP_LOGD(MSC_CLIENT_TAG, "Done"); + vTaskDelete(NULL); +} diff --git a/components/usb/test/usb_host/msc_client_async_seq.c b/components/usb/test/usb_host/msc_client_async_seq.c index 7189f4cd61..279962cbb2 100644 --- a/components/usb/test/usb_host/msc_client_async_seq.c +++ b/components/usb/test/usb_host/msc_client_async_seq.c @@ -6,10 +6,9 @@ #include #include -#include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/semphr.h" #include "esp_err.h" #include "esp_log.h" #include "test_usb_mock_classes.h" @@ -38,8 +37,6 @@ Implementation of an MSC client used for USB Host Tests #define MAX(x,y) (((x) >= (y)) ? (x) : (y)) #define MSC_CLIENT_MAX_EVENT_MSGS 5 -const char *MSC_CLIENT_TAG = "MSC Client"; - typedef enum { TEST_STAGE_WAIT_CONN, TEST_STAGE_DEV_OPEN, diff --git a/components/usb/test/usb_host/test_usb_host_async.c b/components/usb/test/usb_host/test_usb_host_async.c index 4c7f792e7c..5dbef9f535 100644 --- a/components/usb/test/usb_host/test_usb_host_async.c +++ b/components/usb/test/usb_host/test_usb_host_async.c @@ -8,6 +8,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" +#include "esp_err.h" #include "esp_intr_alloc.h" #include "test_usb_mock_classes.h" #include "msc_client.h" diff --git a/components/usb/test/usb_host/test_usb_host_plugging.c b/components/usb/test/usb_host/test_usb_host_plugging.c new file mode 100644 index 0000000000..4136529abf --- /dev/null +++ b/components/usb/test/usb_host/test_usb_host_plugging.c @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "test_usb_common.h" +#include "test_usb_mock_classes.h" +#include "msc_client.h" +#include "ctrl_client.h" +#include "usb/usb_host.h" +#include "unity.h" +#include "test_utils.h" + +// --------------------------------------------------- Test Cases ------------------------------------------------------ + +//Safe approximation of time it takes to connect and enumerate the device +#define TEST_FORCE_DCONN_DELAY_MS 400 + +static void trigger_dconn_timer_cb(TimerHandle_t xTimer) +{ + printf("Forcing Sudden Disconnect\n"); + test_usb_force_conn_state(false, 0); +} + +TEST_CASE("Test USB Host sudden disconnection (no client)", "[usb_host][ignore]") +{ + //Install USB Host Library + usb_host_config_t host_config = { + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + printf("Installed\n"); + + //Allocate timer to force disconnection after a short delay + TimerHandle_t timer_hdl = xTimerCreate("dconn", + pdMS_TO_TICKS(TEST_FORCE_DCONN_DELAY_MS), + pdFALSE, + NULL, + trigger_dconn_timer_cb); + TEST_ASSERT_NOT_EQUAL(NULL, timer_hdl); + TEST_ASSERT_EQUAL(pdPASS, xTimerStart(timer_hdl, portMAX_DELAY)); + + while (1) { + //Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + printf("All devices cleaned up\n"); + break; + } + } + + //Cleanup timer + TEST_ASSERT_EQUAL(pdPASS, xTimerDelete(timer_hdl, portMAX_DELAY)); + //Clean up USB Host + ESP_ERROR_CHECK(usb_host_uninstall()); +} + +#define TEST_FORCE_DCONN_NUM_TRANSFERS 3 +#define TEST_MSC_SCSI_TAG 0xDEADBEEF + +TEST_CASE("Test USB Host sudden disconnection (single client)", "[usb_host][ignore]") +{ + //Install USB Host + usb_host_config_t host_config = { + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + printf("Installed\n"); + + //Create task to run client that communicates with MSC SCSI interface + msc_client_test_param_t params = { + .num_sectors_to_read = 1, //Unused by disconnect MSC client + .num_sectors_per_xfer = TEST_FORCE_DCONN_NUM_TRANSFERS * MOCK_MSC_SCSI_SECTOR_SIZE, + .msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG, + .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR, + .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT, + }; + TaskHandle_t task_hdl; + xTaskCreatePinnedToCore(msc_client_async_dconn_task, "async", 4096, (void *)¶ms, 2, &task_hdl, 0); + //Start the task + xTaskNotifyGive(task_hdl); + + bool all_clients_gone = false; + bool all_dev_free = false; + while (!all_clients_gone || !all_dev_free) { + //Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients\n"); + all_clients_gone = true; + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + printf("All device's freed\n"); + all_dev_free = true; + } + } + + //Short delay to allow task to be cleaned up + vTaskDelay(10); + //Clean up USB Host + ESP_ERROR_CHECK(usb_host_uninstall()); +}