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, ¤t_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