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/semphr.h"
10 #include "unity.h"
11 #include "esp_rom_sys.h"
12 #include "test_utils.h"
13 #include "test_usb_common.h"
14 #include "test_hcd_common.h"
15
16 #define TEST_DEV_ADDR 0
17 #define NUM_URBS 3
18 #define TRANSFER_MAX_BYTES 256
19 #define URB_DATA_BUFF_SIZE (sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
20 #define POST_ENQUEUE_DELAY_US 10
21
22 /*
23 Test a port sudden disconnect and port recovery
24
25 Purpose: Test that when sudden disconnection happens on an HCD port, the port will
26 - Generate the HCD_PORT_EVENT_SUDDEN_DISCONN and be put into the HCD_PORT_STATE_RECOVERY state
27 - Pipes can be halted and flushed after a port error
28
29 Procedure:
30 - Setup the HCD and a port
31 - Trigger a port connection
32 - Create a default pipe
33 - Start transfers but trigger a disconnect after a short delay
34 - Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event.
35 - Check that the default pipe remains in the HCD_PIPE_STATE_ACTIVE after the port error.
36 - Check that the default pipe can be halted.
37 - Check that the default pipe can be flushed, a HCD_PIPE_EVENT_URB_DONE event should be generated
38 - Check that all URBs can be dequeued.
39 - Free default pipe
40 - Recover the port
41 - Trigger connection and disconnection again (to make sure the port works post recovery)
42 - Teardown port and HCD
43 */
44
45 TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
46 {
47 hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
48 usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
49 vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
50
51 //Allocate some URBs and initialize their data buffers with control transfers
52 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)
53 urb_t *urb_list[NUM_URBS];
54 for (int i = 0; i < NUM_URBS; i++) {
55 urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
56 //Initialize with a "Get Config Descriptor request"
57 urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES;
58 USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
59 urb_list[i]->transfer.context = (void *)0xDEADBEEF;
60 }
61
62 //Enqueue URBs but immediately trigger a disconnect
63 printf("Enqueuing URBs\n");
64 for (int i = 0; i < NUM_URBS; i++) {
65 TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
66 }
67 //Add a short delay to let the transfers run for a bit
68 esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
69 test_usb_set_phy_state(false, 0);
70 //Disconnect event should have occurred. Handle the port event
71 test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION);
72 TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl));
73 TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
74 printf("Sudden disconnect\n");
75
76 //We should be able to halt then flush the pipe
77 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
78 TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
79 printf("Pipe halted\n");
80 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
81 TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
82 test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
83 printf("Pipe flushed\n");
84
85 //Dequeue URBs
86 for (int i = 0; i < NUM_URBS; i++) {
87 urb_t *urb = hcd_urb_dequeue(default_pipe);
88 TEST_ASSERT_EQUAL(urb_list[i], urb);
89 TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE);
90 if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
91 //We must have transmitted at least the setup packet, but device may return less than bytes requested
92 TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);
93 TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes);
94 } else {
95 //A failed transfer should 0 actual number of bytes transmitted
96 TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
97 }
98 TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
99 }
100 //Free URB list and pipe
101 for (int i = 0; i < NUM_URBS; i++) {
102 test_hcd_free_urb(urb_list[i]);
103 }
104 test_hcd_pipe_free(default_pipe);
105
106 //Recover the port should return to the to NOT POWERED state
107 TEST_ASSERT_EQUAL(ESP_OK, hcd_port_recover(port_hdl));
108 TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl));
109
110 //Recovered port should be able to connect and disconnect again
111 test_hcd_wait_for_conn(port_hdl);
112 test_hcd_wait_for_disconn(port_hdl, false);
113 test_hcd_teardown(port_hdl);
114 }
115
116 /*
117 Test port suspend and resume with active pipes
118
119 Purpose:
120 - Test port suspend and resume procedure
121 - When suspending, the pipes should be halted before suspending the port
122 - When resuming, the pipes should remain in the halted state
123
124 Procedure:
125 - Setup the HCD and a port
126 - Trigger a port connection
127 - Create a default pipe
128 - Test that port can't be suspended with an active pipe
129 - Halt the default pipe after a short delay
130 - Suspend the port
131 - Resume the port
132 - Check that all the pipe is still halted
133 - Cleanup default pipe
134 - Trigger disconnection and teardown
135 */
136 TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
137 {
138 hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
139 usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
140 vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
141
142 //Allocate some URBs and initialize their data buffers with control transfers
143 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)
144
145 //Test that suspending the port now fails as there is an active pipe
146 TEST_ASSERT_NOT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND));
147
148 //Halt the default pipe before suspending
149 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
150 TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
151 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
152
153 //Suspend the port
154 TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND));
155 TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl));
156 printf("Suspended\n");
157 vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for bus to remain suspended
158
159 //Resume the port
160 TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME));
161 TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl));
162 printf("Resumed\n");
163
164 //Clear the default pipe's halt
165 TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR));
166 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
167 vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed URBs to complete
168
169 test_hcd_pipe_free(default_pipe);
170 //Cleanup
171 test_hcd_wait_for_disconn(port_hdl, false);
172 test_hcd_teardown(port_hdl);
173 }
174
175 /*
176 Test HCD port disable and disconnection
177
178 Purpose:
179 - Test that the port disable command works correctly
180 - Check that port can only be disabled when pipes have been halted
181 - Check that a disconnection after port disable still triggers a HCD_PORT_EVENT_DISCONNECTION event
182
183 Procedure:
184 - Setup HCD, a default pipe, and multiple URBs
185 - Start transfers
186 - Halt the default pipe after a short delay
187 - Check that port can be disabled
188 - Flush the default pipe and cleanup the default pipe
189 - Check that a disconnection still works after disable
190 - Teardown
191 */
192 TEST_CASE("Test HCD port disable", "[hcd][ignore]")
193 {
194 hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
195 usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
196 vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
197
198 //Allocate some URBs and initialize their data buffers with control transfers
199 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)
200 urb_t *urb_list[NUM_URBS];
201 for (int i = 0; i < NUM_URBS; i++) {
202 urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE);
203 //Initialize with a "Get Config Descriptor request"
204 urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES;
205 USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
206 urb_list[i]->transfer.context = (void *)0xDEADBEEF;
207 }
208
209 //Enqueue URBs but immediately disable the port
210 printf("Enqueuing URBs\n");
211 for (int i = 0; i < NUM_URBS; i++) {
212 TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
213 }
214 //Add a short delay to let the transfers run for a bit
215 esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
216 //Halt the default pipe before suspending
217 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
218 TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
219 TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
220
221
222 //Check that port can be disabled
223 TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE));
224 TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl));
225 printf("Disabled\n");
226
227 //Flush pipe
228 TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
229 //Dequeue URBs
230 for (int i = 0; i < NUM_URBS; i++) {
231 urb_t *urb = hcd_urb_dequeue(default_pipe);
232 TEST_ASSERT_EQUAL(urb_list[i], urb);
233 TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || //The transfer completed before the pipe halted
234 urb->transfer.status == USB_TRANSFER_STATUS_CANCELED || //The transfer was stopped mid-way by the halt
235 urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE); //The transfer was never started
236 if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
237 //We must have transmitted at least the setup packet, but device may return less than bytes requested
238 TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);
239 TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes);
240 } else {
241 //A failed transfer should 0 actual number of bytes transmitted
242 TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
243 }
244 TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context);
245 }
246
247 //Free URB list and pipe
248 for (int i = 0; i < NUM_URBS; i++) {
249 test_hcd_free_urb(urb_list[i]);
250 }
251 test_hcd_pipe_free(default_pipe);
252
253 //Trigger a disconnection and cleanup
254 test_hcd_wait_for_disconn(port_hdl, true);
255 test_hcd_teardown(port_hdl);
256 }
257
258 /*
259 Test HCD port command bailout
260
261 Purpose:
262 - Test that if the a port's state changes whilst a command is being executed, the port command should return
263 ESP_ERR_INVALID_RESPONSE
264
265 Procedure:
266 - Setup HCD and wait for connection
267 - Suspend the port
268 - Resume the port but trigger a disconnect from another thread during the resume command
269 - Check that port command returns ESP_ERR_INVALID_RESPONSE
270 */
271
concurrent_task(void * arg)272 static void concurrent_task(void *arg)
273 {
274 SemaphoreHandle_t sync_sem = (SemaphoreHandle_t) arg;
275 xSemaphoreTake(sync_sem, portMAX_DELAY);
276 vTaskDelay(pdMS_TO_TICKS(10)); //Give a short delay let reset command start in main thread
277 //Force a disconnection
278 test_usb_set_phy_state(false, 0);
279 vTaskDelay(portMAX_DELAY); //Block forever and wait to be deleted
280 }
281
282 TEST_CASE("Test HCD port command bailout", "[hcd][ignore]")
283 {
284 hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
285 test_hcd_wait_for_conn(port_hdl); //Trigger a connection
286 vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
287
288 //Create task to run port commands concurrently
289 SemaphoreHandle_t sync_sem = xSemaphoreCreateBinary();
290 TaskHandle_t task_handle;
291 TEST_ASSERT_NOT_EQUAL(NULL, sync_sem);
292 TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(concurrent_task, "tsk", 4096, (void *) sync_sem, UNITY_FREERTOS_PRIORITY + 1, &task_handle, 0));
293
294 //Suspend the device
295 printf("Suspending\n");
296 TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND));
297 vTaskDelay(pdMS_TO_TICKS(20)); //Short delay for device to enter suspend state
298
299 //Attempt to resume the port. But the concurrent task should override this with a disconnection event
300 printf("Attempting to resume\n");
301 xSemaphoreGive(sync_sem); //Trigger concurrent task
302 TEST_ASSERT_EQUAL(ESP_ERR_INVALID_RESPONSE, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME));
303
304 //Check that concurrent task triggered a sudden disconnection
305 test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION);
306 TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl));
307 TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
308
309 //Cleanup task and semaphore
310 vTaskDelay(pdMS_TO_TICKS(10)); //Short delay for concurrent task finish running
311 vTaskDelete(task_handle);
312 vSemaphoreDelete(sync_sem);
313
314 test_hcd_teardown(port_hdl);
315 }
316