/*++ 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(); } } }