Skip to content

Commit 4aae070

Browse files
author
Ingo Molnar
committed
x86: fix "Kernel panic - not syncing: IO-APIC + timer doesn't work!"
this is the tale of a full day spent debugging an ancient but elusive bug. after booting up thousands of random .config kernels, i finally happened to generate a .config that produced the following rare bootup failure on 32-bit x86: | ..TIMER: vector=0x31 apic1=0 pin1=2 apic2=-1 pin2=-1 | ..MP-BIOS bug: 8254 timer not connected to IO-APIC | ...trying to set up timer (IRQ0) through the 8259A ... failed. | ...trying to set up timer as Virtual Wire IRQ... failed. | ...trying to set up timer as ExtINT IRQ... failed :(. | Kernel panic - not syncing: IO-APIC + timer doesn't work! Boot with apic=debug | and send a report. Then try booting with the 'noapic' option this bug has been reported many times during the years, but it was never reproduced nor fixed. the bug that i hit was extremely sensitive to .config details. First i did a .config-bisection - suspecting some .config detail. That led to CONFIG_X86_MCE: enabling X86_MCE magically made the bug disappear and the system would boot up just fine. Debugging my way through the MCE code ended up identifying two unlikely candidates: the thing that made a real difference to the hang was that X86_MCE did two printks: Intel machine check architecture supported. Intel machine check reporting enabled on CPU#1. Adding the same printks to a !CONFIG_X86_MCE kernel made the bug go away! this left timing as the main suspect: i experimented with adding various udelay()s to the arch/x86/kernel/io_apic_32.c:check_timer() function, and the race window turned out to be narrower than 30 microseconds (!). That made debugging especially funny, debugging without having printk ability before the bug hits is ... interesting ;-) eventually i started suspecting IRQ activities - those are pretty much the only thing that happen this early during bootup and have the timescale of a few dozen microseconds. Also, check_timer() changes the IRQ hardware in various creative ways, so the main candidate became IRQ0 interaction. i've added a counter to track timer irqs (on which core they arrived, at what exact time, etc.) and found that no timer IRQ would arrive after the bug condition hits - even if we re-enable IRQ0 and re-initialize the i8259A, but that we'd get a small number of timer irqs right around the time when we call the check_timer() function. Eventually i got the following backtrace triggered from debug code in the timer interrupt: ...trying to set up timer as Virtual Wire IRQ... failed. ...trying to set up timer as ExtINT IRQ... Pid: 1, comm: swapper Not tainted (2.6.24-rc5 torvalds#57) EIP: 0060:[<c044d57e>] EFLAGS: 00000246 CPU: 0 EIP is at _spin_unlock_irqrestore+0x5/0x1c EAX: c0634178 EBX: 00000000 ECX: c4947d63 EDX: 00000246 ESI: 00000002 EDI: 00010031 EBP: c04e0f2e ESP: f7c41df DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068 CR0: 8005003b CR2: ffe04000 CR3: 0063000 CR4: 000006d0 DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000 DR6: ffff0ff0 DR7: 00000400 [<c05f5784>] setup_IO_APIC+0x9c3/0xc5c the spin_unlock() was called from init_8259A(). Wait ... we have an IRQ0 entry while we are in the middle of setting up the local APIC, the i8259A and the PIT?? That is certainly not how it's supposed to work! check_timer() was supposed to be called with irqs turned off - but this eroded away sometime in the past. This code would still work most of the time because this code runs very quickly, but just the right timing conditions are present and IRQ0 hits in this small, ~30 usecs window, timer irqs stop and the system does not boot up. Also, given how early this is during bootup, the hang is very deterministic - but it would only occur on certain machines (and certain configs). The fix was quite simple: disable/restore interrupts properly in this function. With that in place the test-system now boots up just fine. (64-bit x86 io_apic_64.c had the same bug.) Phew! One down, only 1500 other kernel bugs are left ;-) Signed-off-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
1 parent 971e5b3 commit 4aae070

File tree

2 files changed

+24
-8
lines changed

2 files changed

+24
-8
lines changed

arch/x86/kernel/io_apic_32.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,13 +1882,16 @@ __setup("no_timer_check", notimercheck);
18821882
static int __init timer_irq_works(void)
18831883
{
18841884
unsigned long t1 = jiffies;
1885+
unsigned long flags;
18851886

18861887
if (no_timer_check)
18871888
return 1;
18881889

1890+
local_save_flags(flags);
18891891
local_irq_enable();
18901892
/* Let ten ticks pass... */
18911893
mdelay((10 * 1000) / HZ);
1894+
local_irq_restore(flags);
18921895

18931896
/*
18941897
* Expect a few ticks at least, to be sure some possible
@@ -2167,6 +2170,9 @@ static inline void __init check_timer(void)
21672170
int apic1, pin1, apic2, pin2;
21682171
int vector;
21692172
unsigned int ver;
2173+
unsigned long flags;
2174+
2175+
local_irq_save(flags);
21702176

21712177
ver = apic_read(APIC_LVR);
21722178
ver = GET_APIC_VERSION(ver);
@@ -2219,7 +2225,7 @@ static inline void __init check_timer(void)
22192225
}
22202226
if (disable_timer_pin_1 > 0)
22212227
clear_IO_APIC_pin(0, pin1);
2222-
return;
2228+
goto out;
22232229
}
22242230
clear_IO_APIC_pin(apic1, pin1);
22252231
printk(KERN_ERR "..MP-BIOS bug: 8254 timer not connected to "
@@ -2242,7 +2248,7 @@ static inline void __init check_timer(void)
22422248
if (nmi_watchdog == NMI_IO_APIC) {
22432249
setup_nmi();
22442250
}
2245-
return;
2251+
goto out;
22462252
}
22472253
/*
22482254
* Cleanup, just in case ...
@@ -2266,7 +2272,7 @@ static inline void __init check_timer(void)
22662272

22672273
if (timer_irq_works()) {
22682274
printk(" works.\n");
2269-
return;
2275+
goto out;
22702276
}
22712277
apic_write_around(APIC_LVT0, APIC_LVT_MASKED | APIC_DM_FIXED | vector);
22722278
printk(" failed.\n");
@@ -2282,11 +2288,13 @@ static inline void __init check_timer(void)
22822288

22832289
if (timer_irq_works()) {
22842290
printk(" works.\n");
2285-
return;
2291+
goto out;
22862292
}
22872293
printk(" failed :(.\n");
22882294
panic("IO-APIC + timer doesn't work! Boot with apic=debug and send a "
22892295
"report. Then try booting with the 'noapic' option");
2296+
out:
2297+
local_irq_restore(flags);
22902298
}
22912299

22922300
/*

arch/x86/kernel/io_apic_64.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,10 +1281,13 @@ void disable_IO_APIC(void)
12811281
static int __init timer_irq_works(void)
12821282
{
12831283
unsigned long t1 = jiffies;
1284+
unsigned long flags;
12841285

1286+
local_save_flags(flags);
12851287
local_irq_enable();
12861288
/* Let ten ticks pass... */
12871289
mdelay((10 * 1000) / HZ);
1290+
local_irq_restore(flags);
12881291

12891292
/*
12901293
* Expect a few ticks at least, to be sure some possible
@@ -1655,6 +1658,9 @@ static inline void check_timer(void)
16551658
{
16561659
struct irq_cfg *cfg = irq_cfg + 0;
16571660
int apic1, pin1, apic2, pin2;
1661+
unsigned long flags;
1662+
1663+
local_irq_save(flags);
16581664

16591665
/*
16601666
* get/set the timer IRQ vector:
@@ -1696,7 +1702,7 @@ static inline void check_timer(void)
16961702
}
16971703
if (disable_timer_pin_1 > 0)
16981704
clear_IO_APIC_pin(0, pin1);
1699-
return;
1705+
goto out;
17001706
}
17011707
clear_IO_APIC_pin(apic1, pin1);
17021708
apic_printk(APIC_QUIET,KERN_ERR "..MP-BIOS bug: 8254 timer not "
@@ -1718,7 +1724,7 @@ static inline void check_timer(void)
17181724
if (nmi_watchdog == NMI_IO_APIC) {
17191725
setup_nmi();
17201726
}
1721-
return;
1727+
goto out;
17221728
}
17231729
/*
17241730
* Cleanup, just in case ...
@@ -1741,7 +1747,7 @@ static inline void check_timer(void)
17411747

17421748
if (timer_irq_works()) {
17431749
apic_printk(APIC_VERBOSE," works.\n");
1744-
return;
1750+
goto out;
17451751
}
17461752
apic_write(APIC_LVT0, APIC_LVT_MASKED | APIC_DM_FIXED | cfg->vector);
17471753
apic_printk(APIC_VERBOSE," failed.\n");
@@ -1756,10 +1762,12 @@ static inline void check_timer(void)
17561762

17571763
if (timer_irq_works()) {
17581764
apic_printk(APIC_VERBOSE," works.\n");
1759-
return;
1765+
goto out;
17601766
}
17611767
apic_printk(APIC_VERBOSE," failed :(.\n");
17621768
panic("IO-APIC + timer doesn't work! Try using the 'noapic' kernel parameter\n");
1769+
out:
1770+
local_irq_restore(flags);
17631771
}
17641772

17651773
static int __init notimercheck(char *s)

0 commit comments

Comments
 (0)