tick: broadcast: Prevent livelock from event handler
authorThomas Gleixner <tglx@linutronix.de>
Tue, 5 May 2015 08:00:13 +0000 (10:00 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Tue, 5 May 2015 08:25:23 +0000 (10:25 +0200)
With the removal of the hrtimer softirq the switch to highres/nohz
mode happens in the tick interrupt. That leads to a livelock when the
per cpu event handler is directly called from the broadcast handler
under broadcast lock because broadcast lock needs to be taken for the
highres/nohz switch as well.

Solve this by calling the cpu local handler outside the broadcast_lock
held region.

Fixes: c6eb3f70d448 "hrtimer: Get rid of hrtimer softirq"
Reported-and-tested-by: Borislav Petkov <bp@alien8.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
kernel/time/tick-broadcast.c

index 7e8ca4f448a88c5ad5708106bbd889e22715b3ad..5d9e4aab9797cedb6d3036ffd4a7fdc71e579c43 100644 (file)
@@ -255,18 +255,18 @@ int tick_receive_broadcast(void)
 /*
  * Broadcast the event to the cpus, which are set in the mask (mangled).
  */
-static void tick_do_broadcast(struct cpumask *mask)
+static bool tick_do_broadcast(struct cpumask *mask)
 {
        int cpu = smp_processor_id();
        struct tick_device *td;
+       bool local = false;
 
        /*
         * Check, if the current cpu is in the mask
         */
        if (cpumask_test_cpu(cpu, mask)) {
                cpumask_clear_cpu(cpu, mask);
-               td = &per_cpu(tick_cpu_device, cpu);
-               td->evtdev->event_handler(td->evtdev);
+               local = true;
        }
 
        if (!cpumask_empty(mask)) {
@@ -279,16 +279,17 @@ static void tick_do_broadcast(struct cpumask *mask)
                td = &per_cpu(tick_cpu_device, cpumask_first(mask));
                td->evtdev->broadcast(mask);
        }
+       return local;
 }
 
 /*
  * Periodic broadcast:
  * - invoke the broadcast handlers
  */
-static void tick_do_periodic_broadcast(void)
+static bool tick_do_periodic_broadcast(void)
 {
        cpumask_and(tmpmask, cpu_online_mask, tick_broadcast_mask);
-       tick_do_broadcast(tmpmask);
+       return tick_do_broadcast(tmpmask);
 }
 
 /*
@@ -296,34 +297,26 @@ static void tick_do_periodic_broadcast(void)
  */
 static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
 {
-       ktime_t next;
+       struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
+       bool bc_local;
 
        raw_spin_lock(&tick_broadcast_lock);
+       bc_local = tick_do_periodic_broadcast();
 
-       tick_do_periodic_broadcast();
+       if (dev->state == CLOCK_EVT_STATE_ONESHOT) {
+               ktime_t next = ktime_add(dev->next_event, tick_period);
 
-       /*
-        * The device is in periodic mode. No reprogramming necessary:
-        */
-       if (dev->state == CLOCK_EVT_STATE_PERIODIC)
-               goto unlock;
+               clockevents_program_event(dev, next, true);
+       }
+       raw_spin_unlock(&tick_broadcast_lock);
 
        /*
-        * Setup the next period for devices, which do not have
-        * periodic mode. We read dev->next_event first and add to it
-        * when the event already expired. clockevents_program_event()
-        * sets dev->next_event only when the event is really
-        * programmed to the device.
+        * We run the handler of the local cpu after dropping
+        * tick_broadcast_lock because the handler might deadlock when
+        * trying to switch to oneshot mode.
         */
-       for (next = dev->next_event; ;) {
-               next = ktime_add(next, tick_period);
-
-               if (!clockevents_program_event(dev, next, false))
-                       goto unlock;
-               tick_do_periodic_broadcast();
-       }
-unlock:
-       raw_spin_unlock(&tick_broadcast_lock);
+       if (bc_local)
+               td->evtdev->event_handler(td->evtdev);
 }
 
 /**
@@ -622,9 +615,13 @@ again:
                cpumask_and(tmpmask, tmpmask, cpu_online_mask);
 
        /*
-        * Wakeup the cpus which have an expired event.
+        * Wakeup the cpus which have an expired event and handle the
+        * broadcast event of the local cpu.
         */
-       tick_do_broadcast(tmpmask);
+       if (tick_do_broadcast(tmpmask)) {
+               td = this_cpu_ptr(&tick_cpu_device);
+               td->evtdev->event_handler(td->evtdev);
+       }
 
        /*
         * Two reasons for reprogram: