/*++ 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: --*/ #include "halp.h" #include "acpitabl.h" #include "xxacpi.h" #include "kddll.h" #include "ixsleep.h" // // Internal functions // NTSTATUS HalpAcpiSleep( IN PVOID Context, IN LONG NumberProcessors, IN volatile PLONG Number ); VOID HalpSetClockBeforeSleep( VOID ); VOID HalpSetClockAfterSleep( VOID ); BOOLEAN HalpWakeupTimeElapsed( VOID ); VOID HalpFreeTiledCR3 ( VOID ); VOID HalpReenableAcpi( VOID ); VOID HalpPiix4Detect( BOOLEAN DuringBoot ); 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, 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) #pragma alloc_text(PAGELK, HalpMapNvsArea) #pragma alloc_text(PAGELK, HalpFreeNvsBuffers) #endif HAL_WAKEUP_STATE HalpWakeupState; #if DBG BOOLEAN HalpFailSleep = FALSE; #endif #define PM1_TMR_EN 0x0001 #define PM1_RTC_EN 0x0400 // // For re-enabling the debugger's com port. // extern PUCHAR KdComPortInUse; extern PACPI_BIOS_MULTI_NODE HalpAcpiMultiNode; extern PUCHAR HalpAcpiNvsData; extern PVOID *HalpNvsVirtualAddress; BOOLEAN HalpAcpiPreSleep( SLEEP_STATE_CONTEXT Context ) /*++ Routine Description: Arguments: none Return Value: status --*/ { USHORT pmTimer; PUSHORT pm1a; PUSHORT pm1b; PUSHORT pm1astatus; PUSHORT pm1bstatus; BOOLEAN wakeupElapsed; pm1astatus = (PUSHORT) HalpFixedAcpiDescTable.pm1a_evt_blk_io_port; pm1bstatus = (PUSHORT) HalpFixedAcpiDescTable.pm1b_evt_blk_io_port; pm1a = (PUSHORT)(HalpFixedAcpiDescTable.pm1a_evt_blk_io_port + (HalpFixedAcpiDescTable.pm1_evt_len / 2)); pm1b = (PUSHORT)(HalpFixedAcpiDescTable.pm1b_evt_blk_io_port + (HalpFixedAcpiDescTable.pm1_evt_len / 2)); HalpSleepContext.AsULONG = Context.AsULONG; #if DBG if (HalpFailSleep) { return FALSE; } #endif // // If we should have woken up already, don't sleep. // wakeupElapsed = HalpWakeupTimeElapsed(); // // If an RTC alarm is set, then enable it and disable // periodic interrupts (for profiling.) // if (!wakeupElapsed) { HalpSetClockBeforeSleep(); } // // Save the (A)PIC for any sleep state, as we need to play // with it on the way back up again. // HalpSaveInterruptControllerState(); 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 should wake up the computer // pmTimer = READ_PORT_USHORT(pm1a); if (HalpFixedAcpiDescTable.pm1b_evt_blk_io_port) { pmTimer |= READ_PORT_USHORT(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 if 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. // WRITE_PORT_USHORT(pm1a, pmTimer); if (HalpFixedAcpiDescTable.pm1b_evt_blk_io_port) { WRITE_PORT_USHORT(pm1b, pmTimer); } // // At this point, we should be running with interrupts disabled and // the TMR_EN bit cleared. This is a good place to clear the PM1 Status // Register // pmTimer = READ_PORT_USHORT( pm1astatus ); if (HalpFixedAcpiDescTable.pm1b_evt_blk_io_port) { pmTimer |= READ_PORT_USHORT( pm1bstatus ); } WRITE_PORT_USHORT( pm1astatus, pmTimer ); if (HalpFixedAcpiDescTable.pm1b_evt_blk_io_port) { WRITE_PORT_USHORT( pm1bstatus, pmTimer ); } // // 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(); } HalpPreserveNvsArea(); if (wakeupElapsed) { return FALSE; } else { return TRUE; } } BOOLEAN HalpAcpiPostSleep( ULONG Context ) { USHORT pmTimer; PUSHORT pm1a; PUSHORT pm1b; BOOLEAN ProfileInterruptEnabled; #ifdef PICACPI extern ULONG HalpProfilingStopped; ProfileInterruptEnabled = (HalpProfilingStopped == 0); #else extern ULONG HalpProfileRunning; ProfileInterruptEnabled = (HalpProfileRunning == 1); #endif pm1a = (PUSHORT)(HalpFixedAcpiDescTable.pm1a_evt_blk_io_port + (HalpFixedAcpiDescTable.pm1_evt_len / 2)); pm1b = (PUSHORT)(HalpFixedAcpiDescTable.pm1b_evt_blk_io_port + (HalpFixedAcpiDescTable.pm1_evt_len / 2)); // // Read the currently set PM1 Enable bits // pmTimer = READ_PORT_USHORT(pm1a); if (HalpFixedAcpiDescTable.pm1b_evt_blk_io_port) { pmTimer |= READ_PORT_USHORT(pm1b); } // // Set the timer enable bit. Clear the RTC enable bit // pmTimer |= PM1_TMR_EN; pmTimer &= ~PM1_RTC_EN; // // Write back the new PM1 Enable bits // WRITE_PORT_USHORT(pm1a, pmTimer); if (HalpFixedAcpiDescTable.pm1b_evt_blk_io_port) { WRITE_PORT_USHORT(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(); } HalpPiix4Detect(FALSE); // // Enable all GPEs, not just the wake ones // AcpiEnableDisableGPEvents(TRUE); HalpRestoreNvsArea(); HalpResetSBF(); // // If we were profiling before, fire up the profile interrupt // if (ProfileInterruptEnabled) { HalStartProfileInterrupt(0); } 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 ((ULONGLONG)wakeupTime.QuadPart < (ULONGLONG)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: --*/ { // // Always clear the RTC wake --- we expect that someone will // set the alarm after they call this function // HalpWakeupState.RtcWakeupEnable = FALSE; // // Toggle the generate wake up bit // HalpWakeupState.GeneralWakeupEnable = Enable; } 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); } VOID HalpMapNvsArea( VOID ) { NTSTATUS status; ULONG i, bufferSize, bufferOffset, nodeCount; PAGED_CODE(); status = HalpAcpiFindRsdt(&HalpAcpiMultiNode); if (!NT_SUCCESS(status)) { return; } if (HalpAcpiMultiNode->Count == 0) { // // There's no work to do here. // goto HalpMapNvsError; } // // Find total size of the buffer we need. // bufferSize = 0; nodeCount = 0; for (i = 0; i < HalpAcpiMultiNode->Count; i++) { if (HalpAcpiMultiNode->E820Entry[i].Type == AcpiAddressRangeNVS) { ASSERT(HalpAcpiMultiNode->E820Entry[i].Length.HighPart == 0); bufferSize += HalpAcpiMultiNode->E820Entry[i].Length.LowPart; nodeCount++; } } if (bufferSize == 0) { // // There's no work to do here. // goto HalpMapNvsError; } #if DBG if (bufferSize > (20 * PAGE_SIZE)) { DbgPrint("HALACPI: The BIOS wants the OS to preserve %x bytes\n", bufferSize); } #endif HalpAcpiNvsData = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'AlaH'); if (!HalpAcpiNvsData) { DbgPrint("HALACPI: The BIOS's non-volatile data will not be preserved\n"); goto HalpMapNvsError; } HalpNvsVirtualAddress = ExAllocatePoolWithTag(NonPagedPool, (nodeCount + 1) * sizeof(PVOID), 'AlaH'); if (!HalpNvsVirtualAddress) { goto HalpMapNvsError; } // // Make a mapping for each run. // bufferOffset = 0; nodeCount = 0; for (i = 0; i < HalpAcpiMultiNode->Count; i++) { if (HalpAcpiMultiNode->E820Entry[i].Type == AcpiAddressRangeNVS) { HalpNvsVirtualAddress[nodeCount] = MmMapIoSpace(HalpAcpiMultiNode->E820Entry[i].Base, HalpAcpiMultiNode->E820Entry[i].Length.LowPart, TRUE); ASSERT(HalpNvsVirtualAddress[nodeCount]); nodeCount++; } } // // Mark the end. // HalpNvsVirtualAddress[nodeCount] = NULL; return; HalpMapNvsError: if (HalpAcpiMultiNode) ExFreePool(HalpAcpiMultiNode); if (HalpNvsVirtualAddress) ExFreePool(HalpNvsVirtualAddress); if (HalpAcpiNvsData) ExFreePool(HalpAcpiNvsData); HalpAcpiMultiNode = NULL; return; } VOID HalpPreserveNvsArea( VOID ) { ULONG i, dataOffset = 0, nodeCount = 0; if (!HalpAcpiMultiNode) { // // Either there was nothing to save or there // was a fatal error. // return; } for (i = 0; i < HalpAcpiMultiNode->Count; i++) { if (HalpAcpiMultiNode->E820Entry[i].Type == AcpiAddressRangeNVS) { // // Copy from BIOS memory to temporary buffer. // RtlCopyMemory(HalpAcpiNvsData + dataOffset, HalpNvsVirtualAddress[nodeCount], HalpAcpiMultiNode->E820Entry[i].Length.LowPart); nodeCount++; dataOffset += HalpAcpiMultiNode->E820Entry[i].Length.LowPart; } } } VOID HalpRestoreNvsArea( VOID ) { ULONG i, dataOffset = 0, nodeCount = 0; if (!HalpAcpiMultiNode) { // // Either there was nothing to save or there // was a fatal error. // return; } for (i = 0; i < HalpAcpiMultiNode->Count; i++) { if (HalpAcpiMultiNode->E820Entry[i].Type == AcpiAddressRangeNVS) { // // Copy from temporary buffer to BIOS area. // RtlCopyMemory(HalpNvsVirtualAddress[nodeCount], HalpAcpiNvsData + dataOffset, HalpAcpiMultiNode->E820Entry[i].Length.LowPart); nodeCount++; dataOffset += HalpAcpiMultiNode->E820Entry[i].Length.LowPart; } } } VOID HalpFreeNvsBuffers( VOID ) { ULONG i, nodeCount = 0; PAGED_CODE(); if (!HalpAcpiMultiNode) { // // Either there was nothing to save or there // was a fatal error. // return; } for (i = 0; i < HalpAcpiMultiNode->Count; i++) { if (HalpAcpiMultiNode->E820Entry[i].Type == AcpiAddressRangeNVS) { // // Give back all the PTEs that we took earlier // MmUnmapIoSpace(HalpNvsVirtualAddress[nodeCount], HalpAcpiMultiNode->E820Entry[i].Length.LowPart); nodeCount++; } } ASSERT(HalpAcpiMultiNode); ASSERT(HalpNvsVirtualAddress); ASSERT(HalpAcpiNvsData); ExFreePool(HalpAcpiMultiNode); ExFreePool(HalpNvsVirtualAddress); ExFreePool(HalpAcpiNvsData); HalpAcpiMultiNode = NULL; HalpNvsVirtualAddress = NULL; HalpAcpiNvsData = NULL; }