Skip to content

Commit 60dcaad

Browse files
committed
x86/hotplug: Silence APIC and NMI when CPU is dead
In order to support IPI/NMI broadcasting via the shorthand mechanism side effects of shorthands need to be mitigated: Shorthand IPIs and NMIs hit all CPUs including unplugged CPUs Neither of those can be handled on unplugged CPUs for obvious reasons. It would be trivial to just fully disable the APIC via the enable bit in MSR_APICBASE. But that's not possible because clearing that bit on systems based on the 3 wire APIC bus would require a hardware reset to bring it back as the APIC would lose track of bus arbitration. On systems with FSB delivery APICBASE could be disabled, but it has to be guaranteed that no interrupt is sent to the APIC while in that state and it's not clear from the SDM whether it still responds to INIT/SIPI messages. Therefore stay on the safe side and switch the APIC into soft disabled mode so it won't deliver any regular vector to the CPU. NMIs are still propagated to the 'dead' CPUs. To mitigate that add a check for the CPU being offline on early nmi entry and if so bail. Note, this cannot use the stop/restart_nmi() magic which is used in the alternatives code. A dead CPU cannot invoke nmi_enter() or anything else due to RCU and other reasons. Signed-off-by: Thomas Gleixner <[email protected]> Acked-by: Peter Zijlstra (Intel) <[email protected]> Link: https://lkml.kernel.org/r/[email protected]
1 parent 9c92374 commit 60dcaad

File tree

4 files changed

+34
-12
lines changed

4 files changed

+34
-12
lines changed

arch/x86/include/asm/apic.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ extern int lapic_get_maxlvt(void);
136136
extern void clear_local_APIC(void);
137137
extern void disconnect_bsp_APIC(int virt_wire_setup);
138138
extern void disable_local_APIC(void);
139+
extern void apic_soft_disable(void);
139140
extern void lapic_shutdown(void);
140141
extern void sync_Arb_IDs(void);
141142
extern void init_bsp_APIC(void);

arch/x86/kernel/apic/apic.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,25 +1182,38 @@ void clear_local_APIC(void)
11821182
}
11831183

11841184
/**
1185-
* disable_local_APIC - clear and disable the local APIC
1185+
* apic_soft_disable - Clears and software disables the local APIC on hotplug
1186+
*
1187+
* Contrary to disable_local_APIC() this does not touch the enable bit in
1188+
* MSR_IA32_APICBASE. Clearing that bit on systems based on the 3 wire APIC
1189+
* bus would require a hardware reset as the APIC would lose track of bus
1190+
* arbitration. On systems with FSB delivery APICBASE could be disabled,
1191+
* but it has to be guaranteed that no interrupt is sent to the APIC while
1192+
* in that state and it's not clear from the SDM whether it still responds
1193+
* to INIT/SIPI messages. Stay on the safe side and use software disable.
11861194
*/
1187-
void disable_local_APIC(void)
1195+
void apic_soft_disable(void)
11881196
{
1189-
unsigned int value;
1190-
1191-
/* APIC hasn't been mapped yet */
1192-
if (!x2apic_mode && !apic_phys)
1193-
return;
1197+
u32 value;
11941198

11951199
clear_local_APIC();
11961200

1197-
/*
1198-
* Disable APIC (implies clearing of registers
1199-
* for 82489DX!).
1200-
*/
1201+
/* Soft disable APIC (implies clearing of registers for 82489DX!). */
12011202
value = apic_read(APIC_SPIV);
12021203
value &= ~APIC_SPIV_APIC_ENABLED;
12031204
apic_write(APIC_SPIV, value);
1205+
}
1206+
1207+
/**
1208+
* disable_local_APIC - clear and disable the local APIC
1209+
*/
1210+
void disable_local_APIC(void)
1211+
{
1212+
/* APIC hasn't been mapped yet */
1213+
if (!x2apic_mode && !apic_phys)
1214+
return;
1215+
1216+
apic_soft_disable();
12041217

12051218
#ifdef CONFIG_X86_32
12061219
/*

arch/x86/kernel/nmi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,9 @@ NOKPROBE_SYMBOL(is_debug_stack);
512512
dotraplinkage notrace void
513513
do_nmi(struct pt_regs *regs, long error_code)
514514
{
515+
if (IS_ENABLED(CONFIG_SMP) && cpu_is_offline(smp_processor_id()))
516+
return;
517+
515518
if (this_cpu_read(nmi_state) != NMI_NOT_RUNNING) {
516519
this_cpu_write(nmi_state, NMI_LATCHED);
517520
return;

arch/x86/kernel/smpboot.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,12 @@ int native_cpu_disable(void)
15961596
if (ret)
15971597
return ret;
15981598

1599-
clear_local_APIC();
1599+
/*
1600+
* Disable the local APIC. Otherwise IPI broadcasts will reach
1601+
* it. It still responds normally to INIT, NMI, SMI, and SIPI
1602+
* messages.
1603+
*/
1604+
apic_soft_disable();
16001605
cpu_disable_common();
16011606

16021607
return 0;

0 commit comments

Comments
 (0)