// The I/O APIC manages hardware interrupts for an SMP system. // http://www.intel.com/design/chipsets/datashts/29056601.pdf // See also picirq.c. #include "types.h" #include "defs.h" #include "traps.h" #include "memlayout.h" #include "mmu.h" #include "mp.h" #include "param.h" #include "proc.h" #define REG_ID 0x00 // Register index: ID #define REG_VER 0x01 // Register index: version #define REG_TABLE 0x10 // Redirection table base // The redirection table starts at REG_TABLE and uses // two registers to configure each interrupt. // The first (low) register in a pair contains configuration bits. // The second (high) register contains a bitmask telling which // CPUs can serve that interrupt. #define INT_DISABLED 0x00010000 // Interrupt disabled #define INT_LEVEL 0x00008000 // Level-triggered (vs edge-) #define INT_ACTIVELOW 0x00002000 // Active low (vs high) #define INT_LOGICAL 0x00000800 // Destination is CPU id (vs APIC ID) volatile struct ioapic *ioapic; // IO APIC MMIO structure: write reg, then read or write data. struct ioapic { uint reg; uint pad[3]; uint data; }; static uint ioapicread(int reg) { ioapic->reg = reg; return ioapic->data; } static void ioapicwrite(int reg, uint data) { ioapic->reg = reg; ioapic->data = data; } void ioapicinit(void) { int i, id, maxintr; // The firmware's address for it, not a constant: see ioapic_phys in mp.c. // ioremap() rather than P2V because nothing obliges it to be in the low // four gigabytes the direct map is sure to cover. ioapic = ioremap(ioapic_phys, PGSIZE); if(!ioapic) panic("ioapicinit: cannot map the I/O APIC"); maxintr = (ioapicread(REG_VER) >> 16) & 0xFF; id = ioapicread(REG_ID) >> 24; if(id != ioapicid) cprintf("ioapicinit: id isn't equal to ioapicid; not a MP\n"); // Mark all interrupts edge-triggered, active high, disabled, // and not routed to any CPUs. for(i = 0; i <= maxintr; i++){ ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i)); ioapicwrite(REG_TABLE+2*i+1, 0); } } // Route an interrupt to a CPU and unmask it. // // `level_low` picks PCI's signalling instead of ISA's. This distinction did // not matter while every interrupt in the kernel came from an ISA-era device // at a fixed pin, all of which are edge-triggered and active high -- which is // why the original ioapicenable() hardcoded exactly that. A PCI device wired // through an INTx pin is the opposite on both counts, and configuring it as // edge/high does not fail cleanly: the device raises the line, we take one // interrupt, and because the level never goes away there is no second edge // and the device is never heard from again. void ioapicroute(int irq, int cpunum, int level_low) { uint lo = T_IRQ0 + irq; // A mode from include/irq.h; 1, the PCI case, is what `level_low` meant // when it was a flag. if(level_low == 1 || level_low == 2) lo |= INT_LEVEL; if(level_low == 1 || level_low == 3) lo |= INT_ACTIVELOW; // Program the destination before unmasking, so the entry is never briefly // live pointing at CPU 0 when it was meant for another. // // The destination is an APIC *ID*, not a CPU index. This wrote the index, // which is the same number only when the boot processor's APIC ID is 0 -- // true under every emulator and on most boards, and not promised: an // interrupt aimed at an ID nobody has is an interrupt nobody receives. ioapicwrite(REG_TABLE+2*irq+1, (uint)cpus[cpunum].apicid << 24); ioapicwrite(REG_TABLE+2*irq, lo); } void ioapicmask(int irq) { ioapicwrite(REG_TABLE+2*irq, INT_DISABLED | (T_IRQ0 + irq)); } void ioapicenable(int irq, int cpunum) { ioapicroute(irq, cpunum, 0); }