📄 io_apic.c
字号:
* 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 */}int ioapic_ack_new = 1;static void setup_ioapic_ack(char *s){ if ( !strcmp(s, "old") ) ioapic_ack_new = 0; else if ( !strcmp(s, "new") ) ioapic_ack_new = 1; else printk("Unknown ioapic_ack value specified: '%s'\n", s);}custom_param("ioapic_ack", setup_ioapic_ack);static void mask_and_ack_level_ioapic_irq (unsigned int irq){ unsigned long v; int i; if ( ioapic_ack_new ) return; mask_IO_APIC_irq(irq);/* * 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)))) { atomic_inc(&irq_mis_count); spin_lock(&ioapic_lock); __edge_IO_APIC_irq(irq); __level_IO_APIC_irq(irq); spin_unlock(&ioapic_lock); }}static void end_level_ioapic_irq (unsigned int irq){ unsigned long v; int i; if ( !ioapic_ack_new ) { if ( !(irq_desc[IO_APIC_VECTOR(irq)].status & IRQ_DISABLED) ) unmask_IO_APIC_irq(irq); return; }/* * 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)))) { atomic_inc(&irq_mis_count); spin_lock(&ioapic_lock); __mask_IO_APIC_irq(irq); __edge_IO_APIC_irq(irq); __level_IO_APIC_irq(irq); if ( !(irq_desc[IO_APIC_VECTOR(irq)].status & IRQ_DISABLED) ) __unmask_IO_APIC_irq(irq); spin_unlock(&ioapic_lock); }}static unsigned int startup_edge_ioapic_vector(unsigned int vector){ int irq = vector_to_irq(vector); return startup_edge_ioapic_irq(irq);}static void ack_edge_ioapic_vector(unsigned int vector){ int irq = vector_to_irq(vector); ack_edge_ioapic_irq(irq);}static unsigned int startup_level_ioapic_vector(unsigned int vector){ int irq = vector_to_irq(vector); return startup_level_ioapic_irq (irq);}static void mask_and_ack_level_ioapic_vector(unsigned int vector){ int irq = vector_to_irq(vector); mask_and_ack_level_ioapic_irq(irq);}static void end_level_ioapic_vector(unsigned int vector){ int irq = vector_to_irq(vector); end_level_ioapic_irq(irq);}static void mask_IO_APIC_vector(unsigned int vector){ int irq = vector_to_irq(vector); mask_IO_APIC_irq(irq);}static void unmask_IO_APIC_vector(unsigned int vector){ int irq = vector_to_irq(vector); unmask_IO_APIC_irq(irq);}static void set_ioapic_affinity_vector( unsigned int vector, cpumask_t cpu_mask){ int irq = vector_to_irq(vector); set_native_irq_info(vector, cpu_mask); set_ioapic_affinity_irq(irq, cpu_mask);}static void disable_edge_ioapic_vector(unsigned int vector){}static void end_edge_ioapic_vector(unsigned int vector){}/* * 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_type = { .typename = "IO-APIC-edge", .startup = startup_edge_ioapic_vector, .shutdown = disable_edge_ioapic_vector, .enable = unmask_IO_APIC_vector, .disable = disable_edge_ioapic_vector, .ack = ack_edge_ioapic_vector, .end = end_edge_ioapic_vector, .set_affinity = set_ioapic_affinity_vector,};static struct hw_interrupt_type ioapic_level_type = { .typename = "IO-APIC-level", .startup = startup_level_ioapic_vector, .shutdown = mask_IO_APIC_vector, .enable = unmask_IO_APIC_vector, .disable = mask_IO_APIC_vector, .ack = mask_and_ack_level_ioapic_vector, .end = end_level_ioapic_vector, .set_affinity = set_ioapic_affinity_vector,};static inline void init_IO_APIC_traps(void){ int irq; /* Xen: This is way simpler than the Linux implementation. */ for (irq = 0; irq < 16 ; irq++) if (IO_APIC_IRQ(irq) && !IO_APIC_VECTOR(irq)) make_8259A_irq(irq);}static void enable_lapic_vector(unsigned int vector){ unsigned long v; v = apic_read(APIC_LVT0); apic_write_around(APIC_LVT0, v & ~APIC_LVT_MASKED);}static void disable_lapic_vector(unsigned int vector){ unsigned long v; v = apic_read(APIC_LVT0); apic_write_around(APIC_LVT0, v | APIC_LVT_MASKED);}static void ack_lapic_vector(unsigned int vector){ ack_APIC_irq();}static void end_lapic_vector(unsigned int vector) { /* nothing */ }static struct hw_interrupt_type lapic_irq_type = { .typename = "local-APIC-edge", .startup = NULL, /* startup_irq() not used for IRQ0 */ .shutdown = NULL, /* shutdown_irq() not used for IRQ0 */ .enable = enable_lapic_vector, .disable = disable_lapic_vector, .ack = ack_lapic_vector, .end = end_lapic_vector};/* * 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 apic, 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); apic = find_isa_irq_apic(8, mp_INT); if (pin == -1) return; spin_lock_irqsave(&ioapic_lock, flags); *(((int *)&entry0) + 1) = io_apic_read(apic, 0x11 + 2 * pin); *(((int *)&entry0) + 0) = io_apic_read(apic, 0x10 + 2 * pin); spin_unlock_irqrestore(&ioapic_lock, flags); clear_IO_APIC_pin(apic, 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(apic, 0x11 + 2 * pin, *(((int *)&entry1) + 1)); io_apic_write(apic, 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(apic, pin); spin_lock_irqsave(&ioapic_lock, flags); io_apic_write(apic, 0x11 + 2 * pin, *(((int *)&entry0) + 1)); io_apic_write(apic, 0x10 + 2 * pin, *(((int *)&entry0) + 0)); spin_unlock_irqrestore(&ioapic_lock, flags);}int timer_uses_ioapic_pin_0;/* * 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){ int apic1, pin1, apic2, pin2; int vector; /* * get/set the timer IRQ vector: */ disable_8259A_irq(0); vector = assign_irq_vector(0); irq_desc[IO_APIC_VECTOR(0)].action = irq_desc[LEGACY_VECTOR(0)].action; irq_desc[IO_APIC_VECTOR(0)].depth = 0; irq_desc[IO_APIC_VECTOR(0)].status &= ~IRQ_DISABLED; /* * 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); /* XEN: Ripped out the legacy missed-tick logic, so below is not needed. */ /*timer_ack = 1;*/ /*enable_8259A_irq(0);*/ pin1 = find_isa_irq_pin(0, mp_INT); apic1 = find_isa_irq_apic(0, mp_INT); pin2 = ioapic_i8259.pin; apic2 = ioapic_i8259.apic; if (pin1 == 0) timer_uses_ioapic_pin_0 = 1; printk(KERN_INFO "..TIMER: vector=0x%02X apic1=%d pin1=%d apic2=%d pin2=%d\n", vector, apic1, pin1, apic2, pin2); if (pin1 != -1) { /* * Ok, does IRQ0 through the IOAPIC work? */ unmask_IO_APIC_irq(0); if (timer_irq_works()) { if (disable_timer_pin_1 > 0) clear_IO_APIC_pin(apic1, pin1); return; } clear_IO_APIC_pin(apic1, 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(apic2, pin2, vector); if (timer_irq_works()) { printk("works.\n"); if (pin1 != -1) replace_pin_at_irq(0, apic1, pin1, apic2, pin2); else add_pin_to_irq(0, apic2, pin2); return; } /* * Cleanup, just in case ... */ clear_IO_APIC_pin(apic2, pin2); } printk(" failed.\n"); if (nmi_watchdog == NMI_IO_APIC) { printk(KERN_WARNING "timer doesn't 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[vector].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..."); /*timer_ack = 0;*/ 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! Boot with apic=debug and send a " "report. Then try booting with the 'noapic' option");}/* * * IRQ's that are handled by the PIC in the MPS IOAPIC case. * - 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. */#define PIC_IRQS (1 << PIC_CASCADE_IR)void __init setup_IO_APIC(void)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -