1 /*
2  * Event loop based on select() loop
3  * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8 /*
9  * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
10  *
11  * SPDX-License-Identifier: Apache-2.0
12  */
13 
14 #include "includes.h"
15 
16 #include "common.h"
17 #include "list.h"
18 #include "eloop.h"
19 #include "esp_wifi_driver.h"
20 
21 struct eloop_timeout {
22     struct dl_list list;
23     struct os_reltime time;
24     void *eloop_data;
25     void *user_data;
26     eloop_timeout_handler handler;
27 #ifdef ELOOP_DEBUG
28     char func_name[100];
29     int line;
30 #endif
31 };
32 
33 struct eloop_data {
34     struct dl_list timeout;
35     ETSTimer eloop_timer;
36     bool eloop_started;
37 };
38 
39 #define ELOOP_LOCK() os_mutex_lock(eloop_data_lock)
40 #define ELOOP_UNLOCK() os_mutex_unlock(eloop_data_lock)
41 
42 static void *eloop_data_lock = NULL;
43 
44 static struct eloop_data eloop;
45 
eloop_run_wrapper(void * data)46 static int eloop_run_wrapper(void *data)
47 {
48     eloop_run();
49     return 0;
50 }
51 
eloop_run_timer(void * args)52 static void eloop_run_timer(void *args)
53 {
54     /* Execute timers in pptask context to make it thread safe */
55     wifi_ipc_config_t cfg;
56 
57     cfg.fn = eloop_run_wrapper;
58     cfg.arg = NULL;
59     cfg.arg_size = 0;
60     esp_wifi_ipc_internal(&cfg, false);
61 }
62 
eloop_init(void)63 int eloop_init(void)
64 {
65     os_memset(&eloop, 0, sizeof(eloop));
66     dl_list_init(&eloop.timeout);
67     os_timer_disarm(&eloop.eloop_timer);
68     os_timer_setfn(&eloop.eloop_timer, (ETSTimerFunc *)eloop_run_timer, NULL);
69 
70     eloop_data_lock = os_recursive_mutex_create();
71 
72     if (!eloop_data_lock) {
73         wpa_printf(MSG_ERROR, "failed to create eloop data loop");
74         return -1;
75     }
76     eloop.eloop_started = true;
77 
78     return 0;
79 }
80 
81 #ifdef ELOOP_DEBUG
eloop_register_timeout_debug(unsigned int secs,unsigned int usecs,eloop_timeout_handler handler,void * eloop_data,void * user_data,const char * func,int line)82 int eloop_register_timeout_debug(unsigned int secs, unsigned int usecs,
83                                  eloop_timeout_handler handler, void *eloop_data,
84                                  void *user_data, const char *func, int line)
85 #else
86 int eloop_register_timeout(unsigned int secs, unsigned int usecs,
87                            eloop_timeout_handler handler,
88                            void *eloop_data, void *user_data)
89 #endif
90 {
91     struct eloop_timeout *timeout, *tmp;
92     os_time_t now_sec;
93 #ifdef ELOOP_DEBUG
94     int count = 0;
95 #endif
96 
97     timeout = os_zalloc(sizeof(*timeout));
98     if (timeout == NULL) {
99         return -1;
100     }
101     if (os_get_reltime(&timeout->time) < 0) {
102         os_free(timeout);
103         return -1;
104     }
105     now_sec = timeout->time.sec;
106     timeout->time.sec += secs;
107     if (timeout->time.sec < now_sec) {
108         goto overflow;
109     }
110     timeout->time.usec += usecs;
111     while (timeout->time.usec >= 1000000) {
112         timeout->time.sec++;
113         timeout->time.usec -= 1000000;
114     }
115     if (timeout->time.sec < now_sec) {
116         goto overflow;
117     }
118     timeout->eloop_data = eloop_data;
119     timeout->user_data = user_data;
120     timeout->handler = handler;
121 #ifdef ELOOP_DEBUG
122     os_strlcpy(timeout->func_name, func, 100);
123     timeout->line = line;
124 #endif
125 
126     /* Maintain timeouts in order of increasing time */
127     dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {
128         if (os_reltime_before(&timeout->time, &tmp->time)) {
129             ELOOP_LOCK();
130             dl_list_add(tmp->list.prev, &timeout->list);
131             ELOOP_UNLOCK();
132             goto run;
133         }
134 #ifdef ELOOP_DEBUG
135         count++;
136 #endif
137     }
138     ELOOP_LOCK();
139     dl_list_add_tail(&eloop.timeout, &timeout->list);
140     ELOOP_UNLOCK();
141 
142 run:
143 #ifdef ELOOP_DEBUG
144     wpa_printf(MSG_DEBUG, "ELOOP: Added one timer from %s:%d to call %p, current order=%d",
145                timeout->func_name, line, timeout->handler, count);
146 #endif
147     ELOOP_LOCK();
148     os_timer_disarm(&eloop.eloop_timer);
149     os_timer_arm(&eloop.eloop_timer, 0, 0);
150     ELOOP_UNLOCK();
151 
152     return 0;
153 
154 overflow:
155     /*
156      * Integer overflow - assume long enough timeout to be assumed
157      * to be infinite, i.e., the timeout would never happen.
158      */
159     wpa_printf(MSG_DEBUG,
160                "ELOOP: Too long timeout (secs=%u usecs=%u) to ever happen - ignore it",
161                secs, usecs);
162     os_free(timeout);
163     return 0;
164 }
165 
timeout_exists(struct eloop_timeout * old)166 static bool timeout_exists(struct eloop_timeout *old)
167 {
168     struct eloop_timeout *timeout, *prev;
169     dl_list_for_each_safe(timeout, prev, &eloop.timeout,
170                           struct eloop_timeout, list) {
171         if (old == timeout) {
172             return true;
173         }
174     }
175 
176     return false;
177 }
178 
eloop_remove_timeout(struct eloop_timeout * timeout)179 static void eloop_remove_timeout(struct eloop_timeout *timeout)
180 {
181     bool timeout_present = false;
182     ELOOP_LOCK();
183     /* Make sure timeout still exists(Another context may have deleted this) */
184     timeout_present = timeout_exists(timeout);
185     if (timeout_present) {
186         dl_list_del(&timeout->list);
187     }
188     ELOOP_UNLOCK();
189     if (timeout_present) {
190         os_free(timeout);
191     }
192 }
193 
194 #ifdef ELOOP_DEBUG
eloop_cancel_timeout_debug(eloop_timeout_handler handler,void * eloop_data,void * user_data,const char * func,int line)195 int eloop_cancel_timeout_debug(eloop_timeout_handler handler, void *eloop_data,
196                                void *user_data, const char *func, int line)
197 #else
198 int eloop_cancel_timeout(eloop_timeout_handler handler,
199                          void *eloop_data, void *user_data)
200 #endif
201 {
202     struct eloop_timeout *timeout, *prev;
203     int removed = 0;
204 
205     dl_list_for_each_safe(timeout, prev, &eloop.timeout,
206                           struct eloop_timeout, list) {
207         if (timeout->handler == handler &&
208                 (timeout->eloop_data == eloop_data ||
209                  eloop_data == ELOOP_ALL_CTX) &&
210                 (timeout->user_data == user_data ||
211                  user_data == ELOOP_ALL_CTX)) {
212             eloop_remove_timeout(timeout);
213             removed++;
214         }
215     }
216 #ifdef ELOOP_DEBUG
217     wpa_printf(MSG_DEBUG, "ELOOP: %s:%d called to remove timer handler=%p, removed count=%d",
218                func, line, handler, removed);
219 #endif
220 
221     return removed;
222 }
223 
eloop_cancel_timeout_one(eloop_timeout_handler handler,void * eloop_data,void * user_data,struct os_reltime * remaining)224 int eloop_cancel_timeout_one(eloop_timeout_handler handler,
225                              void *eloop_data, void *user_data,
226                              struct os_reltime *remaining)
227 {
228     struct eloop_timeout *timeout, *prev;
229     int removed = 0;
230     struct os_reltime now;
231 
232     os_get_reltime(&now);
233     remaining->sec = remaining->usec = 0;
234 
235     dl_list_for_each_safe(timeout, prev, &eloop.timeout,
236                           struct eloop_timeout, list) {
237         if (timeout->handler == handler &&
238                 (timeout->eloop_data == eloop_data) &&
239                 (timeout->user_data == user_data)) {
240             removed = 1;
241             if (os_reltime_before(&now, &timeout->time)) {
242                 os_reltime_sub(&timeout->time, &now, remaining);
243             }
244             eloop_remove_timeout(timeout);
245             break;
246         }
247     }
248     return removed;
249 }
250 
eloop_is_timeout_registered(eloop_timeout_handler handler,void * eloop_data,void * user_data)251 int eloop_is_timeout_registered(eloop_timeout_handler handler,
252                                 void *eloop_data, void *user_data)
253 {
254     struct eloop_timeout *tmp;
255 
256     dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {
257         if (tmp->handler == handler &&
258                 tmp->eloop_data == eloop_data &&
259                 tmp->user_data == user_data) {
260             return 1;
261         }
262     }
263 
264     return 0;
265 }
266 
eloop_deplete_timeout(unsigned int req_secs,unsigned int req_usecs,eloop_timeout_handler handler,void * eloop_data,void * user_data)267 int eloop_deplete_timeout(unsigned int req_secs, unsigned int req_usecs,
268                           eloop_timeout_handler handler, void *eloop_data,
269                           void *user_data)
270 {
271     struct os_reltime now, requested, remaining;
272     struct eloop_timeout *tmp;
273 
274     dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {
275         if (tmp->handler == handler &&
276                 tmp->eloop_data == eloop_data &&
277                 tmp->user_data == user_data) {
278             requested.sec = req_secs;
279             requested.usec = req_usecs;
280             os_get_reltime(&now);
281             os_reltime_sub(&tmp->time, &now, &remaining);
282             if (os_reltime_before(&requested, &remaining)) {
283                 eloop_cancel_timeout(handler, eloop_data,
284                                      user_data);
285                 eloop_register_timeout(requested.sec,
286                                        requested.usec,
287                                        handler, eloop_data,
288                                        user_data);
289                 return 1;
290             }
291             return 0;
292         }
293     }
294 
295     return -1;
296 }
297 
eloop_replenish_timeout(unsigned int req_secs,unsigned int req_usecs,eloop_timeout_handler handler,void * eloop_data,void * user_data)298 int eloop_replenish_timeout(unsigned int req_secs, unsigned int req_usecs,
299                             eloop_timeout_handler handler, void *eloop_data,
300                             void *user_data)
301 {
302     struct os_reltime now, requested, remaining;
303     struct eloop_timeout *tmp;
304 
305     dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {
306         if (tmp->handler == handler &&
307                 tmp->eloop_data == eloop_data &&
308                 tmp->user_data == user_data) {
309             requested.sec = req_secs;
310             requested.usec = req_usecs;
311             os_get_reltime(&now);
312             os_reltime_sub(&tmp->time, &now, &remaining);
313             if (os_reltime_before(&remaining, &requested)) {
314                 eloop_cancel_timeout(handler, eloop_data,
315                                      user_data);
316                 eloop_register_timeout(requested.sec,
317                                        requested.usec,
318                                        handler, eloop_data,
319                                        user_data);
320                 return 1;
321             }
322             return 0;
323         }
324     }
325 
326     return -1;
327 }
328 
eloop_run(void)329 void eloop_run(void)
330 {
331     struct os_reltime tv, now;
332 
333     while (!dl_list_empty(&eloop.timeout)) {
334         struct eloop_timeout *timeout;
335 
336         timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
337                                 list);
338         if (timeout) {
339             os_get_reltime(&now);
340             if (os_reltime_before(&now, &timeout->time)) {
341                 /* we don't need to process it rn, do it later */
342                 uint32_t ms;
343                 os_reltime_sub(&timeout->time, &now, &tv);
344                 ms = tv.sec * 1000 + tv.usec / 1000;
345                 ELOOP_LOCK();
346                 os_timer_disarm(&eloop.eloop_timer);
347                 os_timer_arm(&eloop.eloop_timer, ms, 0);
348                 ELOOP_UNLOCK();
349                 goto out;
350             }
351         }
352 
353         /* check if some registered timeouts have occurred */
354         timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
355                                 list);
356         if (timeout) {
357             os_get_reltime(&now);
358             if (!os_reltime_before(&now, &timeout->time)) {
359                 void *eloop_data = timeout->eloop_data;
360                 void *user_data = timeout->user_data;
361                 eloop_timeout_handler handler =
362                     timeout->handler;
363 #ifdef ELOOP_DEBUG
364                 char fn_name[100] = {0};
365                 int line = timeout->line;
366                 os_strlcpy(fn_name, timeout->func_name, 100);
367 #endif
368                 eloop_remove_timeout(timeout);
369 #ifdef ELOOP_DEBUG
370                 wpa_printf(MSG_DEBUG, "ELOOP: Running timer fn:%p scheduled by %s:%d ",
371                            handler, fn_name, line);
372 #endif
373                 handler(eloop_data, user_data);
374             }
375         }
376     }
377 out:
378     return;
379 }
380 
eloop_destroy(void)381 void eloop_destroy(void)
382 {
383     struct eloop_timeout *timeout, *prev;
384     struct os_reltime now;
385 
386     if (!eloop.eloop_started) {
387         return;
388     }
389     os_get_reltime(&now);
390     dl_list_for_each_safe(timeout, prev, &eloop.timeout,
391                           struct eloop_timeout, list) {
392         int sec, usec;
393         sec = timeout->time.sec - now.sec;
394         usec = timeout->time.usec - now.usec;
395         if (timeout->time.usec < now.usec) {
396             sec--;
397             usec += 1000000;
398         }
399         wpa_printf(MSG_INFO, "ELOOP: remaining timeout: %d.%06d "
400                    "eloop_data=%p user_data=%p handler=%p",
401                    sec, usec, timeout->eloop_data, timeout->user_data,
402                    timeout->handler);
403         eloop_remove_timeout(timeout);
404     }
405     if (eloop_data_lock) {
406         os_mutex_delete(eloop_data_lock);
407         eloop_data_lock = NULL;
408     }
409     os_timer_disarm(&eloop.eloop_timer);
410     os_timer_done(&eloop.eloop_timer);
411     os_memset(&eloop, 0, sizeof(eloop));
412 }
413