test_hcd_port.c 14 KB

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