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 | |
| 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')
| -rw-r--r-- | kernel/time/clockevents.c | 125 | ||||
| -rw-r--r-- | kernel/time/clocksource.c | 9 | ||||
| -rw-r--r-- | kernel/time/tick-common.c | 24 | ||||
| -rw-r--r-- | kernel/time/tick-internal.h | 7 |
4 files changed, 161 insertions, 4 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 | } |
diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c index 31b90332f47b..6d05b00410cc 100644 --- a/kernel/time/clocksource.c +++ b/kernel/time/clocksource.c | |||
| @@ -31,6 +31,8 @@ | |||
| 31 | #include <linux/tick.h> | 31 | #include <linux/tick.h> |
| 32 | #include <linux/kthread.h> | 32 | #include <linux/kthread.h> |
| 33 | 33 | ||
| 34 | #include "tick-internal.h" | ||
| 35 | |||
| 34 | void timecounter_init(struct timecounter *tc, | 36 | void timecounter_init(struct timecounter *tc, |
| 35 | const struct cyclecounter *cc, | 37 | const struct cyclecounter *cc, |
| 36 | u64 start_tstamp) | 38 | u64 start_tstamp) |
| @@ -174,7 +176,6 @@ clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec) | |||
| 174 | static struct clocksource *curr_clocksource; | 176 | static struct clocksource *curr_clocksource; |
| 175 | static LIST_HEAD(clocksource_list); | 177 | static LIST_HEAD(clocksource_list); |
| 176 | static DEFINE_MUTEX(clocksource_mutex); | 178 | static DEFINE_MUTEX(clocksource_mutex); |
| 177 | #define CS_NAME_LEN 32 | ||
| 178 | static char override_name[CS_NAME_LEN]; | 179 | static char override_name[CS_NAME_LEN]; |
| 179 | static int finished_booting; | 180 | static int finished_booting; |
| 180 | 181 | ||
| @@ -864,7 +865,7 @@ sysfs_show_current_clocksources(struct device *dev, | |||
| 864 | return count; | 865 | return count; |
| 865 | } | 866 | } |
| 866 | 867 | ||
| 867 | static size_t clocksource_get_uname(const char *buf, char *dst, size_t cnt) | 868 | size_t sysfs_get_uname(const char *buf, char *dst, size_t cnt) |
| 868 | { | 869 | { |
| 869 | size_t ret = cnt; | 870 | size_t ret = cnt; |
| 870 | 871 | ||
| @@ -899,7 +900,7 @@ static ssize_t sysfs_override_clocksource(struct device *dev, | |||
| 899 | 900 | ||
| 900 | mutex_lock(&clocksource_mutex); | 901 | mutex_lock(&clocksource_mutex); |
| 901 | 902 | ||
| 902 | ret = clocksource_get_uname(buf, override_name, count); | 903 | ret = sysfs_get_uname(buf, override_name, count); |
| 903 | if (ret >= 0) | 904 | if (ret >= 0) |
| 904 | clocksource_select(); | 905 | clocksource_select(); |
| 905 | 906 | ||
| @@ -925,7 +926,7 @@ static ssize_t sysfs_unbind_clocksource(struct device *dev, | |||
| 925 | char name[CS_NAME_LEN]; | 926 | char name[CS_NAME_LEN]; |
| 926 | size_t ret; | 927 | size_t ret; |
| 927 | 928 | ||
| 928 | ret = clocksource_get_uname(buf, name, count); | 929 | ret = sysfs_get_uname(buf, name, count); |
| 929 | if (ret < 0) | 930 | if (ret < 0) |
| 930 | return ret; | 931 | return ret; |
| 931 | 932 | ||
diff --git a/kernel/time/tick-common.c b/kernel/time/tick-common.c index c34021650348..5edfb4806032 100644 --- a/kernel/time/tick-common.c +++ b/kernel/time/tick-common.c | |||
| @@ -205,6 +205,17 @@ static void tick_setup_device(struct tick_device *td, | |||
| 205 | tick_setup_oneshot(newdev, handler, next_event); | 205 | tick_setup_oneshot(newdev, handler, next_event); |
| 206 | } | 206 | } |
| 207 | 207 | ||
| 208 | void tick_install_replacement(struct clock_event_device *newdev) | ||
| 209 | { | ||
| 210 | struct tick_device *td = &__get_cpu_var(tick_cpu_device); | ||
| 211 | int cpu = smp_processor_id(); | ||
| 212 | |||
| 213 | clockevents_exchange_device(td->evtdev, newdev); | ||
| 214 | tick_setup_device(td, newdev, cpu, cpumask_of(cpu)); | ||
| 215 | if (newdev->features & CLOCK_EVT_FEAT_ONESHOT) | ||
| 216 | tick_oneshot_notify(); | ||
| 217 | } | ||
| 218 | |||
| 208 | static bool tick_check_percpu(struct clock_event_device *curdev, | 219 | static bool tick_check_percpu(struct clock_event_device *curdev, |
| 209 | struct clock_event_device *newdev, int cpu) | 220 | struct clock_event_device *newdev, int cpu) |
| 210 | { | 221 | { |
| @@ -237,6 +248,19 @@ static bool tick_check_preferred(struct clock_event_device *curdev, | |||
| 237 | } | 248 | } |
| 238 | 249 | ||
| 239 | /* | 250 | /* |
| 251 | * Check whether the new device is a better fit than curdev. curdev | ||
| 252 | * can be NULL ! | ||
| 253 | */ | ||
| 254 | bool tick_check_replacement(struct clock_event_device *curdev, | ||
| 255 | struct clock_event_device *newdev) | ||
| 256 | { | ||
| 257 | if (tick_check_percpu(curdev, newdev, smp_processor_id())) | ||
| 258 | return false; | ||
| 259 | |||
| 260 | return tick_check_preferred(curdev, newdev); | ||
| 261 | } | ||
| 262 | |||
| 263 | /* | ||
| 240 | * Check, if the new registered device should be used. Called with | 264 | * Check, if the new registered device should be used. Called with |
| 241 | * clockevents_lock held and interrupts disabled. | 265 | * clockevents_lock held and interrupts disabled. |
| 242 | */ | 266 | */ |
diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h index 06bfc8802dfb..be1690eaecff 100644 --- a/kernel/time/tick-internal.h +++ b/kernel/time/tick-internal.h | |||
| @@ -11,6 +11,8 @@ extern seqlock_t jiffies_lock; | |||
| 11 | #define TICK_DO_TIMER_NONE -1 | 11 | #define TICK_DO_TIMER_NONE -1 |
| 12 | #define TICK_DO_TIMER_BOOT -2 | 12 | #define TICK_DO_TIMER_BOOT -2 |
| 13 | 13 | ||
| 14 | #define CS_NAME_LEN 32 | ||
| 15 | |||
| 14 | DECLARE_PER_CPU(struct tick_device, tick_cpu_device); | 16 | DECLARE_PER_CPU(struct tick_device, tick_cpu_device); |
| 15 | extern ktime_t tick_next_period; | 17 | extern ktime_t tick_next_period; |
| 16 | extern ktime_t tick_period; | 18 | extern ktime_t tick_period; |
| @@ -23,9 +25,14 @@ extern void tick_handover_do_timer(int *cpup); | |||
| 23 | extern void tick_shutdown(unsigned int *cpup); | 25 | extern void tick_shutdown(unsigned int *cpup); |
| 24 | extern void tick_suspend(void); | 26 | extern void tick_suspend(void); |
| 25 | extern void tick_resume(void); | 27 | extern void tick_resume(void); |
| 28 | extern bool tick_check_replacement(struct clock_event_device *curdev, | ||
| 29 | struct clock_event_device *newdev); | ||
| 30 | extern void tick_install_replacement(struct clock_event_device *dev); | ||
| 26 | 31 | ||
| 27 | extern void clockevents_shutdown(struct clock_event_device *dev); | 32 | extern void clockevents_shutdown(struct clock_event_device *dev); |
| 28 | 33 | ||
| 34 | extern size_t sysfs_get_uname(const char *buf, char *dst, size_t cnt); | ||
| 35 | |||
| 29 | /* | 36 | /* |
| 30 | * NO_HZ / high resolution timer shared code | 37 | * NO_HZ / high resolution timer shared code |
| 31 | */ | 38 | */ |
