1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-11-20 Shell Support of condition variable
*/
#define DBG_TAG "ipc.condvar"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include <rtdevice.h>
#include <rtatomic.h>
#include <rtthread.h>
static struct rt_spinlock _local_cv_queue_lock = RT_SPINLOCK_INIT;
#define CV_ASSERT_LOCKED(cv) \
RT_ASSERT(!(cv)->waiting_mtx || \
rt_mutex_get_owner((rt_mutex_t)(cv)->waiting_mtx) == \
rt_thread_self())
void rt_condvar_init(rt_condvar_t cv, char *name)
{
#ifdef USING_RT_OBJECT
/* TODO: support rt object */
rt_object_init();
#endif
rt_wqueue_init(&cv->event);
rt_atomic_store(&cv->waiters_cnt, 0);
rt_atomic_store(&cv->waiting_mtx, 0);
}
static int _waitq_inqueue(rt_wqueue_t *queue, struct rt_wqueue_node *node,
rt_tick_t timeout, int suspend_flag)
{
rt_thread_t tcb = node->polling_thread;
rt_timer_t timer = &(tcb->thread_timer);
rt_err_t ret;
if (queue->flag != RT_WQ_FLAG_WAKEUP)
{
ret = rt_thread_suspend_with_flag(tcb, suspend_flag);
if (ret == RT_EOK)
{
rt_wqueue_add(queue, node);
if (timeout != (rt_tick_t)RT_WAITING_FOREVER)
{
rt_timer_control(timer, RT_TIMER_CTRL_SET_TIME, &timeout);
rt_timer_start(timer);
}
}
}
else
{
ret = RT_EOK;
}
return ret;
}
#define INIT_WAITQ_NODE(node) \
{ \
.polling_thread = rt_thread_self(), .key = 0, \
.wakeup = __wqueue_default_wake, .wqueue = &cv->event, \
.list = RT_LIST_OBJECT_INIT(node.list) \
}
int rt_condvar_timedwait(rt_condvar_t cv, rt_mutex_t mtx, int suspend_flag,
rt_tick_t timeout)
{
rt_err_t acq_mtx_succ, rc;
rt_atomic_t waiting_mtx;
struct rt_wqueue_node node = INIT_WAITQ_NODE(node);
/* not allowed in IRQ & critical section */
RT_DEBUG_SCHEDULER_AVAILABLE(1);
CV_ASSERT_LOCKED(cv);
/**
* for the worst case, this is racy with the following works to reset field
* before mutex is taken. The spinlock then comes to rescue.
*/
rt_spin_lock(&_local_cv_queue_lock);
waiting_mtx = rt_atomic_load(&cv->waiting_mtx);
if (!waiting_mtx)
acq_mtx_succ = rt_atomic_compare_exchange_strong(
&cv->waiting_mtx, &waiting_mtx, (size_t)mtx);
else
acq_mtx_succ = 0;
rt_spin_unlock(&_local_cv_queue_lock);
if (acq_mtx_succ == 1 || waiting_mtx == (size_t)mtx)
{
rt_atomic_add(&cv->waiters_cnt, 1);
rt_enter_critical();
if (suspend_flag == RT_INTERRUPTIBLE)
rc = _waitq_inqueue(&cv->event, &node, timeout, RT_INTERRUPTIBLE);
else /* UNINTERRUPTIBLE is forbidden, since it's not safe for user space */
rc = _waitq_inqueue(&cv->event, &node, timeout, RT_KILLABLE);
acq_mtx_succ = rt_mutex_release(mtx);
RT_ASSERT(acq_mtx_succ == 0);
rt_exit_critical();
if (rc == RT_EOK)
{
rt_schedule();
rc = rt_get_errno();
rc = rc > 0 ? -rc : rc;
}
else
{
LOG_D("%s() failed to suspend", __func__);
}
rt_wqueue_remove(&node);
rt_spin_lock(&_local_cv_queue_lock);
if (rt_atomic_add(&cv->waiters_cnt, -1) == 1)
{
waiting_mtx = (size_t)mtx;
acq_mtx_succ = rt_atomic_compare_exchange_strong(&cv->waiting_mtx,
&waiting_mtx, 0);
RT_ASSERT(acq_mtx_succ == 1);
}
rt_spin_unlock(&_local_cv_queue_lock);
acq_mtx_succ = rt_mutex_take(mtx, RT_WAITING_FOREVER);
RT_ASSERT(acq_mtx_succ == 0);
}
else
{
LOG_D("%s: conflict waiting mutex", __func__);
rc = -EBUSY;
}
return rc;
}
/** Keep in mind that we always operating when cv.waiting_mtx is taken */
int rt_condvar_signal(rt_condvar_t cv)
{
CV_ASSERT_LOCKED(cv);
/* to avoid spurious wakeups */
if (rt_atomic_load(&cv->waiters_cnt) > 0)
rt_wqueue_wakeup(&cv->event, 0);
cv->event.flag = 0;
return 0;
}
int rt_condvar_broadcast(rt_condvar_t cv)
{
CV_ASSERT_LOCKED(cv);
/* to avoid spurious wakeups */
if (rt_atomic_load(&cv->waiters_cnt) > 0)
rt_wqueue_wakeup_all(&cv->event, 0);
cv->event.flag = 0;
return 0;
}
|