diff options
Diffstat (limited to 'kernel/time/clocksource.c')
| -rw-r--r-- | kernel/time/clocksource.c | 266 |
1 files changed, 199 insertions, 67 deletions
diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c index c9583382141a..50a8736757f3 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,11 +176,12 @@ 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 | static char override_name[32]; | 179 | static char override_name[CS_NAME_LEN]; |
| 178 | static int finished_booting; | 180 | static int finished_booting; |
| 179 | 181 | ||
| 180 | #ifdef CONFIG_CLOCKSOURCE_WATCHDOG | 182 | #ifdef CONFIG_CLOCKSOURCE_WATCHDOG |
| 181 | static void clocksource_watchdog_work(struct work_struct *work); | 183 | static void clocksource_watchdog_work(struct work_struct *work); |
| 184 | static void clocksource_select(void); | ||
| 182 | 185 | ||
| 183 | static LIST_HEAD(watchdog_list); | 186 | static LIST_HEAD(watchdog_list); |
| 184 | static struct clocksource *watchdog; | 187 | static struct clocksource *watchdog; |
| @@ -299,13 +302,30 @@ static void clocksource_watchdog(unsigned long data) | |||
| 299 | if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && | 302 | if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && |
| 300 | (cs->flags & CLOCK_SOURCE_IS_CONTINUOUS) && | 303 | (cs->flags & CLOCK_SOURCE_IS_CONTINUOUS) && |
| 301 | (watchdog->flags & CLOCK_SOURCE_IS_CONTINUOUS)) { | 304 | (watchdog->flags & CLOCK_SOURCE_IS_CONTINUOUS)) { |
| 305 | /* Mark it valid for high-res. */ | ||
| 302 | cs->flags |= CLOCK_SOURCE_VALID_FOR_HRES; | 306 | cs->flags |= CLOCK_SOURCE_VALID_FOR_HRES; |
| 307 | |||
| 308 | /* | ||
| 309 | * clocksource_done_booting() will sort it if | ||
| 310 | * finished_booting is not set yet. | ||
| 311 | */ | ||
| 312 | if (!finished_booting) | ||
| 313 | continue; | ||
| 314 | |||
| 303 | /* | 315 | /* |
| 304 | * We just marked the clocksource as highres-capable, | 316 | * If this is not the current clocksource let |
| 305 | * notify the rest of the system as well so that we | 317 | * the watchdog thread reselect it. Due to the |
| 306 | * transition into high-res mode: | 318 | * change to high res this clocksource might |
| 319 | * be preferred now. If it is the current | ||
| 320 | * clocksource let the tick code know about | ||
| 321 | * that change. | ||
| 307 | */ | 322 | */ |
| 308 | tick_clock_notify(); | 323 | if (cs != curr_clocksource) { |
| 324 | cs->flags |= CLOCK_SOURCE_RESELECT; | ||
| 325 | schedule_work(&watchdog_work); | ||
| 326 | } else { | ||
| 327 | tick_clock_notify(); | ||
| 328 | } | ||
| 309 | } | 329 | } |
| 310 | } | 330 | } |
| 311 | 331 | ||
| @@ -388,44 +408,39 @@ static void clocksource_enqueue_watchdog(struct clocksource *cs) | |||
| 388 | 408 | ||
| 389 | static void clocksource_dequeue_watchdog(struct clocksource *cs) | 409 | static void clocksource_dequeue_watchdog(struct clocksource *cs) |
| 390 | { | 410 | { |
| 391 | struct clocksource *tmp; | ||
| 392 | unsigned long flags; | 411 | unsigned long flags; |
| 393 | 412 | ||
| 394 | spin_lock_irqsave(&watchdog_lock, flags); | 413 | spin_lock_irqsave(&watchdog_lock, flags); |
| 395 | if (cs->flags & CLOCK_SOURCE_MUST_VERIFY) { | 414 | if (cs != watchdog) { |
| 396 | /* cs is a watched clocksource. */ | 415 | if (cs->flags & CLOCK_SOURCE_MUST_VERIFY) { |
| 397 | list_del_init(&cs->wd_list); | 416 | /* cs is a watched clocksource. */ |
| 398 | } else if (cs == watchdog) { | 417 | list_del_init(&cs->wd_list); |
| 399 | /* Reset watchdog cycles */ | 418 | /* Check if the watchdog timer needs to be stopped. */ |
| 400 | clocksource_reset_watchdog(); | 419 | clocksource_stop_watchdog(); |
| 401 | /* Current watchdog is removed. Find an alternative. */ | ||
| 402 | watchdog = NULL; | ||
| 403 | list_for_each_entry(tmp, &clocksource_list, list) { | ||
| 404 | if (tmp == cs || tmp->flags & CLOCK_SOURCE_MUST_VERIFY) | ||
| 405 | continue; | ||
| 406 | if (!watchdog || tmp->rating > watchdog->rating) | ||
| 407 | watchdog = tmp; | ||
| 408 | } | 420 | } |
| 409 | } | 421 | } |
| 410 | cs->flags &= ~CLOCK_SOURCE_WATCHDOG; | ||
| 411 | /* Check if the watchdog timer needs to be stopped. */ | ||
| 412 | clocksource_stop_watchdog(); | ||
| 413 | spin_unlock_irqrestore(&watchdog_lock, flags); | 422 | spin_unlock_irqrestore(&watchdog_lock, flags); |
| 414 | } | 423 | } |
| 415 | 424 | ||
| 416 | static int clocksource_watchdog_kthread(void *data) | 425 | static int __clocksource_watchdog_kthread(void) |
| 417 | { | 426 | { |
| 418 | struct clocksource *cs, *tmp; | 427 | struct clocksource *cs, *tmp; |
| 419 | unsigned long flags; | 428 | unsigned long flags; |
| 420 | LIST_HEAD(unstable); | 429 | LIST_HEAD(unstable); |
| 430 | int select = 0; | ||
| 421 | 431 | ||
| 422 | mutex_lock(&clocksource_mutex); | ||
| 423 | spin_lock_irqsave(&watchdog_lock, flags); | 432 | spin_lock_irqsave(&watchdog_lock, flags); |
| 424 | list_for_each_entry_safe(cs, tmp, &watchdog_list, wd_list) | 433 | list_for_each_entry_safe(cs, tmp, &watchdog_list, wd_list) { |
| 425 | if (cs->flags & CLOCK_SOURCE_UNSTABLE) { | 434 | if (cs->flags & CLOCK_SOURCE_UNSTABLE) { |
| 426 | list_del_init(&cs->wd_list); | 435 | list_del_init(&cs->wd_list); |
| 427 | list_add(&cs->wd_list, &unstable); | 436 | list_add(&cs->wd_list, &unstable); |
| 437 | select = 1; | ||
| 428 | } | 438 | } |
| 439 | if (cs->flags & CLOCK_SOURCE_RESELECT) { | ||
| 440 | cs->flags &= ~CLOCK_SOURCE_RESELECT; | ||
| 441 | select = 1; | ||
| 442 | } | ||
| 443 | } | ||
| 429 | /* Check if the watchdog timer needs to be stopped. */ | 444 | /* Check if the watchdog timer needs to be stopped. */ |
| 430 | clocksource_stop_watchdog(); | 445 | clocksource_stop_watchdog(); |
| 431 | spin_unlock_irqrestore(&watchdog_lock, flags); | 446 | spin_unlock_irqrestore(&watchdog_lock, flags); |
| @@ -435,10 +450,23 @@ static int clocksource_watchdog_kthread(void *data) | |||
| 435 | list_del_init(&cs->wd_list); | 450 | list_del_init(&cs->wd_list); |
| 436 | __clocksource_change_rating(cs, 0); | 451 | __clocksource_change_rating(cs, 0); |
| 437 | } | 452 | } |
| 453 | return select; | ||
| 454 | } | ||
| 455 | |||
| 456 | static int clocksource_watchdog_kthread(void *data) | ||
| 457 | { | ||
| 458 | mutex_lock(&clocksource_mutex); | ||
| 459 | if (__clocksource_watchdog_kthread()) | ||
| 460 | clocksource_select(); | ||
| 438 | mutex_unlock(&clocksource_mutex); | 461 | mutex_unlock(&clocksource_mutex); |
| 439 | return 0; | 462 | return 0; |
| 440 | } | 463 | } |
| 441 | 464 | ||
| 465 | static bool clocksource_is_watchdog(struct clocksource *cs) | ||
| 466 | { | ||
| 467 | return cs == watchdog; | ||
| 468 | } | ||
| 469 | |||
| 442 | #else /* CONFIG_CLOCKSOURCE_WATCHDOG */ | 470 | #else /* CONFIG_CLOCKSOURCE_WATCHDOG */ |
| 443 | 471 | ||
| 444 | static void clocksource_enqueue_watchdog(struct clocksource *cs) | 472 | static void clocksource_enqueue_watchdog(struct clocksource *cs) |
| @@ -449,7 +477,8 @@ static void clocksource_enqueue_watchdog(struct clocksource *cs) | |||
| 449 | 477 | ||
| 450 | static inline void clocksource_dequeue_watchdog(struct clocksource *cs) { } | 478 | static inline void clocksource_dequeue_watchdog(struct clocksource *cs) { } |
| 451 | static inline void clocksource_resume_watchdog(void) { } | 479 | static inline void clocksource_resume_watchdog(void) { } |
| 452 | static inline int clocksource_watchdog_kthread(void *data) { return 0; } | 480 | static inline int __clocksource_watchdog_kthread(void) { return 0; } |
| 481 | static bool clocksource_is_watchdog(struct clocksource *cs) { return false; } | ||
| 453 | 482 | ||
| 454 | #endif /* CONFIG_CLOCKSOURCE_WATCHDOG */ | 483 | #endif /* CONFIG_CLOCKSOURCE_WATCHDOG */ |
| 455 | 484 | ||
| @@ -553,24 +582,42 @@ static u64 clocksource_max_deferment(struct clocksource *cs) | |||
| 553 | 582 | ||
| 554 | #ifndef CONFIG_ARCH_USES_GETTIMEOFFSET | 583 | #ifndef CONFIG_ARCH_USES_GETTIMEOFFSET |
| 555 | 584 | ||
| 556 | /** | 585 | static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur) |
| 557 | * clocksource_select - Select the best clocksource available | ||
| 558 | * | ||
| 559 | * Private function. Must hold clocksource_mutex when called. | ||
| 560 | * | ||
| 561 | * Select the clocksource with the best rating, or the clocksource, | ||
| 562 | * which is selected by userspace override. | ||
| 563 | */ | ||
| 564 | static void clocksource_select(void) | ||
| 565 | { | 586 | { |
| 566 | struct clocksource *best, *cs; | 587 | struct clocksource *cs; |
| 567 | 588 | ||
| 568 | if (!finished_booting || list_empty(&clocksource_list)) | 589 | if (!finished_booting || list_empty(&clocksource_list)) |
| 590 | return NULL; | ||
| 591 | |||
| 592 | /* | ||
| 593 | * We pick the clocksource with the highest rating. If oneshot | ||
| 594 | * mode is active, we pick the highres valid clocksource with | ||
| 595 | * the best rating. | ||
| 596 | */ | ||
| 597 | list_for_each_entry(cs, &clocksource_list, list) { | ||
| 598 | if (skipcur && cs == curr_clocksource) | ||
| 599 | continue; | ||
| 600 | if (oneshot && !(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES)) | ||
| 601 | continue; | ||
| 602 | return cs; | ||
| 603 | } | ||
| 604 | return NULL; | ||
| 605 | } | ||
| 606 | |||
| 607 | static void __clocksource_select(bool skipcur) | ||
| 608 | { | ||
| 609 | bool oneshot = tick_oneshot_mode_active(); | ||
| 610 | struct clocksource *best, *cs; | ||
| 611 | |||
| 612 | /* Find the best suitable clocksource */ | ||
| 613 | best = clocksource_find_best(oneshot, skipcur); | ||
| 614 | if (!best) | ||
| 569 | return; | 615 | return; |
| 570 | /* First clocksource on the list has the best rating. */ | 616 | |
| 571 | best = list_first_entry(&clocksource_list, struct clocksource, list); | ||
| 572 | /* Check for the override clocksource. */ | 617 | /* Check for the override clocksource. */ |
| 573 | list_for_each_entry(cs, &clocksource_list, list) { | 618 | list_for_each_entry(cs, &clocksource_list, list) { |
| 619 | if (skipcur && cs == curr_clocksource) | ||
| 620 | continue; | ||
| 574 | if (strcmp(cs->name, override_name) != 0) | 621 | if (strcmp(cs->name, override_name) != 0) |
| 575 | continue; | 622 | continue; |
| 576 | /* | 623 | /* |
| @@ -578,8 +625,7 @@ static void clocksource_select(void) | |||
| 578 | * capable clocksource if the tick code is in oneshot | 625 | * capable clocksource if the tick code is in oneshot |
| 579 | * mode (highres or nohz) | 626 | * mode (highres or nohz) |
| 580 | */ | 627 | */ |
| 581 | if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && | 628 | if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && oneshot) { |
| 582 | tick_oneshot_mode_active()) { | ||
| 583 | /* Override clocksource cannot be used. */ | 629 | /* Override clocksource cannot be used. */ |
| 584 | printk(KERN_WARNING "Override clocksource %s is not " | 630 | printk(KERN_WARNING "Override clocksource %s is not " |
| 585 | "HRT compatible. Cannot switch while in " | 631 | "HRT compatible. Cannot switch while in " |
| @@ -590,16 +636,35 @@ static void clocksource_select(void) | |||
| 590 | best = cs; | 636 | best = cs; |
| 591 | break; | 637 | break; |
| 592 | } | 638 | } |
| 593 | if (curr_clocksource != best) { | 639 | |
| 594 | printk(KERN_INFO "Switching to clocksource %s\n", best->name); | 640 | if (curr_clocksource != best && !timekeeping_notify(best)) { |
| 641 | pr_info("Switched to clocksource %s\n", best->name); | ||
| 595 | curr_clocksource = best; | 642 | curr_clocksource = best; |
| 596 | timekeeping_notify(curr_clocksource); | ||
| 597 | } | 643 | } |
| 598 | } | 644 | } |
| 599 | 645 | ||
| 646 | /** | ||
| 647 | * clocksource_select - Select the best clocksource available | ||
| 648 | * | ||
| 649 | * Private function. Must hold clocksource_mutex when called. | ||
| 650 | * | ||
| 651 | * Select the clocksource with the best rating, or the clocksource, | ||
| 652 | * which is selected by userspace override. | ||
| 653 | */ | ||
| 654 | static void clocksource_select(void) | ||
| 655 | { | ||
| 656 | return __clocksource_select(false); | ||
| 657 | } | ||
| 658 | |||
| 659 | static void clocksource_select_fallback(void) | ||
| 660 | { | ||
| 661 | return __clocksource_select(true); | ||
| 662 | } | ||
| 663 | |||
| 600 | #else /* !CONFIG_ARCH_USES_GETTIMEOFFSET */ | 664 | #else /* !CONFIG_ARCH_USES_GETTIMEOFFSET */ |
| 601 | 665 | ||
| 602 | static inline void clocksource_select(void) { } | 666 | static inline void clocksource_select(void) { } |
| 667 | static inline void clocksource_select_fallback(void) { } | ||
| 603 | 668 | ||
| 604 | #endif | 669 | #endif |
| 605 | 670 | ||
| @@ -614,16 +679,11 @@ static int __init clocksource_done_booting(void) | |||
| 614 | { | 679 | { |
| 615 | mutex_lock(&clocksource_mutex); | 680 | mutex_lock(&clocksource_mutex); |
| 616 | curr_clocksource = clocksource_default_clock(); | 681 | curr_clocksource = clocksource_default_clock(); |
| 617 | mutex_unlock(&clocksource_mutex); | ||
| 618 | |||
| 619 | finished_booting = 1; | 682 | finished_booting = 1; |
| 620 | |||
| 621 | /* | 683 | /* |
| 622 | * Run the watchdog first to eliminate unstable clock sources | 684 | * Run the watchdog first to eliminate unstable clock sources |
| 623 | */ | 685 | */ |
| 624 | clocksource_watchdog_kthread(NULL); | 686 | __clocksource_watchdog_kthread(); |
| 625 | |||
| 626 | mutex_lock(&clocksource_mutex); | ||
| 627 | clocksource_select(); | 687 | clocksource_select(); |
| 628 | mutex_unlock(&clocksource_mutex); | 688 | mutex_unlock(&clocksource_mutex); |
| 629 | return 0; | 689 | return 0; |
| @@ -756,7 +816,6 @@ static void __clocksource_change_rating(struct clocksource *cs, int rating) | |||
| 756 | list_del(&cs->list); | 816 | list_del(&cs->list); |
| 757 | cs->rating = rating; | 817 | cs->rating = rating; |
| 758 | clocksource_enqueue(cs); | 818 | clocksource_enqueue(cs); |
| 759 | clocksource_select(); | ||
| 760 | } | 819 | } |
| 761 | 820 | ||
| 762 | /** | 821 | /** |
| @@ -768,21 +827,47 @@ void clocksource_change_rating(struct clocksource *cs, int rating) | |||
| 768 | { | 827 | { |
| 769 | mutex_lock(&clocksource_mutex); | 828 | mutex_lock(&clocksource_mutex); |
| 770 | __clocksource_change_rating(cs, rating); | 829 | __clocksource_change_rating(cs, rating); |
| 830 | clocksource_select(); | ||
| 771 | mutex_unlock(&clocksource_mutex); | 831 | mutex_unlock(&clocksource_mutex); |
| 772 | } | 832 | } |
| 773 | EXPORT_SYMBOL(clocksource_change_rating); | 833 | EXPORT_SYMBOL(clocksource_change_rating); |
| 774 | 834 | ||
| 835 | /* | ||
| 836 | * Unbind clocksource @cs. Called with clocksource_mutex held | ||
| 837 | */ | ||
| 838 | static int clocksource_unbind(struct clocksource *cs) | ||
| 839 | { | ||
| 840 | /* | ||
| 841 | * I really can't convince myself to support this on hardware | ||
| 842 | * designed by lobotomized monkeys. | ||
| 843 | */ | ||
| 844 | if (clocksource_is_watchdog(cs)) | ||
| 845 | return -EBUSY; | ||
| 846 | |||
| 847 | if (cs == curr_clocksource) { | ||
| 848 | /* Select and try to install a replacement clock source */ | ||
| 849 | clocksource_select_fallback(); | ||
| 850 | if (curr_clocksource == cs) | ||
| 851 | return -EBUSY; | ||
| 852 | } | ||
| 853 | clocksource_dequeue_watchdog(cs); | ||
| 854 | list_del_init(&cs->list); | ||
| 855 | return 0; | ||
| 856 | } | ||
| 857 | |||
| 775 | /** | 858 | /** |
| 776 | * clocksource_unregister - remove a registered clocksource | 859 | * clocksource_unregister - remove a registered clocksource |
| 777 | * @cs: clocksource to be unregistered | 860 | * @cs: clocksource to be unregistered |
| 778 | */ | 861 | */ |
| 779 | void clocksource_unregister(struct clocksource *cs) | 862 | int clocksource_unregister(struct clocksource *cs) |
| 780 | { | 863 | { |
| 864 | int ret = 0; | ||
| 865 | |||
| 781 | mutex_lock(&clocksource_mutex); | 866 | mutex_lock(&clocksource_mutex); |
| 782 | clocksource_dequeue_watchdog(cs); | 867 | if (!list_empty(&cs->list)) |
| 783 | list_del(&cs->list); | 868 | ret = clocksource_unbind(cs); |
| 784 | clocksource_select(); | ||
| 785 | mutex_unlock(&clocksource_mutex); | 869 | mutex_unlock(&clocksource_mutex); |
| 870 | return ret; | ||
| 786 | } | 871 | } |
| 787 | EXPORT_SYMBOL(clocksource_unregister); | 872 | EXPORT_SYMBOL(clocksource_unregister); |
| 788 | 873 | ||
| @@ -808,6 +893,23 @@ sysfs_show_current_clocksources(struct device *dev, | |||
| 808 | return count; | 893 | return count; |
| 809 | } | 894 | } |
| 810 | 895 | ||
| 896 | size_t sysfs_get_uname(const char *buf, char *dst, size_t cnt) | ||
| 897 | { | ||
| 898 | size_t ret = cnt; | ||
| 899 | |||
| 900 | /* strings from sysfs write are not 0 terminated! */ | ||
| 901 | if (!cnt || cnt >= CS_NAME_LEN) | ||
| 902 | return -EINVAL; | ||
| 903 | |||
| 904 | /* strip of \n: */ | ||
| 905 | if (buf[cnt-1] == '\n') | ||
| 906 | cnt--; | ||
| 907 | if (cnt > 0) | ||
| 908 | memcpy(dst, buf, cnt); | ||
| 909 | dst[cnt] = 0; | ||
| 910 | return ret; | ||
| 911 | } | ||
| 912 | |||
| 811 | /** | 913 | /** |
| 812 | * sysfs_override_clocksource - interface for manually overriding clocksource | 914 | * sysfs_override_clocksource - interface for manually overriding clocksource |
| 813 | * @dev: unused | 915 | * @dev: unused |
| @@ -822,22 +924,13 @@ static ssize_t sysfs_override_clocksource(struct device *dev, | |||
| 822 | struct device_attribute *attr, | 924 | struct device_attribute *attr, |
| 823 | const char *buf, size_t count) | 925 | const char *buf, size_t count) |
| 824 | { | 926 | { |
| 825 | size_t ret = count; | 927 | size_t ret; |
| 826 | |||
| 827 | /* strings from sysfs write are not 0 terminated! */ | ||
| 828 | if (count >= sizeof(override_name)) | ||
| 829 | return -EINVAL; | ||
| 830 | |||
| 831 | /* strip of \n: */ | ||
| 832 | if (buf[count-1] == '\n') | ||
| 833 | count--; | ||
| 834 | 928 | ||
| 835 | mutex_lock(&clocksource_mutex); | 929 | mutex_lock(&clocksource_mutex); |
| 836 | 930 | ||
| 837 | if (count > 0) | 931 | ret = sysfs_get_uname(buf, override_name, count); |
| 838 | memcpy(override_name, buf, count); | 932 | if (ret >= 0) |
| 839 | override_name[count] = 0; | 933 | clocksource_select(); |
| 840 | clocksource_select(); | ||
| 841 | 934 | ||
| 842 | mutex_unlock(&clocksource_mutex); | 935 | mutex_unlock(&clocksource_mutex); |
| 843 | 936 | ||
| @@ -845,6 +938,40 @@ static ssize_t sysfs_override_clocksource(struct device *dev, | |||
| 845 | } | 938 | } |
| 846 | 939 | ||
| 847 | /** | 940 | /** |
| 941 | * sysfs_unbind_current_clocksource - interface for manually unbinding clocksource | ||
| 942 | * @dev: unused | ||
| 943 | * @attr: unused | ||
| 944 | * @buf: unused | ||
| 945 | * @count: length of buffer | ||
| 946 | * | ||
| 947 | * Takes input from sysfs interface for manually unbinding a clocksource. | ||
| 948 | */ | ||
| 949 | static ssize_t sysfs_unbind_clocksource(struct device *dev, | ||
| 950 | struct device_attribute *attr, | ||
| 951 | const char *buf, size_t count) | ||
| 952 | { | ||
| 953 | struct clocksource *cs; | ||
| 954 | char name[CS_NAME_LEN]; | ||
| 955 | size_t ret; | ||
| 956 | |||
| 957 | ret = sysfs_get_uname(buf, name, count); | ||
| 958 | if (ret < 0) | ||
| 959 | return ret; | ||
| 960 | |||
| 961 | ret = -ENODEV; | ||
| 962 | mutex_lock(&clocksource_mutex); | ||
| 963 | list_for_each_entry(cs, &clocksource_list, list) { | ||
| 964 | if (strcmp(cs->name, name)) | ||
| 965 | continue; | ||
| 966 | ret = clocksource_unbind(cs); | ||
| 967 | break; | ||
| 968 | } | ||
| 969 | mutex_unlock(&clocksource_mutex); | ||
| 970 | |||
| 971 | return ret ? ret : count; | ||
| 972 | } | ||
| 973 | |||
| 974 | /** | ||
| 848 | * sysfs_show_available_clocksources - sysfs interface for listing clocksource | 975 | * sysfs_show_available_clocksources - sysfs interface for listing clocksource |
| 849 | * @dev: unused | 976 | * @dev: unused |
| 850 | * @attr: unused | 977 | * @attr: unused |
| @@ -886,6 +1013,8 @@ sysfs_show_available_clocksources(struct device *dev, | |||
| 886 | static DEVICE_ATTR(current_clocksource, 0644, sysfs_show_current_clocksources, | 1013 | static DEVICE_ATTR(current_clocksource, 0644, sysfs_show_current_clocksources, |
| 887 | sysfs_override_clocksource); | 1014 | sysfs_override_clocksource); |
| 888 | 1015 | ||
| 1016 | static DEVICE_ATTR(unbind_clocksource, 0200, NULL, sysfs_unbind_clocksource); | ||
| 1017 | |||
| 889 | static DEVICE_ATTR(available_clocksource, 0444, | 1018 | static DEVICE_ATTR(available_clocksource, 0444, |
| 890 | sysfs_show_available_clocksources, NULL); | 1019 | sysfs_show_available_clocksources, NULL); |
| 891 | 1020 | ||
| @@ -910,6 +1039,9 @@ static int __init init_clocksource_sysfs(void) | |||
| 910 | &device_clocksource, | 1039 | &device_clocksource, |
| 911 | &dev_attr_current_clocksource); | 1040 | &dev_attr_current_clocksource); |
| 912 | if (!error) | 1041 | if (!error) |
| 1042 | error = device_create_file(&device_clocksource, | ||
| 1043 | &dev_attr_unbind_clocksource); | ||
| 1044 | if (!error) | ||
| 913 | error = device_create_file( | 1045 | error = device_create_file( |
| 914 | &device_clocksource, | 1046 | &device_clocksource, |
| 915 | &dev_attr_available_clocksource); | 1047 | &dev_attr_available_clocksource); |
