]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - kernel/time/clockevents.c
Merge tag 'char-misc-4.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[mirror_ubuntu-artful-kernel.git] / kernel / time / clockevents.c
index 888ecc114ddc9fe480f95986807d716851b19498..11dc22a6983b55da3c2c356cb375fa27dbad0ed8 100644 (file)
@@ -94,25 +94,76 @@ u64 clockevent_delta2ns(unsigned long latch, struct clock_event_device *evt)
 }
 EXPORT_SYMBOL_GPL(clockevent_delta2ns);
 
+static int __clockevents_set_state(struct clock_event_device *dev,
+                                  enum clock_event_state state)
+{
+       /* Transition with legacy set_mode() callback */
+       if (dev->set_mode) {
+               /* Legacy callback doesn't support new modes */
+               if (state > CLOCK_EVT_STATE_ONESHOT)
+                       return -ENOSYS;
+               /*
+                * 'clock_event_state' and 'clock_event_mode' have 1-to-1
+                * mapping until *_ONESHOT, and so a simple cast will work.
+                */
+               dev->set_mode((enum clock_event_mode)state, dev);
+               dev->mode = (enum clock_event_mode)state;
+               return 0;
+       }
+
+       if (dev->features & CLOCK_EVT_FEAT_DUMMY)
+               return 0;
+
+       /* Transition with new state-specific callbacks */
+       switch (state) {
+       case CLOCK_EVT_STATE_DETACHED:
+               /*
+                * This is an internal state, which is guaranteed to go from
+                * SHUTDOWN to DETACHED. No driver interaction required.
+                */
+               return 0;
+
+       case CLOCK_EVT_STATE_SHUTDOWN:
+               return dev->set_state_shutdown(dev);
+
+       case CLOCK_EVT_STATE_PERIODIC:
+               /* Core internal bug */
+               if (!(dev->features & CLOCK_EVT_FEAT_PERIODIC))
+                       return -ENOSYS;
+               return dev->set_state_periodic(dev);
+
+       case CLOCK_EVT_STATE_ONESHOT:
+               /* Core internal bug */
+               if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
+                       return -ENOSYS;
+               return dev->set_state_oneshot(dev);
+
+       default:
+               return -ENOSYS;
+       }
+}
+
 /**
- * clockevents_set_mode - set the operating mode of a clock event device
+ * clockevents_set_state - set the operating state of a clock event device
  * @dev:       device to modify
- * @mode:      new mode
+ * @state:     new state
  *
  * Must be called with interrupts disabled !
  */
-void clockevents_set_mode(struct clock_event_device *dev,
-                                enum clock_event_mode mode)
+void clockevents_set_state(struct clock_event_device *dev,
+                          enum clock_event_state state)
 {
-       if (dev->mode != mode) {
-               dev->set_mode(mode, dev);
-               dev->mode = mode;
+       if (dev->state != state) {
+               if (__clockevents_set_state(dev, state))
+                       return;
+
+               dev->state = state;
 
                /*
                 * A nsec2cyc multiplicator of 0 is invalid and we'd crash
                 * on it, so fix it up and emit a warning:
                 */
-               if (mode == CLOCK_EVT_MODE_ONESHOT) {
+               if (state == CLOCK_EVT_STATE_ONESHOT) {
                        if (unlikely(!dev->mult)) {
                                dev->mult = 1;
                                WARN_ON(1);
@@ -127,10 +178,28 @@ void clockevents_set_mode(struct clock_event_device *dev,
  */
 void clockevents_shutdown(struct clock_event_device *dev)
 {
-       clockevents_set_mode(dev, CLOCK_EVT_MODE_SHUTDOWN);
+       clockevents_set_state(dev, CLOCK_EVT_STATE_SHUTDOWN);
        dev->next_event.tv64 = KTIME_MAX;
 }
 
+/**
+ * clockevents_tick_resume -   Resume the tick device before using it again
+ * @dev:                       device to resume
+ */
+int clockevents_tick_resume(struct clock_event_device *dev)
+{
+       int ret = 0;
+
+       if (dev->set_mode) {
+               dev->set_mode(CLOCK_EVT_MODE_RESUME, dev);
+               dev->mode = CLOCK_EVT_MODE_RESUME;
+       } else if (dev->tick_resume) {
+               ret = dev->tick_resume(dev);
+       }
+
+       return ret;
+}
+
 #ifdef CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST
 
 /* Limit min_delta to a jiffie */
@@ -183,7 +252,7 @@ static int clockevents_program_min_delta(struct clock_event_device *dev)
                delta = dev->min_delta_ns;
                dev->next_event = ktime_add_ns(ktime_get(), delta);
 
-               if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
+               if (dev->state == CLOCK_EVT_STATE_SHUTDOWN)
                        return 0;
 
                dev->retries++;
@@ -220,7 +289,7 @@ static int clockevents_program_min_delta(struct clock_event_device *dev)
        delta = dev->min_delta_ns;
        dev->next_event = ktime_add_ns(ktime_get(), delta);
 
-       if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
+       if (dev->state == CLOCK_EVT_STATE_SHUTDOWN)
                return 0;
 
        dev->retries++;
@@ -252,7 +321,7 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
 
        dev->next_event = expires;
 
-       if (dev->mode == CLOCK_EVT_MODE_SHUTDOWN)
+       if (dev->state == CLOCK_EVT_STATE_SHUTDOWN)
                return 0;
 
        /* Shortcut for clockevent devices that can deal with ktime. */
@@ -297,7 +366,7 @@ static int clockevents_replace(struct clock_event_device *ced)
        struct clock_event_device *dev, *newdev = NULL;
 
        list_for_each_entry(dev, &clockevent_devices, list) {
-               if (dev == ced || dev->mode != CLOCK_EVT_MODE_UNUSED)
+               if (dev == ced || dev->state != CLOCK_EVT_STATE_DETACHED)
                        continue;
 
                if (!tick_check_replacement(newdev, dev))
@@ -323,7 +392,7 @@ static int clockevents_replace(struct clock_event_device *ced)
 static int __clockevents_try_unbind(struct clock_event_device *ced, int cpu)
 {
        /* Fast track. Device is unused */
-       if (ced->mode == CLOCK_EVT_MODE_UNUSED) {
+       if (ced->state == CLOCK_EVT_STATE_DETACHED) {
                list_del_init(&ced->list);
                return 0;
        }
@@ -373,6 +442,37 @@ int clockevents_unbind_device(struct clock_event_device *ced, int cpu)
 }
 EXPORT_SYMBOL_GPL(clockevents_unbind_device);
 
+/* Sanity check of state transition callbacks */
+static int clockevents_sanity_check(struct clock_event_device *dev)
+{
+       /* Legacy set_mode() callback */
+       if (dev->set_mode) {
+               /* We shouldn't be supporting new modes now */
+               WARN_ON(dev->set_state_periodic || dev->set_state_oneshot ||
+                       dev->set_state_shutdown || dev->tick_resume);
+
+               BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
+               return 0;
+       }
+
+       if (dev->features & CLOCK_EVT_FEAT_DUMMY)
+               return 0;
+
+       /* New state-specific callbacks */
+       if (!dev->set_state_shutdown)
+               return -EINVAL;
+
+       if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
+           !dev->set_state_periodic)
+               return -EINVAL;
+
+       if ((dev->features & CLOCK_EVT_FEAT_ONESHOT) &&
+           !dev->set_state_oneshot)
+               return -EINVAL;
+
+       return 0;
+}
+
 /**
  * clockevents_register_device - register a clock event device
  * @dev:       device to register
@@ -381,7 +481,11 @@ void clockevents_register_device(struct clock_event_device *dev)
 {
        unsigned long flags;
 
-       BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
+       BUG_ON(clockevents_sanity_check(dev));
+
+       /* Initialize state to DETACHED */
+       dev->state = CLOCK_EVT_STATE_DETACHED;
+
        if (!dev->cpumask) {
                WARN_ON(num_possible_cpus() > 1);
                dev->cpumask = cpumask_of(smp_processor_id());
@@ -445,11 +549,11 @@ int __clockevents_update_freq(struct clock_event_device *dev, u32 freq)
 {
        clockevents_config(dev, freq);
 
-       if (dev->mode == CLOCK_EVT_MODE_ONESHOT)
+       if (dev->state == CLOCK_EVT_STATE_ONESHOT)
                return clockevents_program_event(dev, dev->next_event, false);
 
-       if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
-               dev->set_mode(CLOCK_EVT_MODE_PERIODIC, dev);
+       if (dev->state == CLOCK_EVT_STATE_PERIODIC)
+               return __clockevents_set_state(dev, CLOCK_EVT_STATE_PERIODIC);
 
        return 0;
 }
@@ -491,30 +595,27 @@ void clockevents_handle_noop(struct clock_event_device *dev)
  * @old:       device to release (can be NULL)
  * @new:       device to request (can be NULL)
  *
- * Called from the notifier chain. clockevents_lock is held already
+ * Called from various tick functions with clockevents_lock held and
+ * interrupts disabled.
  */
 void clockevents_exchange_device(struct clock_event_device *old,
                                 struct clock_event_device *new)
 {
-       unsigned long flags;
-
-       local_irq_save(flags);
        /*
         * Caller releases a clock event device. We queue it into the
         * released list and do a notify add later.
         */
        if (old) {
                module_put(old->owner);
-               clockevents_set_mode(old, CLOCK_EVT_MODE_UNUSED);
+               clockevents_set_state(old, CLOCK_EVT_STATE_DETACHED);
                list_del(&old->list);
                list_add(&old->list, &clockevents_released);
        }
 
        if (new) {
-               BUG_ON(new->mode != CLOCK_EVT_MODE_UNUSED);
+               BUG_ON(new->state != CLOCK_EVT_STATE_DETACHED);
                clockevents_shutdown(new);
        }
-       local_irq_restore(flags);
 }
 
 /**
@@ -541,74 +642,40 @@ void clockevents_resume(void)
                        dev->resume(dev);
 }
 
-#ifdef CONFIG_GENERIC_CLOCKEVENTS
+#ifdef CONFIG_HOTPLUG_CPU
 /**
- * clockevents_notify - notification about relevant events
- * Returns 0 on success, any other value on error
+ * tick_cleanup_dead_cpu - Cleanup the tick and clockevents of a dead cpu
  */
-int clockevents_notify(unsigned long reason, void *arg)
+void tick_cleanup_dead_cpu(int cpu)
 {
        struct clock_event_device *dev, *tmp;
        unsigned long flags;
-       int cpu, ret = 0;
 
        raw_spin_lock_irqsave(&clockevents_lock, flags);
 
-       switch (reason) {
-       case CLOCK_EVT_NOTIFY_BROADCAST_ON:
-       case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
-       case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
-               tick_broadcast_on_off(reason, arg);
-               break;
-
-       case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
-       case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
-               ret = tick_broadcast_oneshot_control(reason);
-               break;
-
-       case CLOCK_EVT_NOTIFY_CPU_DYING:
-               tick_handover_do_timer(arg);
-               break;
-
-       case CLOCK_EVT_NOTIFY_SUSPEND:
-               tick_suspend();
-               tick_suspend_broadcast();
-               break;
-
-       case CLOCK_EVT_NOTIFY_RESUME:
-               tick_resume();
-               break;
-
-       case CLOCK_EVT_NOTIFY_CPU_DEAD:
-               tick_shutdown_broadcast_oneshot(arg);
-               tick_shutdown_broadcast(arg);
-               tick_shutdown(arg);
-               /*
-                * Unregister the clock event devices which were
-                * released from the users in the notify chain.
-                */
-               list_for_each_entry_safe(dev, tmp, &clockevents_released, list)
+       tick_shutdown_broadcast_oneshot(cpu);
+       tick_shutdown_broadcast(cpu);
+       tick_shutdown(cpu);
+       /*
+        * Unregister the clock event devices which were
+        * released from the users in the notify chain.
+        */
+       list_for_each_entry_safe(dev, tmp, &clockevents_released, list)
+               list_del(&dev->list);
+       /*
+        * Now check whether the CPU has left unused per cpu devices
+        */
+       list_for_each_entry_safe(dev, tmp, &clockevent_devices, list) {
+               if (cpumask_test_cpu(cpu, dev->cpumask) &&
+                   cpumask_weight(dev->cpumask) == 1 &&
+                   !tick_is_broadcast_device(dev)) {
+                       BUG_ON(dev->state != CLOCK_EVT_STATE_DETACHED);
                        list_del(&dev->list);
-               /*
-                * Now check whether the CPU has left unused per cpu devices
-                */
-               cpu = *((int *)arg);
-               list_for_each_entry_safe(dev, tmp, &clockevent_devices, list) {
-                       if (cpumask_test_cpu(cpu, dev->cpumask) &&
-                           cpumask_weight(dev->cpumask) == 1 &&
-                           !tick_is_broadcast_device(dev)) {
-                               BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
-                               list_del(&dev->list);
-                       }
                }
-               break;
-       default:
-               break;
        }
        raw_spin_unlock_irqrestore(&clockevents_lock, flags);
-       return ret;
 }
-EXPORT_SYMBOL_GPL(clockevents_notify);
+#endif
 
 #ifdef CONFIG_SYSFS
 struct bus_type clockevents_subsys = {
@@ -727,5 +794,3 @@ static int __init clockevents_init_sysfs(void)
 }
 device_initcall(clockevents_init_sysfs);
 #endif /* SYSFS */
-
-#endif /* GENERIC_CLOCK_EVENTS */