1 /*
2 * Copyright (c) 2006-2023, RT-Thread Development Team
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Change Logs:
7 * Date Author Notes
8 * 2018/06/26 Bernard Fix the wait queue issue when wakeup a soon
9 * to blocked thread.
10 * 2022-01-24 THEWON let rt_wqueue_wait return thread->error when using signal
11 * 2023-09-15 xqyjlj perf rt_hw_interrupt_disable/enable
12 * 2023-11-21 Shell Support wakeup_all
13 */
14 #define DBG_TAG "ipc.waitqueue"
15 #define DBG_LVL DBG_INFO
16 #include <rtdbg.h>
17
18 #include <stdint.h>
19 #include <rthw.h>
20 #include <rtdevice.h>
21
22 /**
23 * @brief This function will insert a node to the wait queue.
24 *
25 * @param queue is a pointer to the wait queue.
26 *
27 * @param node is a pointer to the node to be inserted.
28 */
rt_wqueue_add(rt_wqueue_t * queue,struct rt_wqueue_node * node)29 void rt_wqueue_add(rt_wqueue_t *queue, struct rt_wqueue_node *node)
30 {
31 rt_base_t level;
32
33 level = rt_spin_lock_irqsave(&(queue->spinlock));
34 node->wqueue = queue;
35 rt_list_insert_before(&(queue->waiting_list), &(node->list));
36 rt_spin_unlock_irqrestore(&(queue->spinlock), level);
37 }
38
39 /**
40 * @brief This function will remove a node from the wait queue.
41 *
42 * @param node is a pointer to the node to be removed.
43 */
rt_wqueue_remove(struct rt_wqueue_node * node)44 void rt_wqueue_remove(struct rt_wqueue_node *node)
45 {
46 rt_base_t level;
47
48 RT_ASSERT(node->wqueue != RT_NULL);
49
50 level = rt_spin_lock_irqsave(&(node->wqueue->spinlock));
51 rt_list_remove(&(node->list));
52 rt_spin_unlock_irqrestore(&(node->wqueue->spinlock), level);
53 }
54
55 /**
56 * @brief This function is the default wakeup function, but it doesn't do anything in actual.
57 * It always return 0, user should define their own wakeup function.
58 *
59 * @param wait is a pointer to the wait queue.
60 *
61 * @param key is the wakeup condition.
62 *
63 * @return always return 0.
64 */
__wqueue_default_wake(struct rt_wqueue_node * wait,void * key)65 int __wqueue_default_wake(struct rt_wqueue_node *wait, void *key)
66 {
67 return 0;
68 }
69
70 /**
71 * @brief This function will wake up a pending thread on the specified
72 * waiting queue that meets the conditions.
73 *
74 * @param queue is a pointer to the wait queue.
75 *
76 * @param key is the wakeup conditions, but it is not effective now, because
77 * default wakeup function always return 0.
78 * If user wants to use it, user should define their own wakeup function.
79 */
rt_wqueue_wakeup(rt_wqueue_t * queue,void * key)80 void rt_wqueue_wakeup(rt_wqueue_t *queue, void *key)
81 {
82 rt_base_t level;
83 int need_schedule = 0;
84
85 rt_list_t *queue_list;
86 struct rt_list_node *node;
87 struct rt_wqueue_node *entry;
88
89 queue_list = &(queue->waiting_list);
90
91 level = rt_spin_lock_irqsave(&(queue->spinlock));
92 /* set wakeup flag in the queue */
93 queue->flag = RT_WQ_FLAG_WAKEUP;
94
95 if (!(rt_list_isempty(queue_list)))
96 {
97 for (node = queue_list->next; node != queue_list; node = node->next)
98 {
99 entry = rt_list_entry(node, struct rt_wqueue_node, list);
100 if (entry->wakeup(entry, key) == 0)
101 {
102 /**
103 * even though another thread may interrupt the thread and
104 * wakeup it meanwhile, we can asuume that condition is ready
105 */
106 entry->polling_thread->error = RT_EOK;
107 if (!rt_thread_resume(entry->polling_thread))
108 {
109 need_schedule = 1;
110
111 rt_list_remove(&(entry->list));
112
113 break;
114 }
115 }
116 }
117 }
118 rt_spin_unlock_irqrestore(&(queue->spinlock), level);
119 if (need_schedule)
120 rt_schedule();
121
122 return;
123 }
124
125 /**
126 * @brief This function will wake up all pending thread on the specified
127 * waiting queue that meets the conditions.
128 *
129 * @param queue is a pointer to the wait queue.
130 *
131 * @param key is the wakeup conditions, but it is not effective now, because
132 * default wakeup function always return 0.
133 * If user wants to use it, user should define their own wakeup
134 * function.
135 */
rt_wqueue_wakeup_all(rt_wqueue_t * queue,void * key)136 void rt_wqueue_wakeup_all(rt_wqueue_t *queue, void *key)
137 {
138 rt_base_t level;
139 int need_schedule = 0;
140
141 rt_list_t *queue_list;
142 struct rt_list_node *node;
143 struct rt_wqueue_node *entry;
144
145 queue_list = &(queue->waiting_list);
146
147 level = rt_spin_lock_irqsave(&(queue->spinlock));
148 /* set wakeup flag in the queue */
149 queue->flag = RT_WQ_FLAG_WAKEUP;
150
151 if (!(rt_list_isempty(queue_list)))
152 {
153 for (node = queue_list->next; node != queue_list; )
154 {
155 entry = rt_list_entry(node, struct rt_wqueue_node, list);
156 if (entry->wakeup(entry, key) == 0)
157 {
158 /**
159 * even though another thread may interrupt the thread and
160 * wakeup it meanwhile, we can asuume that condition is ready
161 */
162 entry->polling_thread->error = RT_EOK;
163 if (!rt_thread_resume(entry->polling_thread))
164 {
165 need_schedule = 1;
166 }
167 else
168 {
169 /* wakeup happened too soon that waker hadn't slept */
170 LOG_D("%s: Thread resume failed", __func__);
171 }
172 node = node->next;
173 }
174 else
175 {
176 node = node->next;
177 }
178 }
179 }
180 rt_spin_unlock_irqrestore(&(queue->spinlock), level);
181 if (need_schedule)
182 rt_schedule();
183
184 return;
185 }
186
187 /**
188 * @brief This function will join a thread to the specified waiting queue, the thread will holds a wait or
189 * timeout return on the specified wait queue.
190 *
191 * @param queue is a pointer to the wait queue.
192 *
193 * @param condition is parameters compatible with POSIX standard interface (currently meaningless, just pass in 0).
194 *
195 * @param msec is the timeout value, unit is millisecond.
196 *
197 * @return Return 0 if the thread is woken up.
198 */
_rt_wqueue_wait(rt_wqueue_t * queue,int condition,int msec,int suspend_flag)199 static int _rt_wqueue_wait(rt_wqueue_t *queue, int condition, int msec, int suspend_flag)
200 {
201 int tick;
202 rt_thread_t tid = rt_thread_self();
203 rt_timer_t tmr = &(tid->thread_timer);
204 struct rt_wqueue_node __wait;
205 rt_base_t level;
206 rt_err_t ret;
207
208 /* current context checking */
209 RT_DEBUG_SCHEDULER_AVAILABLE(RT_TRUE);
210
211 tick = rt_tick_from_millisecond(msec);
212
213 if ((condition) || (tick == 0))
214 return 0;
215
216 __wait.polling_thread = rt_thread_self();
217 __wait.key = 0;
218 __wait.wakeup = __wqueue_default_wake;
219 __wait.wqueue = queue;
220 rt_list_init(&__wait.list);
221
222 level = rt_spin_lock_irqsave(&(queue->spinlock));
223
224 /* reset thread error */
225 tid->error = RT_EOK;
226
227 if (queue->flag == RT_WQ_FLAG_WAKEUP)
228 {
229 /* already wakeup */
230 goto __exit_wakeup;
231 }
232
233 ret = rt_thread_suspend_with_flag(tid, suspend_flag);
234 if (ret != RT_EOK)
235 {
236 rt_spin_unlock_irqrestore(&(queue->spinlock), level);
237 /* suspend failed */
238 return -RT_EINTR;
239 }
240
241 rt_list_insert_before(&(queue->waiting_list), &(__wait.list));
242
243 /* start timer */
244 if (tick != RT_WAITING_FOREVER)
245 {
246 rt_timer_control(tmr,
247 RT_TIMER_CTRL_SET_TIME,
248 &tick);
249
250 rt_timer_start(tmr);
251 }
252 rt_spin_unlock_irqrestore(&(queue->spinlock), level);
253
254 rt_schedule();
255
256 level = rt_spin_lock_irqsave(&(queue->spinlock));
257
258 __exit_wakeup:
259 queue->flag = RT_WQ_FLAG_CLEAN;
260 rt_spin_unlock_irqrestore(&(queue->spinlock), level);
261
262 rt_wqueue_remove(&__wait);
263
264 return tid->error > 0 ? -tid->error : tid->error;
265 }
266
rt_wqueue_wait(rt_wqueue_t * queue,int condition,int msec)267 int rt_wqueue_wait(rt_wqueue_t *queue, int condition, int msec)
268 {
269 return _rt_wqueue_wait(queue, condition, msec, RT_UNINTERRUPTIBLE);
270 }
271
rt_wqueue_wait_killable(rt_wqueue_t * queue,int condition,int msec)272 int rt_wqueue_wait_killable(rt_wqueue_t *queue, int condition, int msec)
273 {
274 return _rt_wqueue_wait(queue, condition, msec, RT_KILLABLE);
275 }
276
rt_wqueue_wait_interruptible(rt_wqueue_t * queue,int condition,int msec)277 int rt_wqueue_wait_interruptible(rt_wqueue_t *queue, int condition, int msec)
278 {
279 return _rt_wqueue_wait(queue, condition, msec, RT_INTERRUPTIBLE);
280 }
281