clockevents: Make minimum delay adjustments configurable
authorMartin Schwidefsky <schwidefsky@de.ibm.com>
Tue, 23 Aug 2011 13:29:42 +0000 (15:29 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Thu, 8 Sep 2011 09:10:56 +0000 (11:10 +0200)
The automatic increase of the min_delta_ns of a clockevents device
should be done in the clockevents code as the minimum delay is an
attribute of the clockevents device.

In addition not all architectures want the automatic adjustment, on a
massively virtualized system it can happen that the programming of a
clock event fails several times in a row because the virtual cpu has
been rescheduled quickly enough. In that case the minimum delay will
erroneously be increased with no way back. The new config symbol
GENERIC_CLOCKEVENTS_MIN_ADJUST is used to enable the automatic
adjustment. The config option is selected only for x86.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Cc: john stultz <johnstul@us.ibm.com>
Link: http://lkml.kernel.org/r/20110823133142.494157493@de.ibm.com
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
arch/x86/Kconfig
include/linux/clockchips.h
kernel/time/Kconfig
kernel/time/clockevents.c
kernel/time/tick-broadcast.c
kernel/time/tick-common.c
kernel/time/tick-internal.h
kernel/time/tick-oneshot.c

index 6a47bb22657fd3d55835c32b2e834cd42e3ad28c..a1609cd7e4c6ce3e4edee547f47d7adad41759d6 100644 (file)
@@ -68,6 +68,7 @@ config X86
        select GENERIC_IRQ_PROBE
        select GENERIC_PENDING_IRQ if SMP
        select GENERIC_IRQ_SHOW
+       select GENERIC_CLOCKEVENTS_MIN_ADJUST
        select IRQ_FORCED_THREADING
        select USE_GENERIC_SMP_HELPERS if SMP
        select HAVE_BPF_JIT if (X86_64 && NET)
index d6733e27af349349bdcf97f1a7674a530bc99e2f..39bb050bdbb211d3f71e8738a317b147b950a3e7 100644 (file)
@@ -140,7 +140,7 @@ extern void clockevents_set_mode(struct clock_event_device *dev,
                                 enum clock_event_mode mode);
 extern int clockevents_register_notifier(struct notifier_block *nb);
 extern int clockevents_program_event(struct clock_event_device *dev,
-                                    ktime_t expires, ktime_t now);
+                                    ktime_t expires, bool force);
 
 extern void clockevents_handle_noop(struct clock_event_device *dev);
 
index f06a8a3656481d7e3120ed074a1b16c04357a9dd..b26c2228fe9259eee90fdea1d0ad1d414d48868f 100644 (file)
@@ -27,3 +27,5 @@ config GENERIC_CLOCKEVENTS_BUILD
        default y
        depends on GENERIC_CLOCKEVENTS || GENERIC_CLOCKEVENTS_MIGR
 
+config GENERIC_CLOCKEVENTS_MIN_ADJUST
+       bool
index e4c699dfa4e8776ee3ed9e671922e962002b6a06..713ef94eceef97349f85366b8c9883e426d1c471 100644 (file)
@@ -94,42 +94,139 @@ void clockevents_shutdown(struct clock_event_device *dev)
        dev->next_event.tv64 = KTIME_MAX;
 }
 
+#ifdef CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST
+
+/* Limit min_delta to a jiffie */
+#define MIN_DELTA_LIMIT                (NSEC_PER_SEC / HZ)
+
+/**
+ * clockevents_increase_min_delta - raise minimum delta of a clock event device
+ * @dev:       device to increase the minimum delta
+ *
+ * Returns 0 on success, -ETIME when the minimum delta reached the limit.
+ */
+static int clockevents_increase_min_delta(struct clock_event_device *dev)
+{
+       /* Nothing to do if we already reached the limit */
+       if (dev->min_delta_ns >= MIN_DELTA_LIMIT) {
+               printk(KERN_WARNING "CE: Reprogramming failure. Giving up\n");
+               dev->next_event.tv64 = KTIME_MAX;
+               return -ETIME;
+       }
+
+       if (dev->min_delta_ns < 5000)
+               dev->min_delta_ns = 5000;
+       else
+               dev->min_delta_ns += dev->min_delta_ns >> 1;
+
+       if (dev->min_delta_ns > MIN_DELTA_LIMIT)
+               dev->min_delta_ns = MIN_DELTA_LIMIT;
+
+       printk(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n",
+              dev->name ? dev->name : "?",
+              (unsigned long long) dev->min_delta_ns);
+       return 0;
+}
+
+/**
+ * clockevents_program_min_delta - Set clock event device to the minimum delay.
+ * @dev:       device to program
+ *
+ * Returns 0 on success, -ETIME when the retry loop failed.
+ */
+static int clockevents_program_min_delta(struct clock_event_device *dev)
+{
+       unsigned long long clc;
+       int64_t delta;
+       int i;
+
+       for (i = 0;;) {
+               delta = dev->min_delta_ns;
+               dev->next_event = ktime_add_ns(ktime_get(), delta);
+
+               if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
+                       return 0;
+
+               dev->retries++;
+               clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
+               if (dev->set_next_event((unsigned long) clc, dev) == 0)
+                       return 0;
+
+               if (++i > 2) {
+                       /*
+                        * We tried 3 times to program the device with the
+                        * given min_delta_ns. Try to increase the minimum
+                        * delta, if that fails as well get out of here.
+                        */
+                       if (clockevents_increase_min_delta(dev))
+                               return -ETIME;
+                       i = 0;
+               }
+       }
+}
+
+#else  /* CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST */
+
+/**
+ * clockevents_program_min_delta - Set clock event device to the minimum delay.
+ * @dev:       device to program
+ *
+ * Returns 0 on success, -ETIME when the retry loop failed.
+ */
+static int clockevents_program_min_delta(struct clock_event_device *dev)
+{
+       unsigned long long clc;
+       int64_t delta;
+
+       delta = dev->min_delta_ns;
+       dev->next_event = ktime_add_ns(ktime_get(), delta);
+
+       if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
+               return 0;
+
+       dev->retries++;
+       clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
+       return dev->set_next_event((unsigned long) clc, dev);
+}
+
+#endif /* CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST */
+
 /**
  * clockevents_program_event - Reprogram the clock event device.
+ * @dev:       device to program
  * @expires:   absolute expiry time (monotonic clock)
+ * @force:     program minimum delay if expires can not be set
  *
  * Returns 0 on success, -ETIME when the event is in the past.
  */
 int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
-                             ktime_t now)
+                             bool force)
 {
        unsigned long long clc;
        int64_t delta;
+       int rc;
 
        if (unlikely(expires.tv64 < 0)) {
                WARN_ON_ONCE(1);
                return -ETIME;
        }
 
-       delta = ktime_to_ns(ktime_sub(expires, now));
-
-       if (delta <= 0)
-               return -ETIME;
-
        dev->next_event = expires;
 
        if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
                return 0;
 
-       if (delta > dev->max_delta_ns)
-               delta = dev->max_delta_ns;
-       if (delta < dev->min_delta_ns)
-               delta = dev->min_delta_ns;
+       delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
+       if (delta <= 0)
+               return force ? clockevents_program_min_delta(dev) : -ETIME;
 
-       clc = delta * dev->mult;
-       clc >>= dev->shift;
+       delta = min(delta, (int64_t) dev->max_delta_ns);
+       delta = max(delta, (int64_t) dev->min_delta_ns);
 
-       return dev->set_next_event((unsigned long) clc, dev);
+       clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
+       rc = dev->set_next_event((unsigned long) clc, dev);
+
+       return (rc && force) ? clockevents_program_min_delta(dev) : rc;
 }
 
 /**
@@ -258,7 +355,7 @@ int clockevents_update_freq(struct clock_event_device *dev, u32 freq)
        if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
                return 0;
 
-       return clockevents_program_event(dev, dev->next_event, ktime_get());
+       return clockevents_program_event(dev, dev->next_event, false);
 }
 
 /*
index c7218d13273861dd65f5a484852001f88d8ce819..f954282d9a82758acf951960392e37b5b07c6014 100644 (file)
@@ -194,7 +194,7 @@ static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
        for (next = dev->next_event; ;) {
                next = ktime_add(next, tick_period);
 
-               if (!clockevents_program_event(dev, next, ktime_get()))
+               if (!clockevents_program_event(dev, next, false))
                        return;
                tick_do_periodic_broadcast();
        }
@@ -373,7 +373,7 @@ static int tick_broadcast_set_event(ktime_t expires, int force)
 {
        struct clock_event_device *bc = tick_broadcast_device.evtdev;
 
-       return tick_dev_program_event(bc, expires, force);
+       return clockevents_program_event(bc, expires, force);
 }
 
 int tick_resume_broadcast_oneshot(struct clock_event_device *bc)
index 119528de82359c2ff913c02584aeb31a9ae7265b..da6c9ecad4e47fc0758ed70d184eec8847400b20 100644 (file)
@@ -94,7 +94,7 @@ void tick_handle_periodic(struct clock_event_device *dev)
         */
        next = ktime_add(dev->next_event, tick_period);
        for (;;) {
-               if (!clockevents_program_event(dev, next, ktime_get()))
+               if (!clockevents_program_event(dev, next, false))
                        return;
                /*
                 * Have to be careful here. If we're in oneshot mode,
@@ -137,7 +137,7 @@ void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
                clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
 
                for (;;) {
-                       if (!clockevents_program_event(dev, next, ktime_get()))
+                       if (!clockevents_program_event(dev, next, false))
                                return;
                        next = ktime_add(next, tick_period);
                }
index 1009b06d6f897ac2acab87e70b61c8d00d6542b5..4e265b901fedee1c00c6c09038029c443a7d53d7 100644 (file)
@@ -26,8 +26,6 @@ extern void clockevents_shutdown(struct clock_event_device *dev);
 extern void tick_setup_oneshot(struct clock_event_device *newdev,
                               void (*handler)(struct clock_event_device *),
                               ktime_t nextevt);
-extern int tick_dev_program_event(struct clock_event_device *dev,
-                                 ktime_t expires, int force);
 extern int tick_program_event(ktime_t expires, int force);
 extern void tick_oneshot_notify(void);
 extern int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *));
index 2d04411a5f05882ce41097e0ab607ffef38d318a..824109060a33808b7f5d15d13ececbd298534859 100644 (file)
 
 #include "tick-internal.h"
 
-/* Limit min_delta to a jiffie */
-#define MIN_DELTA_LIMIT                (NSEC_PER_SEC / HZ)
-
-static int tick_increase_min_delta(struct clock_event_device *dev)
-{
-       /* Nothing to do if we already reached the limit */
-       if (dev->min_delta_ns >= MIN_DELTA_LIMIT)
-               return -ETIME;
-
-       if (dev->min_delta_ns < 5000)
-               dev->min_delta_ns = 5000;
-       else
-               dev->min_delta_ns += dev->min_delta_ns >> 1;
-
-       if (dev->min_delta_ns > MIN_DELTA_LIMIT)
-               dev->min_delta_ns = MIN_DELTA_LIMIT;
-
-       printk(KERN_WARNING "CE: %s increased min_delta_ns to %llu nsec\n",
-              dev->name ? dev->name : "?",
-              (unsigned long long) dev->min_delta_ns);
-       return 0;
-}
-
-/**
- * tick_program_event internal worker function
- */
-int tick_dev_program_event(struct clock_event_device *dev, ktime_t expires,
-                          int force)
-{
-       ktime_t now = ktime_get();
-       int i;
-
-       for (i = 0;;) {
-               int ret = clockevents_program_event(dev, expires, now);
-
-               if (!ret || !force)
-                       return ret;
-
-               dev->retries++;
-               /*
-                * We tried 3 times to program the device with the given
-                * min_delta_ns. If that's not working then we increase it
-                * and emit a warning.
-                */
-               if (++i > 2) {
-                       /* Increase the min. delta and try again */
-                       if (tick_increase_min_delta(dev)) {
-                               /*
-                                * Get out of the loop if min_delta_ns
-                                * hit the limit already. That's
-                                * better than staying here forever.
-                                *
-                                * We clear next_event so we have a
-                                * chance that the box survives.
-                                */
-                               printk(KERN_WARNING
-                                      "CE: Reprogramming failure. Giving up\n");
-                               dev->next_event.tv64 = KTIME_MAX;
-                               return -ETIME;
-                       }
-                       i = 0;
-               }
-
-               now = ktime_get();
-               expires = ktime_add_ns(now, dev->min_delta_ns);
-       }
-}
-
 /**
  * tick_program_event
  */
@@ -96,7 +28,7 @@ int tick_program_event(ktime_t expires, int force)
 {
        struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
 
-       return tick_dev_program_event(dev, expires, force);
+       return clockevents_program_event(dev, expires, force);
 }
 
 /**
@@ -104,11 +36,10 @@ int tick_program_event(ktime_t expires, int force)
  */
 void tick_resume_oneshot(void)
 {
-       struct tick_device *td = &__get_cpu_var(tick_cpu_device);
-       struct clock_event_device *dev = td->evtdev;
+       struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
 
        clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
-       tick_program_event(ktime_get(), 1);
+       clockevents_program_event(dev, ktime_get(), true);
 }
 
 /**
@@ -120,7 +51,7 @@ void tick_setup_oneshot(struct clock_event_device *newdev,
 {
        newdev->event_handler = handler;
        clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
-       tick_dev_program_event(newdev, next_event, 1);
+       clockevents_program_event(newdev, next_event, true);
 }
 
 /**