|
|
/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
interupt.c
Abstract:
This module contains the interupt handler for the ACPI driver
Author:
Stephane Plante (splante)
Environment:
NT Kernel Model Driver only
--*/
#include "pch.h"
//
// From shared\acpiinit.c
// We need to know certain information about the system, such as how
// many GPE bits are present
//
extern PACPIInformation AcpiInformation;
//
// Ignore the first interrupt because some machines are busted
//
BOOLEAN FirstInterrupt = TRUE;
//
// This is the variable that indicates wether or not the DPC is running
//
BOOLEAN AcpiGpeDpcRunning;
//
// This is the variable that indicates wether or not we have requested that
// the DPC be running...
//
BOOLEAN AcpiGpeDpcScheduled;
//
// This is the variable that indicates wether or not the DPC has completed
// real work
//
BOOLEAN AcpiGpeWorkDone;
//
// This is the timer that we use to schedule the DPC...
//
KTIMER AcpiGpeTimer;
//
// This is the DPC routine that we use to process the GPEs...
//
KDPC AcpiGpeDpc;
VOID ACPIInterruptDispatchEvents( ) /*++
Routine Description:
Function reads and dispatches GPE events.
N.B. This function is not re-entrant. Caller disables & enables gpes with ACPIGpeEnableDisableEvents().
Arguments:
None
Return Value:
None
--*/ { NTSTATUS status; UCHAR edg; UCHAR sts; ULONG gpeRegister; ULONG gpeSize;
//
// Remember the size of the GPE registers and that we need a spinlock to
// touch the tables
//
gpeSize = AcpiInformation->GpeSize; KeAcquireSpinLockAtDpcLevel (&GpeTableLock);
//
// Pre-handler processing. Read status bits and clear their enables.
// Eoi any edge firing gpe before gpe handler is invoked
//
for (gpeRegister = 0; gpeRegister < gpeSize; gpeRegister++) {
//
// Read the list of currently trigged method from the hardware
//
sts = ACPIReadGpeStatusRegister(gpeRegister) & GpeCurEnable[gpeRegister];
//
// Remember which sts bits need processed
//
GpePending[gpeRegister] |= sts; GpeRunMethod[gpeRegister] |= sts;
//
// Clear gpe enables for the events we are handling
//
GpeCurEnable[gpeRegister] &= ~sts;
//
// We will need to clear the Edge triggered interrupts, so remember
// which ones are those
//
edg = sts & ~GpeIsLevel[gpeRegister];
//
// Eoi edge gpe sts bits
//
if (edg) {
ACPIWriteGpeStatusRegister(gpeRegister, edg);
}
}
//
// Tell the DPC that we have work to do
//
AcpiGpeWorkDone = TRUE;
//
// If the DPC isn't running, then schedule it
//
if (!AcpiGpeDpcRunning && !AcpiGpeDpcScheduled) {
AcpiGpeDpcScheduled = TRUE; KeInsertQueueDpc( &AcpiGpeDpc, 0, 0);
}
//
// Done with GPE spinlock
//
KeReleaseSpinLockFromDpcLevel(&GpeTableLock); }
VOID ACPIInterruptDispatchEventDpc( IN PKDPC Dpc, IN PVOID DpcContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++
Routine Description:
This is the DPC engine responsible for running all GPE based events. It looks at the outstanding events and executes methods as is appropriate
Arguments:
None used
Return Value:
Void
--*/ { static CHAR methodName[] = "\\_GPE._L00"; ASYNC_GPE_CONTEXT asyncGpeEval; NTSTATUS status; PGPE_VECTOR_OBJECT gpeVectorObject; PNSOBJ pnsobj; UCHAR cmp; UCHAR gpeSTS[MAX_GPE_BUFFER_SIZE]; UCHAR gpeLVL[MAX_GPE_BUFFER_SIZE]; UCHAR gpeCMP[MAX_GPE_BUFFER_SIZE]; UCHAR gpeWAK[MAX_GPE_BUFFER_SIZE]; UCHAR lvl; UCHAR sts; ULONG bitmask; ULONG bitno; ULONG gpeIndex; ULONG gpeRegister; ULONG gpeSize; ULONG i;
UNREFERENCED_PARAMETER( Dpc ); UNREFERENCED_PARAMETER( DpcContext ); UNREFERENCED_PARAMETER( SystemArgument1 ); UNREFERENCED_PARAMETER( SystemArgument2 );
//
// Remember how many gpe bytes we have
//
gpeSize = AcpiInformation->GpeSize;
//
// First step is to acquire the DPC lock
//
KeAcquireSpinLockAtDpcLevel( &GpeTableLock );
//
// Remember that the DPC is no longer scheduled...
//
AcpiGpeDpcScheduled = FALSE;
//
// check to see if another DPC is already running
if (AcpiGpeDpcRunning) {
//
// The DPC is already running, so we need to exit now
//
KeReleaseSpinLockFromDpcLevel( &GpeTableLock ); return;
}
//
// Remember that the DPC is now running
//
AcpiGpeDpcRunning = TRUE;
//
// Make sure that we know that we haven't completed anything
//
RtlZeroMemory( gpeCMP, MAX_GPE_BUFFER_SIZE );
//
// We must try to do *some* work
//
do {
//
// Assume that we haven't done any work
//
AcpiGpeWorkDone = FALSE;
//
// Pre-handler processing. Build up the list of GPEs that we are
// going to run on this iteration of the loop
//
for (gpeRegister = 0; gpeRegister < gpeSize; gpeRegister++) {
//
// We have stored away the list of methods that need to be run
//
sts = GpeRunMethod[gpeRegister];
//
// Make sure that we don't run those methods again, unless
// someone asks us too
//
GpeRunMethod[gpeRegister] = 0;
//
// Remember which of those methods are level trigged
//
lvl = GpeIsLevel[gpeRegister];
//
// Remember which sts bits need processed
//
gpeSTS[gpeRegister] = sts; gpeLVL[gpeRegister] = lvl;
//
// Update the list of bits that have been completed
//
gpeCMP[gpeRegister] |= GpeComplete[gpeRegister]; GpeComplete[gpeRegister] = 0;
}
//
// We want to remember which GPEs are currently armed for Wakeup
// because we have a race condition if we check for GpeWakeEnable()
// after we drop the lock
//
RtlCopyMemory( gpeWAK, GpeWakeEnable, gpeSize );
//
// At this point, we must release the lock
//
KeReleaseSpinLockFromDpcLevel( &GpeTableLock );
//
// Issue gpe handler for each set gpe
//
for (gpeRegister = 0; gpeRegister < gpeSize; gpeRegister++) {
sts = gpeSTS[gpeRegister]; lvl = gpeLVL[gpeRegister]; cmp = 0;
while (sts) {
//
// Determine which bits are set within the current index
//
bitno = FirstSetLeftBit[sts]; bitmask = 1 << bitno; sts &= ~bitmask; gpeIndex = ACPIGpeRegisterToGpeIndex (gpeRegister, bitno);
//
// Do we have a method to run here?
//
if (GpeHandlerType[gpeRegister] & bitmask) {
//
// Run the control method for this gpe
//
methodName[7] = (lvl & bitmask) ? 'L' : 'E'; methodName[8] = HexDigit[gpeIndex >> 4]; methodName[9] = HexDigit[gpeIndex & 0x0f]; status = AMLIGetNameSpaceObject( methodName, NULL, &pnsobj, 0 );
//
// Setup the evaluation context. Note that we cheat
// and instead of allocating a structure, we use the
// pointer to hold the information (since the info is
// so small)
//
asyncGpeEval.GpeRegister = (UCHAR) gpeRegister; asyncGpeEval.StsBit = (UCHAR) bitmask; asyncGpeEval.Lvl = lvl;
//
// Did we find a control method to execute?
//
if (!NT_SUCCESS(status)) {
//
// The GPE is not meaningful to us. Simply disable it -
// which is a nop since it's already been removed
// from the GpeCurEnables.
//
continue;
}
status = AMLIAsyncEvalObject ( pnsobj, NULL, 0, NULL, (PFNACB) ACPIInterruptEventCompletion, (PVOID)ULongToPtr(asyncGpeEval.AsULONG) );
//
// If the evalution has completed re-enable the gpe; otherwise,
// wait for the async completion routine to do it
//
if (NT_SUCCESS(status)) {
if (status != STATUS_PENDING) {
cmp |= bitmask;
}
} else {
LONGLONG dueTime;
//
// We need to modify the table lock
//
KeAcquireSpinLockAtDpcLevel(&GpeTableLock);
//
// Remember that we have to run this method again
//
GpeRunMethod[gpeRegister] |= bitmask;
//
// Have we already scheduled the DPC?
//
if (!AcpiGpeDpcScheduled) {
//
// Remember that we have schedule the DPC...
//
AcpiGpeDpcScheduled = TRUE;
//
// We want approximately a 2 second delay in this case
//
dueTime = -2 * 1000* 1000 * 10;
//
// This is unconditional --- it will fire in 2 seconds
//
KeSetTimer( &AcpiGpeTimer, *(PLARGE_INTEGER) &dueTime, &AcpiGpeDpc );
}
//
// Done with the lock
//
KeReleaseSpinLockFromDpcLevel(&GpeTableLock);
}
} else if (gpeWAK[gpeRegister] & bitmask) {
//
// Vector is used for exlucive wake signalling
//
OSNotifyDeviceWakeByGPEEvent(gpeIndex, gpeRegister, bitmask);
//
// Processing of this gpe complete
//
cmp |= bitmask;
} else {
//
// Notify the target device driver
//
i = GpeMap[ACPIGpeIndexToByteIndex (gpeIndex)]; if (i < GpeVectorTableSize) {
gpeVectorObject = GpeVectorTable[i].GpeVectorObject; if (gpeVectorObject) {
//
// Call the target driver
//
gpeVectorObject->Handler( gpeVectorObject, gpeVectorObject->Context );
} else {
ACPIPrint( ( ACPI_PRINT_CRITICAL, "ACPIInterruptDispatchEvents: No Handler for Gpe: 0x%x\n", gpeIndex ) ); ACPIBreakPoint();
}
//
// Processing of this gpe complete
//
cmp |= bitmask;
} } }
//
// Remember what GPEs have been completed
//
gpeCMP[gpeRegister] |= cmp;
}
//
// Synchronize accesses to the ACPI tables
//
KeAcquireSpinLockAtDpcLevel (&GpeTableLock);
} while ( AcpiGpeWorkDone );
//
// Post-handler processing. EOI any completed lvl firing gpe and re-enable
// any completed gpe event
//
for (gpeRegister = 0; gpeRegister < gpeSize; gpeRegister++) {
cmp = gpeCMP[gpeRegister]; lvl = gpeLVL[gpeRegister] & cmp;
//
// EOI any completed level gpes
//
if (lvl) {
ACPIWriteGpeStatusRegister(gpeRegister, lvl);
}
//
// Calculate which functions it is we have to re-enable
//
ACPIGpeUpdateCurrentEnable( gpeRegister, cmp );
}
//
// Remember that we have exited the DPC...
//
AcpiGpeDpcRunning = FALSE;
//
// Before we exist, we should re-enable the GPEs...
//
ACPIGpeEnableDisableEvents( TRUE );
//
// Done with the table lock
//
KeReleaseSpinLockFromDpcLevel (&GpeTableLock); }
VOID EXPORT ACPIInterruptEventCompletion ( IN PNSOBJ AcpiObject, IN NTSTATUS Status, IN POBJDATA Result OPTIONAL, IN PVOID Context ) /*++
Routine Description:
This function is called when the interpreter has finished executing a GPE. The routine updates some book-keeping and restarts the DPC engine to handle these things
Arguments:
AcpiObject - The method that was run Status - Whether or not the method succeeded Result - Not used Context - Specifies the information required to figure what GPE we executed
Return Value:
None
--*/ { ASYNC_GPE_CONTEXT gpeContext; KIRQL oldIrql; LONGLONG dueTime; ULONG gpeRegister;
//
// We store the context information as part of the pointer. Convert it
// back to a ULONG so that it is useful to us
//
gpeContext.AsULONG = PtrToUlong(Context); gpeContext.Lvl &= gpeContext.StsBit; gpeRegister = gpeContext.GpeRegister;
//
// Need to synchronize access to these values
//
KeAcquireSpinLock (&GpeTableLock, &oldIrql);
//
// We have a different policy if the method failed then if it succeeded
//
if (!NT_SUCCESS(Status)) {
//
// In the failure case, we need to cause to method to run again
//
GpeRunMethod[gpeRegister] |= gpeContext.StsBit;
//
// Did we already schedule the DPC?
//
if (!AcpiGpeDpcScheduled) {
//
// Remember that we have schedule the DPC...
//
AcpiGpeDpcScheduled = TRUE;
//
// We want approximately a 2 second delay in this case
//
dueTime = -2 * 1000 * 1000 * 10;
//
// This is unconditional --- it will fire in 2 seconds
//
KeSetTimer( &AcpiGpeTimer, *(PLARGE_INTEGER) &dueTime, &AcpiGpeDpc ); }
} else {
//
// Remember that we did some work
//
AcpiGpeWorkDone = TRUE;
//
// Remember that this GPE is now complete
//
GpeComplete[gpeRegister] |= gpeContext.StsBit;
//
// If the DPC isn't already running, schedule it...
//
if (!AcpiGpeDpcRunning) {
KeInsertQueueDpc( &AcpiGpeDpc, 0, 0);
}
}
//
// Done with the table lock
//
KeReleaseSpinLock (&GpeTableLock, oldIrql); }
BOOLEAN ACPIInterruptServiceRoutine( IN PKINTERRUPT Interrupt, IN PVOID Context ) /*++
Routine Description:
The interrupt handler for the ACPI driver
Arguments:
Interrupt - Interrupt Object Context - Pointer to the device object which interrupt is associated with
Return Value:
TRUE - It was our interrupt FALSE - Not our interrupt
--*/ { PDEVICE_EXTENSION deviceExtension; ULONG IntStatus; ULONG BitsHandled; ULONG PrevStatus; ULONG i; BOOLEAN Handled;
//
// No need to look at the interrupt object
//
UNREFERENCED_PARAMETER( Interrupt );
//
// Setup ---
//
deviceExtension = (PDEVICE_EXTENSION) Context; Handled = FALSE;
//
// Determine source of interrupt
//
IntStatus = ACPIIoReadPm1Status();
//
// Unfortently due to a piix4 errata we need to check the GPEs because
// a piix4 sometimes forgets to raise an SCI on an asserted GPE
//
if (ACPIGpeIsEvent()) {
IntStatus |= PM1_GPE_PENDING;
}
//
// Nasty hack --- if we don't have any bits to handle at this point,
// that probably means that someone changed the GPE Enable register
// behind our back. The way that we can correct this problem is by
// forcing a check of the GPEs...
//
if (!IntStatus) {
IntStatus |= PM1_GPE_PENDING;
}
//
// Are any status bits set for events which are handled at ISR time?
//
BitsHandled = IntStatus & (PM1_TMR_STS | PM1_BM_STS); if (BitsHandled) {
//
// Clear their status bits then handle them
// (Note no special handling is required for PM1_BM_STS)
//
ACPIIoClearPm1Status ((USHORT) BitsHandled);
//
// If the overflow bit is set handle it
//
if (IntStatus & PM1_TMR_STS) {
HalAcpiTimerInterrupt();
} IntStatus &= ~BitsHandled;
}
//
// If more service bits are pending, they are for the DPC function
//
if (IntStatus) {
//
// If no new status bits, then make sure we check for GPEs
//
if (!(IntStatus & (~deviceExtension->Fdo.Pm1Status))) {
IntStatus |= PM1_GPE_PENDING;
}
//
// If we're going to process outstanding GPEs, disable them
// for DPC processing
//
if (IntStatus & PM1_GPE_PENDING) {
ACPIGpeEnableDisableEvents( FALSE );
}
//
// Clear the status bits we've handled
//
ACPIIoClearPm1Status ((USHORT) IntStatus);
//
// Set status bits for DPC routine to process
//
IntStatus |= PM1_DPC_IN_PROGRESS; PrevStatus = deviceExtension->Fdo.Pm1Status; do {
i = PrevStatus; PrevStatus = InterlockedCompareExchange( &deviceExtension->Fdo.Pm1Status, (i | IntStatus), i );
} while (i != PrevStatus);
//
// Compute which bits are new for the DPC to process
//
BitsHandled |= IntStatus & ~PrevStatus;
//
// If one of the new bits is "dpc in progress", we had better queue a dpc
//
if (BitsHandled & PM1_DPC_IN_PROGRESS) {
KeInsertQueueDpc(&deviceExtension->Fdo.InterruptDpc, NULL, NULL);
}
}
//
// Done
//
return BitsHandled ? TRUE : FALSE; }
VOID ACPIInterruptServiceRoutineDPC( IN PKDPC Dpc, IN PVOID Context, IN PVOID Arg1, IN PVOID Arg2 ) /*++
Routine Description:
This routine is called by the ISR. This is done so that our code is executing at DPC level, and not DIRQL
Arguments:
Dpc - Pointer to the DPC object Context - Pointer to the Device Object Arg1 - Not Used Arg2 - Not Used
--*/ { PDEVICE_EXTENSION deviceExtension; ULONG IntStatus; ULONG NewStatus; ULONG PrevStatus; ULONG BitsHandled; ULONG FixedButtonEvent;
deviceExtension = (PDEVICE_EXTENSION) Context;
UNREFERENCED_PARAMETER( Arg1 ); UNREFERENCED_PARAMETER( Arg2 );
//
// Loop while there's work
//
BitsHandled = 0; IntStatus = 0; for (; ;) {
//
// Get the status bits form the ISR. If there are no more
// status bits then exit
//
PrevStatus = deviceExtension->Fdo.Pm1Status; do {
IntStatus = PrevStatus;
//
// If there's no work pending, try to complete DPC
//
NewStatus = PM1_DPC_IN_PROGRESS; if (!(IntStatus & ~PM1_DPC_IN_PROGRESS)) {
//
// Note: The original code, after this call, would go
// out and check to see if we handeld any GPE Events.
// If we, did, then we would call ACPIGpeEnableDisableEvents
// in this context.
//
// The unfortunate problem with that approach is that it
// is makes us more suspectible to gpe storms. The reason
// is that there isn't a guarantee that GPE DPC has been
// triggered. So, at the price of increasing the latency
// in re-enabling events, we moved the re-enabling of
// GPEs ad the end of the GPE DPC
//
// Before we complete, reenable events
//
ACPIEnablePMInterruptOnly();
NewStatus = 0; BitsHandled = 0;
}
PrevStatus = InterlockedCompareExchange ( &deviceExtension->Fdo.Pm1Status, NewStatus, IntStatus );
} while (IntStatus != PrevStatus);
//
// If NewStatus cleared DPC_IN_PROGRESS, then we're done
//
if (!NewStatus) {
break;
}
//
// Track if GPE ever handled
//
BitsHandled |= IntStatus;
//
// Handle fixed power & sleep button events
//
FixedButtonEvent = 0; if (IntStatus & PM1_PWRBTN_STS) {
FixedButtonEvent |= SYS_BUTTON_POWER;
} if (IntStatus & PM1_SLEEPBTN_STS) {
FixedButtonEvent |= SYS_BUTTON_SLEEP;
} if (FixedButtonEvent) {
if (IntStatus & PM1_WAK_STS) {
FixedButtonEvent = SYS_BUTTON_WAKE;
} ACPIButtonEvent (FixedButtonDeviceObject, FixedButtonEvent, NULL);
}
//
// PM1_GBL_STS is set whenever the BIOS has released the global
// lock (and we are waiting for it). Notify the global lock handler.
//
if (IntStatus & PM1_GBL_STS) {
ACPIHardwareGlobalLockReleased();
}
//
// Handle GP Registers
//
if (IntStatus & PM1_GPE_PENDING) {
ACPIInterruptDispatchEvents();
}
}
}
|