mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
575 lines
14 KiB
575 lines
14 KiB
|
|
/*++
|
|
|
|
Copyright (c) 1999-2001 Microsoft Corporation. All Rights Reserved.
|
|
|
|
|
|
Module Name:
|
|
|
|
apic.c
|
|
|
|
Abstract:
|
|
|
|
This module contains global variables and functions used to program the local APIC.
|
|
The local APIC resides on the processor die of newer Intel and AMD processors. It is
|
|
used to control various interrupt sources both local and external to the processor.
|
|
|
|
Author:
|
|
|
|
Joseph Ballantyne
|
|
|
|
Environment:
|
|
|
|
Kernel Mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
|
|
|
|
#include "common.h"
|
|
#include "apic.h"
|
|
#include "irq.h"
|
|
#include "msr.h"
|
|
#include "rtp.h"
|
|
#include "rt.h"
|
|
#include "x86.h"
|
|
#include "rtexcept.h"
|
|
|
|
#ifndef UNDER_NT
|
|
#include <vmm.h>
|
|
#endif
|
|
|
|
|
|
#pragma LOCKED_DATA
|
|
|
|
// These globals contain addresses pointing at various local APIC registers.
|
|
// They must be in locked memory as they will be used to program the local APIC
|
|
// while in interrupt service routines.
|
|
|
|
CHAR *ApicBase=NULL;
|
|
volatile ULONG *ApicPerfInterrupt=NULL;
|
|
volatile ULONG *ApicTimerInterrupt=NULL;
|
|
volatile ULONG *ApicNmiInterrupt=NULL;
|
|
volatile ULONG *ApicIntrInterrupt=NULL;
|
|
|
|
|
|
// WARNING!!! Do NOT change the ApicTimerVector or ApicErrorVector default
|
|
// settings to a different define, without also updating HookWindowsInterrupts
|
|
// in rt.c! Otherwise any local apic errors will jump to some unknown and
|
|
// incorrect interrupt handler instead of our own - since we will not have
|
|
// hooked the error vector IDT entry.
|
|
// Also make sure you also update rtexcept.c appropriately as well if any
|
|
// of the below variables are initialized with different defines.
|
|
|
|
|
|
ULONG ApicTimerVector=MASKABLEIDTINDEX;
|
|
ULONG ApicPerfVector=RTINTERRUPT;
|
|
ULONG ApicErrorVector=APICERRORIDTINDEX;
|
|
ULONG ApicSpuriousVector=APICSPURIOUSIDTINDEX;
|
|
|
|
|
|
extern ID OriginalApicSpuriousVector;
|
|
|
|
|
|
|
|
NTSTATUS
|
|
HookInterrupt (
|
|
ULONG index,
|
|
ID *originalvector,
|
|
VOID (* handler)(VOID)
|
|
);
|
|
|
|
|
|
|
|
#pragma PAGEABLE_DATA
|
|
#pragma PAGEABLE_CODE
|
|
|
|
#ifndef UNDER_NT
|
|
#pragma warning ( disable : 4035 )
|
|
#endif
|
|
|
|
|
|
PCHAR
|
|
MapPhysToLinear (
|
|
VOID *physicaladdress,
|
|
ULONG numbytes,
|
|
ULONG flags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is a wrapper for OS functions that map physical memory to linear address
|
|
space. On Win9x it wraps MapPhysToLinear, on WinNT it wraps MmMapIoSpace.
|
|
|
|
Arguments:
|
|
|
|
physicaladdress - Supplies the physical address to be mapped into linear address space.
|
|
|
|
numbytes - Supplies the size of the block of physical memory to be mapped.
|
|
|
|
flags - Supplies flags controlling the characteristics of the linear/virtual memory.
|
|
|
|
Return Value:
|
|
|
|
Since this routine is really just a wrapper for OS functions on
|
|
Win9x and WinNT, the return value differs depending on the OS.
|
|
On both platforms if the mapping is successful, the return value is the linear
|
|
address of the mapped physical address.
|
|
On Win9x if the mapping fails, the function returns (-1).
|
|
On WinNT if the mapping fails, the function returns NULL.
|
|
Platform independent callers MUST check for returns of EITHER (-1) or NULL
|
|
when checking for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
#ifdef UNDER_NT
|
|
|
|
|
|
PHYSICAL_ADDRESS address;
|
|
|
|
address.QuadPart=(ULONGLONG)physicaladdress;
|
|
|
|
return MmMapIoSpace(address, numbytes, flags);
|
|
|
|
|
|
#else
|
|
|
|
|
|
__asm push flags
|
|
__asm push numbytes
|
|
__asm push physicaladdress
|
|
|
|
VMMCall( _MapPhysToLinear );
|
|
|
|
__asm add esp,12
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
#ifndef UNDER_NT
|
|
#pragma warning ( default : 4035 )
|
|
#endif
|
|
|
|
|
|
|
|
BOOL
|
|
InitializeAPICBase (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If this routine has already run successfully, then it simply returns TRUE.
|
|
If not, then it first reads the physical address that the local APIC
|
|
is mapped to. Then it maps that physical address to virtual memory
|
|
and saves the virtual memory location in the global ApicBase. If the
|
|
mapping fails, then it returns FALSE, otherwise it loads globals that
|
|
point to specific local APIC interrupt control registers and returns
|
|
TRUE.
|
|
|
|
WARNING: Currently this routine ASSUMES processor support of the
|
|
Intel local APIC mapping MSR.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if local APIC already mapped, or if it is mapped successfully during the call.
|
|
FALSE if mapping the local APIC fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
// If local APIC already mapped, simply return TRUE.
|
|
|
|
if (ApicBase!=NULL && ApicBase!=(CHAR *)(-1)) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Read the local APIC physical location.
|
|
// NOTE: This next line assumes all machines this code runs on support the
|
|
// local APIC mapping MSR that PII and newer Intel processors have.
|
|
// Code that calls this function MUST properly screen for manufacturer,
|
|
// processor family, and local apic support before calling this function.
|
|
|
|
ApicBase=(CHAR *)(ReadIntelMSR(APICBASE)&~(0xfff));
|
|
|
|
// Map the physical address to a virtual address.
|
|
|
|
ApicBase=MapPhysToLinear(ApicBase, 4096, 0);
|
|
|
|
// Return false if the mapping failed.
|
|
|
|
if (ApicBase==(CHAR *)(-1) || ApicBase==NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Mapping succeeded, so load global interrupt control register locations
|
|
// and return TRUE.
|
|
|
|
ApicPerfInterrupt=(ULONG *)(ApicBase+APICPERF);
|
|
ApicTimerInterrupt=(ULONG *)(ApicBase+APICTIMER);
|
|
ApicNmiInterrupt=(ULONG *)(ApicBase+APICNMI);
|
|
ApicIntrInterrupt=(ULONG *)(ApicBase+APICINTR);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
MachineHasAPIC (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check the processor manufacturer, family, and the local APIC feature bit, and
|
|
then call InitializeAPICBase on supported processors.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
FALSE for unsupported processor manufacturers and families and for processors
|
|
without the local APIC feature bit set.
|
|
|
|
If the manufacturer and processor family are supported and the processor sets
|
|
the local APIC feature bit, then we call InitializeAPICBase and return the
|
|
value returned by that function.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
if (CPUManufacturer==INTEL && CPUFamily>=6 && (CPUFeatures&0x20)) {
|
|
|
|
return InitializeAPICBase();
|
|
|
|
}
|
|
|
|
if (CPUManufacturer==AMD && CPUFamily>=6 && (CPUFeatures&0x20)) {
|
|
|
|
return InitializeAPICBase();
|
|
|
|
}
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma LOCKED_CODE
|
|
#pragma LOCKED_DATA
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
__inline
|
|
VOID
|
|
GenerateLocalHardwareInterrupt (
|
|
ULONG interrupt
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine can be used to generate what will appear to be a hardware interrupt.
|
|
The local APIC is used to send the passed interrupt vector number to itself
|
|
and will then handle the interrupt by invoking specified interrupt vector to
|
|
handle the interrupt. I do NOT know whether this will work at all. It may or may not
|
|
work with interrupt handlers that normally handle interrupts coming from an external
|
|
IO APIC, and it may or may not work with interrupt handlers that normally handle
|
|
interrupts coming through an external PIC. It seemed like an idea with interesting
|
|
possibilities, so I wrote the code. It is NOT currently used. Note that for code
|
|
already running in ring 0 on x86 processors, if you just want to run an arbitrary
|
|
interrupt handler, it is much faster to just simulate the interrupt by running
|
|
an __asm int x instruction.
|
|
|
|
WARNING: This function does NOT currently wait until the interrupt is delivered
|
|
before returning.
|
|
|
|
Arguments:
|
|
|
|
interrupt - Contains the interrupt vector number of the interrupt to be simulated.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
// First write this machines local apic ID into the destination register of the ICR.
|
|
// We must do this with interrupts disabled so that it is safe on NT multiproc
|
|
// machines. Otherwise we could read the APIC ID for one processor and be switched
|
|
// out and load it into a different processors register.
|
|
|
|
SaveAndDisableMaskableInterrupts();
|
|
|
|
WriteAPIC(APICICRHIGH,ReadAPIC(APICID));
|
|
|
|
WriteAPIC(APICICRLOW,ASSERTIRQ|interrupt);
|
|
|
|
RestoreMaskableInterrupts();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
BOOL
|
|
TurnOnLocalApic (
|
|
VOID
|
|
)
|
|
|
|
{
|
|
|
|
if (!(CPUFeatures&0x20)) {
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
{
|
|
ULONGLONG apicbaseaddress;
|
|
|
|
// Make sure that APIC turned on by MSRs.
|
|
apicbaseaddress=ReadIntelMSR(APICBASE);
|
|
|
|
if (!(apicbaseaddress&0x800)) { // Apic is turned off.
|
|
|
|
// First disable all interrupts.
|
|
SaveAndDisableMaskableInterrupts();
|
|
|
|
// Try turning it back on. Intel claims this doesn't work. It does.
|
|
apicbaseaddress|=0x800;
|
|
WriteIntelMSR(APICBASE, apicbaseaddress);
|
|
|
|
// The following code should only be run in the case when the local APIC was
|
|
// not turned on and we then turned it on.
|
|
|
|
// Now check if the local APIC is turned on. If so, then set it up.
|
|
|
|
if (ReadIntelMSR(APICBASE)&0x800) {
|
|
|
|
// Local APIC is on.
|
|
|
|
// Hook the APIC spurious interrupt vector if we have not hooked it before.
|
|
if (*(PULONGLONG)&OriginalApicSpuriousVector==0 &&
|
|
HookInterrupt(ApicSpuriousVector, &OriginalApicSpuriousVector, RtpLocalApicSpuriousHandler)!=STATUS_SUCCESS) {
|
|
RestoreMaskableInterrupts();
|
|
return FALSE;
|
|
}
|
|
|
|
// Now enable the APIC itself.
|
|
// This also loads the spurious interrupt vector.
|
|
WriteAPIC(APICSPURIOUS,(ReadAPIC(APICSPURIOUS)&0xfffffc00)|0x100|APICSPURIOUSIDTINDEX);
|
|
|
|
// Now setup INTR.
|
|
// Unmasked, ExtINT.
|
|
WriteAPIC(APICINTR,(ReadAPIC(APICINTR)&0xfffe58ff)|EXTINT);
|
|
|
|
// Now setup NMI.
|
|
// Masked, NMI.
|
|
// Leave external NMI enabled.
|
|
WriteAPIC(APICNMI,(ReadAPIC(APICNMI)&0xfffe58ff)|NMI);
|
|
|
|
// Now reenable interrupts.
|
|
RestoreMaskableInterrupts();
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Local APIC could not be turned on with the MSRs!
|
|
// This WILL happen on some mobile parts.
|
|
|
|
// Now reenable interrupts.
|
|
RestoreMaskableInterrupts();
|
|
|
|
dprintf(("RealTime Executive could not enable local APIC. RT NOT RUNNING!"));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
else { // Local APIC is already turned on!
|
|
|
|
// This will happen for HALs that use the local APIC. (mp, ACPI)
|
|
// We should not touch the spurious, ExtINT, or NMI vectors in this case.
|
|
|
|
// We do however read the settings out of the local APIC for the local timer
|
|
// interrupt vector, and the performance counter interrupt vector. That
|
|
// way we use the same vectors as the HAL initially programmed. (Except
|
|
// that we do set the NMI flag in the performance counter interrupt, so
|
|
// it actually uses interrupt vector 2.)
|
|
|
|
ApicTimerVector=ReadAPIC(APICTIMER)&VECTORMASK;
|
|
#ifdef MASKABLEINTERRUPT
|
|
ApicPerfVector=ReadAPIC(APICPERF)&VECTORMASK;
|
|
#else
|
|
ApicPerfVector=NMI|(ReadAPIC(APICPERF)&VECTORMASK);
|
|
#endif
|
|
|
|
// We also read the error and spurious vector locations since we need
|
|
// them when setting up our private IDTs.
|
|
|
|
ApicErrorVector=ReadAPIC(APICERROR)&VECTORMASK;
|
|
ApicSpuriousVector=ReadAPIC(APICSPURIOUS)&VECTORMASK;
|
|
|
|
// Make sure the vectors we just read are valid. If not, then load them with
|
|
// our defaults. These vectors should never be invalid if the local APIC
|
|
// was turned on. The only way we will hit this case is if the BIOS turns
|
|
// on the local APIC, but then a HAL without local apic support does not
|
|
// turn OFF the local apic.
|
|
|
|
if (!ApicTimerVector) {
|
|
Trap();
|
|
ApicTimerVector=MASKABLEIDTINDEX;
|
|
}
|
|
|
|
if (!ApicPerfVector) {
|
|
Trap();
|
|
ApicPerfVector=RTINTERRUPT;
|
|
}
|
|
|
|
if (!ApicErrorVector) {
|
|
Trap();
|
|
ApicErrorVector=APICERRORIDTINDEX;
|
|
}
|
|
|
|
if (!ApicSpuriousVector) {
|
|
Trap();
|
|
ApicSpuriousVector=APICSPURIOUSIDTINDEX;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
EnableAPIC (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will enable the local APIC on processors it knows are supported.
|
|
We check if the processor manufacturer and family are supported. If so we
|
|
call TurnOnLocalApic to enable the local apic on the processor.
|
|
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
FALSE if processor manufacturer and family are not explicitly supported.
|
|
|
|
If the manufacturer and processor family are supported then we call
|
|
TurnOnLocalApic and return the value returned by that function.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
|
|
// Is manufaturer supported?
|
|
|
|
switch (CPUManufacturer) {
|
|
|
|
case INTEL:
|
|
|
|
// Check the processor family code for Intel.
|
|
|
|
switch (CPUFamily) {
|
|
|
|
case 6: // PII, PIII, Celeron
|
|
case 0xf: // P4
|
|
|
|
return TurnOnLocalApic();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case AMD:
|
|
|
|
// Check the processor family code for AMD.
|
|
|
|
switch (CPUFamily) {
|
|
|
|
case 6: // Athlon, Duron
|
|
|
|
return TurnOnLocalApic();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
#pragma PAGEABLE_CODE
|
|
#pragma PAGEABLE_DATA
|
|
|
|
|