aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavide Libenzi <davidel@xmailserver.org>2007-05-11 01:23:16 -0400
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-05-11 11:29:36 -0400
commitb215e283992899650c4271e7385c79e26fb9a88e (patch)
tree3f950814510422606821f1b0b373d65e4d9ed303
parent6d18c9220965b437287c3a7e803725c24992ceac (diff)
signal/timer/event: timerfd core
This patch introduces a new system call for timers events delivered though file descriptors. This allows timer event to be used with standard POSIX poll(2), select(2) and read(2). As a consequence of supporting the Linux f_op->poll subsystem, they can be used with epoll(2) too. The system call is defined as: int timerfd(int ufd, int clockid, int flags, const struct itimerspec *utmr); The "ufd" parameter allows for re-use (re-programming) of an existing timerfd w/out going through the close/open cycle (same as signalfd). If "ufd" is -1, s new file descriptor will be created, otherwise the existing "ufd" will be re-programmed. The "clockid" parameter is either CLOCK_MONOTONIC or CLOCK_REALTIME. The time specified in the "utmr->it_value" parameter is the expiry time for the timer. If the TFD_TIMER_ABSTIME flag is set in "flags", this is an absolute time, otherwise it's a relative time. If the time specified in the "utmr->it_interval" is not zero (.tv_sec == 0, tv_nsec == 0), this is the period at which the following ticks should be generated. The "utmr->it_interval" should be set to zero if only one tick is requested. Setting the "utmr->it_value" to zero will disable the timer, or will create a timerfd without the timer enabled. The function returns the new (or same, in case "ufd" is a valid timerfd descriptor) file, or -1 in case of error. As stated before, the timerfd file descriptor supports poll(2), select(2) and epoll(2). When a timer event happened on the timerfd, a POLLIN mask will be returned. The read(2) call can be used, and it will return a u32 variable holding the number of "ticks" that happened on the interface since the last call to read(2). The read(2) call supportes the O_NONBLOCK flag too, and EAGAIN will be returned if no ticks happened. A quick test program, shows timerfd working correctly on my amd64 box: http://www.xmailserver.org/timerfd-test.c [akpm@linux-foundation.org: add sys_timerfd to sys_ni.c] Signed-off-by: Davide Libenzi <davidel@xmailserver.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--fs/Makefile1
-rw-r--r--fs/timerfd.c227
-rw-r--r--include/linux/syscalls.h2
-rw-r--r--include/linux/timerfd.h17
-rw-r--r--init/Kconfig10
-rw-r--r--kernel/sys_ni.c1
6 files changed, 258 insertions, 0 deletions
diff --git a/fs/Makefile b/fs/Makefile
index cd8a57aeac04..39625da9e2d6 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_INOTIFY_USER) += inotify_user.o
24obj-$(CONFIG_EPOLL) += eventpoll.o 24obj-$(CONFIG_EPOLL) += eventpoll.o
25obj-$(CONFIG_ANON_INODES) += anon_inodes.o 25obj-$(CONFIG_ANON_INODES) += anon_inodes.o
26obj-$(CONFIG_SIGNALFD) += signalfd.o 26obj-$(CONFIG_SIGNALFD) += signalfd.o
27obj-$(CONFIG_TIMERFD) += timerfd.o
27obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o 28obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o
28 29
29nfsd-$(CONFIG_NFSD) := nfsctl.o 30nfsd-$(CONFIG_NFSD) := nfsctl.o
diff --git a/fs/timerfd.c b/fs/timerfd.c
new file mode 100644
index 000000000000..e329e37f15a8
--- /dev/null
+++ b/fs/timerfd.c
@@ -0,0 +1,227 @@
1/*
2 * fs/timerfd.c
3 *
4 * Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org>
5 *
6 *
7 * Thanks to Thomas Gleixner for code reviews and useful comments.
8 *
9 */
10
11#include <linux/file.h>
12#include <linux/poll.h>
13#include <linux/init.h>
14#include <linux/fs.h>
15#include <linux/sched.h>
16#include <linux/kernel.h>
17#include <linux/list.h>
18#include <linux/spinlock.h>
19#include <linux/time.h>
20#include <linux/hrtimer.h>
21#include <linux/anon_inodes.h>
22#include <linux/timerfd.h>
23
24struct timerfd_ctx {
25 struct hrtimer tmr;
26 ktime_t tintv;
27 spinlock_t lock;
28 wait_queue_head_t wqh;
29 int expired;
30};
31
32/*
33 * This gets called when the timer event triggers. We set the "expired"
34 * flag, but we do not re-arm the timer (in case it's necessary,
35 * tintv.tv64 != 0) until the timer is read.
36 */
37static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr)
38{
39 struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, tmr);
40 unsigned long flags;
41
42 spin_lock_irqsave(&ctx->lock, flags);
43 ctx->expired = 1;
44 wake_up_locked(&ctx->wqh);
45 spin_unlock_irqrestore(&ctx->lock, flags);
46
47 return HRTIMER_NORESTART;
48}
49
50static void timerfd_setup(struct timerfd_ctx *ctx, int clockid, int flags,
51 const struct itimerspec *ktmr)
52{
53 enum hrtimer_mode htmode;
54 ktime_t texp;
55
56 htmode = (flags & TFD_TIMER_ABSTIME) ?
57 HRTIMER_MODE_ABS: HRTIMER_MODE_REL;
58
59 texp = timespec_to_ktime(ktmr->it_value);
60 ctx->expired = 0;
61 ctx->tintv = timespec_to_ktime(ktmr->it_interval);
62 hrtimer_init(&ctx->tmr, clockid, htmode);
63 ctx->tmr.expires = texp;
64 ctx->tmr.function = timerfd_tmrproc;
65 if (texp.tv64 != 0)
66 hrtimer_start(&ctx->tmr, texp, htmode);
67}
68
69static int timerfd_release(struct inode *inode, struct file *file)
70{
71 struct timerfd_ctx *ctx = file->private_data;
72
73 hrtimer_cancel(&ctx->tmr);
74 kfree(ctx);
75 return 0;
76}
77
78static unsigned int timerfd_poll(struct file *file, poll_table *wait)
79{
80 struct timerfd_ctx *ctx = file->private_data;
81 unsigned int events = 0;
82 unsigned long flags;
83
84 poll_wait(file, &ctx->wqh, wait);
85
86 spin_lock_irqsave(&ctx->lock, flags);
87 if (ctx->expired)
88 events |= POLLIN;
89 spin_unlock_irqrestore(&ctx->lock, flags);
90
91 return events;
92}
93
94static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count,
95 loff_t *ppos)
96{
97 struct timerfd_ctx *ctx = file->private_data;
98 ssize_t res;
99 u32 ticks = 0;
100 DECLARE_WAITQUEUE(wait, current);
101
102 if (count < sizeof(ticks))
103 return -EINVAL;
104 spin_lock_irq(&ctx->lock);
105 res = -EAGAIN;
106 if (!ctx->expired && !(file->f_flags & O_NONBLOCK)) {
107 __add_wait_queue(&ctx->wqh, &wait);
108 for (res = 0;;) {
109 set_current_state(TASK_INTERRUPTIBLE);
110 if (ctx->expired) {
111 res = 0;
112 break;
113 }
114 if (signal_pending(current)) {
115 res = -ERESTARTSYS;
116 break;
117 }
118 spin_unlock_irq(&ctx->lock);
119 schedule();
120 spin_lock_irq(&ctx->lock);
121 }
122 __remove_wait_queue(&ctx->wqh, &wait);
123 __set_current_state(TASK_RUNNING);
124 }
125 if (ctx->expired) {
126 ctx->expired = 0;
127 if (ctx->tintv.tv64 != 0) {
128 /*
129 * If tintv.tv64 != 0, this is a periodic timer that
130 * needs to be re-armed. We avoid doing it in the timer
131 * callback to avoid DoS attacks specifying a very
132 * short timer period.
133 */
134 ticks = (u32)
135 hrtimer_forward(&ctx->tmr,
136 hrtimer_cb_get_time(&ctx->tmr),
137 ctx->tintv);
138 hrtimer_restart(&ctx->tmr);
139 } else
140 ticks = 1;
141 }
142 spin_unlock_irq(&ctx->lock);
143 if (ticks)
144 res = put_user(ticks, buf) ? -EFAULT: sizeof(ticks);
145 return res;
146}
147
148static const struct file_operations timerfd_fops = {
149 .release = timerfd_release,
150 .poll = timerfd_poll,
151 .read = timerfd_read,
152};
153
154asmlinkage long sys_timerfd(int ufd, int clockid, int flags,
155 const struct itimerspec __user *utmr)
156{
157 int error;
158 struct timerfd_ctx *ctx;
159 struct file *file;
160 struct inode *inode;
161 struct itimerspec ktmr;
162
163 if (copy_from_user(&ktmr, utmr, sizeof(ktmr)))
164 return -EFAULT;
165
166 if (clockid != CLOCK_MONOTONIC &&
167 clockid != CLOCK_REALTIME)
168 return -EINVAL;
169 if (!timespec_valid(&ktmr.it_value) ||
170 !timespec_valid(&ktmr.it_interval))
171 return -EINVAL;
172
173 if (ufd == -1) {
174 ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
175 if (!ctx)
176 return -ENOMEM;
177
178 init_waitqueue_head(&ctx->wqh);
179 spin_lock_init(&ctx->lock);
180
181 timerfd_setup(ctx, clockid, flags, &ktmr);
182
183 /*
184 * When we call this, the initialization must be complete, since
185 * anon_inode_getfd() will install the fd.
186 */
187 error = anon_inode_getfd(&ufd, &inode, &file, "[timerfd]",
188 &timerfd_fops, ctx);
189 if (error)
190 goto err_tmrcancel;
191 } else {
192 file = fget(ufd);
193 if (!file)
194 return -EBADF;
195 ctx = file->private_data;
196 if (file->f_op != &timerfd_fops) {
197 fput(file);
198 return -EINVAL;
199 }
200 /*
201 * We need to stop the existing timer before reprogramming
202 * it to the new values.
203 */
204 for (;;) {
205 spin_lock_irq(&ctx->lock);
206 if (hrtimer_try_to_cancel(&ctx->tmr) >= 0)
207 break;
208 spin_unlock_irq(&ctx->lock);
209 cpu_relax();
210 }
211 /*
212 * Re-program the timer to the new value ...
213 */
214 timerfd_setup(ctx, clockid, flags, &ktmr);
215
216 spin_unlock_irq(&ctx->lock);
217 fput(file);
218 }
219
220 return ufd;
221
222err_tmrcancel:
223 hrtimer_cancel(&ctx->tmr);
224 kfree(ctx);
225 return error;
226}
227
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index e049f14a75b7..fc637be1d9cf 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -605,6 +605,8 @@ asmlinkage long sys_set_robust_list(struct robust_list_head __user *head,
605 size_t len); 605 size_t len);
606asmlinkage long sys_getcpu(unsigned __user *cpu, unsigned __user *node, struct getcpu_cache __user *cache); 606asmlinkage long sys_getcpu(unsigned __user *cpu, unsigned __user *node, struct getcpu_cache __user *cache);
607asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask); 607asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask);
608asmlinkage long sys_timerfd(int ufd, int clockid, int flags,
609 const struct itimerspec __user *utmr);
608 610
609int kernel_execve(const char *filename, char *const argv[], char *const envp[]); 611int kernel_execve(const char *filename, char *const argv[], char *const envp[]);
610 612
diff --git a/include/linux/timerfd.h b/include/linux/timerfd.h
new file mode 100644
index 000000000000..cf2b10d75731
--- /dev/null
+++ b/include/linux/timerfd.h
@@ -0,0 +1,17 @@
1/*
2 * include/linux/timerfd.h
3 *
4 * Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org>
5 *
6 */
7
8#ifndef _LINUX_TIMERFD_H
9#define _LINUX_TIMERFD_H
10
11
12#define TFD_TIMER_ABSTIME (1 << 0)
13
14
15
16#endif /* _LINUX_TIMERFD_H */
17
diff --git a/init/Kconfig b/init/Kconfig
index db707204b751..02c167de9646 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -502,6 +502,16 @@ config SIGNALFD
502 502
503 If unsure, say Y. 503 If unsure, say Y.
504 504
505config TIMERFD
506 bool "Enable timerfd() system call" if EMBEDDED
507 depends on ANON_INODES
508 default y
509 help
510 Enable the timerfd() system call that allows to receive timer
511 events on a file descriptor.
512
513 If unsure, say Y.
514
505config SHMEM 515config SHMEM
506 bool "Use full shmem filesystem" if EMBEDDED 516 bool "Use full shmem filesystem" if EMBEDDED
507 default y 517 default y
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 807e9bb8fcdb..b18f62549515 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -144,3 +144,4 @@ cond_syscall(sys_ioprio_get);
144 144
145/* New file descriptors */ 145/* New file descriptors */
146cond_syscall(sys_signalfd); 146cond_syscall(sys_signalfd);
147cond_syscall(sys_timerfd);