diff options
author | Todd Poynor <toddpoynor@google.com> | 2013-05-15 17:38:12 -0400 |
---|---|---|
committer | John Stultz <john.stultz@linaro.org> | 2013-05-29 15:57:34 -0400 |
commit | 11ffa9d6065f344a9bd769a2452f26f2f671e5f8 (patch) | |
tree | 8c790f1f8203d797b0b77ca0e4e52da2536796ef /fs | |
parent | 6cffe00f7d4e24679eae6b7aae4caaf915288256 (diff) |
timerfd: Add alarm timers
Add support for clocks CLOCK_REALTIME_ALARM and CLOCK_BOOTTIME_ALARM,
thereby enabling wakeup alarm timers via file descriptors.
Signed-off-by: Todd Poynor <toddpoynor@google.com>
Signed-off-by: John Stultz <john.stultz@linaro.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/timerfd.c | 131 |
1 files changed, 108 insertions, 23 deletions
diff --git a/fs/timerfd.c b/fs/timerfd.c index 32b644f03690..929312180dd0 100644 --- a/fs/timerfd.c +++ b/fs/timerfd.c | |||
@@ -8,6 +8,7 @@ | |||
8 | * | 8 | * |
9 | */ | 9 | */ |
10 | 10 | ||
11 | #include <linux/alarmtimer.h> | ||
11 | #include <linux/file.h> | 12 | #include <linux/file.h> |
12 | #include <linux/poll.h> | 13 | #include <linux/poll.h> |
13 | #include <linux/init.h> | 14 | #include <linux/init.h> |
@@ -26,7 +27,10 @@ | |||
26 | #include <linux/rcupdate.h> | 27 | #include <linux/rcupdate.h> |
27 | 28 | ||
28 | struct timerfd_ctx { | 29 | struct timerfd_ctx { |
29 | struct hrtimer tmr; | 30 | union { |
31 | struct hrtimer tmr; | ||
32 | struct alarm alarm; | ||
33 | } t; | ||
30 | ktime_t tintv; | 34 | ktime_t tintv; |
31 | ktime_t moffs; | 35 | ktime_t moffs; |
32 | wait_queue_head_t wqh; | 36 | wait_queue_head_t wqh; |
@@ -41,14 +45,19 @@ struct timerfd_ctx { | |||
41 | static LIST_HEAD(cancel_list); | 45 | static LIST_HEAD(cancel_list); |
42 | static DEFINE_SPINLOCK(cancel_lock); | 46 | static DEFINE_SPINLOCK(cancel_lock); |
43 | 47 | ||
48 | static inline bool isalarm(struct timerfd_ctx *ctx) | ||
49 | { | ||
50 | return ctx->clockid == CLOCK_REALTIME_ALARM || | ||
51 | ctx->clockid == CLOCK_BOOTTIME_ALARM; | ||
52 | } | ||
53 | |||
44 | /* | 54 | /* |
45 | * This gets called when the timer event triggers. We set the "expired" | 55 | * This gets called when the timer event triggers. We set the "expired" |
46 | * flag, but we do not re-arm the timer (in case it's necessary, | 56 | * flag, but we do not re-arm the timer (in case it's necessary, |
47 | * tintv.tv64 != 0) until the timer is accessed. | 57 | * tintv.tv64 != 0) until the timer is accessed. |
48 | */ | 58 | */ |
49 | static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) | 59 | static void timerfd_triggered(struct timerfd_ctx *ctx) |
50 | { | 60 | { |
51 | struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, tmr); | ||
52 | unsigned long flags; | 61 | unsigned long flags; |
53 | 62 | ||
54 | spin_lock_irqsave(&ctx->wqh.lock, flags); | 63 | spin_lock_irqsave(&ctx->wqh.lock, flags); |
@@ -56,10 +65,25 @@ static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) | |||
56 | ctx->ticks++; | 65 | ctx->ticks++; |
57 | wake_up_locked(&ctx->wqh); | 66 | wake_up_locked(&ctx->wqh); |
58 | spin_unlock_irqrestore(&ctx->wqh.lock, flags); | 67 | spin_unlock_irqrestore(&ctx->wqh.lock, flags); |
68 | } | ||
59 | 69 | ||
70 | static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) | ||
71 | { | ||
72 | struct timerfd_ctx *ctx = container_of(htmr, struct timerfd_ctx, | ||
73 | t.tmr); | ||
74 | timerfd_triggered(ctx); | ||
60 | return HRTIMER_NORESTART; | 75 | return HRTIMER_NORESTART; |
61 | } | 76 | } |
62 | 77 | ||
78 | static enum alarmtimer_restart timerfd_alarmproc(struct alarm *alarm, | ||
79 | ktime_t now) | ||
80 | { | ||
81 | struct timerfd_ctx *ctx = container_of(alarm, struct timerfd_ctx, | ||
82 | t.alarm); | ||
83 | timerfd_triggered(ctx); | ||
84 | return ALARMTIMER_NORESTART; | ||
85 | } | ||
86 | |||
63 | /* | 87 | /* |
64 | * Called when the clock was set to cancel the timers in the cancel | 88 | * Called when the clock was set to cancel the timers in the cancel |
65 | * list. This will wake up processes waiting on these timers. The | 89 | * list. This will wake up processes waiting on these timers. The |
@@ -107,8 +131,9 @@ static bool timerfd_canceled(struct timerfd_ctx *ctx) | |||
107 | 131 | ||
108 | static void timerfd_setup_cancel(struct timerfd_ctx *ctx, int flags) | 132 | static void timerfd_setup_cancel(struct timerfd_ctx *ctx, int flags) |
109 | { | 133 | { |
110 | if (ctx->clockid == CLOCK_REALTIME && (flags & TFD_TIMER_ABSTIME) && | 134 | if ((ctx->clockid == CLOCK_REALTIME || |
111 | (flags & TFD_TIMER_CANCEL_ON_SET)) { | 135 | ctx->clockid == CLOCK_REALTIME_ALARM) && |
136 | (flags & TFD_TIMER_ABSTIME) && (flags & TFD_TIMER_CANCEL_ON_SET)) { | ||
112 | if (!ctx->might_cancel) { | 137 | if (!ctx->might_cancel) { |
113 | ctx->might_cancel = true; | 138 | ctx->might_cancel = true; |
114 | spin_lock(&cancel_lock); | 139 | spin_lock(&cancel_lock); |
@@ -124,7 +149,11 @@ static ktime_t timerfd_get_remaining(struct timerfd_ctx *ctx) | |||
124 | { | 149 | { |
125 | ktime_t remaining; | 150 | ktime_t remaining; |
126 | 151 | ||
127 | remaining = hrtimer_expires_remaining(&ctx->tmr); | 152 | if (isalarm(ctx)) |
153 | remaining = alarm_expires_remaining(&ctx->t.alarm); | ||
154 | else | ||
155 | remaining = hrtimer_expires_remaining(&ctx->t.tmr); | ||
156 | |||
128 | return remaining.tv64 < 0 ? ktime_set(0, 0): remaining; | 157 | return remaining.tv64 < 0 ? ktime_set(0, 0): remaining; |
129 | } | 158 | } |
130 | 159 | ||
@@ -142,11 +171,28 @@ static int timerfd_setup(struct timerfd_ctx *ctx, int flags, | |||
142 | ctx->expired = 0; | 171 | ctx->expired = 0; |
143 | ctx->ticks = 0; | 172 | ctx->ticks = 0; |
144 | ctx->tintv = timespec_to_ktime(ktmr->it_interval); | 173 | ctx->tintv = timespec_to_ktime(ktmr->it_interval); |
145 | hrtimer_init(&ctx->tmr, clockid, htmode); | 174 | |
146 | hrtimer_set_expires(&ctx->tmr, texp); | 175 | if (isalarm(ctx)) { |
147 | ctx->tmr.function = timerfd_tmrproc; | 176 | alarm_init(&ctx->t.alarm, |
177 | ctx->clockid == CLOCK_REALTIME_ALARM ? | ||
178 | ALARM_REALTIME : ALARM_BOOTTIME, | ||
179 | timerfd_alarmproc); | ||
180 | } else { | ||
181 | hrtimer_init(&ctx->t.tmr, clockid, htmode); | ||
182 | hrtimer_set_expires(&ctx->t.tmr, texp); | ||
183 | ctx->t.tmr.function = timerfd_tmrproc; | ||
184 | } | ||
185 | |||
148 | if (texp.tv64 != 0) { | 186 | if (texp.tv64 != 0) { |
149 | hrtimer_start(&ctx->tmr, texp, htmode); | 187 | if (isalarm(ctx)) { |
188 | if (flags & TFD_TIMER_ABSTIME) | ||
189 | alarm_start(&ctx->t.alarm, texp); | ||
190 | else | ||
191 | alarm_start_relative(&ctx->t.alarm, texp); | ||
192 | } else { | ||
193 | hrtimer_start(&ctx->t.tmr, texp, htmode); | ||
194 | } | ||
195 | |||
150 | if (timerfd_canceled(ctx)) | 196 | if (timerfd_canceled(ctx)) |
151 | return -ECANCELED; | 197 | return -ECANCELED; |
152 | } | 198 | } |
@@ -158,7 +204,11 @@ static int timerfd_release(struct inode *inode, struct file *file) | |||
158 | struct timerfd_ctx *ctx = file->private_data; | 204 | struct timerfd_ctx *ctx = file->private_data; |
159 | 205 | ||
160 | timerfd_remove_cancel(ctx); | 206 | timerfd_remove_cancel(ctx); |
161 | hrtimer_cancel(&ctx->tmr); | 207 | |
208 | if (isalarm(ctx)) | ||
209 | alarm_cancel(&ctx->t.alarm); | ||
210 | else | ||
211 | hrtimer_cancel(&ctx->t.tmr); | ||
162 | kfree_rcu(ctx, rcu); | 212 | kfree_rcu(ctx, rcu); |
163 | return 0; | 213 | return 0; |
164 | } | 214 | } |
@@ -215,9 +265,15 @@ static ssize_t timerfd_read(struct file *file, char __user *buf, size_t count, | |||
215 | * callback to avoid DoS attacks specifying a very | 265 | * callback to avoid DoS attacks specifying a very |
216 | * short timer period. | 266 | * short timer period. |
217 | */ | 267 | */ |
218 | ticks += hrtimer_forward_now(&ctx->tmr, | 268 | if (isalarm(ctx)) { |
219 | ctx->tintv) - 1; | 269 | ticks += alarm_forward_now( |
220 | hrtimer_restart(&ctx->tmr); | 270 | &ctx->t.alarm, ctx->tintv) - 1; |
271 | alarm_restart(&ctx->t.alarm); | ||
272 | } else { | ||
273 | ticks += hrtimer_forward_now(&ctx->t.tmr, | ||
274 | ctx->tintv) - 1; | ||
275 | hrtimer_restart(&ctx->t.tmr); | ||
276 | } | ||
221 | } | 277 | } |
222 | ctx->expired = 0; | 278 | ctx->expired = 0; |
223 | ctx->ticks = 0; | 279 | ctx->ticks = 0; |
@@ -259,7 +315,9 @@ SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags) | |||
259 | 315 | ||
260 | if ((flags & ~TFD_CREATE_FLAGS) || | 316 | if ((flags & ~TFD_CREATE_FLAGS) || |
261 | (clockid != CLOCK_MONOTONIC && | 317 | (clockid != CLOCK_MONOTONIC && |
262 | clockid != CLOCK_REALTIME)) | 318 | clockid != CLOCK_REALTIME && |
319 | clockid != CLOCK_REALTIME_ALARM && | ||
320 | clockid != CLOCK_BOOTTIME_ALARM)) | ||
263 | return -EINVAL; | 321 | return -EINVAL; |
264 | 322 | ||
265 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | 323 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
@@ -268,7 +326,15 @@ SYSCALL_DEFINE2(timerfd_create, int, clockid, int, flags) | |||
268 | 326 | ||
269 | init_waitqueue_head(&ctx->wqh); | 327 | init_waitqueue_head(&ctx->wqh); |
270 | ctx->clockid = clockid; | 328 | ctx->clockid = clockid; |
271 | hrtimer_init(&ctx->tmr, clockid, HRTIMER_MODE_ABS); | 329 | |
330 | if (isalarm(ctx)) | ||
331 | alarm_init(&ctx->t.alarm, | ||
332 | ctx->clockid == CLOCK_REALTIME_ALARM ? | ||
333 | ALARM_REALTIME : ALARM_BOOTTIME, | ||
334 | timerfd_alarmproc); | ||
335 | else | ||
336 | hrtimer_init(&ctx->t.tmr, clockid, HRTIMER_MODE_ABS); | ||
337 | |||
272 | ctx->moffs = ktime_get_monotonic_offset(); | 338 | ctx->moffs = ktime_get_monotonic_offset(); |
273 | 339 | ||
274 | ufd = anon_inode_getfd("[timerfd]", &timerfd_fops, ctx, | 340 | ufd = anon_inode_getfd("[timerfd]", &timerfd_fops, ctx, |
@@ -305,8 +371,14 @@ static int do_timerfd_settime(int ufd, int flags, | |||
305 | */ | 371 | */ |
306 | for (;;) { | 372 | for (;;) { |
307 | spin_lock_irq(&ctx->wqh.lock); | 373 | spin_lock_irq(&ctx->wqh.lock); |
308 | if (hrtimer_try_to_cancel(&ctx->tmr) >= 0) | 374 | |
309 | break; | 375 | if (isalarm(ctx)) { |
376 | if (alarm_try_to_cancel(&ctx->t.alarm) >= 0) | ||
377 | break; | ||
378 | } else { | ||
379 | if (hrtimer_try_to_cancel(&ctx->t.tmr) >= 0) | ||
380 | break; | ||
381 | } | ||
310 | spin_unlock_irq(&ctx->wqh.lock); | 382 | spin_unlock_irq(&ctx->wqh.lock); |
311 | cpu_relax(); | 383 | cpu_relax(); |
312 | } | 384 | } |
@@ -317,8 +389,12 @@ static int do_timerfd_settime(int ufd, int flags, | |||
317 | * We do not update "ticks" and "expired" since the timer will be | 389 | * We do not update "ticks" and "expired" since the timer will be |
318 | * re-programmed again in the following timerfd_setup() call. | 390 | * re-programmed again in the following timerfd_setup() call. |
319 | */ | 391 | */ |
320 | if (ctx->expired && ctx->tintv.tv64) | 392 | if (ctx->expired && ctx->tintv.tv64) { |
321 | hrtimer_forward_now(&ctx->tmr, ctx->tintv); | 393 | if (isalarm(ctx)) |
394 | alarm_forward_now(&ctx->t.alarm, ctx->tintv); | ||
395 | else | ||
396 | hrtimer_forward_now(&ctx->t.tmr, ctx->tintv); | ||
397 | } | ||
322 | 398 | ||
323 | old->it_value = ktime_to_timespec(timerfd_get_remaining(ctx)); | 399 | old->it_value = ktime_to_timespec(timerfd_get_remaining(ctx)); |
324 | old->it_interval = ktime_to_timespec(ctx->tintv); | 400 | old->it_interval = ktime_to_timespec(ctx->tintv); |
@@ -345,9 +421,18 @@ static int do_timerfd_gettime(int ufd, struct itimerspec *t) | |||
345 | spin_lock_irq(&ctx->wqh.lock); | 421 | spin_lock_irq(&ctx->wqh.lock); |
346 | if (ctx->expired && ctx->tintv.tv64) { | 422 | if (ctx->expired && ctx->tintv.tv64) { |
347 | ctx->expired = 0; | 423 | ctx->expired = 0; |
348 | ctx->ticks += | 424 | |
349 | hrtimer_forward_now(&ctx->tmr, ctx->tintv) - 1; | 425 | if (isalarm(ctx)) { |
350 | hrtimer_restart(&ctx->tmr); | 426 | ctx->ticks += |
427 | alarm_forward_now( | ||
428 | &ctx->t.alarm, ctx->tintv) - 1; | ||
429 | alarm_restart(&ctx->t.alarm); | ||
430 | } else { | ||
431 | ctx->ticks += | ||
432 | hrtimer_forward_now(&ctx->t.tmr, ctx->tintv) | ||
433 | - 1; | ||
434 | hrtimer_restart(&ctx->t.tmr); | ||
435 | } | ||
351 | } | 436 | } |
352 | t->it_value = ktime_to_timespec(timerfd_get_remaining(ctx)); | 437 | t->it_value = ktime_to_timespec(timerfd_get_remaining(ctx)); |
353 | t->it_interval = ktime_to_timespec(ctx->tintv); | 438 | t->it_interval = ktime_to_timespec(ctx->tintv); |