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  * 2023-09-20   zmq810150896   first version
9  */
10 
11 #include <rtthread.h>
12 #include <dfs_file.h>
13 #include <stdint.h>
14 #include <poll.h>
15 #include <sys/timerfd.h>
16 
17 #define DBG_TAG    "TIMERFD"
18 #define DBG_LVL    DBG_INFO
19 #include <rtdbg.h>
20 
21 #define INIT_PERIODIC 0
22 #define OPEN_PERIODIC 1
23 #define ENTER_PERIODIC 2
24 
25 #define SEC_TO_MSEC 1000
26 #define MSEC_TO_NSEC 1000000
27 #define SEC_TO_NSEC 1000000000
28 
29 #define TIME_INT32_MAX   0x7FFFFFFF
30 
31 #define TIMERFD_MUTEX_NAME "TIMERFD"
32 
33 #define TFD_SHARED_FCNTL_FLAGS (TFD_CLOEXEC | TFD_NONBLOCK)
34 
35 struct rt_timerfd
36 {
37     rt_wqueue_t timerfd_queue;
38     struct itimerspec ittimer;
39     rt_timer_t timer;
40     struct rt_mutex lock;
41     struct timespec pre_time;
42     rt_atomic_t timeout_num;
43     struct rt_wqueue_node wqn;
44     rt_atomic_t ticks;
45     int clockid;
46     int isperiodic;
47     int tick_out;
48 };
49 
50 static int timerfd_close(struct dfs_file *file);
51 static int timerfd_poll(struct dfs_file *file, struct rt_pollreq *req);
52 #ifndef RT_USING_DFS_V2
53 static ssize_t timerfd_read(struct dfs_file *file, void *buf, size_t count);
54 #else
55 static ssize_t timerfd_read(struct dfs_file *file, void *buf, size_t count, off_t *pos);
56 #endif
57 
58 static const struct dfs_file_ops timerfd_fops =
59 {
60     .close      = timerfd_close,
61     .poll       = timerfd_poll,
62     .read       = timerfd_read,
63 };
64 
timerfd_close(struct dfs_file * file)65 static int timerfd_close(struct dfs_file *file)
66 {
67     struct rt_timerfd *tfd;
68 
69     if (file->vnode->ref_count != 1)
70         return 0;
71 
72     tfd = file->vnode->data;
73 
74     if (tfd)
75     {
76         if (tfd->timer != RT_NULL)
77         {
78             rt_timer_stop(tfd->timer);
79             rt_timer_delete(tfd->timer);
80             tfd->timer = RT_NULL;
81         }
82 
83         if (tfd->wqn.wqueue)
84         {
85             rt_wqueue_remove(&tfd->wqn);
86         }
87 
88         rt_mutex_detach(&tfd->lock);
89         rt_free(tfd);
90     }
91 
92     return 0;
93 }
94 
timerfd_poll(struct dfs_file * file,struct rt_pollreq * req)95 static int timerfd_poll(struct dfs_file *file, struct rt_pollreq *req)
96 {
97     struct rt_timerfd *tfd;
98     int events = 0;
99 
100     tfd = file->vnode->data;
101 
102     rt_mutex_take(&tfd->lock, RT_WAITING_FOREVER);
103 
104     rt_poll_add(&tfd->timerfd_queue, req);
105 
106     rt_mutex_release(&tfd->lock);
107 
108     if (rt_atomic_load(&(tfd->ticks)) > 0)
109     {
110         events |= POLLIN;
111     }
112 
113     return events;
114 }
115 
116 #ifndef RT_USING_DFS_V2
timerfd_read(struct dfs_file * file,void * buf,size_t count)117 static ssize_t timerfd_read(struct dfs_file *file, void *buf, size_t count)
118 #else
119 static ssize_t timerfd_read(struct dfs_file *file, void *buf, size_t count, off_t *pos)
120 #endif
121 {
122     struct rt_timerfd *tfd;
123     rt_uint64_t *buffer;
124     int ret = 0;
125 
126     buffer = (rt_uint64_t *)buf;
127 
128     if (sizeof(buffer) > count)
129     {
130         rt_set_errno(EINVAL);
131         return -1;
132     }
133 
134     tfd = file->vnode->data;
135 
136     if (!tfd)
137     {
138         rt_set_errno(EINVAL);
139         return -1;
140     }
141 
142     if ((rt_atomic_load(&(tfd->ticks)) == 0) && (file->flags & O_NONBLOCK))
143     {
144         rt_set_errno(EAGAIN);
145         return -EAGAIN;
146     }
147     else
148     {
149         if (rt_atomic_load(&(tfd->ticks)) == 0)
150         {
151             tfd->wqn.polling_thread = rt_thread_self();
152 
153             if (tfd->wqn.wqueue)
154             {
155                 rt_wqueue_remove(&tfd->wqn);
156             }
157             rt_wqueue_add(&tfd->timerfd_queue, &tfd->wqn);
158 
159             ret = rt_thread_suspend_with_flag(tfd->wqn.polling_thread, RT_INTERRUPTIBLE);
160             if (ret == RT_EOK)
161             {
162                 rt_schedule();
163             }
164             else
165             {
166                 return ret;
167             }
168         }
169 
170         (*buffer) = rt_atomic_load(&(tfd->timeout_num));
171         rt_atomic_store(&(tfd->timeout_num), 0);
172         rt_atomic_store(&(tfd->ticks), 0);
173     }
174 
175     return sizeof(buffer);
176 }
177 
timerfd_wqueue_callback(struct rt_wqueue_node * wait,void * key)178 static int timerfd_wqueue_callback(struct rt_wqueue_node *wait, void *key)
179 {
180     return 0;
181 }
182 
timerfd_do_create(int clockid,int flags)183 static int timerfd_do_create(int clockid, int flags)
184 {
185     struct rt_timerfd *tfd = RT_NULL;
186     struct dfs_file *df;
187     rt_err_t ret = -1;
188     int fd = -1;
189 
190     if ((flags & ~TFD_SHARED_FCNTL_FLAGS) ||
191         (clockid != CLOCK_MONOTONIC &&
192          clockid != CLOCK_REALTIME &&
193          clockid != CLOCK_REALTIME_ALARM &&
194          clockid != CLOCK_BOOTTIME &&
195          clockid != CLOCK_BOOTTIME_ALARM))
196     {
197         rt_set_errno(EINVAL);
198         return -1;
199     }
200 
201     if ((clockid == CLOCK_REALTIME_ALARM ||
202          clockid == CLOCK_BOOTTIME_ALARM))
203     {
204         rt_set_errno(EPERM);
205         return -1;
206     }
207 
208     fd = fd_new();
209     if (fd < 0)
210     {
211         rt_set_errno(EINVAL);
212         return -1;
213     }
214 
215     ret = fd;
216     df = fd_get(fd);
217 
218     if (df)
219     {
220         df->flags |= flags;
221 
222         tfd = (struct rt_timerfd *)rt_calloc(1, sizeof(struct rt_timerfd));
223 
224         if (tfd)
225         {
226             rt_mutex_init(&tfd->lock, TIMERFD_MUTEX_NAME, RT_IPC_FLAG_FIFO);
227             rt_wqueue_init(&tfd->timerfd_queue);
228 
229             tfd->isperiodic = INIT_PERIODIC;
230             tfd->ticks = 0;
231             tfd->timeout_num = 0;
232             tfd->tick_out = 0;
233             tfd->clockid = clockid;
234             tfd->timer = RT_NULL;
235             tfd->pre_time.tv_sec = 0;
236             tfd->pre_time.tv_nsec = 0;
237             tfd->wqn.polling_thread = rt_thread_self();
238             rt_list_init(&(tfd->wqn.list));
239             tfd->wqn.wakeup = timerfd_wqueue_callback;
240 
241             df->vnode = (struct dfs_vnode *)rt_malloc(sizeof(struct dfs_vnode));
242             if (df->vnode)
243             {
244                 dfs_vnode_init(df->vnode, FT_REGULAR, &timerfd_fops);
245                 df->vnode->data = tfd;
246 
247 #ifdef RT_USING_DFS_V2
248                 df->fops = &timerfd_fops;
249 #endif
250             }
251             else
252             {
253                 rt_free(tfd);
254                 fd_release(fd);
255                 rt_set_errno(ENOMEM);
256                 ret = -1;
257             }
258         }
259         else
260         {
261             fd_release(fd);
262             rt_set_errno(ENOMEM);
263             ret = -1;
264         }
265     }
266     else
267     {
268         fd_release(fd);
269         ret = -1;
270     }
271 
272     return ret;
273 }
274 
get_current_time(struct rt_timerfd * tfd,struct timespec * time)275 static int get_current_time(struct rt_timerfd *tfd, struct timespec *time)
276 {
277     int ret = 0;
278     struct timespec *cur_time = RT_NULL;
279 
280     if (time == RT_NULL)
281     {
282         cur_time = &tfd->pre_time;
283     }
284     else
285     {
286         cur_time = time;
287     }
288 
289     if (tfd->clockid >= 0)
290     {
291         ret = clock_gettime(tfd->clockid, cur_time);
292     }
293     else
294     {
295         ret = clock_gettime(CLOCK_MONOTONIC, cur_time);
296     }
297 
298     return ret;
299 }
300 
timerfd_timeout(void * parameter)301 static void timerfd_timeout(void *parameter)
302 {
303     struct rt_timerfd *tfd = RT_NULL;
304 
305     tfd = (struct rt_timerfd *)parameter;
306 
307     if (tfd == RT_NULL)
308     {
309         return ;
310     }
311 
312     rt_wqueue_wakeup(&tfd->timerfd_queue, (void *)POLLIN);
313 
314     rt_atomic_store(&(tfd->ticks), 1);
315     rt_atomic_add(&(tfd->timeout_num), 1);
316 
317     rt_mutex_take(&tfd->lock, RT_WAITING_FOREVER);
318 
319     get_current_time(tfd, RT_NULL);
320     if (tfd->isperiodic == OPEN_PERIODIC)
321     {
322         if (tfd->timer)
323         {
324             rt_timer_stop(tfd->timer);
325             rt_timer_delete(tfd->timer);
326             tfd->timer = RT_NULL;
327         }
328         tfd->isperiodic = ENTER_PERIODIC;
329         tfd->timer = rt_timer_create(TIMERFD_MUTEX_NAME, timerfd_timeout,
330                         tfd, tfd->tick_out,
331                         RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
332 
333         if (tfd->timer == RT_NULL)
334         {
335             LOG_E("rt_timer_create fail \n");
336             rt_mutex_release(&tfd->lock);
337             return ;
338         }
339         rt_timer_start(tfd->timer);
340     }
341 
342     rt_mutex_release(&tfd->lock);
343 }
344 
timerfd_time_operation(time_t * sec,long * nsec)345 static void timerfd_time_operation(time_t *sec, long *nsec)
346 {
347     if (*nsec < 0)
348     {
349         if (*sec > 0)
350         {
351             *sec -= 1;
352             *nsec = 1 * SEC_TO_NSEC + *nsec;
353         }
354     }
355 
356     if (*sec < 0 || *nsec < 0)
357     {
358         *sec = 0;
359         *nsec = 0;
360     }
361 }
362 
timerfd_do_settime(int fd,int flags,const struct itimerspec * new,struct itimerspec * old)363 static int timerfd_do_settime(int fd, int flags, const struct itimerspec *new, struct itimerspec *old)
364 {
365     int ret = 0;
366     struct rt_timerfd *tfd;
367     struct dfs_file *df;
368     struct timespec current_time;
369     int tick_out;
370     rt_int64_t value_msec;
371     rt_int64_t interval_msec;
372     rt_int64_t cur_time = 0;
373 
374     if (fd < 0)
375     {
376         rt_set_errno(EINVAL);
377         return -EINVAL;
378     }
379 
380     df = fd_get(fd);
381     if (!df)
382         return -EINVAL;
383 
384     tfd = df->vnode->data;
385 
386     rt_atomic_store(&(tfd->ticks), 0);
387     rt_atomic_store(&(tfd->timeout_num), 0);
388 
389     rt_mutex_take(&tfd->lock, RT_WAITING_FOREVER);
390     tfd->isperiodic = INIT_PERIODIC;
391 
392     if (old)
393     {
394         old->it_interval.tv_nsec = tfd->ittimer.it_interval.tv_nsec;
395         old->it_interval.tv_sec = tfd->ittimer.it_interval.tv_sec;
396         old->it_value.tv_nsec = tfd->ittimer.it_value.tv_nsec;
397         old->it_value.tv_sec = tfd->ittimer.it_value.tv_sec;
398     }
399 
400     if (new)
401     {
402         if (tfd->timer != RT_NULL)
403         {
404             rt_timer_stop(tfd->timer);
405             rt_timer_delete(tfd->timer);
406             tfd->timer = RT_NULL;
407         }
408 
409         if (new->it_value.tv_nsec == 0 && new->it_value.tv_sec == 0)
410         {
411             rt_mutex_release(&tfd->lock);
412             return 0;
413         }
414 
415         value_msec = (new->it_value.tv_nsec / MSEC_TO_NSEC) + (new->it_value.tv_sec * SEC_TO_MSEC);
416         interval_msec = (new->it_interval.tv_nsec / MSEC_TO_NSEC) + (new->it_interval.tv_sec * SEC_TO_MSEC);
417 
418         current_time.tv_nsec = 0;
419         current_time.tv_sec = 0;
420 
421         if (flags == TFD_TIMER_ABSTIME)
422         {
423             ret = get_current_time(tfd, &current_time);
424 
425             if (ret < 0)
426             {
427                 rt_mutex_release(&tfd->lock);
428                 return ret;
429             }
430 
431             cur_time = current_time.tv_sec * SEC_TO_MSEC + (current_time.tv_nsec / MSEC_TO_NSEC);
432             value_msec = value_msec - cur_time;
433         }
434 
435         tfd->ittimer.it_interval.tv_nsec = new->it_interval.tv_nsec;
436         tfd->ittimer.it_interval.tv_sec = new->it_interval.tv_sec;
437         tfd->ittimer.it_value.tv_sec = new->it_value.tv_sec - current_time.tv_sec;
438         tfd->ittimer.it_value.tv_nsec = new->it_value.tv_nsec - current_time.tv_nsec;
439         timerfd_time_operation(&tfd->ittimer.it_value.tv_sec, &tfd->ittimer.it_value.tv_nsec);
440 
441         if ((interval_msec > 0) && (interval_msec <= TIME_INT32_MAX))
442         {
443             tfd->tick_out = rt_tick_from_millisecond(interval_msec);
444             if (tfd->tick_out < 0)
445             {
446                 rt_mutex_release(&tfd->lock);
447                 return -EINVAL;
448             }
449             tfd->isperiodic = OPEN_PERIODIC;
450         }
451 
452         get_current_time(tfd, RT_NULL);
453 
454         if (value_msec > 0)
455         {
456             if (value_msec > TIME_INT32_MAX)
457             {
458                 rt_mutex_release(&tfd->lock);
459                 return -EINVAL;
460             }
461 
462             tick_out = rt_tick_from_millisecond(value_msec);
463             if (tick_out < 0)
464             {
465                 rt_mutex_release(&tfd->lock);
466                 return -EINVAL;
467             }
468             tfd->timer = rt_timer_create(TIMERFD_MUTEX_NAME, timerfd_timeout,
469                             tfd, tick_out,
470                             RT_TIMER_FLAG_ONE_SHOT | RT_TIMER_FLAG_SOFT_TIMER);
471             if (tfd->timer == RT_NULL)
472             {
473                 LOG_E("rt_timer_create fail \n");
474                 rt_mutex_release(&tfd->lock);
475                 return -ENOMEM;
476             }
477             rt_timer_start(tfd->timer);
478         }
479         else
480         {
481             timerfd_timeout(tfd);
482         }
483     }
484     else
485     {
486         rt_set_errno(EINVAL);
487         ret = -1;
488     }
489 
490     rt_mutex_release(&tfd->lock);
491 
492     return ret;
493 }
494 
timerfd_do_gettime(int fd,struct itimerspec * cur)495 static int timerfd_do_gettime(int fd, struct itimerspec *cur)
496 {
497     struct rt_timerfd *tfd;
498     struct dfs_file *df = RT_NULL;
499     struct timespec cur_time;
500     rt_int64_t tv_sec = 0;
501     rt_int64_t tv_nsec = 0;
502 
503     df = fd_get(fd);
504 
505     if (df == RT_NULL)
506     {
507         rt_set_errno(EINVAL);
508         return -1;
509     }
510 
511     tfd = df->vnode->data;
512 
513     get_current_time(tfd, &cur_time);
514 
515     rt_mutex_take(&tfd->lock, RT_WAITING_FOREVER);
516 
517     tv_sec = cur_time.tv_sec - tfd->pre_time.tv_sec;
518     tv_nsec = cur_time.tv_nsec - tfd->pre_time.tv_nsec;
519     timerfd_time_operation(&tv_sec, &tv_nsec);
520     cur->it_interval.tv_nsec = tfd->ittimer.it_interval.tv_nsec;
521     cur->it_interval.tv_sec = tfd->ittimer.it_interval.tv_sec;
522 
523     if (tfd->isperiodic == ENTER_PERIODIC)
524     {
525         cur->it_value.tv_nsec = tfd->ittimer.it_interval.tv_nsec - tv_nsec;
526         cur->it_value.tv_sec = tfd->ittimer.it_interval.tv_sec - tv_sec;
527         timerfd_time_operation(&cur->it_value.tv_sec, &cur->it_value.tv_nsec);
528     }
529     else
530     {
531         if (rt_atomic_load(&(tfd->timeout_num)) == 1)
532         {
533             cur->it_value.tv_nsec = 0;
534             cur->it_value.tv_sec = 0;
535         }
536         else
537         {
538             cur->it_value.tv_nsec = tfd->ittimer.it_value.tv_nsec - tv_nsec;
539             cur->it_value.tv_sec = tfd->ittimer.it_value.tv_sec - tv_sec;
540             timerfd_time_operation(&cur->it_value.tv_sec, &cur->it_value.tv_nsec);
541         }
542     }
543 
544     rt_mutex_release(&tfd->lock);
545 
546     return 0;
547 }
548 
549 /**
550  * @brief Creates a file descriptor for a timer.
551  *
552  * The `timerfd_create` function creates a new timer object that generates
553  * timer expiration notifications via a file descriptor.
554  *
555  * @param clockid The clock ID that specifies the clock to be used as the
556  *                timing base for the timer. Common values include:
557  *                - `CLOCK_REALTIME`: A system-wide clock representing
558  *                  wall-clock time.
559  *                - `CLOCK_MONOTONIC`: A clock that cannot be set and
560  *                  represents monotonic time since some unspecified
561  *                  starting point.
562  * @param flags   A bitmask that can include the following flags:
563  *                - `TFD_CLOEXEC`: Close the file descriptor on `execve`.
564  *                - `TFD_NONBLOCK`: Set the file descriptor to non-blocking mode.
565  *
566  * @return On success, returns a file descriptor for the timer. On error,
567  *         returns -1 and sets `errno` appropriately.
568  *
569  * @note The file descriptor can be used with select, poll, or epoll to wait
570  *       for timer expirations.
571  *
572  * @warning The timerfd interface is Linux-specific and may not be available
573  *          on other operating systems.
574  *
575  * @see timerfd_settime, timerfd_gettime
576  */
timerfd_create(int clockid,int flags)577 int timerfd_create(int clockid, int flags)
578 {
579     return timerfd_do_create(clockid, flags);
580 }
581 
582 /**
583  * @brief Sets the time for a timer file descriptor.
584  *
585  * The `timerfd_settime` function starts or modifies the timer associated
586  * with the specified timer file descriptor.
587  *
588  * @param fd      The file descriptor of the timer, obtained from
589  *                `timerfd_create`.
590  * @param flags   Flags that control the behavior of the timer. Possible
591  *                values include:
592  *                - `0`: Relative time is specified in `new`.
593  *                - `TFD_TIMER_ABSTIME`: Use absolute time instead of
594  *                  relative time.
595  * @param new     A pointer to a `itimerspec` structure that specifies the
596  *                new timer settings:
597  *                - `it_value`: The initial expiration time. A zero value
598  *                  means the timer is disabled.
599  *                - `it_interval`: The interval for periodic timers. A zero
600  *                  value means the timer is not periodic.
601  * @param old     A pointer to a `itimerspec` structure to store the
602  *                previous timer settings. Can be `NULL` if this information
603  *                is not needed.
604  *
605  * @return On success, returns 0. On error, returns -1 and sets `errno`
606  *         appropriately.
607  *
608  * @note The timer starts counting down immediately after this call if
609  *       `it_value` is non-zero.
610  *
611  * @warning If the timer is set to a very short interval, high-frequency
612  *          events may impact system performance.
613  *
614  * @see timerfd_create, timerfd_gettime
615  */
timerfd_settime(int fd,int flags,const struct itimerspec * new,struct itimerspec * old)616 int timerfd_settime(int fd, int flags, const struct itimerspec *new, struct itimerspec *old)
617 {
618     return timerfd_do_settime(fd, flags, new, old);
619 }
620 
621 /**
622  * @brief Retrieves the current value and interval of a timer.
623  *
624  * The `timerfd_gettime` function queries the settings of the timer associated
625  * with the specified timer file descriptor.
626  *
627  * @param fd   The file descriptor of the timer, obtained from `timerfd_create`.
628  * @param cur  A pointer to a `itimerspec` structure where the current timer
629  *             settings will be stored:
630  *             - `it_value`: The time remaining until the next expiration.
631  *               If zero, the timer is disabled.
632  *             - `it_interval`: The interval for periodic timers. Zero if the
633  *               timer is not periodic.
634  *
635  * @return On success, returns 0. On error, returns -1 and sets `errno`
636  *         appropriately.
637  *
638  * @note This function does not reset or modify the timer; it only retrieves
639  *       the current settings.
640  *
641  * @see timerfd_create, timerfd_settime
642  */
timerfd_gettime(int fd,struct itimerspec * cur)643 int timerfd_gettime(int fd, struct itimerspec *cur)
644 {
645     return timerfd_do_gettime(fd, cur);
646 }
647