@@ -257,6 +257,33 @@ ioapic_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
s->ioredtbl[index] &= 0xffffffff;
s->ioredtbl[index] |= (uint64_t)val << 32;
} else {
+ uint32_t mask = 1 << index;
+ uint8_t trig_mode =
+ ((s->ioredtbl[index] >>
+ IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1);
+ uint8_t trig_mode_new =
+ ((val >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1);
+ if ((s->irr & mask) && !(val & IOAPIC_LVT_MASKED) &&
+ trig_mode == IOAPIC_TRIGGER_EDGE &&
+ trig_mode_new == IOAPIC_TRIGGER_LEVEL) {
+ /*
+ * edge interrupt was raised and it has been masked.
+ * Now it is unmasked and its mode is changed to level.
+ *
+ * The device thinks it raised the interrupts as edge,
+ * thus they don't have to lower it.
+ * So if we just handle this pending interrupts as
+ * level, the interrupt will be never lowered.
+ * So OS interprets this interrupt storm as
+ * the interrupt line is broken.
+ *
+ * To avoid such interrupt storm, unmask the interrupt,
+ * service the pending interrupts as edge,
+ * and then change the trigger mode.
+ */
+ s->ioredtbl[index] &= ~IOAPIC_LVT_MASKED;
+ ioapic_service(s);
+ }
s->ioredtbl[index] &= ~0xffffffffULL;
s->ioredtbl[index] |= val;
}