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