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 *)¶ms, 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