1 /**
2  * @file lv_freertos.c
3  *
4  */
5 
6 /**
7  * Copyright 2023 NXP
8  *
9  * SPDX-License-Identifier: MIT
10  */
11 
12 /*********************
13  *      INCLUDES
14  *********************/
15 #include "lv_os.h"
16 #if LV_USE_OS == LV_OS_FREERTOS
17 
18 #include "atomic.h"
19 
20 #include "../tick/lv_tick.h"
21 #include "../misc/lv_log.h"
22 #include "../core/lv_global.h"
23 
24 /*********************
25  *      DEFINES
26  *********************/
27 
28 #define ulMAX_COUNT 10U
29 
30 #define globals LV_GLOBAL_DEFAULT()
31 
32 /**********************
33  *      TYPEDEFS
34  **********************/
35 
36 /**********************
37  *  STATIC PROTOTYPES
38  **********************/
39 
40 static void prvRunThread(void * pxArg);
41 
42 static void prvMutexInit(lv_mutex_t * pxMutex);
43 
44 static void prvCheckMutexInit(lv_mutex_t * pxMutex);
45 
46 static void prvCondInit(lv_thread_sync_t * pxCond);
47 
48 static void prvCheckCondInit(lv_thread_sync_t * pxCond);
49 
50 static void prvCheckCondInitIsr(lv_thread_sync_t * pxCond);
51 
52 #if !LV_USE_FREERTOS_TASK_NOTIFY
53 static void prvTestAndDecrement(lv_thread_sync_t * pxCond,
54                                 uint32_t ulLocalWaitingThreads);
55 #endif
56 
57 /**********************
58  *  STATIC VARIABLES
59  **********************/
60 
61 #ifdef ESP_PLATFORM
62     static portMUX_TYPE critSectionMux = portMUX_INITIALIZER_UNLOCKED;
63 #endif
64 
65 /**********************
66  *      MACROS
67  **********************/
68 
69 #ifdef ESP_PLATFORM
70     #define _enter_critical()   taskENTER_CRITICAL(&critSectionMux);
71     #define _exit_critical()    taskEXIT_CRITICAL(&critSectionMux);
72     #define _enter_critical_isr() taskENTER_CRITICAL_FROM_ISR();
73     #define _exit_critical_isr(x) taskEXIT_CRITICAL_FROM_ISR(x);
74 #else
75     #define _enter_critical()   taskENTER_CRITICAL();
76     #define _exit_critical()    taskEXIT_CRITICAL();
77     #define _enter_critical_isr() taskENTER_CRITICAL_FROM_ISR();
78     #define _exit_critical_isr(x) taskEXIT_CRITICAL_FROM_ISR(x);
79 #endif
80 
81 /**********************
82  *   GLOBAL FUNCTIONS
83  **********************/
84 
lv_thread_init(lv_thread_t * pxThread,const char * const name,lv_thread_prio_t xSchedPriority,void (* pvStartRoutine)(void *),size_t usStackSize,void * xAttr)85 lv_result_t lv_thread_init(lv_thread_t * pxThread,  const char * const name,
86                            lv_thread_prio_t xSchedPriority,
87                            void (*pvStartRoutine)(void *), size_t usStackSize,
88                            void * xAttr)
89 {
90     pxThread->pTaskArg = xAttr;
91     pxThread->pvStartRoutine = pvStartRoutine;
92 
93     BaseType_t xTaskCreateStatus = xTaskCreate(
94                                        prvRunThread,
95                                        name,
96                                        (configSTACK_DEPTH_TYPE)(usStackSize / sizeof(StackType_t)),
97                                        (void *)pxThread,
98                                        tskIDLE_PRIORITY + xSchedPriority,
99                                        &pxThread->xTaskHandle);
100 
101     /* Ensure that the FreeRTOS task was successfully created. */
102     if(xTaskCreateStatus != pdPASS) {
103         LV_LOG_ERROR("xTaskCreate failed!");
104         return LV_RESULT_INVALID;
105     }
106 
107     return LV_RESULT_OK;
108 }
109 
lv_thread_delete(lv_thread_t * pxThread)110 lv_result_t lv_thread_delete(lv_thread_t * pxThread)
111 {
112     vTaskDelete(pxThread->xTaskHandle);
113 
114     return LV_RESULT_OK;
115 }
116 
lv_mutex_init(lv_mutex_t * pxMutex)117 lv_result_t lv_mutex_init(lv_mutex_t * pxMutex)
118 {
119     /* If mutex in uninitialized, perform initialization. */
120     prvCheckMutexInit(pxMutex);
121 
122     return LV_RESULT_OK;
123 }
124 
lv_mutex_lock(lv_mutex_t * pxMutex)125 lv_result_t lv_mutex_lock(lv_mutex_t * pxMutex)
126 {
127     /* If mutex in uninitialized, perform initialization. */
128     prvCheckMutexInit(pxMutex);
129 
130     BaseType_t xMutexTakeStatus = xSemaphoreTakeRecursive(pxMutex->xMutex, portMAX_DELAY);
131     if(xMutexTakeStatus != pdTRUE) {
132         LV_LOG_ERROR("xSemaphoreTake failed!");
133         return LV_RESULT_INVALID;
134     }
135 
136     return LV_RESULT_OK;
137 }
138 
lv_mutex_lock_isr(lv_mutex_t * pxMutex)139 lv_result_t lv_mutex_lock_isr(lv_mutex_t * pxMutex)
140 {
141     /* If mutex in uninitialized, perform initialization. */
142     prvCheckMutexInit(pxMutex);
143 
144     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
145 
146     BaseType_t xMutexTakeStatus = xSemaphoreTakeFromISR(pxMutex->xMutex, &xHigherPriorityTaskWoken);
147     if(xMutexTakeStatus != pdTRUE) {
148         LV_LOG_ERROR("xSemaphoreTake failed!");
149         return LV_RESULT_INVALID;
150     }
151 
152     /* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
153     should be performed to ensure the interrupt returns directly to the highest
154     priority task.  The macro used for this purpose is dependent on the port in
155     use and may be called portEND_SWITCHING_ISR(). */
156     portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
157 
158     return LV_RESULT_OK;
159 }
160 
lv_mutex_unlock(lv_mutex_t * pxMutex)161 lv_result_t lv_mutex_unlock(lv_mutex_t * pxMutex)
162 {
163     /* If mutex in uninitialized, perform initialization. */
164     prvCheckMutexInit(pxMutex);
165 
166     BaseType_t xMutexGiveStatus = xSemaphoreGiveRecursive(pxMutex->xMutex);
167     if(xMutexGiveStatus != pdTRUE) {
168         LV_LOG_ERROR("xSemaphoreGive failed!");
169         return LV_RESULT_INVALID;
170     }
171 
172     return LV_RESULT_OK;
173 }
174 
lv_mutex_delete(lv_mutex_t * pxMutex)175 lv_result_t lv_mutex_delete(lv_mutex_t * pxMutex)
176 {
177     if(pxMutex->xIsInitialized == pdFALSE)
178         return LV_RESULT_INVALID;
179     vSemaphoreDelete(pxMutex->xMutex);
180     pxMutex->xIsInitialized = pdFALSE;
181 
182     return LV_RESULT_OK;
183 }
184 
lv_thread_sync_init(lv_thread_sync_t * pxCond)185 lv_result_t lv_thread_sync_init(lv_thread_sync_t * pxCond)
186 {
187     /* If the cond is uninitialized, perform initialization. */
188     prvCheckCondInit(pxCond);
189 
190     return LV_RESULT_OK;
191 }
192 
lv_thread_sync_wait(lv_thread_sync_t * pxCond)193 lv_result_t lv_thread_sync_wait(lv_thread_sync_t * pxCond)
194 {
195     lv_result_t lvRes = LV_RESULT_OK;
196 
197     /* If the cond is uninitialized, perform initialization. */
198     prvCheckCondInit(pxCond);
199 
200 #if LV_USE_FREERTOS_TASK_NOTIFY
201     TaskHandle_t xCurrentTaskHandle = xTaskGetCurrentTaskHandle();
202 
203     _enter_critical();
204     BaseType_t xSyncSygnal = pxCond->xSyncSignal;
205     pxCond->xSyncSignal = pdFALSE;
206     if(xSyncSygnal == pdFALSE) {
207         /* The signal hasn't been sent yet. Tell the sender to notify this task */
208         pxCond->xTaskToNotify = xCurrentTaskHandle;
209     }
210     /* If we have a signal from the other task, we should not ask to be notified */
211     _exit_critical();
212 
213     if(xSyncSygnal == pdFALSE) {
214         /* Wait for other task to notify this task. */
215         ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
216     }
217     /* If the signal was received, no wait needs to be done */
218 #else
219     uint32_t ulLocalWaitingThreads;
220 
221     /* Acquire the mutex. */
222     xSemaphoreTake(pxCond->xSyncMutex, portMAX_DELAY);
223 
224     while(!pxCond->xSyncSignal) {
225         /* Increase the counter of threads blocking on condition variable, then
226          * release the mutex. */
227 
228         /* Atomically increments thread waiting by 1, and
229          * stores number of threads waiting before increment. */
230         ulLocalWaitingThreads = Atomic_Increment_u32(&pxCond->ulWaitingThreads);
231 
232         BaseType_t xMutexStatus = xSemaphoreGive(pxCond->xSyncMutex);
233 
234         /* Wait on the condition variable. */
235         if(xMutexStatus == pdTRUE) {
236             BaseType_t xCondWaitStatus = xSemaphoreTake(
237                                              pxCond->xCondWaitSemaphore,
238                                              portMAX_DELAY);
239 
240             /* Relock the mutex. */
241             xSemaphoreTake(pxCond->xSyncMutex, portMAX_DELAY);
242 
243             if(xCondWaitStatus != pdTRUE) {
244                 LV_LOG_ERROR("xSemaphoreTake(xCondWaitSemaphore) failed!");
245                 lvRes = LV_RESULT_INVALID;
246 
247                 /* Atomically decrements thread waiting by 1.
248                  * If iLocalWaitingThreads is updated by other thread(s) in between,
249                  * this implementation guarantees to decrement by 1 based on the
250                  * value currently in pxCond->ulWaitingThreads. */
251                 prvTestAndDecrement(pxCond, ulLocalWaitingThreads + 1);
252             }
253         }
254         else {
255             LV_LOG_ERROR("xSemaphoreGive(xSyncMutex) failed!");
256             lvRes = LV_RESULT_INVALID;
257 
258             /* Atomically decrements thread waiting by 1.
259              * If iLocalWaitingThreads is updated by other thread(s) in between,
260              * this implementation guarantees to decrement by 1 based on the
261              * value currently in pxCond->ulWaitingThreads. */
262             prvTestAndDecrement(pxCond, ulLocalWaitingThreads + 1);
263         }
264     }
265 
266     pxCond->xSyncSignal = pdFALSE;
267 
268     /* Release the mutex. */
269     xSemaphoreGive(pxCond->xSyncMutex);
270 #endif
271 
272     return lvRes;
273 }
274 
lv_thread_sync_signal(lv_thread_sync_t * pxCond)275 lv_result_t lv_thread_sync_signal(lv_thread_sync_t * pxCond)
276 {
277     /* If the cond is uninitialized, perform initialization. */
278     prvCheckCondInit(pxCond);
279 
280 #if LV_USE_FREERTOS_TASK_NOTIFY
281     _enter_critical();
282     TaskHandle_t xTaskToNotify = pxCond->xTaskToNotify;
283     pxCond->xTaskToNotify = NULL;
284     if(xTaskToNotify == NULL) {
285         /* No task waiting to be notified. Send this signal for later */
286         pxCond->xSyncSignal = pdTRUE;
287     }
288     /* If a task is already waiting, there is no need to set the sync signal */
289     _exit_critical();
290 
291     if(xTaskToNotify != NULL) {
292         /* There is a task waiting. Send a notification to it */
293         xTaskNotifyGive(xTaskToNotify);
294     }
295     /* If there was no task waiting to be notified, we sent a signal for it to see later. */
296 #else
297     /* Acquire the mutex. */
298     xSemaphoreTake(pxCond->xSyncMutex, portMAX_DELAY);
299 
300     pxCond->xSyncSignal = pdTRUE;
301 
302     /* Local copy of number of threads waiting. */
303     uint32_t ulLocalWaitingThreads = pxCond->ulWaitingThreads;
304 
305     /* Test local copy of threads waiting is larger than zero. */
306     while(ulLocalWaitingThreads > 0) {
307         /* Atomically check whether the copy in memory has changed.
308          * If not, set the copy of threads waiting in memory to zero. */
309         if(ATOMIC_COMPARE_AND_SWAP_SUCCESS == Atomic_CompareAndSwap_u32(
310                &pxCond->ulWaitingThreads,
311                0,
312                ulLocalWaitingThreads)) {
313             /* Unblock all. */
314             for(uint32_t i = 0; i < ulLocalWaitingThreads; i++) {
315                 xSemaphoreGive(pxCond->xCondWaitSemaphore);
316             }
317 
318             break;
319         }
320 
321         /* Local copy is out dated. Reload from memory and retry. */
322         ulLocalWaitingThreads = pxCond->ulWaitingThreads;
323     }
324 
325     /* Release the mutex. */
326     xSemaphoreGive(pxCond->xSyncMutex);
327 #endif
328 
329     return LV_RESULT_OK;
330 }
331 
lv_thread_sync_delete(lv_thread_sync_t * pxCond)332 lv_result_t lv_thread_sync_delete(lv_thread_sync_t * pxCond)
333 {
334 #if !LV_USE_FREERTOS_TASK_NOTIFY
335     /* Cleanup all resources used by the cond. */
336     vSemaphoreDelete(pxCond->xCondWaitSemaphore);
337     vSemaphoreDelete(pxCond->xSyncMutex);
338     pxCond->ulWaitingThreads = 0;
339 #endif
340     pxCond->xSyncSignal = pdFALSE;
341     pxCond->xIsInitialized = pdFALSE;
342 
343     return LV_RESULT_OK;
344 }
345 
lv_thread_sync_signal_isr(lv_thread_sync_t * pxCond)346 lv_result_t lv_thread_sync_signal_isr(lv_thread_sync_t * pxCond)
347 {
348     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
349 
350     /* If the cond is uninitialized, perform initialization. */
351     prvCheckCondInitIsr(pxCond);
352 
353 #if LV_USE_FREERTOS_TASK_NOTIFY
354     uint32_t mask = _enter_critical_isr();
355     TaskHandle_t xTaskToNotify = pxCond->xTaskToNotify;
356     pxCond->xTaskToNotify = NULL;
357     if(xTaskToNotify == NULL) {
358         /* No task waiting to be notified. Send this signal for later */
359         pxCond->xSyncSignal = pdTRUE;
360     }
361     /* If a task is already waiting, there is no need to set the sync signal */
362     _exit_critical_isr(mask);
363 
364     if(xTaskToNotify != NULL) {
365         /* There is a task waiting. Send a notification to it */
366         vTaskNotifyGiveFromISR(xTaskToNotify, &xHigherPriorityTaskWoken);
367         portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
368     }
369     /* If there was no task waiting to be notified, we sent a signal for it to see later. */
370 #else
371     /* Enter critical section to prevent preemption. */
372     uint32_t mask = _enter_critical_isr();
373 
374     pxCond->xSyncSignal = pdTRUE;
375     BaseType_t xAnyHigherPriorityTaskWoken = pdFALSE;
376 
377     /* Unblock all. */
378     for(uint32_t i = 0; i < pxCond->ulWaitingThreads; i++) {
379         xSemaphoreGiveFromISR(pxCond->xCondWaitSemaphore, &xAnyHigherPriorityTaskWoken);
380         xHigherPriorityTaskWoken |= xAnyHigherPriorityTaskWoken;
381     }
382 
383     _exit_critical_isr(mask);
384     portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
385 #endif
386 
387     return LV_RESULT_OK;
388 }
389 
390 
lv_freertos_task_switch_in(const char * name)391 void lv_freertos_task_switch_in(const char * name)
392 {
393     if(lv_strcmp(name, "IDLE")) globals->freertos_idle_task_running = false;
394     else globals->freertos_idle_task_running = true;
395 
396     globals->freertos_task_switch_timestamp = lv_tick_get();
397 }
398 
lv_freertos_task_switch_out(void)399 void lv_freertos_task_switch_out(void)
400 {
401     uint32_t elaps = lv_tick_elaps(globals->freertos_task_switch_timestamp);
402     if(globals->freertos_idle_task_running) globals->freertos_idle_time_sum += elaps;
403     else globals->freertos_non_idle_time_sum += elaps;
404 }
405 
lv_os_get_idle_percent(void)406 uint32_t lv_os_get_idle_percent(void)
407 {
408     if(globals->freertos_non_idle_time_sum + globals->freertos_idle_time_sum == 0) {
409         LV_LOG_WARN("Not enough time elapsed to provide idle percentage");
410         return 0;
411     }
412 
413     uint32_t pct = (globals->freertos_idle_time_sum * 100) / (globals->freertos_idle_time_sum +
414                                                               globals->freertos_non_idle_time_sum);
415 
416     globals->freertos_non_idle_time_sum = 0;
417     globals->freertos_idle_time_sum = 0;
418 
419     return pct;
420 }
421 
422 /**********************
423  *   STATIC FUNCTIONS
424  **********************/
425 
prvRunThread(void * pxArg)426 static void prvRunThread(void * pxArg)
427 {
428     lv_thread_t * pxThread = (lv_thread_t *)pxArg;
429 
430     /* Run the thread routine. */
431     pxThread->pvStartRoutine((void *)pxThread->pTaskArg);
432 
433     vTaskDelete(NULL);
434 }
435 
prvMutexInit(lv_mutex_t * pxMutex)436 static void prvMutexInit(lv_mutex_t * pxMutex)
437 {
438     pxMutex->xMutex = xSemaphoreCreateRecursiveMutex();
439 
440     /* Ensure that the FreeRTOS mutex was successfully created. */
441     if(pxMutex->xMutex == NULL) {
442         LV_LOG_ERROR("xSemaphoreCreateMutex failed!");
443         return;
444     }
445 
446     /* Mutex successfully created. */
447     pxMutex->xIsInitialized = pdTRUE;
448 }
449 
prvCheckMutexInit(lv_mutex_t * pxMutex)450 static void prvCheckMutexInit(lv_mutex_t * pxMutex)
451 {
452     /* Check if the mutex needs to be initialized. */
453     if(pxMutex->xIsInitialized == pdFALSE) {
454         /* Mutex initialization must be in a critical section to prevent two threads
455          * from initializing it at the same time. */
456         _enter_critical();
457 
458         /* Check again that the mutex is still uninitialized, i.e. it wasn't
459          * initialized while this function was waiting to enter the critical
460          * section. */
461         if(pxMutex->xIsInitialized == pdFALSE) {
462             prvMutexInit(pxMutex);
463         }
464 
465         /* Exit the critical section. */
466         _exit_critical();
467     }
468 }
469 
prvCondInit(lv_thread_sync_t * pxCond)470 static void prvCondInit(lv_thread_sync_t * pxCond)
471 {
472     pxCond->xIsInitialized = pdTRUE;
473     pxCond->xSyncSignal = pdFALSE;
474 
475 #if LV_USE_FREERTOS_TASK_NOTIFY
476     pxCond->xTaskToNotify = NULL;
477 #else
478     pxCond->xCondWaitSemaphore = xSemaphoreCreateCounting(ulMAX_COUNT, 0U);
479 
480     /* Ensure that the FreeRTOS semaphore was successfully created. */
481     if(pxCond->xCondWaitSemaphore == NULL) {
482         LV_LOG_ERROR("xSemaphoreCreateCounting failed!");
483         return;
484     }
485 
486     pxCond->xSyncMutex = xSemaphoreCreateMutex();
487 
488     /* Ensure that the FreeRTOS mutex was successfully created. */
489     if(pxCond->xSyncMutex == NULL) {
490         LV_LOG_ERROR("xSemaphoreCreateMutex failed!");
491         /* Cleanup. */
492         vSemaphoreDelete(pxCond->xCondWaitSemaphore);
493         return;
494     }
495 
496     /* Condition variable successfully created. */
497     pxCond->ulWaitingThreads = 0;
498 #endif
499 }
500 
prvCheckCondInit(lv_thread_sync_t * pxCond)501 static void prvCheckCondInit(lv_thread_sync_t * pxCond)
502 {
503     /* Check if the condition variable needs to be initialized. */
504     if(pxCond->xIsInitialized == pdFALSE) {
505         /* Cond initialization must be in a critical section to prevent two
506          * threads from initializing it at the same time. */
507         _enter_critical();
508 
509         /* Check again that the condition is still uninitialized, i.e. it wasn't
510          * initialized while this function was waiting to enter the critical
511          * section. */
512         if(pxCond->xIsInitialized == pdFALSE) {
513             prvCondInit(pxCond);
514         }
515 
516         /* Exit the critical section. */
517         _exit_critical();
518     }
519 }
520 
prvCheckCondInitIsr(lv_thread_sync_t * pxCond)521 static void prvCheckCondInitIsr(lv_thread_sync_t * pxCond)
522 {
523     /* Check if the condition variable needs to be initialized. */
524     if(pxCond->xIsInitialized == pdFALSE) {
525         /* Cond initialization must be in a critical section to prevent two
526          * threads from initializing it at the same time. */
527         uint32_t mask = _enter_critical_isr();
528 
529         /* Check again that the condition is still uninitialized, i.e. it wasn't
530          * initialized while this function was waiting to enter the critical
531          * section. */
532         if(pxCond->xIsInitialized == pdFALSE) {
533             prvCondInit(pxCond);
534         }
535 
536         /* Exit the critical section. */
537         _exit_critical_isr(mask);
538     }
539 }
540 
541 #if !LV_USE_FREERTOS_TASK_NOTIFY
prvTestAndDecrement(lv_thread_sync_t * pxCond,uint32_t ulLocalWaitingThreads)542 static void prvTestAndDecrement(lv_thread_sync_t * pxCond,
543                                 uint32_t ulLocalWaitingThreads)
544 {
545     /* Test local copy of threads waiting is larger than zero. */
546     while(ulLocalWaitingThreads > 0) {
547         /* Atomically check whether the copy in memory has changed.
548          * If not, decrease the copy of threads waiting in memory. */
549         if(ATOMIC_COMPARE_AND_SWAP_SUCCESS == Atomic_CompareAndSwap_u32(
550                &pxCond->ulWaitingThreads,
551                ulLocalWaitingThreads - 1,
552                ulLocalWaitingThreads)) {
553             /* Signal one succeeded. Break. */
554             break;
555         }
556 
557         /* Local copy may be out dated. Reload from memory and retry. */
558         ulLocalWaitingThreads = pxCond->ulWaitingThreads;
559     }
560 }
561 #endif
562 
563 #endif /*LV_USE_OS == LV_OS_FREERTOS*/
564