/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * FILE: hal/halx86/mp/ioapic.c * PURPOSE: * PROGRAMMER: */ /* INCLUDES *****************************************************************/ #include #define NDEBUG #include /* GLOBALS *****************************************************************/ MP_CONFIGURATION_INTSRC IRQMap[MAX_IRQ_SOURCE]; /* Map of all IRQs */ ULONG IRQCount = 0; /* Number of IRQs */ ULONG IrqApicMap[MAX_IRQ_SOURCE]; UCHAR BUSMap[MAX_BUS]; /* Map of all buses in the system */ UCHAR PCIBUSMap[MAX_BUS]; /* Map of all PCI buses in the system */ IOAPIC_INFO IOAPICMap[MAX_IOAPIC]; /* Map of all I/O APICs in the system */ ULONG IOAPICCount; /* Number of I/O APICs in the system */ ULONG IRQVectorMap[MAX_IRQ_SOURCE]; /* IRQ to vector map */ /* EISA interrupts are always polarity zero and can be edge or level * trigger depending on the ELCR value. If an interrupt is listed as * EISA conforming in the MP table, that means its trigger type must * be read in from the ELCR */ #define default_EISA_trigger(idx) (EISA_ELCR_Read(IRQMap[idx].SrcBusIrq)) #define default_EISA_polarity(idx) (0) /* ISA interrupts are always polarity zero edge triggered, * when listed as conforming in the MP table. */ #define default_ISA_trigger(idx) (0) #define default_ISA_polarity(idx) (0) /* PCI interrupts are always polarity one level triggered, * when listed as conforming in the MP table. */ #define default_PCI_trigger(idx) (1) #define default_PCI_polarity(idx) (1) /* MCA interrupts are always polarity zero level triggered, * when listed as conforming in the MP table. */ #define default_MCA_trigger(idx) (1) #define default_MCA_polarity(idx) (0) /***************************************************************************/ extern VOID Disable8259AIrq(ULONG irq); ULONG IOAPICRead(ULONG Apic, ULONG Offset); VOID IOAPICWrite(ULONG Apic, ULONG Offset, ULONG Value); /* FUNCTIONS ***************************************************************/ /* * EISA Edge/Level control register, ELCR */ static ULONG EISA_ELCR_Read(ULONG irq) { if (irq < 16) { PUCHAR port = (PUCHAR)(0x4d0 + (irq >> 3)); return (READ_PORT_UCHAR(port) >> (irq & 7)) & 1; } DPRINT("Broken MPtable reports ISA irq %d\n", irq); return 0; } static ULONG IRQPolarity(ULONG idx) { ULONG bus = IRQMap[idx].SrcBusId; ULONG polarity; /* * Determine IRQ line polarity (high active or low active): */ switch (IRQMap[idx].IrqFlag & 3) { case 0: /* conforms, ie. bus-type dependent polarity */ { switch (BUSMap[bus]) { case MP_BUS_ISA: /* ISA pin */ polarity = default_ISA_polarity(idx); break; case MP_BUS_EISA: /* EISA pin */ polarity = default_EISA_polarity(idx); break; case MP_BUS_PCI: /* PCI pin */ polarity = default_PCI_polarity(idx); break; case MP_BUS_MCA: /* MCA pin */ polarity = default_MCA_polarity(idx); break; default: DPRINT("Broken BIOS!!\n"); polarity = 1; } } break; case 1: /* high active */ polarity = 0; break; case 2: /* reserved */ DPRINT("Broken BIOS!!\n"); polarity = 1; break; case 3: /* low active */ polarity = 1; break; default: /* invalid */ DPRINT("Broken BIOS!!\n"); polarity = 1; } return polarity; } static ULONG IRQTrigger(ULONG idx) { ULONG bus = IRQMap[idx].SrcBusId; ULONG trigger; /* * Determine IRQ trigger mode (edge or level sensitive): */ switch ((IRQMap[idx].IrqFlag >> 2) & 3) { case 0: /* conforms, ie. bus-type dependent */ { switch (BUSMap[bus]) { case MP_BUS_ISA: /* ISA pin */ trigger = default_ISA_trigger(idx); break; case MP_BUS_EISA: /* EISA pin */ trigger = default_EISA_trigger(idx); break; case MP_BUS_PCI: /* PCI pin */ trigger = default_PCI_trigger(idx); break; case MP_BUS_MCA: /* MCA pin */ trigger = default_MCA_trigger(idx); break; default: DPRINT("Broken BIOS!!\n"); trigger = 1; } } break; case 1: /* edge */ trigger = 0; break; case 2: /* reserved */ DPRINT("Broken BIOS!!\n"); trigger = 1; break; case 3: /* level */ trigger = 1; break; default: /* invalid */ DPRINT("Broken BIOS!!\n"); trigger = 0; } return trigger; } static ULONG Pin2Irq(ULONG idx, ULONG apic, ULONG pin) { ULONG irq, i; ULONG bus = IRQMap[idx].SrcBusId; /* * Debugging check, we are in big trouble if this message pops up! */ if (IRQMap[idx].DstApicInt != pin) { DPRINT("broken BIOS or MPTABLE parser, ayiee!!\n"); } switch (BUSMap[bus]) { case MP_BUS_ISA: /* ISA pin */ case MP_BUS_EISA: case MP_BUS_MCA: irq = IRQMap[idx].SrcBusIrq; break; case MP_BUS_PCI: /* PCI pin */ /* * PCI IRQs are mapped in order */ i = irq = 0; while (i < apic) { irq += IOAPICMap[i++].EntryCount; } irq += pin; break; default: DPRINT("Unknown bus type %d.\n",bus); irq = 0; } return irq; } static ULONG AssignIrqVector(ULONG irq) { #if 0 static ULONG current_vector = FIRST_DEVICE_VECTOR, vector_offset = 0; #endif ULONG vector; /* There may already have been assigned a vector for this IRQ */ vector = IRQVectorMap[irq]; if (vector > 0) { return vector; } #if 0 if (current_vector > FIRST_SYSTEM_VECTOR) { vector_offset++; current_vector = FIRST_DEVICE_VECTOR + vector_offset; } else if (current_vector == FIRST_SYSTEM_VECTOR) { DPRINT1("Ran out of interrupt sources!"); ASSERT(FALSE); } vector = current_vector; IRQVectorMap[irq] = vector; current_vector += 8; return vector; #else vector = IRQ2VECTOR(irq); IRQVectorMap[irq] = vector; return vector; #endif } /* * Find the IRQ entry number of a certain pin. */ static ULONG IOAPICGetIrqEntry(ULONG apic, ULONG pin, ULONG type) { ULONG i; for (i = 0; i < IRQCount; i++) { if (IRQMap[i].IrqType == type && (IRQMap[i].DstApicId == IOAPICMap[apic].ApicId || IRQMap[i].DstApicId == MP_APIC_ALL) && IRQMap[i].DstApicInt == pin) { return i; } } return -1; } VOID IOAPICSetupIrqs(VOID) { IOAPIC_ROUTE_ENTRY entry; ULONG apic, pin, idx, irq, first_notcon = 1, vector, trigger; DPRINT("Init IO_APIC IRQs\n"); /* Setup IRQ to vector translation map */ memset(&IRQVectorMap, 0, sizeof(IRQVectorMap)); for (apic = 0; apic < IOAPICCount; apic++) { for (pin = 0; pin < IOAPICMap[apic].EntryCount; pin++) { /* * add it to the IO-APIC irq-routing table */ memset(&entry,0,sizeof(entry)); entry.delivery_mode = (APIC_DM_LOWEST >> 8); entry.dest_mode = 1; /* logical delivery */ entry.mask = 1; /* disable IRQ */ entry.dest.logical.logical_dest = 0; idx = IOAPICGetIrqEntry(apic,pin,INT_VECTORED); if (idx == (ULONG)-1) { if (first_notcon) { DPRINT(" IO-APIC (apicid-pin) %d-%d\n", IOAPICMap[apic].ApicId, pin); first_notcon = 0; } else { DPRINT(", %d-%d\n", IOAPICMap[apic].ApicId, pin); } continue; } trigger = IRQTrigger(idx); entry.polarity = IRQPolarity(idx); if (trigger) { entry.trigger = 1; } irq = Pin2Irq(idx, apic, pin); vector = AssignIrqVector(irq); entry.vector = vector; DPRINT("vector 0x%.08x assigned to irq 0x%.02x\n", vector, irq); if (irq == 0) { /* Mask timer IRQ */ entry.mask = 1; } if ((apic == 0) && (irq < 16)) { Disable8259AIrq(irq); } IOAPICWrite(apic, IOAPIC_REDTBL+2*pin+1, *(((PULONG)&entry)+1)); IOAPICWrite(apic, IOAPIC_REDTBL+2*pin, *(((PULONG)&entry)+0)); IrqApicMap[irq] = apic; DPRINT("Vector %x, Pin %x, Irq %x\n", vector, pin, irq); } } } static VOID IOAPICClearPin(ULONG Apic, ULONG Pin) { IOAPIC_ROUTE_ENTRY Entry; DPRINT("IOAPICClearPin(Apic %d, Pin %d\n", Apic, Pin); /* * Disable it in the IO-APIC irq-routing table */ memset(&Entry, 0, sizeof(Entry)); Entry.mask = 1; IOAPICWrite(Apic, IOAPIC_REDTBL + 2 * Pin, *(((PULONG)&Entry) + 0)); IOAPICWrite(Apic, IOAPIC_REDTBL + 1 + 2 * Pin, *(((PULONG)&Entry) + 1)); } static VOID IOAPICClear(ULONG Apic) { ULONG Pin; for (Pin = 0; Pin < /*IOAPICMap[Apic].EntryCount*/24; Pin++) { IOAPICClearPin(Apic, Pin); } } static VOID IOAPICClearAll(VOID) { ULONG Apic; for (Apic = 0; Apic < IOAPICCount; Apic++) { IOAPICClear(Apic); } } VOID IOAPICEnable(VOID) { ULONG i, tmp; /* Setup IRQ to vector translation map */ memset(&IRQVectorMap, 0, sizeof(IRQVectorMap)); /* * The number of IO-APIC IRQ registers (== #pins): */ for (i = 0; i < IOAPICCount; i++) { tmp = IOAPICRead(i, IOAPIC_VER); IOAPICMap[i].EntryCount = GET_IOAPIC_MRE(tmp) + 1; } /* * Do not trust the IO-APIC being empty at bootup */ IOAPICClearAll(); } VOID IOAPICSetupIds(VOID) { ULONG tmp, apic, i; UCHAR old_id; /* * Set the IOAPIC ID to the value stored in the MPC table. */ for (apic = 0; apic < IOAPICCount; apic++) { /* Read the register 0 value */ tmp = IOAPICRead(apic, IOAPIC_ID); old_id = IOAPICMap[apic].ApicId; if (IOAPICMap[apic].ApicId >= 0xf) { DPRINT1("BIOS bug, IO-APIC#%d ID is %d in the MPC table!...\n", apic, IOAPICMap[apic].ApicId); DPRINT1("... fixing up to %d. (tell your hw vendor)\n", GET_IOAPIC_ID(tmp)); IOAPICMap[apic].ApicId = GET_IOAPIC_ID(tmp); } /* * We need to adjust the IRQ routing table * if the ID changed. */ if (old_id != IOAPICMap[apic].ApicId) { for (i = 0; i < IRQCount; i++) { if (IRQMap[i].DstApicId == old_id) { IRQMap[i].DstApicId = IOAPICMap[apic].ApicId; } } } /* * Read the right value from the MPC table and * write it into the ID register. */ DPRINT("Changing IO-APIC physical APIC ID to %d\n", IOAPICMap[apic].ApicId); tmp &= ~IOAPIC_ID_MASK; tmp |= SET_IOAPIC_ID(IOAPICMap[apic].ApicId); IOAPICWrite(apic, IOAPIC_ID, tmp); /* * Sanity check */ tmp = IOAPICRead(apic, 0); if (GET_IOAPIC_ID(tmp) != IOAPICMap[apic].ApicId) { DPRINT1("Could not set I/O APIC ID!\n"); ASSERT(FALSE); } } } /* This is performance critical and should probably be done in assembler */ VOID IOAPICMaskIrq(ULONG Irq) { IOAPIC_ROUTE_ENTRY Entry; ULONG Apic = IrqApicMap[Irq]; *(((PULONG)&Entry)+0) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq); *(((PULONG)&Entry)+1) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq+1); Entry.dest.logical.logical_dest &= ~(1 << KeGetCurrentProcessorNumber()); if (Entry.dest.logical.logical_dest == 0) { Entry.mask = 1; } IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq+1, *(((PULONG)&Entry)+1)); IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq, *(((PULONG)&Entry)+0)); } /* This is performance critical and should probably be done in assembler */ VOID IOAPICUnmaskIrq(ULONG Irq) { IOAPIC_ROUTE_ENTRY Entry; ULONG Apic = IrqApicMap[Irq]; *(((PULONG)&Entry)+0) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq); *(((PULONG)&Entry)+1) = IOAPICRead(Apic, IOAPIC_REDTBL+2*Irq+1); Entry.dest.logical.logical_dest |= 1 << KeGetCurrentProcessorNumber(); Entry.mask = 0; IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq+1, *(((PULONG)&Entry)+1)); IOAPICWrite(Apic, IOAPIC_REDTBL+2*Irq, *(((PULONG)&Entry)+0)); } VOID IOAPICDump(VOID) { ULONG apic, i; ULONG reg0, reg1, reg2=0; DbgPrint("Number of MP IRQ sources: %d.\n", IRQCount); for (i = 0; i < IOAPICCount; i++) { DbgPrint("Number of IO-APIC #%d registers: %d.\n", IOAPICMap[i].ApicId, IOAPICMap[i].EntryCount); } /* * We are a bit conservative about what we expect. We have to * know about every hardware change ASAP. */ DbgPrint("Testing the IO APIC.......................\n"); for (apic = 0; apic < IOAPICCount; apic++) { reg0 = IOAPICRead(apic, IOAPIC_ID); reg1 = IOAPICRead(apic, IOAPIC_VER); if (GET_IOAPIC_VERSION(reg1) >= 0x10) { reg2 = IOAPICRead(apic, IOAPIC_ARB); } DbgPrint("\n"); DbgPrint("IO APIC #%d......\n", IOAPICMap[apic].ApicId); DbgPrint(".... register #00: %08X\n", reg0); DbgPrint("....... : physical APIC id: %02X\n", GET_IOAPIC_ID(reg0)); if (reg0 & 0xF0FFFFFF) { DbgPrint(" WARNING: Unexpected IO-APIC\n"); } DbgPrint(".... register #01: %08X\n", reg1); i = GET_IOAPIC_MRE(reg1); DbgPrint("....... : max redirection entries: %04X\n", i); if ((i != 0x0f) && /* older (Neptune) boards */ (i != 0x17) && /* typical ISA+PCI boards */ (i != 0x1b) && /* Compaq Proliant boards */ (i != 0x1f) && /* dual Xeon boards */ (i != 0x22) && /* bigger Xeon boards */ (i != 0x2E) && (i != 0x3F)) { DbgPrint(" WARNING: Unexpected IO-APIC\n"); } i = GET_IOAPIC_VERSION(reg1); DbgPrint("....... : IO APIC version: %04X\n", i); if ((i != 0x01) && /* 82489DX IO-APICs */ (i != 0x10) && /* oldest IO-APICs */ (i != 0x11) && /* Pentium/Pro IO-APICs */ (i != 0x13)) /* Xeon IO-APICs */ { DbgPrint(" WARNING: Unexpected IO-APIC\n"); } if (reg1 & 0xFF00FF00) { DbgPrint(" WARNING: Unexpected IO-APIC\n"); } if (GET_IOAPIC_VERSION(reg1) >= 0x10) { DbgPrint(".... register #02: %08X\n", reg2); DbgPrint("....... : arbitration: %02X\n", GET_IOAPIC_ARB(reg2)); if (reg2 & 0xF0FFFFFF) { DbgPrint(" WARNING: Unexpected IO-APIC\n"); } } DbgPrint(".... IRQ redirection table:\n"); DbgPrint(" NR Log Phy Mask Trig IRR Pol" " Stat Dest Deli Vect: \n"); for (i = 0; i <= GET_IOAPIC_MRE(reg1); i++) { IOAPIC_ROUTE_ENTRY entry; *(((PULONG)&entry)+0) = IOAPICRead(apic, 0x10+i*2); *(((PULONG)&entry)+1) = IOAPICRead(apic, 0x11+i*2); DbgPrint(" %02x %03X %02X ", i, entry.dest.logical.logical_dest, entry.dest.physical.physical_dest); DbgPrint("%C %C %1d %C %C %C %03X %02X\n", (entry.mask == 0) ? 'U' : 'M', // Unmasked/masked (entry.trigger == 0) ? 'E' : 'L', // Edge/level sensitive entry.irr, (entry.polarity == 0) ? 'H' : 'L', // Active high/active low (entry.delivery_status == 0) ? 'I' : 'S', // Idle / send pending (entry.dest_mode == 0) ? 'P' : 'L', // Physical logical entry.delivery_mode, entry.vector); } } DbgPrint(".................................... done.\n"); } VOID HaliReconfigurePciInterrupts(VOID) { ULONG i; for (i = 0; i < IRQCount; i++) { if (BUSMap[IRQMap[i].SrcBusId] == MP_BUS_PCI) { DPRINT("%02x: IrqType %02x, IrqFlag %02x, SrcBusId %02x, SrcBusIrq %02x" ", DstApicId %02x, DstApicInt %02x\n", i, IRQMap[i].IrqType, IRQMap[i].IrqFlag, IRQMap[i].SrcBusId, IRQMap[i].SrcBusIrq, IRQMap[i].DstApicId, IRQMap[i].DstApicInt); HalSetBusDataByOffset(PCIConfiguration, IRQMap[i].SrcBusId, (IRQMap[i].SrcBusIrq >> 2) & 0x1f, &IRQMap[i].DstApicInt, 0x3c /*PCI_INTERRUPT_LINE*/, 1); } } } VOID Disable8259AIrq(ULONG irq) { UCHAR tmp; if (irq >= 8) { tmp = READ_PORT_UCHAR((PUCHAR)0xA1); tmp |= (1 << (irq - 8)); WRITE_PORT_UCHAR((PUCHAR)0xA1, tmp); } else { tmp = READ_PORT_UCHAR((PUCHAR)0x21); tmp |= (1 << irq); WRITE_PORT_UCHAR((PUCHAR)0x21, tmp); } } ULONG IOAPICRead(ULONG Apic, ULONG Offset) { PULONG Base; Base = (PULONG)IOAPICMap[Apic].ApicAddress; *Base = Offset; return *((PULONG)((ULONG)Base + IOAPIC_IOWIN)); } VOID IOAPICWrite(ULONG Apic, ULONG Offset, ULONG Value) { PULONG Base; Base = (PULONG)IOAPICMap[Apic].ApicAddress; *Base = Offset; *((PULONG)((ULONG)Base + IOAPIC_IOWIN)) = Value; } /* EOF */