test_yielding.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /*
  2. * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. /*
  7. * Unit tests for FreeRTOS task yielding
  8. */
  9. #include "freertos/FreeRTOS.h"
  10. #include "freertos/task.h"
  11. #include "freertos/semphr.h"
  12. #include "unity.h"
  13. #include "test_utils.h"
  14. #include <string.h>
  15. // Array to store the task ids of the test threads being yielded
  16. static volatile uint32_t task_yield_sequence[3];
  17. // Index variable to access the yield sequence array
  18. static volatile uint32_t idx = 0;
  19. // Lock to protect the shared variables to store task id
  20. static portMUX_TYPE idx_lock;
  21. // Synchronization variable to have a deterministic dispatch sequence of the test threads
  22. static volatile bool task_sequence_ready;
  23. // Synchronization variable between the test threads and the unity task
  24. static volatile uint32_t count;
  25. // Lock variable to create a blocked task scenario
  26. static volatile SemaphoreHandle_t task_mutex;
  27. // This helper macro is used to store the task id atomically
  28. #define STORE_TASK_ID(task_id) ({ \
  29. portENTER_CRITICAL(&idx_lock); \
  30. task_yield_sequence[idx++] = task_id; \
  31. portEXIT_CRITICAL(&idx_lock); \
  32. })
  33. /*
  34. * Test yielding for same priority tasks on the same core.
  35. *
  36. * The test performs the following actions:
  37. * - Creates 2 tasks with the same priority on the same core.
  38. * - Each task pushes its task_id on to a queue and then yields.
  39. * - Unity task checks the sequence of the tasks run once the yield_tasks are done.
  40. */
  41. static void yield_task1(void *arg)
  42. {
  43. uint32_t task_id = (uint32_t)arg;
  44. /* Store task_id in the sequence array */
  45. STORE_TASK_ID(task_id);
  46. /* Notify the yield_task2 to run */
  47. task_sequence_ready = true;
  48. /* Yield */
  49. taskYIELD();
  50. /* Increment task count to notify unity task */
  51. count++;
  52. /* Delete self */
  53. vTaskDelete(NULL);
  54. }
  55. static void yield_task2(void *arg)
  56. {
  57. uint32_t task_id = (uint32_t)arg;
  58. /* Wait for the other task to run for the test to begin */
  59. while (!task_sequence_ready) {
  60. taskYIELD();
  61. };
  62. /* Store task_id in the sequence array */
  63. STORE_TASK_ID(task_id);
  64. /* Yield */
  65. taskYIELD();
  66. /* Increment task count to notify unity task */
  67. count++;
  68. /* Delete self */
  69. vTaskDelete(NULL);
  70. }
  71. TEST_CASE("Task yield must run the next ready task of the same priority", "[freertos]")
  72. {
  73. /* Reset yield sequence index */
  74. idx = 0;
  75. /* Reset yield sequence array */
  76. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  77. /* Initialize idx lock */
  78. portMUX_INITIALIZE(&idx_lock);
  79. /* Reset task count */
  80. count = 0;
  81. /* Reset task sequence flag */
  82. task_sequence_ready = false;
  83. /* Create test tasks */
  84. xTaskCreatePinnedToCore(yield_task1, "yield_task1", 2048, (void *)1, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
  85. xTaskCreatePinnedToCore(yield_task2, "yield_task2", 2048, (void *)2, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
  86. /* Wait for the tasks to finish up */
  87. while (count != 2) {
  88. vTaskDelay(10);
  89. }
  90. idx = 0;
  91. /* Verify that the yield is successful and the next ready task is run */
  92. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx++]);
  93. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  94. }
  95. /*
  96. * Test yielding behavior when a task is blocked
  97. *
  98. * The test performs the following actions:
  99. * - Creates 2 tasks with the same priority on the same core.
  100. * - One task blocks on a mutex.
  101. * - Second task does not contest for a mutex and yields.
  102. * - Unity task verifies that the blocked task is not scheduled unless it is ready to run.
  103. */
  104. static void test_task1(void *arg)
  105. {
  106. uint32_t task_id = (uint32_t)arg;
  107. /* Block on mutex taken by the unity task */
  108. TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(task_mutex, portMAX_DELAY));
  109. /* Store task_id in the sequence array */
  110. STORE_TASK_ID(task_id);
  111. /* Increment task count to notify unity task */
  112. count++;
  113. /* Release mutex */
  114. TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreGive(task_mutex));
  115. /* Delete self */
  116. vTaskDelete(NULL);
  117. }
  118. static void test_task2(void *arg)
  119. {
  120. uint32_t task_id = (uint32_t)arg;
  121. /* Store task_id in the sequence array */
  122. STORE_TASK_ID(task_id);
  123. /* Yield */
  124. taskYIELD();
  125. /* Store task_id in the sequence array */
  126. STORE_TASK_ID(task_id);
  127. /* Increment task count to notify unity task */
  128. count++;
  129. /* Delete self */
  130. vTaskDelete(NULL);
  131. }
  132. TEST_CASE("Task yield must not run a blocked task", "[freertos]")
  133. {
  134. /* Reset yield sequence index */
  135. idx = 0;
  136. /* Reset yield sequence array */
  137. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  138. /* Initialize idx lock */
  139. portMUX_INITIALIZE(&idx_lock);
  140. /* Reset task count */
  141. count = 0;
  142. /* Create mutex and acquire it */
  143. task_mutex = xSemaphoreCreateMutex();
  144. TEST_ASSERT_NOT_EQUAL(NULL, task_mutex);
  145. TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(task_mutex, portMAX_DELAY));
  146. /* Create test_task1. This gets blocked. */
  147. xTaskCreatePinnedToCore(test_task1, "test_task1", 2048, (void *)1, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
  148. /* Wait for test_task1 to start up and get blocked */
  149. vTaskDelay(10);
  150. /* Create test_task2. This issues the yield. */
  151. xTaskCreatePinnedToCore(test_task2, "test_task2", 2048, (void *)2, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
  152. /* Wait for test_task2 to finish up */
  153. while (count != 1) {
  154. vTaskDelay(10);
  155. }
  156. /* Release mutex. This should unblock test_task1. */
  157. TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreGive(task_mutex));
  158. /* Wait for test_task1 to finish up */
  159. vTaskDelay(10);
  160. idx = 0;
  161. /* Verify that the yield results in the same task running again and not the blocked task */
  162. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  163. /* Verify that the task yield did not result in a context switch */
  164. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  165. /* Verify that the other task is scheduled once it is unblocked */
  166. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx++]);
  167. /* Cleanup task mutex */
  168. vSemaphoreDelete(task_mutex);
  169. }
  170. /*
  171. * Test yielding behavior when the scheduler is suspended
  172. *
  173. * The test performs the following actions:
  174. * - Creates 2 tasks with the same priority on the same core.
  175. * - One task suspends the scheduler and then yields.
  176. * - Unity task verifies that the yield does not happen until the scheduler is resumed.
  177. */
  178. static void test_critical_task1(void *arg)
  179. {
  180. uint32_t task_id = (uint32_t)arg;
  181. /* Store task_id in the sequence array */
  182. STORE_TASK_ID(task_id);
  183. /* Suspend scheduler */
  184. vTaskSuspendAll();
  185. /* Set the task sequence flag once test_critical_task1 runs */
  186. task_sequence_ready = true;
  187. /* Yield */
  188. taskYIELD();
  189. /* Store task_id in the sequence array.
  190. * No need for a lock when the scheduler is suspended.
  191. */
  192. task_yield_sequence[idx++] = task_id;
  193. /* Increment task count to notify unity task */
  194. count++;
  195. /* Resume scheduler */
  196. xTaskResumeAll();
  197. /* Delete self */
  198. vTaskDelete(NULL);
  199. }
  200. static void test_critical_task2(void *arg)
  201. {
  202. uint32_t task_id = (uint32_t)arg;
  203. /* Wait for the other task to run for the test to begin */
  204. while (!task_sequence_ready) {
  205. taskYIELD();
  206. };
  207. /* Store task_id in the sequence array */
  208. STORE_TASK_ID(task_id);
  209. /* Increment task count to notify unity task */
  210. count++;
  211. /* Delete self */
  212. vTaskDelete(NULL);
  213. }
  214. TEST_CASE("Task yield must not happen when scheduler is suspended", "[freertos]")
  215. {
  216. /* Reset yield sequence index */
  217. idx = 0;
  218. /* Reset yield sequence array */
  219. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  220. /* Initialize idx lock */
  221. portMUX_INITIALIZE(&idx_lock);
  222. /* Reset task count */
  223. count = 0;
  224. /* Reset task sequence flag */
  225. task_sequence_ready = false;
  226. /* Create test tasks */
  227. xTaskCreatePinnedToCore(test_critical_task1, "test_critical_task1", 2048, (void *)1, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
  228. xTaskCreatePinnedToCore(test_critical_task2, "test_critical_task2", 2048, (void *)2, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
  229. /* Wait for both the tasks to finish up */
  230. while (count != 2) {
  231. vTaskDelay(10);
  232. }
  233. idx = 0;
  234. /* Verify that test_critical_task1 runs first */
  235. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx++]);
  236. /* Verify that the task yield, when the scheduler is suspended, did not result in a context switch */
  237. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx++]);
  238. /* Verify that test_critical_task2 is scheduled once the scheduler is resumed */
  239. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  240. }
  241. /*
  242. * Test yielding behavior when a lower priority task creates a higher priority task
  243. *
  244. * The test performs the following actions:
  245. * - Creates a task with a priority higher than the unity task.
  246. * - Unity task verifies that it yields immediately to the newly created task.
  247. */
  248. static void high_prio_task(void *arg)
  249. {
  250. uint32_t task_id = (uint32_t)arg;
  251. /* Store task_id in the sequence array */
  252. STORE_TASK_ID(task_id);
  253. /* Increment task count to notify unity task */
  254. count++;
  255. /* Delete self */
  256. vTaskDelete(NULL);
  257. }
  258. TEST_CASE("Task yield must happen when a task creates a higher priority task", "[freertos]")
  259. {
  260. /* Reset yield sequence index */
  261. idx = 0;
  262. /* Reset yield sequence array */
  263. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  264. /* Initialize idx lock */
  265. portMUX_INITIALIZE(&idx_lock);
  266. /* Reset task count */
  267. count = 0;
  268. /* Create test task */
  269. xTaskCreatePinnedToCore(high_prio_task, "high_prio_task", 2048, (void *)1, UNITY_FREERTOS_PRIORITY + 1, NULL, UNITY_FREERTOS_CPU);
  270. uint32_t unity_task_id = 2;
  271. /* Store task_id in the sequence array */
  272. STORE_TASK_ID(unity_task_id);
  273. /* Wait for the test task to finish up */
  274. while (count == 0) {
  275. vTaskDelay(10);
  276. }
  277. idx = 0;
  278. /* Verify that the unity task yields as soon as a higher prio task is created */
  279. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx++]);
  280. /* Verify that the unity task_id is stored after the higher priority task runs */
  281. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  282. }
  283. /*
  284. * Test yielding behavior when a lower priority task raises the priority of another task
  285. *
  286. * The test performs the following actions:
  287. * - Creates a task with a priority lower than the unity task.
  288. * - Unity task raises the priority of the newly created task.
  289. * - Unity task verifies that it yields once the priority is raised.
  290. */
  291. static void low_prio_task(void *arg)
  292. {
  293. uint32_t task_id = (uint32_t)arg;
  294. /* Store task_id in the sequence array */
  295. STORE_TASK_ID(task_id);
  296. /* Increment task count to notify unity task */
  297. count++;
  298. /* Delete self */
  299. vTaskDelete(NULL);
  300. }
  301. TEST_CASE("Task yield must happed when a task raises the priority of another priority task", "[freertos]")
  302. {
  303. /* Reset yield sequence index */
  304. idx = 0;
  305. /* Reset yield sequence array */
  306. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  307. /* Initialize idx lock */
  308. portMUX_INITIALIZE(&idx_lock);
  309. /* Reset task count */
  310. count = 0;
  311. /* Create test task */
  312. TaskHandle_t task_handle;
  313. xTaskCreatePinnedToCore(low_prio_task, "low_prio_task", 2048, (void *)1, UNITY_FREERTOS_PRIORITY - 1, &task_handle, UNITY_FREERTOS_CPU);
  314. uint32_t unity_task_id = 2;
  315. /* Store task_id in the sequence array */
  316. STORE_TASK_ID(unity_task_id);
  317. /* Raise the priority of the lower priority task */
  318. vTaskPrioritySet(task_handle, UNITY_FREERTOS_PRIORITY + 1);
  319. /* Store unity task_id in the sequence array again */
  320. STORE_TASK_ID(unity_task_id);
  321. /* Wait for the test task to finish up */
  322. while (count == 0) {
  323. vTaskDelay(10);
  324. }
  325. idx = 0;
  326. /* Verify that the unity task does not yield to a lower priority task when it is created */
  327. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  328. /* Verify that the unity task_id yielded once the priority of the lower priority task is raised */
  329. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx++]);
  330. /* Verify that the unity task_id is stored again once the test task finishes up */
  331. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx++]);
  332. }
  333. #if (portNUM_PROCESSORS > 1) && !(CONFIG_FREERTOS_UNICORE)
  334. /*
  335. * Test yielding behavior when a task on one core forces an yield on the other core
  336. *
  337. * The test performs the following actions:
  338. * - Creates 2 tasks with the same priority on the core on which unity task is not running.
  339. * - One task spins and does not let the other task run.
  340. * - Force a cross-core yield from the unity task.
  341. * - Verify that the cross-core yield happens and the second task is scheduled to run.
  342. */
  343. static void other_core_task1(void *arg)
  344. {
  345. uint32_t task_id = (uint32_t)arg;
  346. /* Store task_id in the sequence array */
  347. STORE_TASK_ID(task_id);
  348. while (1) {
  349. vTaskDelay(10);
  350. }
  351. }
  352. static void other_core_task2(void *arg)
  353. {
  354. uint32_t task_id = (uint32_t)arg;
  355. /* Wait for the other task to run for the test to begin */
  356. while (!task_sequence_ready) {
  357. taskYIELD();
  358. };
  359. /* Store task_id in the sequence array */
  360. STORE_TASK_ID(task_id);
  361. /* Increment task count to notify unity task */
  362. count++;
  363. while (1) {
  364. vTaskDelay(10);
  365. }
  366. }
  367. TEST_CASE("Task yield must happen when issued from another core", "[freertos]")
  368. {
  369. TaskHandle_t other_core_taskhandle1;
  370. TaskHandle_t other_core_taskhandle2;
  371. /* Reset yield sequence index */
  372. idx = 0;
  373. /* Reset yield sequence array */
  374. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  375. /* Initialize idx lock */
  376. portMUX_INITIALIZE(&idx_lock);
  377. /* Reset task count */
  378. count = 0;
  379. /* Reset task sequence flag */
  380. task_sequence_ready = false;
  381. /* Create test tasks */
  382. xTaskCreatePinnedToCore(other_core_task1, "test_task1", 2048, (void *)1, UNITY_FREERTOS_PRIORITY - 1, &other_core_taskhandle1, !UNITY_FREERTOS_CPU);
  383. xTaskCreatePinnedToCore(other_core_task2, "test_task2", 2048, (void *)2, UNITY_FREERTOS_PRIORITY - 1, &other_core_taskhandle2, !UNITY_FREERTOS_CPU);
  384. /* Wait for everything to be setup */
  385. vTaskDelay(10);
  386. uint32_t idx1 = 0;
  387. /* Verify that other_core_task1 runs first */
  388. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx1++]);
  389. /* Set the task sequence flag once other_core_task1 runs */
  390. task_sequence_ready = true;
  391. /* Force an yield on the other core */
  392. #if CONFIG_FREERTOS_SMP
  393. portYIELD_CORE(!UNITY_FREERTOS_CPU);
  394. #else
  395. vPortYieldOtherCore(!UNITY_FREERTOS_CPU);
  396. #endif
  397. /* Wait for the test task to finish up */
  398. while (count == 0) {
  399. vTaskDelay(10);
  400. }
  401. /* Verify that other_core_task1 yields and other_core_task2 runs */
  402. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx1++]);
  403. /* Cleanup test tasks */
  404. vTaskDelete(other_core_taskhandle1);
  405. vTaskDelete(other_core_taskhandle2);
  406. }
  407. #if !CONFIG_FREERTOS_SMP
  408. static volatile bool yield_triggered = false;
  409. /*
  410. * Test cross-core yielding behavior when the scheduler is suspended
  411. *
  412. * The test performs the following actions:
  413. * - Creates 2 tasks with the same priority on the other core.
  414. * - One task suspends the scheduler.
  415. * - Unity task forces a cross-core yield.
  416. * - Unity task verifies that the yield does not happen until the scheduler is resumed.
  417. *
  418. * Note: This test case is not valid when FreeRTOS SMP is used as the scheduler suspension
  419. * is not per core but across cores and hence the test cannot be executed.
  420. */
  421. static void other_core_critical_task1(void *arg)
  422. {
  423. uint32_t task_id = (uint32_t)arg;
  424. /* Store task_id in the sequence array */
  425. STORE_TASK_ID(task_id);
  426. /* Suspend scheduler*/
  427. vTaskSuspendAll();
  428. /* Store task_id in the sequence array again.
  429. * No need for a lock when the scheduler is supended.
  430. */
  431. task_yield_sequence[idx++] = task_id;
  432. /* Set the task sequence flag once other_core_critical_task1 runs */
  433. task_sequence_ready = true;
  434. /* Increment task count to notify unity task */
  435. count++;
  436. while (!yield_triggered) { }
  437. /* Resume scheduler */
  438. xTaskResumeAll();
  439. /* Delete self */
  440. vTaskDelete(NULL);
  441. }
  442. static void other_core_critical_task2(void *arg)
  443. {
  444. uint32_t task_id = (uint32_t)arg;
  445. /* Wait for the other task to run for the test to begin */
  446. while (!task_sequence_ready) {
  447. taskYIELD();
  448. };
  449. /* Store task_id in the sequence array */
  450. STORE_TASK_ID(task_id);
  451. /* Increment task count to notify unity task */
  452. count++;
  453. /* Delete self */
  454. vTaskDelete(NULL);
  455. }
  456. TEST_CASE("Task yield on other core must not happen when scheduler is suspended", "[freertos]")
  457. {
  458. /* Reset yield sequence index */
  459. idx = 0;
  460. /* Reset yield sequence array */
  461. memset((void *)task_yield_sequence, 0, sizeof(task_yield_sequence));
  462. /* Initialize idx lock */
  463. portMUX_INITIALIZE(&idx_lock);
  464. /* Reset task count */
  465. count = 0;
  466. /* Reset task sequence flag */
  467. task_sequence_ready = false;
  468. /* Create test tasks */
  469. xTaskCreatePinnedToCore(other_core_critical_task1, "other_core_critical_task1", 2048, (void *)1, UNITY_FREERTOS_PRIORITY - 1, NULL, !UNITY_FREERTOS_CPU);
  470. xTaskCreatePinnedToCore(other_core_critical_task2, "other_core_critical_task2", 2048, (void *)2, UNITY_FREERTOS_PRIORITY - 1, NULL, !UNITY_FREERTOS_CPU);
  471. /* Wait for at least one of the tasks to finish up */
  472. while (count == 0) {
  473. vTaskDelay(10);
  474. }
  475. /* Force an yield on the other core */
  476. vPortYieldOtherCore(!UNITY_FREERTOS_CPU);
  477. /* Set yield triggered flag */
  478. yield_triggered = true;
  479. uint32_t idx1 = 0;
  480. /* Verify that the first task runs */
  481. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx1++]);
  482. /* Verify that the task yield when the scheduler is suspended did not result in a context switch */
  483. TEST_ASSERT_EQUAL(1, task_yield_sequence[idx1++]);
  484. /* Wait for the second task to finish up */
  485. while (count != 2) {
  486. vTaskDelay(10);
  487. }
  488. /* Verify that the second task is scheduled once the critical section is over */
  489. TEST_ASSERT_EQUAL(2, task_yield_sequence[idx1++]);
  490. }
  491. #endif // !CONFIG_FREERTOS_SMP
  492. #endif // (portNUM_PROCESSORS > 1) && !(CONFIG_FREERTOS_UNICORE)