📄 io_apic.c
字号:
spin_unlock_irqrestore(&ioapic_lock, flags); /* * Sanity check */ spin_lock_irqsave(&ioapic_lock, flags); *(int *)®_00 = io_apic_read(apic, 0); spin_unlock_irqrestore(&ioapic_lock, flags); if (reg_00.ID != mp_ioapics[apic].mpc_apicid) panic("could not set ID!\n"); else printk(" ok.\n"); }}/* * There is a nasty bug in some older SMP boards, their mptable lies * about the timer IRQ. We do the following to work around the situation: * * - timer IRQ defaults to IO-APIC IRQ * - if this function detects that timer IRQs are defunct, then we fall * back to ISA timer IRQs */static int __init timer_irq_works(void){ unsigned int t1 = jiffies; sti(); /* Let ten ticks pass... */ mdelay((10 * 1000) / HZ); /* * Expect a few ticks at least, to be sure some possible * glue logic does not lock up after one or two first * ticks in a non-ExtINT mode. Also the local APIC * might have cached one ExtINT interrupt. Finally, at * least one tick may be lost due to delays. */ if (jiffies - t1 > 4) return 1; return 0;}/* * In the SMP+IOAPIC case it might happen that there are an unspecified * number of pending IRQ events unhandled. These cases are very rare, * so we 'resend' these IRQs via IPIs, to the same CPU. It's much * better to do it this way as thus we do not have to be aware of * 'pending' interrupts in the IRQ path, except at this point. *//* * Edge triggered needs to resend any interrupt * that was delayed but this is now handled in the device * independent code. */#define enable_edge_ioapic_irq unmask_IO_APIC_irqstatic void disable_edge_ioapic_irq (unsigned int irq) { /* nothing */ }/* * Starting up a edge-triggered IO-APIC interrupt is * nasty - we need to make sure that we get the edge. * If it is already asserted for some reason, we need * return 1 to indicate that is was pending. * * This is not complete - we should be able to fake * an edge even if it isn't on the 8259A... */static unsigned int startup_edge_ioapic_irq(unsigned int irq){ int was_pending = 0; unsigned long flags; spin_lock_irqsave(&ioapic_lock, flags); if (irq < 16) { disable_8259A_irq(irq); if (i8259A_irq_pending(irq)) was_pending = 1; } __unmask_IO_APIC_irq(irq); spin_unlock_irqrestore(&ioapic_lock, flags); return was_pending;}#define shutdown_edge_ioapic_irq disable_edge_ioapic_irq/* * Once we have recorded IRQ_PENDING already, we can mask the * interrupt for real. This prevents IRQ storms from unhandled * devices. */static void ack_edge_ioapic_irq(unsigned int irq){ if ((irq_desc[irq].status & (IRQ_PENDING | IRQ_DISABLED)) == (IRQ_PENDING | IRQ_DISABLED)) mask_IO_APIC_irq(irq); ack_APIC_irq();}static void end_edge_ioapic_irq (unsigned int i) { /* nothing */ }/* * Level triggered interrupts can just be masked, * and shutting down and starting up the interrupt * is the same as enabling and disabling them -- except * with a startup need to return a "was pending" value. * * Level triggered interrupts are special because we * do not touch any IO-APIC register while handling * them. We ack the APIC in the end-IRQ handler, not * in the start-IRQ-handler. Protection against reentrance * from the same interrupt is still provided, both by the * generic IRQ layer and by the fact that an unacked local * APIC does not accept IRQs. */static unsigned int startup_level_ioapic_irq (unsigned int irq){ unmask_IO_APIC_irq(irq); return 0; /* don't check for pending */}#define shutdown_level_ioapic_irq mask_IO_APIC_irq#define enable_level_ioapic_irq unmask_IO_APIC_irq#define disable_level_ioapic_irq mask_IO_APIC_irqstatic void end_level_ioapic_irq (unsigned int irq){ unsigned long v; int i;/* * It appears there is an erratum which affects at least version 0x11 * of I/O APIC (that's the 82093AA and cores integrated into various * chipsets). Under certain conditions a level-triggered interrupt is * erroneously delivered as edge-triggered one but the respective IRR * bit gets set nevertheless. As a result the I/O unit expects an EOI * message but it will never arrive and further interrupts are blocked * from the source. The exact reason is so far unknown, but the * phenomenon was observed when two consecutive interrupt requests * from a given source get delivered to the same CPU and the source is * temporarily disabled in between. * * A workaround is to simulate an EOI message manually. We achieve it * by setting the trigger mode to edge and then to level when the edge * trigger mode gets detected in the TMR of a local APIC for a * level-triggered interrupt. We mask the source for the time of the * operation to prevent an edge-triggered interrupt escaping meanwhile. * The idea is from Manfred Spraul. --macro */ i = IO_APIC_VECTOR(irq); v = apic_read(APIC_TMR + ((i & ~0x1f) >> 1)); ack_APIC_irq(); if (!(v & (1 << (i & 0x1f)))) {#ifdef APIC_LOCKUP_DEBUG struct irq_pin_list *entry;#endif#ifdef APIC_MISMATCH_DEBUG atomic_inc(&irq_mis_count);#endif spin_lock(&ioapic_lock); __mask_and_edge_IO_APIC_irq(irq);#ifdef APIC_LOCKUP_DEBUG for (entry = irq_2_pin + irq;;) { unsigned int reg; if (entry->pin == -1) break; reg = io_apic_read(entry->apic, 0x10 + entry->pin * 2); if (reg & 0x00004000) printk(KERN_CRIT "Aieee!!! Remote IRR" " still set after unlock!\n"); if (!entry->next) break; entry = irq_2_pin + entry->next; }#endif __unmask_and_level_IO_APIC_irq(irq); spin_unlock(&ioapic_lock); }}static void mask_and_ack_level_ioapic_irq (unsigned int irq) { /* nothing */ }static void set_ioapic_affinity (unsigned int irq, unsigned long mask){ unsigned long flags; /* * Only the first 8 bits are valid. */ mask = mask << 24; spin_lock_irqsave(&ioapic_lock, flags); __DO_ACTION(1, = mask, ) spin_unlock_irqrestore(&ioapic_lock, flags);}/* * Level and edge triggered IO-APIC interrupts need different handling, * so we use two separate IRQ descriptors. Edge triggered IRQs can be * handled with the level-triggered descriptor, but that one has slightly * more overhead. Level-triggered interrupts cannot be handled with the * edge-triggered handler, without risking IRQ storms and other ugly * races. */static struct hw_interrupt_type ioapic_edge_irq_type = { "IO-APIC-edge", startup_edge_ioapic_irq, shutdown_edge_ioapic_irq, enable_edge_ioapic_irq, disable_edge_ioapic_irq, ack_edge_ioapic_irq, end_edge_ioapic_irq, set_ioapic_affinity,};static struct hw_interrupt_type ioapic_level_irq_type = { "IO-APIC-level", startup_level_ioapic_irq, shutdown_level_ioapic_irq, enable_level_ioapic_irq, disable_level_ioapic_irq, mask_and_ack_level_ioapic_irq, end_level_ioapic_irq, set_ioapic_affinity,};static inline void init_IO_APIC_traps(void){ int irq; /* * NOTE! The local APIC isn't very good at handling * multiple interrupts at the same interrupt level. * As the interrupt level is determined by taking the * vector number and shifting that right by 4, we * want to spread these out a bit so that they don't * all fall in the same interrupt level. * * Also, we've got to be careful not to trash gate * 0x80, because int 0x80 is hm, kind of importantish. ;) */ for (irq = 0; irq < NR_IRQS ; irq++) { if (IO_APIC_IRQ(irq) && !IO_APIC_VECTOR(irq)) { /* * Hmm.. We don't have an entry for this, * so default to an old-fashioned 8259 * interrupt if we can.. */ if (irq < 16) make_8259A_irq(irq); else /* Strange. Oh, well.. */ irq_desc[irq].handler = &no_irq_type; } }}static void enable_lapic_irq (unsigned int irq){ unsigned long v; v = apic_read(APIC_LVT0); apic_write_around(APIC_LVT0, v & ~APIC_LVT_MASKED);}static void disable_lapic_irq (unsigned int irq){ unsigned long v; v = apic_read(APIC_LVT0); apic_write_around(APIC_LVT0, v | APIC_LVT_MASKED);}static void ack_lapic_irq (unsigned int irq){ ack_APIC_irq();}static void end_lapic_irq (unsigned int i) { /* nothing */ }static struct hw_interrupt_type lapic_irq_type = { "local-APIC-edge", NULL, /* startup_irq() not used for IRQ0 */ NULL, /* shutdown_irq() not used for IRQ0 */ enable_lapic_irq, disable_lapic_irq, ack_lapic_irq, end_lapic_irq};static void enable_NMI_through_LVT0 (void * dummy){ unsigned int v, ver; ver = apic_read(APIC_LVR); ver = GET_APIC_VERSION(ver); v = APIC_DM_NMI; /* unmask and set to NMI */ if (!APIC_INTEGRATED(ver)) /* 82489DX */ v |= APIC_LVT_LEVEL_TRIGGER; apic_write_around(APIC_LVT0, v);}static void setup_nmi (void){ /* * Dirty trick to enable the NMI watchdog ... * We put the 8259A master into AEOI mode and * unmask on all local APICs LVT0 as NMI. * * The idea to use the 8259A in AEOI mode ('8259A Virtual Wire') * is from Maciej W. Rozycki - so we do not have to EOI from * the NMI handler or the timer interrupt. */ printk(KERN_INFO "activating NMI Watchdog ..."); smp_call_function(enable_NMI_through_LVT0, NULL, 1, 1); enable_NMI_through_LVT0(NULL); printk(" done.\n");}/* * This looks a bit hackish but it's about the only one way of sending * a few INTA cycles to 8259As and any associated glue logic. ICR does * not support the ExtINT mode, unfortunately. We need to send these * cycles as some i82489DX-based boards have glue logic that keeps the * 8259A interrupt line asserted until INTA. --macro */static inline void unlock_ExtINT_logic(void){ int pin, i; struct IO_APIC_route_entry entry0, entry1; unsigned char save_control, save_freq_select; unsigned long flags; pin = find_isa_irq_pin(8, mp_INT); if (pin == -1) return; spin_lock_irqsave(&ioapic_lock, flags); *(((int *)&entry0) + 1) = io_apic_read(0, 0x11 + 2 * pin); *(((int *)&entry0) + 0) = io_apic_read(0, 0x10 + 2 * pin); spin_unlock_irqrestore(&ioapic_lock, flags); clear_IO_APIC_pin(0, pin); memset(&entry1, 0, sizeof(entry1)); entry1.dest_mode = 0; /* physical delivery */ entry1.mask = 0; /* unmask IRQ now */ entry1.dest.physical.physical_dest = hard_smp_processor_id(); entry1.delivery_mode = dest_ExtINT; entry1.polarity = entry0.polarity; entry1.trigger = 0; entry1.vector = 0; spin_lock_irqsave(&ioapic_lock, flags); io_apic_write(0, 0x11 + 2 * pin, *(((int *)&entry1) + 1)); io_apic_write(0, 0x10 + 2 * pin, *(((int *)&entry1) + 0)); spin_unlock_irqrestore(&ioapic_lock, flags); save_control = CMOS_READ(RTC_CONTROL); save_freq_select = CMOS_READ(RTC_FREQ_SELECT); CMOS_WRITE((save_freq_select & ~RTC_RATE_SELECT) | 0x6, RTC_FREQ_SELECT); CMOS_WRITE(save_control | RTC_PIE, RTC_CONTROL); i = 100; while (i-- > 0) { mdelay(10); if ((CMOS_READ(RTC_INTR_FLAGS) & RTC_PF) == RTC_PF) i -= 10; } CMOS_WRITE(save_control, RTC_CONTROL); CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); clear_IO_APIC_pin(0, pin); spin_lock_irqsave(&ioapic_lock, flags); io_apic_write(0, 0x11 + 2 * pin, *(((int *)&entry0) + 1)); io_apic_write(0, 0x10 + 2 * pin, *(((int *)&entry0) + 0)); spin_unlock_irqrestore(&ioapic_lock, flags);}/* * This code may look a bit paranoid, but it's supposed to cooperate with * a wide range of boards and BIOS bugs. Fortunately only the timer IRQ * is so screwy. Thanks to Brian Perkins for testing/hacking this beast * fanatically on his truly buggy board. */static inline void check_timer(void){ extern int timer_ack; int pin1, pin2; int vector; /* * get/set the timer IRQ vector: */ disable_8259A_irq(0); vector = assign_irq_vector(0); set_intr_gate(vector, interrupt[0]); /* * Subtle, code in do_timer_interrupt() expects an AEOI * mode for the 8259A whenever interrupts are routed * through I/O APICs. Also IRQ0 has to be enabled in * the 8259A which implies the virtual wire has to be * disabled in the local APIC. */ apic_write_around(APIC_LVT0, APIC_LVT_MASKED | APIC_DM_EXTINT); init_8259A(1); timer_ack = 1; enable_8259A_irq(0); pin1 = find_isa_irq_pin(0, mp_INT); pin2 = find_isa_irq_pin(0, mp_ExtINT); printk(KERN_INFO "..TIMER: vector=0x%02X pin1=%d pin2=%d\n", vector, pin1, pin2); if (pin1 != -1) { /* * Ok, does IRQ0 through the IOAPIC work? */ unmask_IO_APIC_irq(0); if (timer_irq_works()) { if (nmi_watchdog == NMI_IO_APIC) { disable_8259A_irq(0); setup_nmi(); enable_8259A_irq(0); check_nmi_watchdog(); } return; } clear_IO_APIC_pin(0, pin1); printk(KERN_ERR "..MP-BIOS bug: 8254 timer not connected to IO-APIC\n"); } printk(KERN_INFO "...trying to set up timer (IRQ0) through the 8259A ... "); if (pin2 != -1) { printk("\n..... (found pin %d) ...", pin2); /* * legacy devices should be connected to IO APIC #0 */ setup_ExtINT_IRQ0_pin(pin2, vector); if (timer_irq_works()) { printk("works.\n"); if (nmi_watchdog == NMI_IO_APIC) { setup_nmi(); check_nmi_watchdog(); } return; } /* * Cleanup, just in case ... */ clear_IO_APIC_pin(0, pin2); } printk(" failed.\n"); if (nmi_watchdog) { printk(KERN_WARNING "timer doesnt work through the IO-APIC - disabling NMI Watchdog!\n"); nmi_watchdog = 0; } printk(KERN_INFO "...trying to set up timer as Virtual Wire IRQ..."); disable_8259A_irq(0); irq_desc[0].handler = &lapic_irq_type; apic_write_around(APIC_LVT0, APIC_DM_FIXED | vector); /* Fixed mode */ enable_8259A_irq(0); if (timer_irq_works()) { printk(" works.\n"); return; } apic_write_around(APIC_LVT0, APIC_LVT_MASKED | APIC_DM_FIXED | vector); printk(" failed.\n"); printk(KERN_INFO "...trying to set up timer as ExtINT IRQ..."); init_8259A(0); make_8259A_irq(0); apic_write_around(APIC_LVT0, APIC_DM_EXTINT); unlock_ExtINT_logic(); if (timer_irq_works()) { printk(" works.\n"); return; } printk(" failed :(.\n"); panic("IO-APIC + timer doesn't work! pester mingo@redhat.com");}/* * * IRQ's that are handled by the old PIC in all cases: * - IRQ2 is the cascade IRQ, and cannot be a io-apic IRQ. * Linux doesn't really care, as it's not actually used * for any interrupt handling anyway. * - There used to be IRQ13 here as well, but all * MPS-compliant must not use it for FPU coupling and we * want to use exception 16 anyway. And there are * systems who connect it to an I/O APIC for other uses. * Thus we don't mark it special any longer. * * Additionally, something is definitely wrong with irq9 * on PIIX4 boards. */#define PIC_IRQS (1<<2)void __init setup_IO_APIC(void){ enable_IO_APIC(); io_apic_irqs = ~PIC_IRQS; printk("ENABLING IO-APIC IRQs\n"); /* * Set up the IO-APIC IRQ routing table by parsing the MP-BIOS * mptable: */ setup_ioapic_ids_from_mpc(); sync_Arb_IDs(); setup_IO_APIC_irqs(); init_IO_APIC_traps(); check_timer(); print_IO_APIC();}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -