diff options
Diffstat (limited to 'kernel/time/clockevents.c')
| -rw-r--r-- | kernel/time/clockevents.c | 271 |
1 files changed, 240 insertions, 31 deletions
diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index c6d6400ee137..38959c866789 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c | |||
| @@ -15,20 +15,23 @@ | |||
| 15 | #include <linux/hrtimer.h> | 15 | #include <linux/hrtimer.h> |
| 16 | #include <linux/init.h> | 16 | #include <linux/init.h> |
| 17 | #include <linux/module.h> | 17 | #include <linux/module.h> |
| 18 | #include <linux/notifier.h> | ||
| 19 | #include <linux/smp.h> | 18 | #include <linux/smp.h> |
| 19 | #include <linux/device.h> | ||
| 20 | 20 | ||
| 21 | #include "tick-internal.h" | 21 | #include "tick-internal.h" |
| 22 | 22 | ||
| 23 | /* The registered clock event devices */ | 23 | /* The registered clock event devices */ |
| 24 | static LIST_HEAD(clockevent_devices); | 24 | static LIST_HEAD(clockevent_devices); |
| 25 | static LIST_HEAD(clockevents_released); | 25 | static LIST_HEAD(clockevents_released); |
| 26 | |||
| 27 | /* Notification for clock events */ | ||
| 28 | static RAW_NOTIFIER_HEAD(clockevents_chain); | ||
| 29 | |||
| 30 | /* Protection for the above */ | 26 | /* Protection for the above */ |
| 31 | 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 | }; | ||
| 32 | 35 | ||
| 33 | /** | 36 | /** |
| 34 | * clockevents_delta2ns - Convert a latch value (device ticks) to nanoseconds | 37 | * clockevents_delta2ns - Convert a latch value (device ticks) to nanoseconds |
| @@ -232,47 +235,107 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, | |||
| 232 | return (rc && force) ? clockevents_program_min_delta(dev) : rc; | 235 | return (rc && force) ? clockevents_program_min_delta(dev) : rc; |
| 233 | } | 236 | } |
| 234 | 237 | ||
| 235 | /** | 238 | /* |
| 236 | * clockevents_register_notifier - register a clock events change listener | 239 | * Called after a notify add to make devices available which were |
| 240 | * released from the notifier call. | ||
| 237 | */ | 241 | */ |
| 238 | int clockevents_register_notifier(struct notifier_block *nb) | 242 | static void clockevents_notify_released(void) |
| 239 | { | 243 | { |
| 240 | unsigned long flags; | 244 | struct clock_event_device *dev; |
| 241 | int ret; | ||
| 242 | 245 | ||
| 243 | raw_spin_lock_irqsave(&clockevents_lock, flags); | 246 | while (!list_empty(&clockevents_released)) { |
| 244 | ret = raw_notifier_chain_register(&clockevents_chain, nb); | 247 | dev = list_entry(clockevents_released.next, |
| 245 | raw_spin_unlock_irqrestore(&clockevents_lock, flags); | 248 | struct clock_event_device, list); |
| 249 | list_del(&dev->list); | ||
| 250 | list_add(&dev->list, &clockevent_devices); | ||
| 251 | tick_check_new_device(dev); | ||
| 252 | } | ||
| 253 | } | ||
| 246 | 254 | ||
| 247 | return ret; | 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; | ||
| 248 | } | 281 | } |
| 249 | 282 | ||
| 250 | /* | 283 | /* |
| 251 | * Notify about a clock event change. Called with clockevents_lock | 284 | * Called with clockevents_mutex and clockevents_lock held |
| 252 | * held. | ||
| 253 | */ | 285 | */ |
| 254 | static void clockevents_do_notify(unsigned long reason, void *dev) | 286 | static int __clockevents_try_unbind(struct clock_event_device *ced, int cpu) |
| 255 | { | 287 | { |
| 256 | raw_notifier_call_chain(&clockevents_chain, reason, dev); | 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; | ||
| 257 | } | 295 | } |
| 258 | 296 | ||
| 259 | /* | 297 | /* |
| 260 | * Called after a notify add to make devices available which were | 298 | * SMP function call to unbind a device |
| 261 | * released from the notifier call. | ||
| 262 | */ | 299 | */ |
| 263 | static void clockevents_notify_released(void) | 300 | static void __clockevents_unbind(void *arg) |
| 264 | { | 301 | { |
| 265 | struct clock_event_device *dev; | 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 | } | ||
| 266 | 312 | ||
| 267 | while (!list_empty(&clockevents_released)) { | 313 | /* |
| 268 | dev = list_entry(clockevents_released.next, | 314 | * Issues smp function call to unbind a per cpu device. Called with |
| 269 | struct clock_event_device, list); | 315 | * clockevents_mutex held. |
| 270 | list_del(&dev->list); | 316 | */ |
| 271 | list_add(&dev->list, &clockevent_devices); | 317 | static int clockevents_unbind(struct clock_event_device *ced, int cpu) |
| 272 | clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev); | 318 | { |
| 273 | } | 319 | struct ce_unbind cu = { .ce = ced, .res = -ENODEV }; |
| 320 | |||
| 321 | smp_call_function_single(cpu, __clockevents_unbind, &cu, 1); | ||
| 322 | return cu.res; | ||
| 274 | } | 323 | } |
| 275 | 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 | |||
| 276 | /** | 339 | /** |
| 277 | * clockevents_register_device - register a clock event device | 340 | * clockevents_register_device - register a clock event device |
| 278 | * @dev: device to register | 341 | * @dev: device to register |
| @@ -290,7 +353,7 @@ void clockevents_register_device(struct clock_event_device *dev) | |||
| 290 | raw_spin_lock_irqsave(&clockevents_lock, flags); | 353 | raw_spin_lock_irqsave(&clockevents_lock, flags); |
| 291 | 354 | ||
| 292 | list_add(&dev->list, &clockevent_devices); | 355 | list_add(&dev->list, &clockevent_devices); |
| 293 | clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev); | 356 | tick_check_new_device(dev); |
| 294 | clockevents_notify_released(); | 357 | clockevents_notify_released(); |
| 295 | 358 | ||
| 296 | raw_spin_unlock_irqrestore(&clockevents_lock, flags); | 359 | raw_spin_unlock_irqrestore(&clockevents_lock, flags); |
| @@ -386,6 +449,7 @@ void clockevents_exchange_device(struct clock_event_device *old, | |||
| 386 | * released list and do a notify add later. | 449 | * released list and do a notify add later. |
| 387 | */ | 450 | */ |
| 388 | if (old) { | 451 | if (old) { |
| 452 | module_put(old->owner); | ||
| 389 | clockevents_set_mode(old, CLOCK_EVT_MODE_UNUSED); | 453 | clockevents_set_mode(old, CLOCK_EVT_MODE_UNUSED); |
| 390 | list_del(&old->list); | 454 | list_del(&old->list); |
| 391 | list_add(&old->list, &clockevents_released); | 455 | list_add(&old->list, &clockevents_released); |
| @@ -433,10 +497,36 @@ void clockevents_notify(unsigned long reason, void *arg) | |||
| 433 | int cpu; | 497 | int cpu; |
| 434 | 498 | ||
| 435 | raw_spin_lock_irqsave(&clockevents_lock, flags); | 499 | raw_spin_lock_irqsave(&clockevents_lock, flags); |
| 436 | clockevents_do_notify(reason, arg); | ||
| 437 | 500 | ||
| 438 | switch (reason) { | 501 | switch (reason) { |
| 502 | case CLOCK_EVT_NOTIFY_BROADCAST_ON: | ||
| 503 | case CLOCK_EVT_NOTIFY_BROADCAST_OFF: | ||
| 504 | case CLOCK_EVT_NOTIFY_BROADCAST_FORCE: | ||
| 505 | tick_broadcast_on_off(reason, arg); | ||
| 506 | break; | ||
| 507 | |||
| 508 | case CLOCK_EVT_NOTIFY_BROADCAST_ENTER: | ||
| 509 | case CLOCK_EVT_NOTIFY_BROADCAST_EXIT: | ||
| 510 | tick_broadcast_oneshot_control(reason); | ||
| 511 | break; | ||
| 512 | |||
| 513 | case CLOCK_EVT_NOTIFY_CPU_DYING: | ||
| 514 | tick_handover_do_timer(arg); | ||
| 515 | break; | ||
| 516 | |||
| 517 | case CLOCK_EVT_NOTIFY_SUSPEND: | ||
| 518 | tick_suspend(); | ||
| 519 | tick_suspend_broadcast(); | ||
| 520 | break; | ||
| 521 | |||
| 522 | case CLOCK_EVT_NOTIFY_RESUME: | ||
| 523 | tick_resume(); | ||
| 524 | break; | ||
| 525 | |||
| 439 | case CLOCK_EVT_NOTIFY_CPU_DEAD: | 526 | case CLOCK_EVT_NOTIFY_CPU_DEAD: |
| 527 | tick_shutdown_broadcast_oneshot(arg); | ||
| 528 | tick_shutdown_broadcast(arg); | ||
| 529 | tick_shutdown(arg); | ||
| 440 | /* | 530 | /* |
| 441 | * Unregister the clock event devices which were | 531 | * Unregister the clock event devices which were |
| 442 | * released from the users in the notify chain. | 532 | * released from the users in the notify chain. |
| @@ -462,4 +552,123 @@ void clockevents_notify(unsigned long reason, void *arg) | |||
| 462 | raw_spin_unlock_irqrestore(&clockevents_lock, flags); | 552 | raw_spin_unlock_irqrestore(&clockevents_lock, flags); |
| 463 | } | 553 | } |
| 464 | EXPORT_SYMBOL_GPL(clockevents_notify); | 554 | EXPORT_SYMBOL_GPL(clockevents_notify); |
| 555 | |||
| 556 | #ifdef CONFIG_SYSFS | ||
| 557 | struct bus_type clockevents_subsys = { | ||
| 558 | .name = "clockevents", | ||
| 559 | .dev_name = "clockevent", | ||
| 560 | }; | ||
| 561 | |||
| 562 | static DEFINE_PER_CPU(struct device, tick_percpu_dev); | ||
| 563 | static struct tick_device *tick_get_tick_dev(struct device *dev); | ||
| 564 | |||
| 565 | static ssize_t sysfs_show_current_tick_dev(struct device *dev, | ||
| 566 | struct device_attribute *attr, | ||
| 567 | char *buf) | ||
| 568 | { | ||
| 569 | struct tick_device *td; | ||
| 570 | ssize_t count = 0; | ||
| 571 | |||
| 572 | raw_spin_lock_irq(&clockevents_lock); | ||
| 573 | td = tick_get_tick_dev(dev); | ||
| 574 | if (td && td->evtdev) | ||
| 575 | count = snprintf(buf, PAGE_SIZE, "%s\n", td->evtdev->name); | ||
| 576 | raw_spin_unlock_irq(&clockevents_lock); | ||
| 577 | return count; | ||
| 578 | } | ||
| 579 | static DEVICE_ATTR(current_device, 0444, sysfs_show_current_tick_dev, NULL); | ||
| 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 | |||
| 613 | #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST | ||
| 614 | static struct device tick_bc_dev = { | ||
| 615 | .init_name = "broadcast", | ||
| 616 | .id = 0, | ||
| 617 | .bus = &clockevents_subsys, | ||
| 618 | }; | ||
| 619 | |||
| 620 | static struct tick_device *tick_get_tick_dev(struct device *dev) | ||
| 621 | { | ||
| 622 | return dev == &tick_bc_dev ? tick_get_broadcast_device() : | ||
| 623 | &per_cpu(tick_cpu_device, dev->id); | ||
| 624 | } | ||
| 625 | |||
| 626 | static __init int tick_broadcast_init_sysfs(void) | ||
| 627 | { | ||
| 628 | int err = device_register(&tick_bc_dev); | ||
| 629 | |||
| 630 | if (!err) | ||
| 631 | err = device_create_file(&tick_bc_dev, &dev_attr_current_device); | ||
| 632 | return err; | ||
| 633 | } | ||
| 634 | #else | ||
| 635 | static struct tick_device *tick_get_tick_dev(struct device *dev) | ||
| 636 | { | ||
| 637 | return &per_cpu(tick_cpu_device, dev->id); | ||
| 638 | } | ||
| 639 | static inline int tick_broadcast_init_sysfs(void) { return 0; } | ||
| 465 | #endif | 640 | #endif |
| 641 | |||
| 642 | static int __init tick_init_sysfs(void) | ||
| 643 | { | ||
| 644 | int cpu; | ||
| 645 | |||
| 646 | for_each_possible_cpu(cpu) { | ||
| 647 | struct device *dev = &per_cpu(tick_percpu_dev, cpu); | ||
| 648 | int err; | ||
| 649 | |||
| 650 | dev->id = cpu; | ||
| 651 | dev->bus = &clockevents_subsys; | ||
| 652 | err = device_register(dev); | ||
| 653 | if (!err) | ||
| 654 | err = device_create_file(dev, &dev_attr_current_device); | ||
| 655 | if (!err) | ||
| 656 | err = device_create_file(dev, &dev_attr_unbind_device); | ||
| 657 | if (err) | ||
| 658 | return err; | ||
| 659 | } | ||
| 660 | return tick_broadcast_init_sysfs(); | ||
| 661 | } | ||
| 662 | |||
| 663 | static int __init clockevents_init_sysfs(void) | ||
| 664 | { | ||
| 665 | int err = subsys_system_register(&clockevents_subsys, NULL); | ||
| 666 | |||
| 667 | if (!err) | ||
| 668 | err = tick_init_sysfs(); | ||
| 669 | return err; | ||
| 670 | } | ||
| 671 | device_initcall(clockevents_init_sysfs); | ||
| 672 | #endif /* SYSFS */ | ||
| 673 | |||
| 674 | #endif /* GENERIC_CLOCK_EVENTS */ | ||
