Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+25 −28
Original line number Original line Diff line number Diff line
@@ -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)
		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)
 */
 */
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 @@ again:
		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: