Commit 5d0e600d903caa09e790824cc5812f0d97113b23

Authored by Ingo Molnar
Committed by Andi Kleen
1 parent 310adfdd91

[PATCH] x86: fix laptop bootup hang in init_acpi()

During kernel bootup, a new T60 laptop (CoreDuo, 32-bit) hangs about
10%-20% of the time in acpi_init():

 Calling initcall 0xc055ce1a: topology_init+0x0/0x2f()
 Calling initcall 0xc055d75e: mtrr_init_finialize+0x0/0x2c()
 Calling initcall 0xc05664f3: param_sysfs_init+0x0/0x175()
 Calling initcall 0xc014cb65: pm_sysrq_init+0x0/0x17()
 Calling initcall 0xc0569f99: init_bio+0x0/0xf4()
 Calling initcall 0xc056b865: genhd_device_init+0x0/0x50()
 Calling initcall 0xc056c4bd: fbmem_init+0x0/0x87()
 Calling initcall 0xc056dd74: acpi_init+0x0/0x1ee()

It's a hard hang that not even an NMI could punch through!  Frustratingly,
adding printks or function tracing to the ACPI code made the hangs go away
...

After some time an additional detail emerged: disabling the NMI watchdog
made these occasional hangs go away.

So i spent the better part of today trying to debug this and trying out
various theories when i finally found the likely reason for the hang: if
acpi_ns_initialize_devices() executes an _INI AML method and an NMI
happens to hit that AML execution in the wrong moment, the machine would
hang.  (my theory is that this must be some sort of chipset setup method
doing stores to chipset mmio registers?)

Unfortunately given the characteristics of the hang it was sheer
impossible to figure out which of the numerous AML methods is impacted
by this problem.

As a workaround i wrote an interface to disable chipset-based NMIs while
executing _INI sections - and indeed this fixed the hang.  I did a
boot-loop of 100 separate reboots and none hung - while without the patch
it would hang every 5-10 attempts.  Out of caution i did not touch the
nmi_watchdog=2 case (it's not related to the chipset anyway and didnt
hang).

I implemented this for both x86_64 and i686, tested the i686 laptop both
with nmi_watchdog=1 [which triggered the hangs] and nmi_watchdog=2, and
tested an Athlon64 box with the 64-bit kernel as well. Everything builds
and works with the patch applied.

Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Andi Kleen <ak@suse.de>
Cc: Andi Kleen <ak@suse.de>
Cc: Len Brown <lenb@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

Showing 4 changed files with 72 additions and 1 deletions Side-by-side Diff

arch/i386/kernel/nmi.c
... ... @@ -383,6 +383,34 @@
383 383 }
384 384 }
385 385  
  386 +static void __acpi_nmi_disable(void *__unused)
  387 +{
  388 + apic_write_around(APIC_LVT0, APIC_DM_NMI | APIC_LVT_MASKED);
  389 +}
  390 +
  391 +/*
  392 + * Disable timer based NMIs on all CPUs:
  393 + */
  394 +void acpi_nmi_disable(void)
  395 +{
  396 + if (atomic_read(&nmi_active) && nmi_watchdog == NMI_IO_APIC)
  397 + on_each_cpu(__acpi_nmi_disable, NULL, 0, 1);
  398 +}
  399 +
  400 +static void __acpi_nmi_enable(void *__unused)
  401 +{
  402 + apic_write_around(APIC_LVT0, APIC_DM_NMI);
  403 +}
  404 +
  405 +/*
  406 + * Enable timer based NMIs on all CPUs:
  407 + */
  408 +void acpi_nmi_enable(void)
  409 +{
  410 + if (atomic_read(&nmi_active) && nmi_watchdog == NMI_IO_APIC)
  411 + on_each_cpu(__acpi_nmi_enable, NULL, 0, 1);
  412 +}
  413 +
386 414 #ifdef CONFIG_PM
387 415  
388 416 static int nmi_pm_active; /* nmi_active before suspend */
arch/x86_64/kernel/nmi.c
... ... @@ -368,6 +368,33 @@
368 368 }
369 369 }
370 370  
  371 +static void __acpi_nmi_disable(void *__unused)
  372 +{
  373 + apic_write(APIC_LVT0, APIC_DM_NMI | APIC_LVT_MASKED);
  374 +}
  375 +
  376 +/*
  377 + * Disable timer based NMIs on all CPUs:
  378 + */
  379 +void acpi_nmi_disable(void)
  380 +{
  381 + if (atomic_read(&nmi_active) && nmi_watchdog == NMI_IO_APIC)
  382 + on_each_cpu(__acpi_nmi_disable, NULL, 0, 1);
  383 +}
  384 +
  385 +static void __acpi_nmi_enable(void *__unused)
  386 +{
  387 + apic_write(APIC_LVT0, APIC_DM_NMI);
  388 +}
  389 +
  390 +/*
  391 + * Enable timer based NMIs on all CPUs:
  392 + */
  393 +void acpi_nmi_enable(void)
  394 +{
  395 + if (atomic_read(&nmi_active) && nmi_watchdog == NMI_IO_APIC)
  396 + on_each_cpu(__acpi_nmi_enable, NULL, 0, 1);
  397 +}
371 398 #ifdef CONFIG_PM
372 399  
373 400 static int nmi_pm_active; /* nmi_active before suspend */
drivers/acpi/namespace/nsinit.c
... ... @@ -45,6 +45,7 @@
45 45 #include <acpi/acnamesp.h>
46 46 #include <acpi/acdispat.h>
47 47 #include <acpi/acinterp.h>
  48 +#include <linux/nmi.h>
48 49  
49 50 #define _COMPONENT ACPI_NAMESPACE
50 51 ACPI_MODULE_NAME("nsinit")
51 52  
... ... @@ -534,7 +535,15 @@
534 535 info->parameter_type = ACPI_PARAM_ARGS;
535 536 info->flags = ACPI_IGNORE_RETURN_VALUE;
536 537  
  538 + /*
  539 + * Some hardware relies on this being executed as atomically
  540 + * as possible (without an NMI being received in the middle of
  541 + * this) - so disable NMIs and initialize the device:
  542 + */
  543 + acpi_nmi_disable();
537 544 status = acpi_ns_evaluate(info);
  545 + acpi_nmi_enable();
  546 +
538 547 if (ACPI_SUCCESS(status)) {
539 548 walk_info->num_INI++;
540 549  
... ... @@ -17,8 +17,15 @@
17 17 #ifdef ARCH_HAS_NMI_WATCHDOG
18 18 #include <asm/nmi.h>
19 19 extern void touch_nmi_watchdog(void);
  20 +extern void acpi_nmi_disable(void);
  21 +extern void acpi_nmi_enable(void);
20 22 #else
21   -# define touch_nmi_watchdog() touch_softlockup_watchdog()
  23 +static inline void touch_nmi_watchdog(void)
  24 +{
  25 + touch_softlockup_watchdog();
  26 +}
  27 +static inline void acpi_nmi_disable(void) { }
  28 +static inline void acpi_nmi_enable(void) { }
22 29 #endif
23 30  
24 31 #ifndef trigger_all_cpu_backtrace