/*++ Copyright (c) 1997 Microsoft Corporation Module Name: pmsleep.c Abstract: This file provides the code that changes the system from the ACPI S0 (running) state to any one of the sleep states. Author: Jake Oshins (jakeo) Feb. 11, 1997 Revision History: Todd Kjos (HP) (v-tkjos) 1-Jun-1998: Initial port to IA64 --*/ #include "halp.h" #include "acpitabl.h" #include "xxacpi.h" #include "ixsleep.h" #include "kddll.h" // // Internal functions // VOID HalpLockedIncrementUlong( PULONG SyncVariable ); VOID HalpReboot ( VOID ); NTSTATUS HaliAcpiFakeSleep( IN PVOID Context, IN PENTER_STATE_SYSTEM_HANDLER SystemHandler OPTIONAL, IN PVOID SystemContext, IN LONG NumberProcessors, IN volatile PLONG Number ); NTSTATUS HaliAcpiSleep( IN PVOID Context, IN PENTER_STATE_SYSTEM_HANDLER SystemHandler OPTIONAL, IN PVOID SystemContext, IN LONG NumberProcessors, IN volatile PLONG Number ); VOID HalpSetClockBeforeSleep( VOID ); VOID HalpSetClockAfterSleep( VOID ); BOOLEAN HalpWakeupTimeElapsed( VOID ); VOID HalpReenableAcpi( VOID ); VOID HalpSetInterruptControllerWakeupState( ULONG Context ); typedef struct _ERESOURCE { LIST_ENTRY SystemResourcesList; PVOID OwnerTable; SHORT ActiveCount; USHORT Flag; PKSEMAPHORE SharedWaiters; PKEVENT ExclusiveWaiters; LIST_ENTRY OwnerThreads[2]; ULONG ContentionCount; USHORT NumberOfSharedWaiters; USHORT NumberOfExclusiveWaiters; union { PVOID Address; ULONG CreatorBackTraceIndex; }; KSPIN_LOCK SpinLock; } ERESOURCE, *PERESOURCE; #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGELK, HaliAcpiSleep) #pragma alloc_text(PAGELK, HaliAcpiFakeSleep) #pragma alloc_text(PAGELK, HalpAcpiPreSleep) #pragma alloc_text(PAGELK, HalpAcpiPostSleep) #pragma alloc_text(PAGELK, HalpWakeupTimeElapsed) #pragma alloc_text(PAGELK, HalpReenableAcpi) #pragma alloc_text(PAGELK, HaliSetWakeEnable) #pragma alloc_text(PAGELK, HaliSetWakeAlarm) #endif HAL_WAKEUP_STATE HalpWakeupState; ULONG Barrier; volatile ULONG HalpSleepSync; PKPROCESSOR_STATE HalpHiberProcState; #if DBG BOOLEAN HalpFailSleep = FALSE; #endif #define PM1_TMR_EN 0x0001 #define PM1_RTC_EN 0x0400 #define WAK_STS 0x8000 #define HAL_PRIMARY_PROCESSOR 0 // // For re-enabling the debugger's com port. // extern PUCHAR KdComPortInUse; VOID HalpAcpiFlushCache( ) { HalSweepDcache(); HalSweepIcache(); } VOID HalpSaveProcessorStateAndWait( IN PKPROCESSOR_STATE ProcessorState, IN volatile PULONG Count ) /*++ Rountine description: This function saves the volatile, non-volatile and special register state of the current processor. N.B. floating point state is NOT captured. Arguments: ProcessorState - Address of processor state record to fill in. pBarrier - Address of a value to use as a lock. Return Value: None. This function does not return. --*/ { #if 0 // // Fill in ProcessorState // KeSaveStateForHibernate(ProcessorState); // // Save return address, not caller's return address. // ProcessorState->ContextFrame.StIIP = HalpGetReturnAddress(); #endif // // Flush the cache, as the processor may be about to power off. // // HalpAcpiFlushCache(); // // Singal that this processor has saved its state. // HalpLockedIncrementUlong(Count); // // Wait for the hibernation file to be written. // Processor 0 will zero Barrier when it is // finished. // // N.B. We can't return from this function // before the hibernation file is finished // because we would be tearing down the very same // stack that we will be jumping onto when the // processor resumes. But after the hibernation // file is written, it doesn't matter, because // the stack will be restored from disk. // while (*Count != 0); } BOOLEAN HalpAcpiPreSleep( SLEEP_STATE_CONTEXT Context ) /*++ Routine Description: Arguments: none Return Value: status --*/ { USHORT pmTimer; GEN_ADDR pm1a; GEN_ADDR pm1b; pm1a = HalpFixedAcpiDescTable.x_pm1a_evt_blk; pm1a.Address.QuadPart += (HalpFixedAcpiDescTable.x_pm1a_evt_blk.BitWidth / 2 / 8); // 2 because we want to cut it in half, 8 because we want to convert bits to bytes pm1a.BitWidth = HalpFixedAcpiDescTable.x_pm1a_evt_blk.BitWidth / 2; pm1b = HalpFixedAcpiDescTable.x_pm1b_evt_blk; pm1b.Address.QuadPart += (HalpFixedAcpiDescTable.x_pm1b_evt_blk.BitWidth / 2 / 8); pm1b.BitWidth = HalpFixedAcpiDescTable.x_pm1b_evt_blk.BitWidth / 2; HalpSleepContext.AsULONG = Context.AsULONG; #if DBG if (HalpFailSleep) { return FALSE; } #endif // // If we should have woken up already, don't sleep. // if (HalpWakeupTimeElapsed()) { return FALSE; } // // If an RTC alarm is set, then enable it and disable // periodic interrupts (for profiling.) // HalpSetClockBeforeSleep(); // // Check to see if we need to disable all wakeup events. // if (!HalpWakeupState.GeneralWakeupEnable) { AcpiEnableDisableGPEvents(FALSE); } else { // // Only call this before going to sleep --- waking up should // reset the GPEs to the 'proper' value // AcpiGpeEnableWakeEvents(); } if (Context.bits.Flags & SLEEP_STATE_SAVE_MOTHERBOARD) { HalpSaveDmaControllerState(); HalpSaveTimerState(); } // // We need to make sure that the PM timer is disabled from // this point onward. We also need to make that the // RTC Enable is only enabled if the RTC shold wake up the compiler // pmTimer = (USHORT)HalpReadGenAddr(&pm1a); if (HalpFixedAcpiDescTable.x_pm1b_evt_blk.Address.QuadPart) { pmTimer |= (USHORT)HalpReadGenAddr(&pm1b); } // // Clear the timer enable bit. // pmTimer &= ~PM1_TMR_EN; // // Check to see if we the machine supports RTC wake in Fixed Feature // space. Some machines implement RTC support via control methods. // if (!(HalpFixedAcpiDescTable.flags & RTC_WAKE_GENERIC) ) { // // Check to see f we need to disable/enable the RTC alarm // if (!HalpWakeupState.RtcWakeupEnable) { pmTimer &= ~PM1_RTC_EN; } else { pmTimer |= PM1_RTC_EN; } } // // Write it back into the hardware. // HalpWriteGenAddr(&pm1a, pmTimer); if (HalpFixedAcpiDescTable.x_pm1b_evt_blk.Address.QuadPart) { HalpWriteGenAddr(&pm1b, pmTimer); } return TRUE; } BOOLEAN HalpAcpiPostSleep( ULONG Context ) { USHORT pmTimer; GEN_ADDR pm1a; GEN_ADDR pm1b; pm1a = HalpFixedAcpiDescTable.x_pm1a_evt_blk; pm1a.Address.QuadPart += (HalpFixedAcpiDescTable.pm1_evt_len / 2); pm1b = HalpFixedAcpiDescTable.x_pm1b_evt_blk; pm1b.Address.QuadPart += (HalpFixedAcpiDescTable.pm1_evt_len / 2); // // Read te currently set PM1 Enable bits. // pmTimer = (USHORT)HalpReadGenAddr(&pm1a); if (HalpFixedAcpiDescTable.x_pm1b_evt_blk.Address.QuadPart) { pmTimer |= (USHORT)HalpReadGenAddr(&pm1b); } // // Set the timer enable bit. Clear the RTC enable bit. // pmTimer &= ~PM1_RTC_EN; // // Write it back the new PM1 Enable bits // HalpWriteGenAddr(&pm1a, pmTimer); if (HalpFixedAcpiDescTable.x_pm1b_evt_blk.Address.QuadPart) { HalpWriteGenAddr(&pm1b, pmTimer); } // // Unset the RTC alarm and re-enable periodic interrupts. // HalpSetClockAfterSleep(); HalpWakeupState.RtcWakeupEnable = FALSE; *((PULONG)HalpWakeVector) = 0; HalpSetInterruptControllerWakeupState(Context); if (HalpSleepContext.bits.Flags & SLEEP_STATE_SAVE_MOTHERBOARD) { // // If Kd was in use, then invalidate it. It will re-sync itself. // if (KdComPortInUse) { KdRestore(TRUE); } HalpRestoreDmaControllerState(); HalpRestoreTimerState(); } // // Enable all GPEs, not just the wake ones // AcpiEnableDisableGPEvents(TRUE); return TRUE; } BOOLEAN HalpWakeupTimeElapsed( VOID ) { LARGE_INTEGER wakeupTime, currentTime; TIME_FIELDS currentTimeFields; // // Check to see if a wakeup timer has already expired. // if (HalpWakeupState.RtcWakeupEnable) { HalQueryRealTimeClock(¤tTimeFields); RtlTimeFieldsToTime(¤tTimeFields, ¤tTime); RtlTimeFieldsToTime(&HalpWakeupState.RtcWakeupTime, &wakeupTime); if (wakeupTime.QuadPart < currentTime.QuadPart) { return TRUE; } } return FALSE; } NTSTATUS HaliSetWakeAlarm ( IN ULONGLONG WakeSystemTime, IN PTIME_FIELDS WakeTimeFields OPTIONAL ) /*++ Routine Description: This routine sets the real-time clock's alarm to go off at a specified time in the future and programs the ACPI chipset so that this wakes the computer. Arguments: WakeSystemTime - amount of time that passes before we wake WakeTimeFields - time to wake broken down into TIME_FIELDS Return Value: status --*/ { if (WakeSystemTime == 0) { HalpWakeupState.RtcWakeupEnable = FALSE; return STATUS_SUCCESS; } ASSERT( WakeTimeFields ); HalpWakeupState.RtcWakeupEnable = TRUE; HalpWakeupState.RtcWakeupTime = *WakeTimeFields; return HalpSetWakeAlarm(WakeSystemTime, WakeTimeFields); } VOID HaliSetWakeEnable( IN BOOLEAN Enable ) /*++ Routine Description: This routine is called to set the policy for waking up. As we go to sleep, the global HalpWakeupState will be read and the hardware set accordingly. Arguments: Enable - true or false Return Value: --*/ { if (Enable) { HalpWakeupState.GeneralWakeupEnable = TRUE; } else { HalpWakeupState.GeneralWakeupEnable = FALSE; HalpWakeupState.RtcWakeupEnable = FALSE; } } VOID HalpReenableAcpi( VOID ) /*++ Routine Description: This calls into the ACPI driver to switch back into ACPI mode, presumably after S4 and sets the ACPI registers that the HAL controls. Arguments: Return Value: --*/ { // TEMPTEMP? HalpInitializeClock(); AcpiInitEnableAcpi(TRUE); AcpiEnableDisableGPEvents(TRUE); } /*++ Routine Description: This is a stub to allow us to perform device powerdown testing on IA64 machines before they actually support real sleep states. Arguments: Return Value: STATUS_NOT_SUPPORTED --*/ NTSTATUS HaliAcpiFakeSleep( IN PVOID Context, IN PENTER_STATE_SYSTEM_HANDLER SystemHandler OPTIONAL, IN PVOID SystemContext, IN LONG NumberProcessors, IN volatile PLONG Number ) { return STATUS_NOT_SUPPORTED; } NTSTATUS HaliAcpiSleep( IN PVOID Context, IN PENTER_STATE_SYSTEM_HANDLER SystemHandler OPTIONAL, IN PVOID SystemContext, IN LONG NumberProcessors, IN volatile PLONG Number ) /*++ Routine Description: At some point in time this function will be called to put PCs into a sleep state. It saves motherboard state and then bails out. For now this function is only called to implement S5 on Itanium. Arguments: --*/ { NTSTATUS Status = STATUS_SUCCESS; KIRQL OldIrql; SLEEP_STATE_CONTEXT SleepContext; USHORT SlpTypA, SlpTypB, Pm1Control; PKPROCESSOR_STATE CurrentProcessorState; GEN_ADDR Pm1bEvt; PKPRCB Prcb; // // initial setup. // HalpDisableInterrupts(); KeRaiseIrql(HIGH_LEVEL, &OldIrql); SleepContext.AsULONG = (ULONG) (((ULONGLONG) Context) & 0xffffffff); SlpTypA = (USHORT)HalpReadGenAddr(&HalpFixedAcpiDescTable.x_pm1a_ctrl_blk); if (HalpFixedAcpiDescTable.x_pm1b_ctrl_blk.Address.QuadPart) { SlpTypB = (USHORT)HalpReadGenAddr(&HalpFixedAcpiDescTable.x_pm1b_ctrl_blk); } // // If it is not processor 0, then goto wait loop. // Prcb = PCR->Prcb; if (Prcb->Number != 0) { // // Get processor number, get size of proc state and generate an index // into HalpHiberProcState. // CurrentProcessorState = HalpHiberProcState + Prcb->Number; HalpSaveProcessorStateAndWait(CurrentProcessorState, (PULONG) &HalpSleepSync); // // Wait for next phase // while (HalpSleepSync != 0); // wait for barrier to move } else { // processor 0 Barrier = 0; // // Make sure the other processors have saved their // state and begun to spin. // HalpLockedIncrementUlong((PULONG) &HalpSleepSync); while (NumberProcessors != (LONG) HalpSleepSync); // // Take care of chores (RTC, interrupt controller, etc.) // // // The hal has all of it's state saved into ram and is ready // for the power down. If there's a system state handler give // it a shot // if (SystemHandler) { Status = (*SystemHandler)(SystemContext); if (!NT_SUCCESS(Status)) { HalpReenableAcpi(); // // Restore the SLP_TYP registers. (So that embedded controllers // and BIOSes can be sure that we think the machine is awake.) // HalpWriteGenAddr (&HalpFixedAcpiDescTable.x_pm1a_ctrl_blk, SlpTypA); if (HalpFixedAcpiDescTable.x_pm1b_ctrl_blk.Address.QuadPart) { HalpWriteGenAddr(&HalpFixedAcpiDescTable.x_pm1b_ctrl_blk, SlpTypB); } HalpAcpiPostSleep(SleepContext.AsULONG); } } else { if (HalpAcpiPreSleep(SleepContext)) { // // If we will not be losing processor state, go to sleep. // if ((SleepContext.bits.Flags & SLEEP_STATE_FIRMWARE_RESTART) == 0) { // // Reset WAK_STS // HalpWriteGenAddr(&HalpFixedAcpiDescTable.x_pm1a_evt_blk, (USHORT) WAK_STS); if (HalpFixedAcpiDescTable.x_pm1b_evt_blk.Address.QuadPart) { HalpWriteGenAddr(&HalpFixedAcpiDescTable.x_pm1b_evt_blk, (USHORT) WAK_STS); } // // Flush the caches if necessary // if (SleepContext.bits.Flags & SLEEP_STATE_FLUSH_CACHE) { HalpAcpiFlushCache(); } // // Issue SLP commands to PM1a_CNT and PM1b_CNT // // // nibble 0 is 1a sleep type, put it in position and enable sleep. // preserve some bits in Pm1aCnt. // Pm1Control = (USHORT)HalpReadGenAddr(&HalpFixedAcpiDescTable.x_pm1a_ctrl_blk); Pm1Control = (USHORT) ((Pm1Control & CTL_PRESERVE) | (SleepContext.bits.Pm1aVal << SLP_TYP_SHIFT) | SLP_EN); HalpWriteGenAddr (&HalpFixedAcpiDescTable.x_pm1a_ctrl_blk, Pm1Control); // // nibble 1 is 1b sleep type, put it in position and enable sleep // preserve some bits in Pm1bCnt. // if (HalpFixedAcpiDescTable.x_pm1b_ctrl_blk.Address.QuadPart) { Pm1Control = (USHORT)HalpReadGenAddr(&HalpFixedAcpiDescTable.x_pm1b_ctrl_blk); Pm1Control = (USHORT) ((Pm1Control & CTL_PRESERVE) | (SleepContext.bits.Pm1bVal << SLP_TYP_SHIFT) | SLP_EN); HalpWriteGenAddr(&HalpFixedAcpiDescTable.x_pm1b_ctrl_blk, Pm1Control); } // // Wait for sleep to be over // if (HalpFixedAcpiDescTable.x_pm1b_evt_blk.Address.QuadPart) { Pm1bEvt = HalpFixedAcpiDescTable.x_pm1b_evt_blk; } else { Pm1bEvt = HalpFixedAcpiDescTable.x_pm1a_evt_blk; } while ( ((HalpReadGenAddr(&HalpFixedAcpiDescTable.x_pm1a_evt_blk) & WAK_STS) == 0) && ((HalpReadGenAddr(&Pm1bEvt) & WAK_STS) == 0) ); } else { CurrentProcessorState = HalpHiberProcState + Prcb->Number; // HalpSetupStateForResume(CurrentProcessorState); } } // HalpAcpiPreSleep() == 0 } // SystemHandler == 0 // // Notify other processor of completion // HalpSleepSync = 0; } // processor 0 // // Restore each processor's APIC state. // // HalpPostSleepMP; // // Restore caller's IRQL. // KeLowerIrql(OldIrql); // // Exit. // // HalpSleepSync = 0; return(Status); }