1 /*
2  * Copyright (c) 2006-2024 RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2010-10-26     Bernard      the first version
9  * 2022-06-27     xiangxistu   use atomic operation to protect pthread conditional variable
10  */
11 
12 #include <rthw.h>
13 #include <pthread.h>
14 #include "pthread_internal.h"
15 
pthread_condattr_destroy(pthread_condattr_t * attr)16 int pthread_condattr_destroy(pthread_condattr_t *attr)
17 {
18     if (!attr)
19         return EINVAL;
20 
21     return 0;
22 }
23 RTM_EXPORT(pthread_condattr_destroy);
24 
pthread_condattr_init(pthread_condattr_t * attr)25 int pthread_condattr_init(pthread_condattr_t *attr)
26 {
27     if (!attr)
28         return EINVAL;
29     *attr = PTHREAD_PROCESS_PRIVATE;
30 
31     return 0;
32 }
33 RTM_EXPORT(pthread_condattr_init);
34 
pthread_condattr_getclock(const pthread_condattr_t * attr,clockid_t * clock_id)35 int pthread_condattr_getclock(const pthread_condattr_t *attr,
36                               clockid_t *clock_id)
37 {
38     return 0;
39 }
40 RTM_EXPORT(pthread_condattr_getclock);
41 
pthread_condattr_setclock(pthread_condattr_t * attr,clockid_t clock_id)42 int pthread_condattr_setclock(pthread_condattr_t *attr,
43                               clockid_t clock_id)
44 {
45     return 0;
46 }
47 RTM_EXPORT(pthread_condattr_setclock);
48 
pthread_condattr_getpshared(const pthread_condattr_t * attr,int * pshared)49 int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared)
50 {
51     if (!attr || !pshared)
52         return EINVAL;
53 
54     *pshared = PTHREAD_PROCESS_PRIVATE;
55 
56     return 0;
57 }
58 RTM_EXPORT(pthread_condattr_getpshared);
59 
pthread_condattr_setpshared(pthread_condattr_t * attr,int pshared)60 int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared)
61 {
62     if ((pshared != PTHREAD_PROCESS_PRIVATE) &&
63         (pshared != PTHREAD_PROCESS_SHARED))
64     {
65         return EINVAL;
66     }
67 
68     if (pshared != PTHREAD_PROCESS_PRIVATE)
69         return ENOSYS;
70 
71     return 0;
72 }
73 RTM_EXPORT(pthread_condattr_setpshared);
74 
75 /**
76  * @brief Initializes a condition variable.
77  *
78  * This function initializes the condition variable pointed to by `cond` with the attributes
79  * specified by `attr`. If `attr` is NULL, the condition variable is initialized with the
80  * default attributes.
81  *
82  * @param cond A pointer to the condition variable to be initialized.
83  *             Must point to valid memory.
84  * @param attr A pointer to the condition variable attributes object.
85  *             If NULL, default attributes are used.
86  *
87  * @return
88  * - `0` on success.
89  * - A non-zero error code on failure, including:
90  *   - `EINVAL`: Invalid attributes, invalid condition variable pointer, or semaphore init failed.
91  *
92  * @note
93  * - The condition variable must not be used until it has been initialized.
94  * - Each condition variable must be destroyed using `pthread_cond_destroy()`
95  *   once it is no longer needed.
96  *
97  * @see pthread_cond_destroy, pthread_cond_wait, pthread_cond_signal, pthread_cond_broadcast
98  */
pthread_cond_init(pthread_cond_t * cond,const pthread_condattr_t * attr)99 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
100 {
101     rt_err_t result;
102     char cond_name[RT_NAME_MAX];
103     static rt_uint16_t cond_num = 0;
104 
105     /* parameter check */
106     if (cond == RT_NULL)
107         return EINVAL;
108     if ((attr != RT_NULL) && (*attr != PTHREAD_PROCESS_PRIVATE))
109         return EINVAL;
110 
111     rt_snprintf(cond_name, sizeof(cond_name), "cond%02d", cond_num++);
112 
113     /* use default value */
114     if (attr == RT_NULL)
115     {
116         cond->attr = PTHREAD_PROCESS_PRIVATE;
117     }
118     else
119     {
120         cond->attr = *attr;
121     }
122 
123     result = rt_sem_init(&cond->sem, cond_name, 0, RT_IPC_FLAG_FIFO);
124     if (result != RT_EOK)
125     {
126         return EINVAL;
127     }
128 
129     /* detach the object from system object container */
130     rt_object_detach(&(cond->sem.parent.parent));
131     cond->sem.parent.parent.type = RT_Object_Class_Semaphore;
132 
133     return 0;
134 }
135 RTM_EXPORT(pthread_cond_init);
136 
137 /**
138  * @brief Destroys a condition variable.
139  *
140  * This function destroys the condition variable pointed to by `cond`. After a condition
141  * variable is destroyed, it must not be used until it is reinitialized with
142  * `pthread_cond_init`.
143  *
144  * @param cond A pointer to the condition variable to be destroyed.
145  *             Must point to a valid, previously initialized condition variable.
146  *
147  * @return
148  * - `0` on success.
149  * - A non-zero error code on failure, including:
150  *   - `EBUSY`: The condition variable is currently in use by other threads.
151  *   - `EINVAL`: The condition variable is invalid or uninitialized.
152  *
153  * @note
154  * - The condition variable must not be destroyed while it is being used by other threads
155  *   (e.g., in `pthread_cond_wait` or `pthread_cond_timedwait`).
156  * - Attempting to destroy a condition variable that has not been initialized results in
157  *   undefined behavior.
158  *
159  * @see pthread_cond_init, pthread_cond_wait, pthread_cond_signal, pthread_cond_broadcast
160  */
pthread_cond_destroy(pthread_cond_t * cond)161 int pthread_cond_destroy(pthread_cond_t *cond)
162 {
163     rt_err_t result;
164     if (cond == RT_NULL)
165     {
166         return EINVAL;
167     }
168     /* which is not initialized */
169     if (cond->attr == -1)
170     {
171         return 0;
172     }
173 
174     if (!rt_list_isempty(&cond->sem.parent.suspend_thread))
175     {
176         return EBUSY;
177     }
178 __retry:
179     result = rt_sem_trytake(&(cond->sem));
180     if (result == EBUSY)
181     {
182         pthread_cond_broadcast(cond);
183         goto __retry;
184     }
185 
186     /* clean condition */
187     rt_memset(cond, 0, sizeof(pthread_cond_t));
188     cond->attr = -1;
189 
190     return 0;
191 }
192 RTM_EXPORT(pthread_cond_destroy);
193 
194 /**
195  * @brief Unblocks all threads waiting on the specified condition variable.
196  *
197  * This function wakes up all threads that are currently blocked on the condition variable
198  * pointed to by `cond`. The condition variable must be associated with a mutex, and
199  * threads waiting on the condition variable should recheck the condition after being
200  * unblocked.
201  *
202  * @param cond A pointer to the condition variable.
203  *             Must point to a valid, initialized condition variable.
204  *
205  * @return
206  * - `0` on success.
207  * - A non-zero error code on failure, including:
208  *   - `EINVAL`: The condition variable is invalid or uninitialized.
209  *
210  * @note
211  * - Calling this function does not release the associated mutex.
212  * - Waking up threads does not guarantee that any specific thread will acquire the
213  *   mutex immediately, as thread scheduling depends on the system.
214  * - Typically used when the condition might allow multiple waiting threads to proceed.
215  *
216  * @see pthread_cond_signal, pthread_cond_wait, pthread_cond_init, pthread_cond_destroy
217  */
pthread_cond_broadcast(pthread_cond_t * cond)218 int pthread_cond_broadcast(pthread_cond_t *cond)
219 {
220     rt_err_t result;
221 
222     if (cond == RT_NULL)
223         return EINVAL;
224     if (cond->attr == -1)
225         pthread_cond_init(cond, RT_NULL);
226 
227     while (1)
228     {
229         /* try to take condition semaphore */
230         result = rt_sem_trytake(&(cond->sem));
231         if (result == -RT_ETIMEOUT)
232         {
233             /* it's timeout, release this semaphore */
234             rt_sem_release(&(cond->sem));
235         }
236         else if (result == RT_EOK)
237         {
238             /* has taken this semaphore, release it */
239             rt_sem_release(&(cond->sem));
240             break;
241         }
242         else
243         {
244             return EINVAL;
245         }
246     }
247 
248     return 0;
249 }
250 RTM_EXPORT(pthread_cond_broadcast);
251 
252 /**
253  * @brief Wakes up one thread waiting on the specified condition variable.
254  *
255  * This function unblocks one thread that is currently waiting on the
256  * condition variable `cond`. If multiple threads are waiting, the thread to wake
257  * up is determined by the system's scheduling policies.
258  *
259  * @param cond A pointer to the condition variable to signal.
260  *             Must point to a valid and initialized condition variable.
261  *
262  * @return
263  * - `0` on success.
264  * - A non-zero error code on failure, including:
265  *   - `EINVAL`: The condition variable is invalid or uninitialized.
266  *
267  * @note
268  * - This function does not release the associated mutex.
269  * - If no threads are currently waiting on the condition variable, the call has no effect.
270  * - The awakened thread will not run until it can reacquire the associated mutex and
271  *   re-evaluate the waiting condition.
272  * - It is typically used when only one waiting thread should be allowed to proceed.
273  *
274  * @see pthread_cond_broadcast, pthread_cond_wait, pthread_cond_init, pthread_cond_destroy
275  */
pthread_cond_signal(pthread_cond_t * cond)276 int pthread_cond_signal(pthread_cond_t *cond)
277 {
278     rt_base_t temp;
279     rt_err_t result;
280 
281     if (cond == RT_NULL)
282         return EINVAL;
283     if (cond->attr == -1)
284         pthread_cond_init(cond, RT_NULL);
285 
286     /* disable interrupt */
287     temp = rt_hw_interrupt_disable();
288     if (rt_list_isempty(&cond->sem.parent.suspend_thread))
289     {
290         /* enable interrupt */
291         rt_hw_interrupt_enable(temp);
292         return 0;
293     }
294     else
295     {
296         /* enable interrupt */
297         rt_hw_interrupt_enable(temp);
298         result = rt_sem_release(&(cond->sem));
299         if (result == RT_EOK)
300         {
301             return 0;
302         }
303 
304         return 0;
305     }
306 }
307 RTM_EXPORT(pthread_cond_signal);
308 
309 /**
310  * @brief Waits on a condition variable with a timeout.
311  *
312  * This function causes the calling thread to block on the condition variable `cond`,
313  * releasing the associated mutex `mutex`. The thread will remain blocked until
314  * one of the following occurs:
315  * - It is signaled or broadcast using `pthread_cond_signal` or `pthread_cond_broadcast`.
316  * - The specified timeout expires.
317  *
318  * @param cond A pointer to the condition variable to wait on.
319  *             Must point to a valid, initialized condition variable.
320  * @param mutex A pointer to the mutex associated with the condition variable.
321  *              Must be locked by the calling thread before invoking this function.
322  * @param timeout The timeout duration in milliseconds. A value of `RT_WAITING_FOREVER`
323  *                indicates the thread will wait indefinitely.
324  *
325  * @return
326  * - `RT_EOK` on successful wakeup (signaled or broadcast).
327  * - `-RT_ETIMEOUT` if the timeout expires before the condition variable is signaled.
328  * - `-RT_ERROR` if an error occurs (e.g., invalid parameters).
329  *
330  * @note
331  * - The mutex is automatically released while the thread waits and re-acquired before
332  *   the function returns.
333  * - If `timeout` is 0, the function behaves as a non-blocking check.
334  * - Ensure the condition variable and mutex are properly initialized before use.
335  *
336  * @see pthread_cond_signal, pthread_cond_broadcast, pthread_mutex_lock, pthread_mutex_unlock
337  */
_pthread_cond_timedwait(pthread_cond_t * cond,pthread_mutex_t * mutex,rt_int32_t timeout)338 rt_err_t _pthread_cond_timedwait(pthread_cond_t *cond,
339                                  pthread_mutex_t *mutex,
340                                  rt_int32_t timeout)
341 {
342     rt_err_t result = RT_EOK;
343     rt_sem_t sem;
344     rt_int32_t time;
345 
346     sem = &(cond->sem);
347     if (sem == RT_NULL)
348     {
349         return -RT_ERROR;
350     }
351     time = timeout;
352 
353     if (!cond || !mutex)
354     {
355         return -RT_ERROR;
356     }
357     /* check whether initialized */
358     if (cond->attr == -1)
359     {
360         pthread_cond_init(cond, RT_NULL);
361     }
362 
363     /* The mutex was not owned by the current thread at the time of the call. */
364     if (mutex->lock.owner != rt_thread_self())
365     {
366         return -RT_ERROR;
367     }
368 
369     {
370         struct rt_thread *thread;
371 
372         /* parameter check */
373         RT_ASSERT(sem != RT_NULL);
374         RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
375 
376         rt_enter_critical();
377 
378         if (sem->value > 0)
379         {
380             /* semaphore is available */
381             sem->value--;
382 
383             rt_exit_critical();
384         }
385         else
386         {
387             /* no waiting, return with timeout */
388             if (time == 0)
389             {
390                 rt_exit_critical();
391 
392                 return -RT_ETIMEOUT;
393             }
394             else
395             {
396                 /* current context checking */
397                 RT_DEBUG_IN_THREAD_CONTEXT;
398 
399                 /* semaphore is unavailable, push to suspend list */
400                 /* get current thread */
401                 thread = rt_thread_self();
402 
403                 /* reset thread error number */
404                 thread->error = RT_EOK;
405 
406                 /* suspend thread */
407                 rt_thread_suspend(thread);
408 
409                 /* Only support FIFO */
410                 rt_list_insert_before(&(sem->parent.suspend_thread), &RT_THREAD_LIST_NODE(thread));
411 
412                 /**
413                 rt_ipc_list_suspend(&(sem->parent.suspend_thread),
414                                     thread,
415                                     sem->parent.parent.flag);
416                 */
417 
418                 /* has waiting time, start thread timer */
419                 if (time > 0)
420                 {
421                     /* reset the timeout of thread timer and start it */
422                     rt_timer_control(&(thread->thread_timer),
423                                      RT_TIMER_CTRL_SET_TIME,
424                                      &time);
425                     rt_timer_start(&(thread->thread_timer));
426                 }
427 
428                 /* to avoid the lost of singal< cond->sem > */
429                 if (pthread_mutex_unlock(mutex) != 0)
430                 {
431                     return -RT_ERROR;
432                 }
433 
434                 /* exit critical and do schedule */
435                 rt_exit_critical();
436 
437                 result = thread->error;
438 
439                 /* lock mutex again */
440                 pthread_mutex_lock(mutex);
441             }
442         }
443     }
444 
445     return result;
446 }
447 RTM_EXPORT(_pthread_cond_timedwait);
448 
449 /**
450  * @brief Waits on a condition variable.
451  *
452  * This function blocks the calling thread on the condition variable `cond` and releases
453  * the associated mutex `mutex`. The thread remains blocked until it is signaled or
454  * broadcast using `pthread_cond_signal` or `pthread_cond_broadcast`. When the thread
455  * is awakened, it re-acquires the mutex and resumes execution.
456  *
457  * @param cond A pointer to the condition variable to wait on.
458  *             Must point to a valid, initialized condition variable.
459  * @param mutex A pointer to the mutex associated with the condition variable.
460  *              Must be locked by the calling thread before invoking this function.
461  *
462  * @return
463  * - `0` on success.
464  * - A non-zero error code on failure, including:
465  *   - `EINVAL`: The condition variable or mutex is invalid or uninitialized.
466  *
467  * @note
468  * - The mutex must be locked before calling this function.
469  * - Upon returning, the mutex is locked again by the calling thread.
470  * - Spurious wakeups may occur, so the thread should always recheck the waiting
471  *   condition upon wakeup.
472  * - This function may block indefinitely unless the condition is signaled or broadcast.
473  *
474  * @see pthread_cond_signal, pthread_cond_broadcast, pthread_cond_timedwait, pthread_mutex_lock
475  */
pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex)476 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
477 {
478     rt_err_t result;
479 
480 __retry:
481     result = _pthread_cond_timedwait(cond, mutex, RT_WAITING_FOREVER);
482     if (result == RT_EOK)
483     {
484         return 0;
485     }
486     else if (result == -RT_EINTR)
487     {
488         /* https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html
489          * These functions shall not return an error code of [EINTR].
490          */
491         goto __retry;
492     }
493 
494     return EINVAL;
495 }
496 RTM_EXPORT(pthread_cond_wait);
497 
498 /**
499  * @brief Waits on a condition variable with a timeout.
500  *
501  * This function blocks the calling thread on the condition variable `cond`, releasing
502  * the associated mutex `mutex`. The thread remains blocked until one of the following occurs:
503  * - The condition variable is signaled or broadcast using `pthread_cond_signal` or `pthread_cond_broadcast`.
504  * - The specified absolute timeout `abstime` is reached.
505  * - A spurious wakeup occurs (requiring the thread to recheck the condition).
506  *
507  * @param cond A pointer to the condition variable to wait on.
508  *             Must point to a valid, initialized condition variable.
509  * @param mutex A pointer to the mutex associated with the condition variable.
510  *              Must be locked by the calling thread before invoking this function.
511  * @param abstime A pointer to a `struct timespec` specifying the absolute timeout (in seconds and nanoseconds
512  *                since the Epoch). If the time specified is already reached, the function immediately returns.
513  *
514  * @return
515  * - `0` on successful wakeup (signaled or broadcast).
516  * - `ETIMEDOUT` if the timeout expires before the condition variable is signaled.
517  * - A non-zero error code on failure, including:
518  *   - `EINVAL`: The condition variable, mutex, or `abstime` is invalid.
519  *   - `EPERM`: The mutex is not owned by the calling thread.
520  *
521  * @note
522  * - The mutex is released while the thread is waiting and re-acquired before the function returns.
523  * - Spurious wakeups may occur, so the thread must always recheck the waiting condition upon wakeup.
524  * - The timeout is specified in absolute time, not relative duration.
525  * - Ensure the condition variable and mutex are properly initialized before use.
526  *
527  * @see pthread_cond_wait, pthread_cond_signal, pthread_cond_broadcast, pthread_mutex_lock
528  */
pthread_cond_timedwait(pthread_cond_t * cond,pthread_mutex_t * mutex,const struct timespec * abstime)529 int pthread_cond_timedwait(pthread_cond_t *cond,
530                            pthread_mutex_t *mutex,
531                            const struct timespec *abstime)
532 {
533     int timeout;
534     rt_err_t result;
535 
536     timeout = rt_timespec_to_tick(abstime);
537     result = _pthread_cond_timedwait(cond, mutex, timeout);
538     if (result == RT_EOK)
539     {
540         return 0;
541     }
542     if (result == -RT_ETIMEOUT)
543     {
544         return ETIMEDOUT;
545     }
546 
547     return EINVAL;
548 }
549 RTM_EXPORT(pthread_cond_timedwait);
550