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