x86/irq: Seperate unused system vectors from spurious entry again
authorThomas Gleixner <tglx@linutronix.de>
Fri, 28 Jun 2019 11:11:54 +0000 (13:11 +0200)
committerThomas Gleixner <tglx@linutronix.de>
Wed, 3 Jul 2019 08:12:31 +0000 (10:12 +0200)
Quite some time ago the interrupt entry stubs for unused vectors in the
system vector range got removed and directly mapped to the spurious
interrupt vector entry point.

Sounds reasonable, but it's subtly broken. The spurious interrupt vector
entry point pushes vector number 0xFF on the stack which makes the whole
logic in __smp_spurious_interrupt() pointless.

As a consequence any spurious interrupt which comes from a vector != 0xFF
is treated as a real spurious interrupt (vector 0xFF) and not
acknowledged. That subsequently stalls all interrupt vectors of equal and
lower priority, which brings the system to a grinding halt.

This can happen because even on 64-bit the system vector space is not
guaranteed to be fully populated. A full compile time handling of the
unused vectors is not possible because quite some of them are conditonally
populated at runtime.

Bring the entry stubs back, which wastes 160 bytes if all stubs are unused,
but gains the proper handling back. There is no point to selectively spare
some of the stubs which are known at compile time as the required code in
the IDT management would be way larger and convoluted.

Do not route the spurious entries through common_interrupt and do_IRQ() as
the original code did. Route it to smp_spurious_interrupt() which evaluates
the vector number and acts accordingly now that the real vector numbers are
handed in.

Fixup the pr_warn so the actual spurious vector (0xff) is clearly
distiguished from the other vectors and also note for the vectored case
whether it was pending in the ISR or not.

 "Spurious APIC interrupt (vector 0xFF) on CPU#0, should never happen."
 "Spurious interrupt vector 0xed on CPU#1. Acked."
 "Spurious interrupt vector 0xee on CPU#1. Not pending!."

Fixes: 2414e021ac8d ("x86: Avoid building unused IRQ entry stubs")
Reported-by: Jan Kiszka <jan.kiszka@siemens.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Marc Zyngier <marc.zyngier@arm.com>
Cc: Jan Beulich <jbeulich@suse.com>
Link: https://lkml.kernel.org/r/20190628111440.550568228@linutronix.de
arch/x86/entry/entry_32.S
arch/x86/entry/entry_64.S
arch/x86/include/asm/hw_irq.h
arch/x86/kernel/apic/apic.c
arch/x86/kernel/idt.c

index 7b23431be5cb6a535f657c27868dbb0600d65423..44c6e6f54bf7d4732ace4fc3f8267be8f46a2f1d 100644 (file)
@@ -1104,6 +1104,30 @@ ENTRY(irq_entries_start)
     .endr
 END(irq_entries_start)
 
+#ifdef CONFIG_X86_LOCAL_APIC
+       .align 8
+ENTRY(spurious_entries_start)
+    vector=FIRST_SYSTEM_VECTOR
+    .rept (NR_VECTORS - FIRST_SYSTEM_VECTOR)
+       pushl   $(~vector+0x80)                 /* Note: always in signed byte range */
+    vector=vector+1
+       jmp     common_spurious
+       .align  8
+    .endr
+END(spurious_entries_start)
+
+common_spurious:
+       ASM_CLAC
+       addl    $-0x80, (%esp)                  /* Adjust vector into the [-256, -1] range */
+       SAVE_ALL switch_stacks=1
+       ENCODE_FRAME_POINTER
+       TRACE_IRQS_OFF
+       movl    %esp, %eax
+       call    smp_spurious_interrupt
+       jmp     ret_from_intr
+ENDPROC(common_interrupt)
+#endif
+
 /*
  * the CPU automatically disables interrupts when executing an IRQ vector,
  * so IRQ-flags tracing has to follow that:
index 20e45d9b4e156cc90a464715b0370f27f259ee80..6d835991bb23b3ca91346088053d4ea34a829a06 100644 (file)
@@ -375,6 +375,18 @@ ENTRY(irq_entries_start)
     .endr
 END(irq_entries_start)
 
+       .align 8
+ENTRY(spurious_entries_start)
+    vector=FIRST_SYSTEM_VECTOR
+    .rept (NR_VECTORS - FIRST_SYSTEM_VECTOR)
+       UNWIND_HINT_IRET_REGS
+       pushq   $(~vector+0x80)                 /* Note: always in signed byte range */
+       jmp     common_spurious
+       .align  8
+       vector=vector+1
+    .endr
+END(spurious_entries_start)
+
 .macro DEBUG_ENTRY_ASSERT_IRQS_OFF
 #ifdef CONFIG_DEBUG_ENTRY
        pushq %rax
@@ -571,10 +583,20 @@ _ASM_NOKPROBE(interrupt_entry)
 
 /* Interrupt entry/exit. */
 
-       /*
-        * The interrupt stubs push (~vector+0x80) onto the stack and
-        * then jump to common_interrupt.
-        */
+/*
+ * The interrupt stubs push (~vector+0x80) onto the stack and
+ * then jump to common_spurious/interrupt.
+ */
+common_spurious:
+       addq    $-0x80, (%rsp)                  /* Adjust vector to [-256, -1] range */
+       call    interrupt_entry
+       UNWIND_HINT_REGS indirect=1
+       call    smp_spurious_interrupt          /* rdi points to pt_regs */
+       jmp     ret_from_intr
+END(common_spurious)
+_ASM_NOKPROBE(common_spurious)
+
+/* common_interrupt is a hotpath. Align it */
        .p2align CONFIG_X86_L1_CACHE_SHIFT
 common_interrupt:
        addq    $-0x80, (%rsp)                  /* Adjust vector to [-256, -1] range */
index 626e1ac6516ee63628765d83187011afff887d49..cbd97e22d2f3197053f785dd45f59e08448e7d6a 100644 (file)
@@ -150,6 +150,8 @@ extern char irq_entries_start[];
 #define trace_irq_entries_start irq_entries_start
 #endif
 
+extern char spurious_entries_start[];
+
 #define VECTOR_UNUSED          NULL
 #define VECTOR_SHUTDOWN                ((void *)~0UL)
 #define VECTOR_RETRIGGERED     ((void *)~1UL)
index 29fd50840b55a039d2a876eef70989426023b1c7..a5241b209ea5c726c522247a583fd598338ded0c 100644 (file)
@@ -2068,21 +2068,32 @@ __visible void __irq_entry smp_spurious_interrupt(struct pt_regs *regs)
        entering_irq();
        trace_spurious_apic_entry(vector);
 
+       inc_irq_stat(irq_spurious_count);
+
+       /*
+        * If this is a spurious interrupt then do not acknowledge
+        */
+       if (vector == SPURIOUS_APIC_VECTOR) {
+               /* See SDM vol 3 */
+               pr_info("Spurious APIC interrupt (vector 0xFF) on CPU#%d, should never happen.\n",
+                       smp_processor_id());
+               goto out;
+       }
+
        /*
-        * Check if this really is a spurious interrupt and ACK it
-        * if it is a vectored one.  Just in case...
-        * Spurious interrupts should not be ACKed.
+        * If it is a vectored one, verify it's set in the ISR. If set,
+        * acknowledge it.
         */
        v = apic_read(APIC_ISR + ((vector & ~0x1f) >> 1));
-       if (v & (1 << (vector & 0x1f)))
+       if (v & (1 << (vector & 0x1f))) {
+               pr_info("Spurious interrupt (vector 0x%02x) on CPU#%d. Acked\n",
+                       vector, smp_processor_id());
                ack_APIC_irq();
-
-       inc_irq_stat(irq_spurious_count);
-
-       /* see sw-dev-man vol 3, chapter 7.4.13.5 */
-       pr_info("spurious APIC interrupt through vector %02x on CPU#%d, "
-               "should never happen.\n", vector, smp_processor_id());
-
+       } else {
+               pr_info("Spurious interrupt (vector 0x%02x) on CPU#%d. Not pending!\n",
+                       vector, smp_processor_id());
+       }
+out:
        trace_spurious_apic_exit(vector);
        exiting_irq();
 }
index 6d8917875f44a844e1f1eb20a35fc73a65655984..cc4444cb3898ca8ff8933062ddbf81d1c3b2defa 100644 (file)
@@ -320,7 +320,8 @@ void __init idt_setup_apic_and_irq_gates(void)
 #ifdef CONFIG_X86_LOCAL_APIC
        for_each_clear_bit_from(i, system_vectors, NR_VECTORS) {
                set_bit(i, system_vectors);
-               set_intr_gate(i, spurious_interrupt);
+               entry = spurious_entries_start + 8 * (i - FIRST_SYSTEM_VECTOR);
+               set_intr_gate(i, entry);
        }
 #endif
 }