diff options
author | Thomas Gleixner <tglx@linutronix.de> | 2013-04-25 16:31:50 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2013-05-16 05:09:18 -0400 |
commit | 03e13cf5ee60584fe0c831682c67212effb7fca4 (patch) | |
tree | b40a9a0a88a675e4511ad6b6c0bf072b6a1ef9f4 /kernel/time/clockevents.c | |
parent | 45cb8e01b2ecef1c2afb18333e95793fa1a90281 (diff) |
clockevents: Implement unbind functionality
Provide a sysfs interface to allow unbinding of clockevent
devices. The device is unbound if it is unused or if there is a
replacement device available. Unbinding of broadcast devices is not
supported as we don't want to foster that nonsense. If no replacement
device is available the unbind returns -EBUSY. Unbind is available
from the kernel and through sysfs, which is necessary to drop the
module refcount.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: John Stultz <john.stultz@linaro.org>
Cc: Magnus Damm <magnus.damm@gmail.com>
Link: http://lkml.kernel.org/r/20130425143436.499216659@linutronix.de
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'kernel/time/clockevents.c')
-rw-r--r-- | kernel/time/clockevents.c | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 0a23f4f29934..38959c866789 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c | |||
@@ -25,6 +25,13 @@ static LIST_HEAD(clockevent_devices); | |||
25 | static LIST_HEAD(clockevents_released); | 25 | static LIST_HEAD(clockevents_released); |
26 | /* Protection for the above */ | 26 | /* Protection for the above */ |
27 | static DEFINE_RAW_SPINLOCK(clockevents_lock); | 27 | static DEFINE_RAW_SPINLOCK(clockevents_lock); |
28 | /* Protection for unbind operations */ | ||
29 | static DEFINE_MUTEX(clockevents_mutex); | ||
30 | |||
31 | struct ce_unbind { | ||
32 | struct clock_event_device *ce; | ||
33 | int res; | ||
34 | }; | ||
28 | 35 | ||
29 | /** | 36 | /** |
30 | * clockevents_delta2ns - Convert a latch value (device ticks) to nanoseconds | 37 | * clockevents_delta2ns - Convert a latch value (device ticks) to nanoseconds |
@@ -245,6 +252,90 @@ static void clockevents_notify_released(void) | |||
245 | } | 252 | } |
246 | } | 253 | } |
247 | 254 | ||
255 | /* | ||
256 | * Try to install a replacement clock event device | ||
257 | */ | ||
258 | static int clockevents_replace(struct clock_event_device *ced) | ||
259 | { | ||
260 | struct clock_event_device *dev, *newdev = NULL; | ||
261 | |||
262 | list_for_each_entry(dev, &clockevent_devices, list) { | ||
263 | if (dev == ced || dev->mode != CLOCK_EVT_MODE_UNUSED) | ||
264 | continue; | ||
265 | |||
266 | if (!tick_check_replacement(newdev, dev)) | ||
267 | continue; | ||
268 | |||
269 | if (!try_module_get(dev->owner)) | ||
270 | continue; | ||
271 | |||
272 | if (newdev) | ||
273 | module_put(newdev->owner); | ||
274 | newdev = dev; | ||
275 | } | ||
276 | if (newdev) { | ||
277 | tick_install_replacement(newdev); | ||
278 | list_del_init(&ced->list); | ||
279 | } | ||
280 | return newdev ? 0 : -EBUSY; | ||
281 | } | ||
282 | |||
283 | /* | ||
284 | * Called with clockevents_mutex and clockevents_lock held | ||
285 | */ | ||
286 | static int __clockevents_try_unbind(struct clock_event_device *ced, int cpu) | ||
287 | { | ||
288 | /* Fast track. Device is unused */ | ||
289 | if (ced->mode == CLOCK_EVT_MODE_UNUSED) { | ||
290 | list_del_init(&ced->list); | ||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | return ced == per_cpu(tick_cpu_device, cpu).evtdev ? -EAGAIN : -EBUSY; | ||
295 | } | ||
296 | |||
297 | /* | ||
298 | * SMP function call to unbind a device | ||
299 | */ | ||
300 | static void __clockevents_unbind(void *arg) | ||
301 | { | ||
302 | struct ce_unbind *cu = arg; | ||
303 | int res; | ||
304 | |||
305 | raw_spin_lock(&clockevents_lock); | ||
306 | res = __clockevents_try_unbind(cu->ce, smp_processor_id()); | ||
307 | if (res == -EAGAIN) | ||
308 | res = clockevents_replace(cu->ce); | ||
309 | cu->res = res; | ||
310 | raw_spin_unlock(&clockevents_lock); | ||
311 | } | ||
312 | |||
313 | /* | ||
314 | * Issues smp function call to unbind a per cpu device. Called with | ||
315 | * clockevents_mutex held. | ||
316 | */ | ||
317 | static int clockevents_unbind(struct clock_event_device *ced, int cpu) | ||
318 | { | ||
319 | struct ce_unbind cu = { .ce = ced, .res = -ENODEV }; | ||
320 | |||
321 | smp_call_function_single(cpu, __clockevents_unbind, &cu, 1); | ||
322 | return cu.res; | ||
323 | } | ||
324 | |||
325 | /* | ||
326 | * Unbind a clockevents device. | ||
327 | */ | ||
328 | int clockevents_unbind_device(struct clock_event_device *ced, int cpu) | ||
329 | { | ||
330 | int ret; | ||
331 | |||
332 | mutex_lock(&clockevents_mutex); | ||
333 | ret = clockevents_unbind(ced, cpu); | ||
334 | mutex_unlock(&clockevents_mutex); | ||
335 | return ret; | ||
336 | } | ||
337 | EXPORT_SYMBOL_GPL(clockevents_unbind); | ||
338 | |||
248 | /** | 339 | /** |
249 | * clockevents_register_device - register a clock event device | 340 | * clockevents_register_device - register a clock event device |
250 | * @dev: device to register | 341 | * @dev: device to register |
@@ -487,6 +578,38 @@ static ssize_t sysfs_show_current_tick_dev(struct device *dev, | |||
487 | } | 578 | } |
488 | static DEVICE_ATTR(current_device, 0444, sysfs_show_current_tick_dev, NULL); | 579 | static DEVICE_ATTR(current_device, 0444, sysfs_show_current_tick_dev, NULL); |
489 | 580 | ||
581 | /* We don't support the abomination of removable broadcast devices */ | ||
582 | static ssize_t sysfs_unbind_tick_dev(struct device *dev, | ||
583 | struct device_attribute *attr, | ||
584 | const char *buf, size_t count) | ||
585 | { | ||
586 | char name[CS_NAME_LEN]; | ||
587 | size_t ret = sysfs_get_uname(buf, name, count); | ||
588 | struct clock_event_device *ce; | ||
589 | |||
590 | if (ret < 0) | ||
591 | return ret; | ||
592 | |||
593 | ret = -ENODEV; | ||
594 | mutex_lock(&clockevents_mutex); | ||
595 | raw_spin_lock_irq(&clockevents_lock); | ||
596 | list_for_each_entry(ce, &clockevent_devices, list) { | ||
597 | if (!strcmp(ce->name, name)) { | ||
598 | ret = __clockevents_try_unbind(ce, dev->id); | ||
599 | break; | ||
600 | } | ||
601 | } | ||
602 | raw_spin_unlock_irq(&clockevents_lock); | ||
603 | /* | ||
604 | * We hold clockevents_mutex, so ce can't go away | ||
605 | */ | ||
606 | if (ret == -EAGAIN) | ||
607 | ret = clockevents_unbind(ce, dev->id); | ||
608 | mutex_unlock(&clockevents_mutex); | ||
609 | return ret ? ret : count; | ||
610 | } | ||
611 | static DEVICE_ATTR(unbind_device, 0200, NULL, sysfs_unbind_tick_dev); | ||
612 | |||
490 | #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST | 613 | #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST |
491 | static struct device tick_bc_dev = { | 614 | static struct device tick_bc_dev = { |
492 | .init_name = "broadcast", | 615 | .init_name = "broadcast", |
@@ -529,6 +652,8 @@ static int __init tick_init_sysfs(void) | |||
529 | err = device_register(dev); | 652 | err = device_register(dev); |
530 | if (!err) | 653 | if (!err) |
531 | err = device_create_file(dev, &dev_attr_current_device); | 654 | err = device_create_file(dev, &dev_attr_current_device); |
655 | if (!err) | ||
656 | err = device_create_file(dev, &dev_attr_unbind_device); | ||
532 | if (err) | 657 | if (err) |
533 | return err; | 658 | return err; |
534 | } | 659 | } |