Commit 551adc60 authored by Thomas Gleixner's avatar Thomas Gleixner

x86/irq: Cure live lock in fixup_irqs()

Harry reported, that he's able to trigger a system freeze with cpu hot
unplug. The freeze turned out to be a live lock caused by recent changes in
irq_force_complete_move().

When fixup_irqs() and from there irq_force_complete_move() is called on the
dying cpu, then all other cpus are in stop machine an wait for the dying cpu
to complete the teardown. If there is a move of an interrupt pending then
irq_force_complete_move() sends the cleanup IPI to the cpus in the old_domain
mask and waits for them to clear the mask. That's obviously impossible as
those cpus are firmly stuck in stop machine with interrupts disabled.

I should have known that, but I completely overlooked it being concentrated on
the locking issues around the vectors. And the existance of the call to
__irq_complete_move() in the code, which actually sends the cleanup IPI made
it reasonable to wait for that cleanup to complete. That call was bogus even
before the recent changes as it was just a pointless distraction.

We have to look at two cases:

1) The move_in_progress flag of the interrupt is set

   This means the ioapic has been updated with the new vector, but it has not
   fired yet. In theory there is a race:

   set_ioapic(new_vector) <-- Interrupt is raised before update is effective,
   			      i.e. it's raised on the old vector. 

   So if the target cpu cannot handle that interrupt before the old vector is
   cleaned up, we get a spurious interrupt and in the worst case the ioapic
   irq line becomes stale, but my experiments so far have only resulted in
   spurious interrupts.

   But in case of cpu hotplug this should be a non issue because if the
   affinity update happens right before all cpus rendevouz in stop machine,
   there is no way that the interrupt can be blocked on the target cpu because
   all cpus loops first with interrupts enabled in stop machine, so the old
   vector is not yet cleaned up when the interrupt fires.

   So the only way to run into this issue is if the delivery of the interrupt
   on the apic/system bus would be delayed beyond the point where the target
   cpu disables interrupts in stop machine. I doubt that it can happen, but at
   least there is a theroretical chance. Virtualization might be able to
   expose this, but AFAICT the IOAPIC emulation is not as stupid as the real
   hardware.

   I've spent quite some time over the weekend to enforce that situation,
   though I was not able to trigger the delayed case.

2) The move_in_progress flag is not set and the old_domain cpu mask is not
   empty.

   That means, that an interrupt was delivered after the change and the
   cleanup IPI has been sent to the cpus in old_domain, but not all CPUs have
   responded to it yet.

In both cases we can assume that the next interrupt will arrive on the new
vector, so we can cleanup the old vectors on the cpus in the old_domain cpu
mask.

Fixes: 98229aa3 "x86/irq: Plug vector cleanup race"
Reported-by: default avatarHarry Junior <harryjr@outlook.fr>
Tested-by: default avatarTony Luck <tony.luck@intel.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Joe Lawrence <joe.lawrence@stratus.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Ben Hutchings <ben@decadent.org.uk>
Cc: stable@vger.kernel.org
Link: http://lkml.kernel.org/r/alpine.DEB.2.11.1603140931430.3657@nanosSigned-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent f508a5ba
...@@ -141,6 +141,7 @@ struct irq_alloc_info { ...@@ -141,6 +141,7 @@ struct irq_alloc_info {
struct irq_cfg { struct irq_cfg {
unsigned int dest_apicid; unsigned int dest_apicid;
u8 vector; u8 vector;
u8 old_vector;
}; };
extern struct irq_cfg *irq_cfg(unsigned int irq); extern struct irq_cfg *irq_cfg(unsigned int irq);
......
...@@ -213,6 +213,7 @@ static int __assign_irq_vector(int irq, struct apic_chip_data *d, ...@@ -213,6 +213,7 @@ static int __assign_irq_vector(int irq, struct apic_chip_data *d,
*/ */
cpumask_and(d->old_domain, d->old_domain, cpu_online_mask); cpumask_and(d->old_domain, d->old_domain, cpu_online_mask);
d->move_in_progress = !cpumask_empty(d->old_domain); d->move_in_progress = !cpumask_empty(d->old_domain);
d->cfg.old_vector = d->move_in_progress ? d->cfg.vector : 0;
d->cfg.vector = vector; d->cfg.vector = vector;
cpumask_copy(d->domain, vector_cpumask); cpumask_copy(d->domain, vector_cpumask);
success: success:
...@@ -655,46 +656,97 @@ void irq_complete_move(struct irq_cfg *cfg) ...@@ -655,46 +656,97 @@ void irq_complete_move(struct irq_cfg *cfg)
} }
/* /*
* Called with @desc->lock held and interrupts disabled. * Called from fixup_irqs() with @desc->lock held and interrupts disabled.
*/ */
void irq_force_complete_move(struct irq_desc *desc) void irq_force_complete_move(struct irq_desc *desc)
{ {
struct irq_data *irqdata = irq_desc_get_irq_data(desc); struct irq_data *irqdata = irq_desc_get_irq_data(desc);
struct apic_chip_data *data = apic_chip_data(irqdata); struct apic_chip_data *data = apic_chip_data(irqdata);
struct irq_cfg *cfg = data ? &data->cfg : NULL; struct irq_cfg *cfg = data ? &data->cfg : NULL;
unsigned int cpu;
if (!cfg) if (!cfg)
return; return;
__irq_complete_move(cfg, cfg->vector);
/* /*
* This is tricky. If the cleanup of @data->old_domain has not been * This is tricky. If the cleanup of @data->old_domain has not been
* done yet, then the following setaffinity call will fail with * done yet, then the following setaffinity call will fail with
* -EBUSY. This can leave the interrupt in a stale state. * -EBUSY. This can leave the interrupt in a stale state.
* *
* The cleanup cannot make progress because we hold @desc->lock. So in * All CPUs are stuck in stop machine with interrupts disabled so
* case @data->old_domain is not yet cleaned up, we need to drop the * calling __irq_complete_move() would be completely pointless.
* lock and acquire it again. @desc cannot go away, because the
* hotplug code holds the sparse irq lock.
*/ */
raw_spin_lock(&vector_lock); raw_spin_lock(&vector_lock);
/* Clean out all offline cpus (including ourself) first. */ /*
* Clean out all offline cpus (including the outgoing one) from the
* old_domain mask.
*/
cpumask_and(data->old_domain, data->old_domain, cpu_online_mask); cpumask_and(data->old_domain, data->old_domain, cpu_online_mask);
while (!cpumask_empty(data->old_domain)) {
raw_spin_unlock(&vector_lock);
raw_spin_unlock(&desc->lock);
cpu_relax();
raw_spin_lock(&desc->lock);
/* /*
* Reevaluate apic_chip_data. It might have been cleared after * If move_in_progress is cleared and the old_domain mask is empty,
* we dropped @desc->lock. * then there is nothing to cleanup. fixup_irqs() will take care of
* the stale vectors on the outgoing cpu.
*/ */
data = apic_chip_data(irqdata); if (!data->move_in_progress && cpumask_empty(data->old_domain)) {
if (!data) raw_spin_unlock(&vector_lock);
return; return;
raw_spin_lock(&vector_lock);
} }
/*
* 1) The interrupt is in move_in_progress state. That means that we
* have not seen an interrupt since the io_apic was reprogrammed to
* the new vector.
*
* 2) The interrupt has fired on the new vector, but the cleanup IPIs
* have not been processed yet.
*/
if (data->move_in_progress) {
/*
* In theory there is a race:
*
* set_ioapic(new_vector) <-- Interrupt is raised before update
* is effective, i.e. it's raised on
* the old vector.
*
* So if the target cpu cannot handle that interrupt before
* the old vector is cleaned up, we get a spurious interrupt
* and in the worst case the ioapic irq line becomes stale.
*
* But in case of cpu hotplug this should be a non issue
* because if the affinity update happens right before all
* cpus rendevouz in stop machine, there is no way that the
* interrupt can be blocked on the target cpu because all cpus
* loops first with interrupts enabled in stop machine, so the
* old vector is not yet cleaned up when the interrupt fires.
*
* So the only way to run into this issue is if the delivery
* of the interrupt on the apic/system bus would be delayed
* beyond the point where the target cpu disables interrupts
* in stop machine. I doubt that it can happen, but at least
* there is a theroretical chance. Virtualization might be
* able to expose this, but AFAICT the IOAPIC emulation is not
* as stupid as the real hardware.
*
* Anyway, there is nothing we can do about that at this point
* w/o refactoring the whole fixup_irq() business completely.
* We print at least the irq number and the old vector number,
* so we have the necessary information when a problem in that
* area arises.
*/
pr_warn("IRQ fixup: irq %d move in progress, old vector %d\n",
irqdata->irq, cfg->old_vector);
}
/*
* If old_domain is not empty, then other cpus still have the irq
* descriptor set in their vector array. Clean it up.
*/
for_each_cpu(cpu, data->old_domain)
per_cpu(vector_irq, cpu)[cfg->old_vector] = VECTOR_UNUSED;
/* Cleanup the left overs of the (half finished) move */
cpumask_clear(data->old_domain);
data->move_in_progress = 0;
raw_spin_unlock(&vector_lock); raw_spin_unlock(&vector_lock);
} }
#endif #endif
......
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