/* loApicIntr.c - Intel Pentium[234] Local APIC/xAPIC driver */ /* * Copyright 1984-2008 Wind River Systems, Inc. * * The right to copy, distribute, modify or otherwise make use * of this software may be licensed only pursuant to the terms * of an applicable Wind River license agreement. */ /* modification history -------------------- 01p,27aug08,jpb Renamed VSB header file 01o,18jun08,jpb Renamed _WRS_VX_SMP to _WRS_CONFIG_SMP. Added include path for kernel configurations options set in vsb. 01n,15mar07,scm correct mask in loApicIpi... 01m,07dec06,scm smp:adjust loApicIpi for shorthand support... 01l,15nov06,scm adjust loApicIpi for shortHand delivery... 01k,13oct06,scm AP work... 01j,09oct06,scm remove sysBp dependencey for SMP... 01i,06sep06,scm debug for SMP... 01h,26jul06,scm update for pentium/xeon support... 01g,01apr04,dmh add support for multiple APICs 01f,15may02,hdn changed apicId 8 bit to 16 bit, added DBG_MSG. added Error Status Register checking 01e,08mar02,hdn updated loApicInit() and loApicIpi() added loApicEnable() for HTT (spr 73738) 01d,29nov01,hdn doc update for 5.5 01c,21jun01,hdn added support for Pentium4 Local xAPIC moved show routines to loApicIntrShow.c 01b,25mar98,hdn re-written. 01a,20jun97,sub written. */ /* DESCRIPTION This module is a driver for the local APIC/xAPIC (Advanced Programmable Interrupt Controller) in P6 (PentiumPro, II, III) family processors and P7 (Pentium4) family processors. The local APIC/xAPIC is included in selected P6 (PentiumPro, II, III) and P7 (Pentium4) family processors. Beginning with the P6 family processors, the presence or absence of an on-chip local APIC can be detected using the CPUID instruction. When the CPUID instruction is executed, bit 9 of the feature flags returned in the EDX register indicates the presence (set) or absence (clear) of an on-chip local APIC. The local APIC performs two main functions for the processor: - It processes local external interrupts that the processor receives at its interrupt pins and local internal interrupts that software generates. - In multiple-processor systems, it communicates with an external IO APIC chip. The external IO APIC receives external interrupt events from the system and interprocessor interrupts from the processors on the system bus and distributes them to the processors on the system bus. The IO APIC is part of Intel's system chip set. The local APIC controls the dispatching of interrupts (to its associated processor) that it receives either locally or from the IO APIC. It provides facilities for queuing, nesting and masking of interrupts. It handles the interrupt delivery protocol with its local processor and accesses to APIC registers, and also manages interprocessor interrupts and remote APIC register reads. A timer on the local APIC allows local generation of interrupts, and local interrupt pins permit local reception of processor-specific interrupts. The local APIC can be disabled and used in conjunction with a standard 8259A style interrupt controller. Disabling the local APIC can be done in hardware for the Pentium processors or in software for the P6 and P7 (Pentium4) family processors. The local APIC in the Pentium4 processors (called the xAPIC) is an extension of the local APIC found in the P6 family processors. The primary difference between the APIC architecture and xAPIC architecture is that with Pentium4 processors, the local xAPICs and IO xAPIC communicate with one another through the processors system bus; whereas, with the P6 family processors, communication between the local APICs and the IO APIC is handled through a dedicated 3-wire APIC bus. Also, some of the architectural features of the local APIC have been extended and/or modified in the local xAPIC. The base address of the local APIC and IO APIC is taken from the MP configuration table (see Intel MP Specification Version 1.4) or the IA32_APIC_BASE MSR. It uses LOAPIC_BASE and IOAPIC_INDEX_BASE defined in the BSP, if it is not able to find the addresses. This driver contains three routines for use. They are: loApicInit() initializes the Local APIC for the interrupt mode chosen. loApicEnable() enables or disables the Local APIC. loApicIpi() delivers the inter processor interrupt to the specified local APIC. Local APIC is used in the Virtual Wire Mode (define VIRTUAL_WIRE_MODE in the BSP) and the Symmetric IO Mode (define SYMMETRIC_IO_MODE in the BSP), but not in the PIC Mode which is the default interrupt mode and uses 8259A PIC. Virtual Wire Mode is one of three interrupt modes defined by the MP specification. In this mode, interrupts are generated by the 8259A equivalent PICs, but delivered to the Boot Strap Processor by the local APIC that is programmed to act as a "virtual Wire"; that is, the local APIC is logically indistinguishable from a hardware connection. This is a uniprocessor compatibility mode. Symmetric IO Mode is one of three interrupt modes defined by the MP specification. In this mode, the local and IO APICs are fully functional, and interrupts are generated and delivered to the processors by the APICs. Any interrupt can be delivered to any processor. This is the only multiprocessor interrupt mode. The local and IO APICs support interrupts in the range of 32 to 255. Interrupt priority is implied by its vector, according to the following relationship: "priority = vector / 16". Here the quotient is rounded down to the nearest integer value to determine the priority, with 1 being the lowest and 15 is the highest. Because vectors 0 through 31 are reserved for exclusive use by the processor, the priority of user defined interrupts range from 2 to 15. A value of 15 in the Interrupt Class field of the Task Priority Register (TPR) will mask off all interrupts, which require interrupt service. The P6 family processor's local APIC includes an in-service entry and a holding entry for each priority level. To avoid losing interrupts, software should allocate no more than 2 interrupt vectors per priority. The Pentium4 processor expands this support of all acceptance of two interrupts per vector rather than per priority level. INCLUDE FILES: loApic.h */ /* includes */ #include #include "drv/intrCtl/loApic.h" /* defines */ #undef LOAPIC_DBG #ifdef LOAPIC_DBG # define LOAPIC_DBG_MSG(STR,VALUE) logMsg(STR,VALUE,0,0,0,0,0); #else # define LOAPIC_DBG_MSG(STR,VALUE) #endif /* IPI_DEBUG */ /* externs */ IMPORT CPUID sysCpuId; /* CPUID structure */ IMPORT UINT sysProcessor; /* CPU family */ #ifndef _WRS_CONFIG_SMP IMPORT BOOL sysBp; /* TRUE(default) for BP, FALSE for AP */ #endif /* !_WRS_CONFIG_SMP */ /* globals */ UINT32 loApicBase = LOAPIC_BASE; /* def Local APIC addr */ UINT32 loApicId; /* local APIC Id */ UINT32 loApicVersion; /* local APIC Version */ UINT32 loApicMaxLvt; /* local APIC Max LVT */ UINT32 loApicBusy = 0; /* counter for not sending IPI */ /* locals */ LOCAL UINT32 sysIntMaskLocalApic = 0; LOCAL UINT32 loApicOldSvr = 0; /* original SVR */ LOCAL UINT32 loApicOldLint0 = 0; /* original LINT0 */ LOCAL UINT32 loApicOldLint1 = 0; /* original LINT1 */ /* forward declarations */ LOCAL void loApicIntEoi (INT32 irq); #ifdef _WRS_CONFIG_SMP IMPORT int sysCpuNumGet (); /* define in sysLib.c of BSP */ #endif /* _WRS_CONFIG_SMP */ /******************************************************************************* * * loApicInit - initialize the Local APIC or xAPIC * * This routine initializes Local APIC or xAPIC. * * RETURNS: N/A * */ void loApicInit (void) { UINT32 apicBase[2]; /* override loApicBase, sysBp from MSR if available */ if (sysCpuId.featuresEdx & CPUID_APIC) { pentiumMsrGet (IA32_APIC_BASE, (long long int *)&apicBase); loApicBase = apicBase[0] & LOAPIC_BASE_MASK; /* override */ #ifndef _WRS_CONFIG_SMP sysBp = apicBase[0] & LOAPIC_BSP ? TRUE : FALSE; /* override */ #endif /* !_WRS_CONFIG_SMP */ /* enable the Local APIC explicitly */ apicBase[0] |= LOAPIC_GLOBAL_ENABLE; /* set E(nable) bit */ pentiumMsrSet (IA32_APIC_BASE, (long long int *)&apicBase); } /* remember the original state : SVR, LINT0, LINT1 for now */ loApicOldSvr = *(int *)(loApicBase + LOAPIC_SVR); loApicOldLint0 = *(int *)(loApicBase + LOAPIC_LINT0); loApicOldLint1 = *(int *)(loApicBase + LOAPIC_LINT1); /* enable the Local APIC */ loApicEnable (TRUE); *(int *)(loApicBase + LOAPIC_SVR) |= LOAPIC_FOCUS_DISABLE | INT_NUM_LOAPIC_SPURIOUS; /* get the Local APIC ID from Local APIC ID register */ loApicId = (*(int *)(loApicBase + LOAPIC_ID) & LOAPIC_ID_MASK) >> 24; /* get the Local APIC Version from Local APIC Version register */ loApicVersion = *(int *)(loApicBase + LOAPIC_VER) & LOAPIC_VERSION_MASK; loApicMaxLvt = (*(int *)(loApicBase + LOAPIC_VER) & LOAPIC_MAXLVT_MASK) >> 16; /* reset the DFR, TPR, TIMER_CONFIG, and TIMER_ICR */ *(int *)(loApicBase + LOAPIC_DFR) = (int)0xffffffff; *(int *)(loApicBase + LOAPIC_TPR) = (int)0x0; *(int *)(loApicBase + LOAPIC_TIMER_CONFIG) = (int)0x0; *(int *)(loApicBase + LOAPIC_TIMER_ICR) = (int)0x0; #if defined(VIRTUAL_WIRE_MODE) /* program Local Vector Table for the Virtual Wire Mode */ #ifndef _WRS_CONFIG_SMP if (sysBp) #else if (sysCpuNumGet() == 0) #endif /* !_WRS_CONFIG_SMP */ { /* set LINT0: extInt, high-polarity, edge-trigger, not-masked */ *(int *)(loApicBase + LOAPIC_LINT0) = (*(int *)(loApicBase + LOAPIC_LINT0) & ~(LOAPIC_MODE | LOAPIC_LOW | LOAPIC_LEVEL | LOAPIC_MASK)) | (LOAPIC_EXT | LOAPIC_HIGH | LOAPIC_EDGE); /* set LINT1: NMI, high-polarity, edge-trigger, not-masked */ *(int *)(loApicBase + LOAPIC_LINT1) = (*(int *)(loApicBase + LOAPIC_LINT1) & ~(LOAPIC_MODE | LOAPIC_LOW | LOAPIC_LEVEL | LOAPIC_MASK)) | (LOAPIC_NMI | LOAPIC_HIGH | LOAPIC_EDGE); } else { *(int *)(loApicBase + LOAPIC_LINT0) = LOAPIC_MASK; *(int *)(loApicBase + LOAPIC_LINT1) = LOAPIC_MASK; } #elif defined(SYMMETRIC_IO_MODE) /* program Local Vector Table for the Symmetric IO Mode */ *(int *)(loApicBase + LOAPIC_LINT0) = LOAPIC_MASK; *(int *)(loApicBase + LOAPIC_LINT1) = LOAPIC_MASK; #endif /* defined(VIRTUAL_WIRE_MODE) */ /* lock the Local APIC interrupts */ *(int *)(loApicBase + LOAPIC_TIMER) = LOAPIC_MASK; *(int *)(loApicBase + LOAPIC_ERROR) = LOAPIC_MASK; if (loApicMaxLvt >= LOAPIC_LVT_P6) *(int *)(loApicBase + LOAPIC_PMC) = LOAPIC_MASK; if (loApicMaxLvt >= LOAPIC_LVT_PENTIUM4) *(int *)(loApicBase + LOAPIC_THERMAL) = LOAPIC_MASK; /* discard a pending interrupt if any */ *(int *)(loApicBase + LOAPIC_EOI) = 0; } /******************************************************************************* * * loApicEnable - enable or disable the Local xAPIC * * This routine enables or disables the Local xAPIC. If the parameter is TRUE, * the Local xAPIC is enabled. If the parameter is FALSE, the Local xAPIC * is disabled. * * RETURNS: N/A */ void loApicEnable ( BOOL enable /* enable the Local xAPIC */ ) { INT32 oldLevel; BOOL loApicImcr = FALSE; /* TRUE if IMCR exist (MP Table) */ MP_FPS *pFps = mpApicFps; /* check if the IMCR exists or not */ loApicImcr = (pFps->featureByte[1] & 0x80) ? TRUE : FALSE; oldLevel = intLock (); /* LOCK INTERRUPTS */ /* enable or disable the Local APIC */ if (enable) { /* if the original mode is PIC mode, enable the Local APIC */ if ((loApicOldSvr & LOAPIC_ENABLE) == 0) { *(int *)(loApicBase + LOAPIC_SVR) |= LOAPIC_ENABLE; } #if defined (SYMMETRIC_IO_MODE) /* switch to SYMMETRIC_IO mode from PIC/VIRTUAL_WIRE mode */ if (loApicImcr) { sysOutByte (IMCR_ADRS, IMCR_REG_SEL); sysOutByte (IMCR_DATA, IMCR_IOAPIC_ON); } #endif /* defined (SYMMETRIC_IO_MODE) */ } else { #if defined (SYMMETRIC_IO_MODE) /* switch from SYMMETRIC_IO mode to PIC/VIRTUAL_WIRE mode */ if (loApicImcr) { sysOutByte (IMCR_ADRS, IMCR_REG_SEL); sysOutByte (IMCR_DATA, IMCR_IOAPIC_OFF); } #endif /* defined (SYMMETRIC_IO_MODE) */ /* if the original mode is PIC mode, disable the Local APIC */ if ((loApicOldSvr & LOAPIC_ENABLE) == 0) { *(int *)(loApicBase + LOAPIC_SVR) &= ~LOAPIC_ENABLE; } else { /* restore the original value */ *(int *)(loApicBase + LOAPIC_SVR) = loApicOldSvr; *(int *)(loApicBase + LOAPIC_LINT0) = loApicOldLint0; *(int *)(loApicBase + LOAPIC_LINT1) = loApicOldLint1; } } intUnlock (oldLevel); /* UNLOCK INTERRUPTS */ } /******************************************************************************* * * loApicIntLock - lock out all Local APIC interrupts * * This routine locks out all Local APIC interrupts * It should be called in the interrupt disable state(IF bit is 0). * * RETURNS: N/A * * SEE ALSO: loApicIntUnlock() * * ARGSUSED0 */ LOCAL void loApicIntLock (void) { if (!(*(int *)(loApicBase + LOAPIC_TIMER) & LOAPIC_MASK)) { sysIntMaskLocalApic |= LOCKED_TIMER; *(int *)(loApicBase + LOAPIC_TIMER) |= LOAPIC_MASK; } if (!(*(int *)(loApicBase + LOAPIC_LINT0) & LOAPIC_MASK)) { sysIntMaskLocalApic |= LOCKED_LINT0; *(int *)(loApicBase + LOAPIC_LINT0) |= LOAPIC_MASK; } if (!(*(int *)(loApicBase + LOAPIC_LINT1) & LOAPIC_MASK)) { sysIntMaskLocalApic |= LOCKED_LINT1; *(int *)(loApicBase + LOAPIC_LINT1) |= LOAPIC_MASK; } if (!(*(int *)(loApicBase + LOAPIC_ERROR) & LOAPIC_MASK)) { sysIntMaskLocalApic |= LOCKED_ERROR; *(int *)(loApicBase + LOAPIC_ERROR) |= LOAPIC_MASK; } if ((loApicMaxLvt >= LOAPIC_LVT_P6) && (!(*(int *)(loApicBase + LOAPIC_PMC) & LOAPIC_MASK))) { sysIntMaskLocalApic |= LOCKED_PMC; *(int *)(loApicBase + LOAPIC_PMC) |= LOAPIC_MASK; } if ((loApicMaxLvt >= LOAPIC_LVT_PENTIUM4) && (!(*(int *)(loApicBase + LOAPIC_THERMAL) & LOAPIC_MASK))) { sysIntMaskLocalApic |= LOCKED_THERMAL; *(int *)(loApicBase + LOAPIC_THERMAL) |= LOAPIC_MASK; } } /******************************************************************************* * * loApicIntUnlock - unlock the Local APIC interrupts * * This routine unlocks the Local APIC interrupts * It should be called in the interrupt disable state(IF bit is 0). * * RETURNS: N/A * * SEE ALSO: loApicIntLock() * * ARGSUSED0 */ LOCAL void loApicIntUnlock (void) { if (sysIntMaskLocalApic & LOCKED_TIMER) *(int *)(loApicBase + LOAPIC_TIMER) &= ~LOAPIC_MASK; if (sysIntMaskLocalApic & LOCKED_LINT0) *(int *)(loApicBase + LOAPIC_LINT0) &= ~LOAPIC_MASK; if (sysIntMaskLocalApic & LOCKED_LINT1) *(int *)(loApicBase + LOAPIC_LINT1) &= ~LOAPIC_MASK; if (sysIntMaskLocalApic & LOCKED_ERROR) *(int *)(loApicBase + LOAPIC_ERROR) &= ~LOAPIC_MASK; if ((loApicMaxLvt >= LOAPIC_LVT_P6) && (sysIntMaskLocalApic & LOCKED_PMC)) *(int *)(loApicBase + LOAPIC_PMC) &= ~LOAPIC_MASK; if ((loApicMaxLvt >= LOAPIC_LVT_PENTIUM4) && (sysIntMaskLocalApic & LOCKED_THERMAL)) *(int *)(loApicBase + LOAPIC_THERMAL) &= ~LOAPIC_MASK; sysIntMaskLocalApic = 0; /* reset the flags */ } /******************************************************************************* * * loApicIntEoi - send EOI (End Of Interrupt) signal to Local APIC * * This routine sends an EOI signal to the Local APIC's interrupting source. * * RETURNS: N/A */ LOCAL void loApicIntEoi ( INT32 irqNo /* INIIN number to send EOI */ ) { #ifdef SYS_INT_DEBUG extern UINT32 sysIntCount []; sysIntCount [irqNo]++; #endif /* SYS_INT_DEBUG */ *(int *)(loApicBase + LOAPIC_EOI) = 0; } /****************************************************************************** * * loApicIpi - generate a Inter Processor Interrupt * * This routine delivers an Inter-Processor interrupt to the specified CPU * in the specified mode, with the specified vectorNo. Level should always * be 1 except INIT De-assert mode. * * RETURNS: OK, or ERROR if the delivery status is not idle */ STATUS loApicIpi ( INT32 apicId, /* APIC ID number to send the interrupt */ INT32 shortHand, /* destination short hand */ INT32 trigger, /* trigger mode. 0: edge or 1: level */ INT32 level, /* level. 0: de-assert or 1: assert */ INT32 destMode, /* destination mode. 0: physical or 1: logical */ INT32 deliMode, /* delivery mode. 000: fixed ... 110: startup */ INT32 vectorNo /* vector number */ ) { UINT32 icrHi = (*(int *)(loApicBase + LOAPIC_ICRHI)) & 0x00ffffff; UINT32 icrLo = (*(int *)(loApicBase + LOAPIC_ICRLO)) & 0xfff32000; /* ShortHand, Trigger, Level, DeliveryMode and Vector */ icrLo |= ((shortHand << 18) & 0xc0000) | ((trigger << 15) & 0x8000) | ((level << 14) & 0x4000) | ((destMode << 11) & 0x800) | ((deliMode << 8) & 0x700) | (vectorNo & 0xff); /* Destination Apic Id */ icrHi |= (apicId << 24) & 0xff000000; /* is the interrupt idle? */ if (*(int *)(loApicBase + LOAPIC_ICRLO) & STATUS_PEND) { loApicBusy++; LOAPIC_DBG_MSG ("ipi: LOAPIC busy 0x%x\n", *(int *)(loApicBase + LOAPIC_ESR)) return (ERROR); } /* reset the error status register */ #ifndef _WRS_CONFIG_SMP *(int *)(loApicBase + LOAPIC_ESR) = 0; *(int *)(loApicBase + LOAPIC_ESR) = 0; #endif /* !_WRS_CONFIG_SMP */ if (*(int *)(loApicBase + LOAPIC_ESR) != 0) { LOAPIC_DBG_MSG ("ipi: 0. ESR=0x%x\n", *(int *)(loApicBase + LOAPIC_ESR)) return (ERROR); } /* Send it! */ #ifdef _WRS_CONFIG_SMP /* Can send shorthand message by performing a single * 32-bit memory write to the low half of the ICR. */ if ((shortHand == 0x00) || (destMode == 0x01)) *(int *)(loApicBase + LOAPIC_ICRHI) = icrHi; #else *(int *)(loApicBase + LOAPIC_ICRHI) = icrHi; #endif *(int *)(loApicBase + LOAPIC_ICRLO) = icrLo; /* Wait till interrupt is sent */ while (*(int *)(loApicBase + LOAPIC_ICRLO) & STATUS_PEND) if (*(int *)(loApicBase + LOAPIC_ESR) != 0) { LOAPIC_DBG_MSG ("ipi: 1. ESR=0x%x\n", *(int *)(loApicBase + LOAPIC_ESR)) return (ERROR); } return(OK); }