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