Commit 2951d5c0 authored by Thomas Gleixner's avatar Thomas Gleixner

tick: broadcast: Prevent livelock from event handler

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: c6eb3f70 "hrtimer: Get rid of hrtimer softirq"
Reported-and-tested-by: default avatarBorislav Petkov <bp@alien8.de>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent 30fbd590
...@@ -255,18 +255,18 @@ int tick_receive_broadcast(void) ...@@ -255,18 +255,18 @@ int tick_receive_broadcast(void)
/* /*
* Broadcast the event to the cpus, which are set in the mask (mangled). * 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(); int cpu = smp_processor_id();
struct tick_device *td; struct tick_device *td;
bool local = false;
/* /*
* Check, if the current cpu is in the mask * Check, if the current cpu is in the mask
*/ */
if (cpumask_test_cpu(cpu, mask)) { if (cpumask_test_cpu(cpu, mask)) {
cpumask_clear_cpu(cpu, mask); cpumask_clear_cpu(cpu, mask);
td = &per_cpu(tick_cpu_device, cpu); local = true;
td->evtdev->event_handler(td->evtdev);
} }
if (!cpumask_empty(mask)) { if (!cpumask_empty(mask)) {
...@@ -279,16 +279,17 @@ static void tick_do_broadcast(struct cpumask *mask) ...@@ -279,16 +279,17 @@ static void tick_do_broadcast(struct cpumask *mask)
td = &per_cpu(tick_cpu_device, cpumask_first(mask)); td = &per_cpu(tick_cpu_device, cpumask_first(mask));
td->evtdev->broadcast(mask); td->evtdev->broadcast(mask);
} }
return local;
} }
/* /*
* Periodic broadcast: * Periodic broadcast:
* - invoke the broadcast handlers * - 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); 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) ...@@ -296,34 +297,26 @@ static void tick_do_periodic_broadcast(void)
*/ */
static void tick_handle_periodic_broadcast(struct clock_event_device *dev) 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); 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);
/* clockevents_program_event(dev, next, true);
* The device is in periodic mode. No reprogramming necessary: }
*/ raw_spin_unlock(&tick_broadcast_lock);
if (dev->state == CLOCK_EVT_STATE_PERIODIC)
goto unlock;
/* /*
* Setup the next period for devices, which do not have * We run the handler of the local cpu after dropping
* periodic mode. We read dev->next_event first and add to it * tick_broadcast_lock because the handler might deadlock when
* when the event already expired. clockevents_program_event() * trying to switch to oneshot mode.
* sets dev->next_event only when the event is really
* programmed to the device.
*/ */
for (next = dev->next_event; ;) { if (bc_local)
next = ktime_add(next, tick_period); td->evtdev->event_handler(td->evtdev);
if (!clockevents_program_event(dev, next, false))
goto unlock;
tick_do_periodic_broadcast();
}
unlock:
raw_spin_unlock(&tick_broadcast_lock);
} }
/** /**
...@@ -622,9 +615,13 @@ static void tick_handle_oneshot_broadcast(struct clock_event_device *dev) ...@@ -622,9 +615,13 @@ static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)
cpumask_and(tmpmask, tmpmask, cpu_online_mask); 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: * Two reasons for reprogram:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment