]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - kernel/time/clocksource.c
clocksource: Let clocksource_unregister() return success/error
[mirror_ubuntu-bionic-kernel.git] / kernel / time / clocksource.c
index c9583382141a439ce10980bfc399792e8c6f8423..31b90332f47b362742162fae8ca5e110d06e5d20 100644 (file)
@@ -174,7 +174,8 @@ clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
 static struct clocksource *curr_clocksource;
 static LIST_HEAD(clocksource_list);
 static DEFINE_MUTEX(clocksource_mutex);
-static char override_name[32];
+#define CS_NAME_LEN            32
+static char override_name[CS_NAME_LEN];
 static int finished_booting;
 
 #ifdef CONFIG_CLOCKSOURCE_WATCHDOG
@@ -388,28 +389,17 @@ static void clocksource_enqueue_watchdog(struct clocksource *cs)
 
 static void clocksource_dequeue_watchdog(struct clocksource *cs)
 {
-       struct clocksource *tmp;
        unsigned long flags;
 
        spin_lock_irqsave(&watchdog_lock, flags);
-       if (cs->flags & CLOCK_SOURCE_MUST_VERIFY) {
-               /* cs is a watched clocksource. */
-               list_del_init(&cs->wd_list);
-       } else if (cs == watchdog) {
-               /* Reset watchdog cycles */
-               clocksource_reset_watchdog();
-               /* Current watchdog is removed. Find an alternative. */
-               watchdog = NULL;
-               list_for_each_entry(tmp, &clocksource_list, list) {
-                       if (tmp == cs || tmp->flags & CLOCK_SOURCE_MUST_VERIFY)
-                               continue;
-                       if (!watchdog || tmp->rating > watchdog->rating)
-                               watchdog = tmp;
+       if (cs != watchdog) {
+               if (cs->flags & CLOCK_SOURCE_MUST_VERIFY) {
+                       /* cs is a watched clocksource. */
+                       list_del_init(&cs->wd_list);
+                       /* Check if the watchdog timer needs to be stopped. */
+                       clocksource_stop_watchdog();
                }
        }
-       cs->flags &= ~CLOCK_SOURCE_WATCHDOG;
-       /* Check if the watchdog timer needs to be stopped. */
-       clocksource_stop_watchdog();
        spin_unlock_irqrestore(&watchdog_lock, flags);
 }
 
@@ -439,6 +429,11 @@ static int clocksource_watchdog_kthread(void *data)
        return 0;
 }
 
+static bool clocksource_is_watchdog(struct clocksource *cs)
+{
+       return cs == watchdog;
+}
+
 #else /* CONFIG_CLOCKSOURCE_WATCHDOG */
 
 static void clocksource_enqueue_watchdog(struct clocksource *cs)
@@ -450,6 +445,7 @@ static void clocksource_enqueue_watchdog(struct clocksource *cs)
 static inline void clocksource_dequeue_watchdog(struct clocksource *cs) { }
 static inline void clocksource_resume_watchdog(void) { }
 static inline int clocksource_watchdog_kthread(void *data) { return 0; }
+static bool clocksource_is_watchdog(struct clocksource *cs) { return false; }
 
 #endif /* CONFIG_CLOCKSOURCE_WATCHDOG */
 
@@ -553,24 +549,42 @@ static u64 clocksource_max_deferment(struct clocksource *cs)
 
 #ifndef CONFIG_ARCH_USES_GETTIMEOFFSET
 
-/**
- * clocksource_select - Select the best clocksource available
- *
- * Private function. Must hold clocksource_mutex when called.
- *
- * Select the clocksource with the best rating, or the clocksource,
- * which is selected by userspace override.
- */
-static void clocksource_select(void)
+static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur)
 {
-       struct clocksource *best, *cs;
+       struct clocksource *cs;
 
        if (!finished_booting || list_empty(&clocksource_list))
+               return NULL;
+
+       /*
+        * We pick the clocksource with the highest rating. If oneshot
+        * mode is active, we pick the highres valid clocksource with
+        * the best rating.
+        */
+       list_for_each_entry(cs, &clocksource_list, list) {
+               if (skipcur && cs == curr_clocksource)
+                       continue;
+               if (oneshot && !(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES))
+                       continue;
+               return cs;
+       }
+       return NULL;
+}
+
+static void __clocksource_select(bool skipcur)
+{
+       bool oneshot = tick_oneshot_mode_active();
+       struct clocksource *best, *cs;
+
+       /* Find the best suitable clocksource */
+       best = clocksource_find_best(oneshot, skipcur);
+       if (!best)
                return;
-       /* First clocksource on the list has the best rating. */
-       best = list_first_entry(&clocksource_list, struct clocksource, list);
+
        /* Check for the override clocksource. */
        list_for_each_entry(cs, &clocksource_list, list) {
+               if (skipcur && cs == curr_clocksource)
+                       continue;
                if (strcmp(cs->name, override_name) != 0)
                        continue;
                /*
@@ -578,8 +592,7 @@ static void clocksource_select(void)
                 * capable clocksource if the tick code is in oneshot
                 * mode (highres or nohz)
                 */
-               if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) &&
-                   tick_oneshot_mode_active()) {
+               if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && oneshot) {
                        /* Override clocksource cannot be used. */
                        printk(KERN_WARNING "Override clocksource %s is not "
                               "HRT compatible. Cannot switch while in "
@@ -590,13 +603,31 @@ static void clocksource_select(void)
                        best = cs;
                break;
        }
-       if (curr_clocksource != best) {
-               printk(KERN_INFO "Switching to clocksource %s\n", best->name);
+
+       if (curr_clocksource != best && !timekeeping_notify(best)) {
+               pr_info("Switched to clocksource %s\n", best->name);
                curr_clocksource = best;
-               timekeeping_notify(curr_clocksource);
        }
 }
 
+/**
+ * clocksource_select - Select the best clocksource available
+ *
+ * Private function. Must hold clocksource_mutex when called.
+ *
+ * Select the clocksource with the best rating, or the clocksource,
+ * which is selected by userspace override.
+ */
+static void clocksource_select(void)
+{
+       return __clocksource_select(false);
+}
+
+static void clocksource_select_fallback(void)
+{
+       return __clocksource_select(true);
+}
+
 #else /* !CONFIG_ARCH_USES_GETTIMEOFFSET */
 
 static inline void clocksource_select(void) { }
@@ -772,17 +803,42 @@ void clocksource_change_rating(struct clocksource *cs, int rating)
 }
 EXPORT_SYMBOL(clocksource_change_rating);
 
+/*
+ * Unbind clocksource @cs. Called with clocksource_mutex held
+ */
+static int clocksource_unbind(struct clocksource *cs)
+{
+       /*
+        * I really can't convince myself to support this on hardware
+        * designed by lobotomized monkeys.
+        */
+       if (clocksource_is_watchdog(cs))
+               return -EBUSY;
+
+       if (cs == curr_clocksource) {
+               /* Select and try to install a replacement clock source */
+               clocksource_select_fallback();
+               if (curr_clocksource == cs)
+                       return -EBUSY;
+       }
+       clocksource_dequeue_watchdog(cs);
+       list_del_init(&cs->list);
+       return 0;
+}
+
 /**
  * clocksource_unregister - remove a registered clocksource
  * @cs:        clocksource to be unregistered
  */
-void clocksource_unregister(struct clocksource *cs)
+int clocksource_unregister(struct clocksource *cs)
 {
+       int ret = 0;
+
        mutex_lock(&clocksource_mutex);
-       clocksource_dequeue_watchdog(cs);
-       list_del(&cs->list);
-       clocksource_select();
+       if (!list_empty(&cs->list))
+               ret = clocksource_unbind(cs);
        mutex_unlock(&clocksource_mutex);
+       return ret;
 }
 EXPORT_SYMBOL(clocksource_unregister);
 
@@ -808,6 +864,23 @@ sysfs_show_current_clocksources(struct device *dev,
        return count;
 }
 
+static size_t clocksource_get_uname(const char *buf, char *dst, size_t cnt)
+{
+       size_t ret = cnt;
+
+       /* strings from sysfs write are not 0 terminated! */
+       if (!cnt || cnt >= CS_NAME_LEN)
+               return -EINVAL;
+
+       /* strip of \n: */
+       if (buf[cnt-1] == '\n')
+               cnt--;
+       if (cnt > 0)
+               memcpy(dst, buf, cnt);
+       dst[cnt] = 0;
+       return ret;
+}
+
 /**
  * sysfs_override_clocksource - interface for manually overriding clocksource
  * @dev:       unused
@@ -822,28 +895,53 @@ static ssize_t sysfs_override_clocksource(struct device *dev,
                                          struct device_attribute *attr,
                                          const char *buf, size_t count)
 {
-       size_t ret = count;
-
-       /* strings from sysfs write are not 0 terminated! */
-       if (count >= sizeof(override_name))
-               return -EINVAL;
-
-       /* strip of \n: */
-       if (buf[count-1] == '\n')
-               count--;
+       size_t ret;
 
        mutex_lock(&clocksource_mutex);
 
-       if (count > 0)
-               memcpy(override_name, buf, count);
-       override_name[count] = 0;
-       clocksource_select();
+       ret = clocksource_get_uname(buf, override_name, count);
+       if (ret >= 0)
+               clocksource_select();
 
        mutex_unlock(&clocksource_mutex);
 
        return ret;
 }
 
+/**
+ * sysfs_unbind_current_clocksource - interface for manually unbinding clocksource
+ * @dev:       unused
+ * @attr:      unused
+ * @buf:       unused
+ * @count:     length of buffer
+ *
+ * Takes input from sysfs interface for manually unbinding a clocksource.
+ */
+static ssize_t sysfs_unbind_clocksource(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf, size_t count)
+{
+       struct clocksource *cs;
+       char name[CS_NAME_LEN];
+       size_t ret;
+
+       ret = clocksource_get_uname(buf, name, count);
+       if (ret < 0)
+               return ret;
+
+       ret = -ENODEV;
+       mutex_lock(&clocksource_mutex);
+       list_for_each_entry(cs, &clocksource_list, list) {
+               if (strcmp(cs->name, name))
+                       continue;
+               ret = clocksource_unbind(cs);
+               break;
+       }
+       mutex_unlock(&clocksource_mutex);
+
+       return ret ? ret : count;
+}
+
 /**
  * sysfs_show_available_clocksources - sysfs interface for listing clocksource
  * @dev:       unused
@@ -886,6 +984,8 @@ sysfs_show_available_clocksources(struct device *dev,
 static DEVICE_ATTR(current_clocksource, 0644, sysfs_show_current_clocksources,
                   sysfs_override_clocksource);
 
+static DEVICE_ATTR(unbind_clocksource, 0200, NULL, sysfs_unbind_clocksource);
+
 static DEVICE_ATTR(available_clocksource, 0444,
                   sysfs_show_available_clocksources, NULL);
 
@@ -909,6 +1009,9 @@ static int __init init_clocksource_sysfs(void)
                error = device_create_file(
                                &device_clocksource,
                                &dev_attr_current_clocksource);
+       if (!error)
+               error = device_create_file(&device_clocksource,
+                                          &dev_attr_unbind_clocksource);
        if (!error)
                error = device_create_file(
                                &device_clocksource,