1 /*
2  * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdio.h>
8 #include "freertos/FreeRTOS.h"
9 #include "freertos/task.h"
10 #include "freertos/semphr.h"
11 #include "esp_err.h"
12 #include "esp_intr_alloc.h"
13 #include "test_usb_common.h"
14 #include "test_usb_mock_classes.h"
15 #include "msc_client.h"
16 #include "ctrl_client.h"
17 #include "usb/usb_host.h"
18 #include "unity.h"
19 #include "test_utils.h"
20 
21 #define TEST_MSC_NUM_SECTORS_TOTAL          10
22 #define TEST_MSC_NUM_SECTORS_PER_XFER       2
23 #define TEST_MSC_SCSI_TAG                   0xDEADBEEF
24 #define TEST_CTRL_NUM_TRANSFERS             30
25 
26 // --------------------------------------------------- Test Cases ------------------------------------------------------
27 
28 /*
29 Test USB Host Asynchronous API single client
30 
31 Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class)
32 
33 Purpose:
34     - Test that USB Host Asynchronous API works correctly with a single client
35     - Test that a client can be created
36     - Test that client can operate concurrently in a separate thread
37     - Test that the main thread is able to detect library events (such as USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
38 
39 Procedure:
40     - Install USB Host Library
41     - Create a task to run an MSC client
42     - Start the MSC client task. It will execute a bunch of MSC SCSI sector reads
43     - Wait for the host library event handler to report a USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS event
44     - Free all devices
45     - Uninstall USB Host Library
46 */
47 
48 TEST_CASE("Test USB Host async client (single client)", "[usb_host][ignore]")
49 {
50     test_usb_init_phy();    //Initialize the internal USB PHY and USB Controller for testing
51     //Install USB Host
52     usb_host_config_t host_config = {
53         .skip_phy_setup = true,     //test_usb_init_phy() will already have setup the internal USB PHY for us
54         .intr_flags = ESP_INTR_FLAG_LEVEL1,
55     };
56     ESP_ERROR_CHECK(usb_host_install(&host_config));
57     printf("Installed\n");
58 
59     //Create task to run client that communicates with MSC SCSI interface
60     msc_client_test_param_t params = {
61         .num_sectors_to_read = TEST_MSC_NUM_SECTORS_TOTAL,
62         .num_sectors_per_xfer = TEST_MSC_NUM_SECTORS_PER_XFER,
63         .msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG,
64         .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR,
65         .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT,
66     };
67     TaskHandle_t task_hdl;
68     xTaskCreatePinnedToCore(msc_client_async_seq_task, "async", 4096, (void *)&params, 2, &task_hdl, 0);
69     //Start the task
70     xTaskNotifyGive(task_hdl);
71 
72     while (1) {
73         //Start handling system events
74         uint32_t event_flags;
75         usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
76         if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
77             printf("No more clients\n");
78             TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, usb_host_device_free_all());
79         }
80         if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
81             break;
82         }
83     }
84 
85     //Short delay to allow task to be cleaned up
86     vTaskDelay(10);
87     //Clean up USB Host
88     ESP_ERROR_CHECK(usb_host_uninstall());
89     test_usb_deinit_phy();  //Deinitialize the internal USB PHY after testing
90 }
91 
92 /*
93 Test USB Host Asynchronous API with multiple clients
94 
95 Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class)
96 
97 Purpose:
98     - Test the USB Host Asynchronous API works correctly with multiple clients
99     - Test that multiple clients can be created
100     - Test that multiple clients can operate concurrently in separate threads
101     - Test that the main thread is able to detect library events (such as USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
102 
103 Procedure:
104     - Install USB Host Library
105     - Create separate tasks to run an MSC client and Ctrl Client
106         - MSC Client will execute a bunch of MSC SCSI sector reads
107         - Ctrl Client will execute a bunch of control transfers
108     - Wait for the host library event handler to report a USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS event
109     - Free all devices
110     - Uninstall USB Host Library
111 */
112 TEST_CASE("Test USB Host async client (multi client)", "[usb_host][ignore]")
113 {
114     test_usb_init_phy();    //Initialize the internal USB PHY and USB Controller for testing
115     //Install USB Host
116     usb_host_config_t host_config = {
117         .skip_phy_setup = true,     //test_usb_init_phy() will already have setup the internal USB PHY for us
118         .intr_flags = ESP_INTR_FLAG_LEVEL1,
119     };
120     ESP_ERROR_CHECK(usb_host_install(&host_config));
121     printf("Installed\n");
122 
123     //Create task to run the MSC client
124     msc_client_test_param_t msc_params = {
125         .num_sectors_to_read = TEST_MSC_NUM_SECTORS_TOTAL,
126         .num_sectors_per_xfer = TEST_MSC_NUM_SECTORS_PER_XFER,
127         .msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG,
128         .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR,
129         .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT,
130     };
131     TaskHandle_t msc_task_hdl;
132     xTaskCreatePinnedToCore(msc_client_async_seq_task, "msc", 4096, (void *)&msc_params, 2, &msc_task_hdl, 0);
133 
134     //Create task a control transfer client
135     ctrl_client_test_param_t ctrl_params = {
136         .num_ctrl_xfer_to_send = TEST_CTRL_NUM_TRANSFERS,
137         .idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR,
138         .idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT,
139     };
140     TaskHandle_t ctrl_task_hdl;
141     xTaskCreatePinnedToCore(ctrl_client_async_seq_task, "ctrl", 4096, (void *)&ctrl_params, 2, &ctrl_task_hdl, 0);
142 
143     //Start both tasks
144     xTaskNotifyGive(msc_task_hdl);
145     xTaskNotifyGive(ctrl_task_hdl);
146 
147     while (1) {
148         //Start handling system events
149         uint32_t event_flags;
150         usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
151         if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
152             printf("No more clients\n");
153             TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, usb_host_device_free_all());
154         }
155         if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
156             break;
157         }
158     }
159 
160     //Short delay to allow task to be cleaned up
161     vTaskDelay(10);
162     //Clean up USB Host
163     ESP_ERROR_CHECK(usb_host_uninstall());
164     test_usb_deinit_phy();  //Deinitialize the internal USB PHY after testing
165 }
166 
167 /*
168 Test USB Host Asynchronous API Usage
169 
170 Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class)
171 
172 Purpose:
173     - Test that incorrect usage of USB Host Asynchronous API will returns errors
174 
175 Procedure:
176     - Install USB Host Library
177     - Register two clients and all event handler functions from the same loop
178     - Wait for each client to detect device connection
179     - Check that both clients can open the same device
180     - Check that a client cannot open a non-existent device
181     - Check that only one client can claim a particular interface
182     - Check that a client cannot release an already released interface
183     - Wait for device disconnection
184     - Cleanup
185 */
186 
187 static uint8_t dev_addr = 0;
188 
189 typedef enum {
190     CLIENT_TEST_STAGE_NONE,
191     CLIENT_TEST_STAGE_CONN,
192     CLIENT_TEST_STAGE_DCONN,
193 } client_test_stage_t;
194 
test_async_client_cb(const usb_host_client_event_msg_t * event_msg,void * arg)195 static void test_async_client_cb(const usb_host_client_event_msg_t *event_msg, void *arg)
196 {
197     client_test_stage_t *stage = (client_test_stage_t *)arg;
198 
199     switch (event_msg->event) {
200         case USB_HOST_CLIENT_EVENT_NEW_DEV:
201             if (dev_addr == 0) {
202                 dev_addr = event_msg->new_dev.address;
203             } else {
204                 TEST_ASSERT_EQUAL(dev_addr, event_msg->new_dev.address);
205             }
206             *stage = CLIENT_TEST_STAGE_CONN;
207             break;
208         case USB_HOST_CLIENT_EVENT_DEV_GONE:
209             *stage = CLIENT_TEST_STAGE_DCONN;
210             break;
211         default:
212             abort();
213             break;
214     }
215 }
216 
217 TEST_CASE("Test USB Host async API", "[usb_host][ignore]")
218 {
219     test_usb_init_phy();    //Initialize the internal USB PHY and USB Controller for testing
220 
221     //Install USB Host
222     usb_host_config_t host_config = {
223         .skip_phy_setup = true,     //test_usb_init_phy() will already have setup the internal USB PHY for us
224         .intr_flags = ESP_INTR_FLAG_LEVEL1,
225     };
226     ESP_ERROR_CHECK(usb_host_install(&host_config));
227     printf("Installed\n");
228 
229     //Register two clients
230     client_test_stage_t client0_stage = CLIENT_TEST_STAGE_NONE;
231     client_test_stage_t client1_stage = CLIENT_TEST_STAGE_NONE;
232 
233     usb_host_client_config_t client_config = {
234         .is_synchronous = false,
235         .max_num_event_msg = 5,
236         .async = {
237             .client_event_callback = test_async_client_cb,
238             .callback_arg = (void *)&client0_stage,
239         },
240     };
241     usb_host_client_handle_t client0_hdl;
242     usb_host_client_handle_t client1_hdl;
243     TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &client0_hdl));
244     client_config.async.callback_arg = (void *)&client1_stage;
245     TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &client1_hdl));
246 
247     //Wait until the device connects and the clients receive the event
248     while (!(client0_stage == CLIENT_TEST_STAGE_CONN && client1_stage == CLIENT_TEST_STAGE_CONN)) {
249         usb_host_lib_handle_events(0, NULL);
250         usb_host_client_handle_events(client0_hdl, 0);
251         usb_host_client_handle_events(client1_hdl, 0);
252         vTaskDelay(10);
253     }
254 
255     //Check that both clients can open the device
256     TEST_ASSERT_NOT_EQUAL(0, dev_addr);
257     usb_device_handle_t client0_dev_hdl;
258     usb_device_handle_t client1_dev_hdl;
259     TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(client0_hdl, dev_addr, &client0_dev_hdl));
260     TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(client1_hdl, dev_addr, &client1_dev_hdl));
261     TEST_ASSERT_EQUAL(client0_dev_hdl, client1_dev_hdl);    //Check that its the same device
262     //Check that a client cannot open a non-existent device
263     TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_device_open(client0_hdl, 0, &client0_dev_hdl));
264 
265     //Check that the device cannot be opened again by the same client
266     usb_device_handle_t dummy_dev_hdl;
267     TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_device_open(client0_hdl, dev_addr, &dummy_dev_hdl));
268     TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_device_open(client1_hdl, dev_addr, &dummy_dev_hdl));
269     //Check that both clients cannot claim the same interface
270     TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_claim(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING));
271     TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_interface_claim(client1_hdl, client1_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING));
272     //Check that client0 cannot claim the same interface multiple times
273     TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_interface_claim(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING));
274 
275     //Check that client0 can release the interface
276     TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_release(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER));
277     //Check that client0 cannot release interface it has not claimed
278     TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_interface_release(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER));
279 
280     //Wait until the device disconnects and the clients receive the event
281     test_usb_set_phy_state(false, 0);
282     while (!(client0_stage == CLIENT_TEST_STAGE_DCONN && client1_stage == CLIENT_TEST_STAGE_DCONN)) {
283         usb_host_lib_handle_events(0, NULL);
284         usb_host_client_handle_events(client0_hdl, 0);
285         usb_host_client_handle_events(client1_hdl, 0);
286         vTaskDelay(10);
287     }
288     TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(client0_hdl, client0_dev_hdl));
289     TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(client1_hdl, client1_dev_hdl));
290 
291     //Deregister the clients
292     TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(client0_hdl));
293     TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(client1_hdl));
294 
295     while (1) {
296         uint32_t event_flags;
297         usb_host_lib_handle_events(0, &event_flags);
298         if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
299             break;
300         }
301         vTaskDelay(10);
302     }
303 
304     //Cleanup
305     TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall());
306     test_usb_deinit_phy();
307 }
308