961 lines
24 KiB
C
961 lines
24 KiB
C
/*++
|
||
|
||
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();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|