/*++ Copyright (c) 1990 Microsoft Corporation Module Name: sysdev.c Abstract: This module interfaces to the system power state IRPs for devices Author: Ken Reneris (kenr) 17-Jan-1997 Revision History: --*/ #include "pop.h" // // External used to determine if the device tree has changed between // passes of informing devices of a system power state // extern ULONG IoDeviceNodeTreeSequence; // // Internal prototypes // VOID PopSleepDeviceList ( IN PPOP_DEVICE_SYS_STATE DevState, IN PPO_NOTIFY_ORDER_LEVEL Level ); VOID PopWakeDeviceList ( IN PPOP_DEVICE_SYS_STATE DevState, IN PPO_NOTIFY_ORDER_LEVEL Level ); VOID PopNotifyDevice ( IN PPOP_DEVICE_SYS_STATE DevState, IN PPO_DEVICE_NOTIFY Notify ); VOID PopWaitForSystemPowerIrp ( IN PPOP_DEVICE_SYS_STATE DevState, IN BOOLEAN WaitForAll ); NTSTATUS PopCompleteSystemPowerIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ); BOOLEAN PopCheckSystemPowerIrpStatus ( IN PPOP_DEVICE_SYS_STATE DevState, IN PIRP Irp, IN BOOLEAN AllowTestFailure ); VOID PopDumpSystemIrp ( IN PUCHAR Desc, IN PPOP_DEVICE_POWER_IRP PowerIrp ); VOID PopResetChildCount( IN PLIST_ENTRY ListHead ); VOID PopSetupListForWake( IN PPO_NOTIFY_ORDER_LEVEL Level, IN PLIST_ENTRY ListHead ); VOID PopWakeSystemTimeout( IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGELK, PopSetDevicesSystemState) #pragma alloc_text(PAGELK, PopWakeDeviceList) #pragma alloc_text(PAGELK, PopSleepDeviceList) #pragma alloc_text(PAGELK, PopResetChildCount) #pragma alloc_text(PAGELK, PopSetupListForWake) #pragma alloc_text(PAGELK, PopNotifyDevice) #pragma alloc_text(PAGELK, PopWaitForSystemPowerIrp) #pragma alloc_text(PAGELK, PopCompleteSystemPowerIrp) #pragma alloc_text(PAGELK, PopCheckSystemPowerIrpStatus) #pragma alloc_text(PAGELK, PopCleanupDevState) #pragma alloc_text(PAGELK, PopRestartSetSystemState) #pragma alloc_text(PAGELK, PopReportDevState) #pragma alloc_text(PAGELK, PopDumpSystemIrp) #pragma alloc_text(PAGELK, PopWakeSystemTimeout) #pragma alloc_text(PAGE, PopAllocateDevState) #endif ULONG PopCurrentLevel=0; LONG PopWakeTimer = 1; KTIMER PopWakeTimeoutTimer; KDPC PopWakeTimeoutDpc; NTSTATUS PopSetDevicesSystemState ( IN BOOLEAN Wake ) /*++ Routine Description: Sends a system power irp of IrpMinor and SystemState from PopAction to all devices. N.B. Function is not re-entrant. N.B. Policy lock must be held. This function releases and reacquires the policy lock. Arguments: Wake - TRUE if a transition to S0 should be broadcast to all drivers. FALSE if the appropriate sleep transition can be found in PopAction.DevState Return Value: Status. SUCCESS - all devices contacted without any errors. CANCELLED - operation was aborted. Error - error code of first failure. All failed IRPs and related device objects are on the Failed list. --*/ { LONG i; NTSTATUS Status; PLIST_ENTRY ListHead; BOOLEAN NotifyGdi; BOOLEAN DidIoMmShutdown = FALSE; PPO_DEVICE_NOTIFY NotifyDevice; PLIST_ENTRY Link; PPOP_DEVICE_POWER_IRP PowerIrp; POWER_ACTION powerOperation; PPOP_DEVICE_SYS_STATE DevState; ASSERT(PopAction.DevState ); DevState = PopAction.DevState; // // Intialize DevState for this pass // DevState->IrpMinor = PopAction.IrpMinor; DevState->SystemState = PopAction.SystemState; DevState->Status = STATUS_SUCCESS; DevState->FailedDevice = NULL; DevState->Cancelled = FALSE; DevState->IgnoreErrors = FALSE; DevState->IgnoreNotImplemented = FALSE; DevState->Waking = Wake; NotifyGdi = FALSE; if (PERFINFO_IS_GROUP_ON(PERF_POWER)) { PERFINFO_SET_DEVICES_STATE LogEntry; LogEntry.SystemState = (ULONG) DevState->SystemState; LogEntry.IrpMinor = PopAction.IrpMinor; LogEntry.Waking = Wake; LogEntry.Shutdown = PopAction.Shutdown; PerfInfoLogBytes(PERFINFO_LOG_TYPE_SET_DEVICES_STATE, &LogEntry, sizeof(LogEntry)); } // // If this is a set operation, and the Gdi state is on then we need to // notify gdi of the set power operation // if (PopAction.IrpMinor == IRP_MN_SET_POWER && AnyBitsSet (PopFullWake, PO_FULL_WAKE_STATUS | PO_GDI_STATUS)) { NotifyGdi = TRUE; } // // If the request is for Query on a shutdown operarion, ignore any // drivers which don't implment it. If it's for a Set on a // shutdown operation, ignore any errors - the system is going to // shutdown // if (PopAction.Shutdown) { DevState->IgnoreNotImplemented = TRUE; if (PopAction.IrpMinor == IRP_MN_SET_POWER) { DevState->IgnoreErrors = TRUE; } } // // This function is not re-entrant, and the operation has been // serialized before here // ASSERT (DevState->Thread == KeGetCurrentThread()); // // Notify all devices. // if (!Wake) { // // If it's time to update the device list, then do so // if (DevState->GetNewDeviceList) { DevState->GetNewDeviceList = FALSE; IoFreePoDeviceNotifyList (&DevState->Order); DevState->Status = IoBuildPoDeviceNotifyList (&DevState->Order); } else { // // Reset the active child count of each notification // for (i=0;i<=PO_ORDER_MAXIMUM;i++) { PopResetChildCount(&DevState->Order.OrderLevel[i].WaitSleep); PopResetChildCount(&DevState->Order.OrderLevel[i].ReadySleep); PopResetChildCount(&DevState->Order.OrderLevel[i].ReadyS0); PopResetChildCount(&DevState->Order.OrderLevel[i].WaitS0); PopResetChildCount(&DevState->Order.OrderLevel[i].Complete); } } if (NT_SUCCESS(DevState->Status)) { // // Notify all devices of operation in forward order. Wait between each level. // DidIoMmShutdown = FALSE; for (i=PO_ORDER_MAXIMUM; i >= 0; i--) { // // Notify this list // if (DevState->Order.OrderLevel[i].DeviceCount) { if ((NotifyGdi) && (i <= PO_ORDER_GDI_NOTIFICATION)) { NotifyGdi = FALSE; InterlockedExchange (&PopFullWake, 0); if (PopEventCallout) { // // Turn off the special system irp dispatcher here // as when we call into GDI it is going to block on its // D irp and we are not going to get control back. // PopSystemIrpDispatchWorker(TRUE); PopEventCalloutDispatch (PsW32GdiOff, DevState->SystemState); } } // // If we're shutting down and if we're done // notifying paged devices, shut down filesystems // and MM to free up all resources on the paging // path (which we should no longer need). // if (PopAction.Shutdown && !DidIoMmShutdown && (i < PO_ORDER_PAGABLE)) { // ISSUE-2000/03/14-earhart: shutdown // filesystems here. // // Swap in the worker threads, to keep them // from paging // // ExShutdownSystem (1); // // Send shutdown IRPs to all drivers that asked for it. This is // primarily for the filesystems as we will soon be closing the pagefile // handle causing the filesystems to unload. Drivers must free (or lock // down) their pagable data before this call returns. // // IoShutdownSystem (1); // // Memory management will close the pagefile handle(s) here, causing the // filesystem stack to unload. // // NO MORE REFERENCES TO PAGABLE CODE OR DATA MAY BE MADE. // // MmShutdownSystem (1); DidIoMmShutdown = TRUE; } // // Remove the warm eject node if we might have gotten here // without a query. // if (PopAction.Flags & POWER_ACTION_CRITICAL) { *DevState->Order.WarmEjectPdoPointer = NULL; } // // Notify this list // PopCurrentLevel = i; PopSleepDeviceList (DevState, &DevState->Order.OrderLevel[i]); PopWaitForSystemPowerIrp (DevState, TRUE); } // // If there's been an error, stop and issue wakes to all devices // if (!NT_SUCCESS(DevState->Status)) { Wake = TRUE; if ((DevState->FailedDevice != NULL) && (PopAction.NextSystemState == PowerSystemWorking)) { powerOperation = PopMapInternalActionToIrpAction( PopAction.Action, DevState->SystemState, FALSE ); IoNotifyPowerOperationVetoed( powerOperation, (powerOperation == PowerActionWarmEject) ? *DevState->Order.WarmEjectPdoPointer : NULL, DevState->FailedDevice ); } break; } } } // // This will cause us to wake up all the devices after putting // them to sleep. Useful for test automation. // if ((PopSimulate & POP_WAKE_DEVICE_AFTER_SLEEP) && (PopAction.IrpMinor == IRP_MN_SET_POWER)) { DbgPrint ("po: POP_WAKE_DEVICE_AFTER_SLEEP enabled.\n"); Wake = TRUE; DevState->Status = STATUS_UNSUCCESSFUL; } } // // Just in case we somehow managed to not shutdown paging // before, we'll make sure we do it here. // if (PopAction.Shutdown && !DidIoMmShutdown) { // ExShutdownSystem(1); // IoShutdownSystem(1); // MmShutdownSystem(1); DidIoMmShutdown = TRUE; } // // Some debugging code here. If the debug flag is set, then loop on failed // devices and continue to retry them. This will allow someone to step // them through the driver stack to determine where the failure is. // while ((PopSimulate & POP_LOOP_ON_FAILED_DRIVERS) && !IsListEmpty(&PopAction.DevState->Head.Failed)) { Link = PopAction.DevState->Head.Failed.Flink; RemoveEntryList(Link); PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Failed); PopDumpSystemIrp ("Retry", PowerIrp); IoFreeIrp (PowerIrp->Irp); NotifyDevice = PowerIrp->Notify; PowerIrp->Irp = NULL; PowerIrp->Notify = NULL; PushEntryList ( &PopAction.DevState->Head.Free, &PowerIrp->Free ); DbgBreakPoint (); PopNotifyDevice (DevState, NotifyDevice); PopWaitForSystemPowerIrp (DevState, TRUE); } // // If waking, send set power to the working state to all devices which where // send something else // DevState->Waking = Wake; if (DevState->Waking) { DevState->IgnoreErrors = TRUE; DevState->IrpMinor = IRP_MN_SET_POWER; DevState->SystemState = PowerSystemWorking; // // Notify all devices of the wake operation in reverse (level) order. // KeInitializeTimer(&PopWakeTimeoutTimer); KeInitializeDpc(&PopWakeTimeoutDpc, PopWakeSystemTimeout, NULL); for (i=0; i <= PO_ORDER_MAXIMUM; i++) { PopCurrentLevel = i; PopWakeDeviceList (DevState, &DevState->Order.OrderLevel[i]); PopWaitForSystemPowerIrp (DevState, TRUE); if (PopSimulate & POP_WAKE_DEADMAN) { KeCancelTimer(&PopWakeTimeoutTimer); } } // restore DevState->IrpMinor = PopAction.IrpMinor; DevState->SystemState = PopAction.SystemState; } // // Done // if (PERFINFO_IS_GROUP_ON(PERF_POWER)) { PERFINFO_SET_DEVICES_STATE_RET LogEntry; LogEntry.Status = DevState->Status; PerfInfoLogBytes(PERFINFO_LOG_TYPE_SET_DEVICES_STATE_RET, &LogEntry, sizeof(LogEntry)); } return DevState->Status; } VOID PopReportDevState ( IN BOOLEAN LogErrors ) /*++ Routine Description: Verifies that the DevState structure is idle Arguments: None Return Value: None --*/ { PIRP Irp; PLIST_ENTRY Link; PPOP_DEVICE_POWER_IRP PowerIrp; PUCHAR IrpType; PIO_ERROR_LOG_PACKET ErrLog; if (!PopAction.DevState) { return ; } // // Cleanup any irps on the failed list // while (!IsListEmpty(&PopAction.DevState->Head.Failed)) { Link = PopAction.DevState->Head.Failed.Flink; RemoveEntryList(Link); PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Failed); Irp = PowerIrp->Irp; PopDumpSystemIrp ( LogErrors ? "Abort" : "fyi", PowerIrp ); if (LogErrors) { ErrLog = IoAllocateErrorLogEntry ( PowerIrp->Notify->TargetDevice->DriverObject, ERROR_LOG_MAXIMUM_SIZE ); if (ErrLog) { RtlZeroMemory (ErrLog, sizeof (*ErrLog)); ErrLog->FinalStatus = Irp->IoStatus.Status; ErrLog->DeviceOffset.QuadPart = Irp->IoStatus.Information; ErrLog->MajorFunctionCode = IRP_MJ_POWER; ErrLog->UniqueErrorValue = (PopAction.DevState->IrpMinor << 16) | PopAction.DevState->SystemState; ErrLog->ErrorCode = IO_SYSTEM_SLEEP_FAILED; IoWriteErrorLogEntry (ErrLog); } } IoFreeIrp (Irp); PowerIrp->Irp = NULL; PowerIrp->Notify = NULL; PushEntryList ( &PopAction.DevState->Head.Free, &PowerIrp->Free ); } // // Errors have been purged, we can now allocate a new device notification list if needed // if (PopAction.DevState->Order.DevNodeSequence != IoDeviceNodeTreeSequence) { PopAction.DevState->GetNewDeviceList = TRUE; } } VOID PopAllocateDevState( VOID ) /*++ Routine Description: Allocates and initialies the DevState structure. Arguments: None Return Value: PopAction.DevState != NULL if successful. PopAction.DevState == NULL otherwise. --*/ { PPOP_DEVICE_SYS_STATE DevState; ULONG i; PAGED_CODE(); ASSERT(PopAction.DevState == NULL); // // Allocate a device state structure // DevState = (PPOP_DEVICE_SYS_STATE) ExAllocatePoolWithTag(NonPagedPool, sizeof (POP_DEVICE_SYS_STATE), POP_PDSS_TAG); if (!DevState) { PopAction.DevState = NULL; return; } RtlZeroMemory (DevState, sizeof(POP_DEVICE_SYS_STATE)); DevState->Thread = KeGetCurrentThread(); DevState->GetNewDeviceList = TRUE; KeInitializeSpinLock (&DevState->SpinLock); KeInitializeEvent (&DevState->Event, SynchronizationEvent, FALSE); DevState->Head.Free.Next = NULL; InitializeListHead (&DevState->Head.Pending); InitializeListHead (&DevState->Head.Complete); InitializeListHead (&DevState->Head.Abort); InitializeListHead (&DevState->Head.Failed); InitializeListHead (&DevState->PresentIrpQueue); for (i=0; i < MAX_SYSTEM_POWER_IRPS; i++) { DevState->PowerIrpState[i].Irp = NULL; PushEntryList (&DevState->Head.Free, &DevState->PowerIrpState[i].Free); } for (i=0; i <= PO_ORDER_MAXIMUM; i++) { KeInitializeEvent(&DevState->Order.OrderLevel[i].LevelReady, NotificationEvent, FALSE); InitializeListHead(&DevState->Order.OrderLevel[i].WaitSleep); InitializeListHead(&DevState->Order.OrderLevel[i].ReadySleep); InitializeListHead(&DevState->Order.OrderLevel[i].Pending); InitializeListHead(&DevState->Order.OrderLevel[i].Complete); InitializeListHead(&DevState->Order.OrderLevel[i].ReadyS0); InitializeListHead(&DevState->Order.OrderLevel[i].WaitS0); } PopAction.DevState = DevState; } VOID PopCleanupDevState ( VOID ) /*++ Routine Description: Verifies that the DevState structure is idle Arguments: None Return Value: None --*/ { // // Notify power irp code that the device system state irps // are done // PopSystemIrpDispatchWorker (TRUE); // // Verify all lists are empty // ASSERT(IsListEmpty(&PopAction.DevState->Head.Pending) && IsListEmpty(&PopAction.DevState->Head.Complete) && IsListEmpty(&PopAction.DevState->Head.Abort) && IsListEmpty(&PopAction.DevState->Head.Failed) && IsListEmpty(&PopAction.DevState->PresentIrpQueue)); ExFreePool (PopAction.DevState); PopAction.DevState = NULL; } #define STATE_DONE_WAITING 0 #define STATE_COMPLETE_IRPS 1 #define STATE_PRESENT_PAGABLE_IRPS 2 #define STATE_CHECK_CANCEL 3 #define STATE_WAIT_NOW 4 VOID PopWaitForSystemPowerIrp ( IN PPOP_DEVICE_SYS_STATE DevState, IN BOOLEAN WaitForAll ) /*++ Routine Description: Called to wait for one or more system power irps to complete. Handles final processing of any completed irp. Arguments: DevState - Current DevState structure WaitForAll - If TRUE all outstanding IRPs are waited for, else any outstanding irp will do Return Value: None --*/ { KIRQL OldIrql; ULONG State; BOOLEAN IrpCompleted; BOOLEAN NotImplemented; PIRP Irp; PLIST_ENTRY Link; PPOP_DEVICE_POWER_IRP PowerIrp; PPO_DEVICE_NOTIFY Notify; NTSTATUS Status; LARGE_INTEGER Timeout; IrpCompleted = FALSE; KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); // // Signal completion function that we are waiting // State = STATE_COMPLETE_IRPS; while (State != STATE_DONE_WAITING) { switch (State) { case STATE_COMPLETE_IRPS: // // Assume we're going to advance to the next state // State += 1; // // If there arn't any irps on the complete list, move to the // next state // if (IsListEmpty(&DevState->Head.Complete)) { break; } // // Handle the completed irps // IrpCompleted = TRUE; while (!IsListEmpty(&DevState->Head.Complete)) { Link = DevState->Head.Complete.Flink; RemoveEntryList(Link); PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Complete); Notify = PowerIrp->Notify; PowerIrp->Complete.Flink = NULL; Irp = PowerIrp->Irp; // // Verify the device driver called PoStartNextPowerIrp // if ((Notify->TargetDevice->DeviceObjectExtension->PowerFlags & POPF_SYSTEM_ACTIVE) || (Notify->DeviceObject->DeviceObjectExtension->PowerFlags & POPF_SYSTEM_ACTIVE)) { PDEVICE_OBJECT DeviceObject = Notify->DeviceObject; KeReleaseSpinLock (&DevState->SpinLock, OldIrql); PopDumpSystemIrp ("SYS STATE", PowerIrp); PopInternalAddToDumpFile ( NULL, 0, DeviceObject, NULL, NULL, NULL ); PopInternalAddToDumpFile ( NULL, 0, Notify->TargetDevice, NULL, NULL, NULL ); KeBugCheckEx ( DRIVER_POWER_STATE_FAILURE, 0x500, DEVICE_SYSTEM_STATE_HUNG, (ULONG_PTR) Notify->TargetDevice, (ULONG_PTR) DeviceObject ); } // // If success, or cancelled, or not implemented that's OK, then // the irp is complete // if (PopCheckSystemPowerIrpStatus(DevState, Irp, TRUE)) { // // See if IRP is failure being allowed for testing // if (!PopCheckSystemPowerIrpStatus(DevState, Irp, FALSE)) { KeReleaseSpinLock (&DevState->SpinLock, OldIrql); PopDumpSystemIrp ("ignored", PowerIrp); KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); } // // Request is complete, free it // IoFreeIrp (Irp); PowerIrp->Irp = NULL; PowerIrp->Notify = NULL; PushEntryList ( &DevState->Head.Free, &PowerIrp->Free ); } else { // // Some sort of error. Keep track of the failure // ASSERT (!DevState->Waking); InsertTailList(&DevState->Head.Failed, &PowerIrp->Failed); } } break; case STATE_PRESENT_PAGABLE_IRPS: // // Assume we're going to advance to the next state // State += 1; // // If the last device that a system irp was sent to was pagable, // we use a thread to present them to the driver so it can page. // if (!(PopCallSystemState & PO_CALL_NON_PAGED)) { KeReleaseSpinLock (&DevState->SpinLock, OldIrql); PopSystemIrpDispatchWorker (FALSE); KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); } break; case STATE_CHECK_CANCEL: // // Assume we're going to advance to the next state // State += 1; // // If there's no error or we've already canceled move to the state // if (NT_SUCCESS(DevState->Status) || DevState->Cancelled || DevState->Waking) { break; } // // First time the error has been seen. Cancel anything outstanding. // Build list of all pending irps // DevState->Cancelled = TRUE; for (Link = DevState->Head.Pending.Flink; Link != &DevState->Head.Pending; Link = Link->Flink) { PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Pending); InsertTailList (&DevState->Head.Abort, &PowerIrp->Abort); } // // Drop completion lock and cancel irps on abort list // KeReleaseSpinLock (&DevState->SpinLock, OldIrql); for (Link = DevState->Head.Abort.Flink; Link != &DevState->Head.Abort; Link = Link->Flink) { PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Abort); IoCancelIrp (PowerIrp->Irp); } KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); InitializeListHead (&DevState->Head.Abort); // // After canceling check for more completed irps // State = STATE_COMPLETE_IRPS; break; case STATE_WAIT_NOW: // // Check for wait condition // if ((!WaitForAll && IrpCompleted) || IsListEmpty(&DevState->Head.Pending)) { // // Done. After waiting, verify there's at least struct on the // free list. If not, recycle something off the failured list // if (!DevState->Head.Free.Next && !IsListEmpty(&DevState->Head.Failed)) { Link = DevState->Head.Failed.Blink; PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Failed); RemoveEntryList(Link); PowerIrp->Failed.Flink = NULL; PowerIrp->Irp = NULL; PowerIrp->Notify = NULL; PushEntryList ( &DevState->Head.Free, &PowerIrp->Free ); } State = STATE_DONE_WAITING; break; } // // Signal completion function that we are waiting // DevState->WaitAll = TRUE; DevState->WaitAny = !WaitForAll; // // Drop locks and wait for event to be signalled // KeClearEvent (&DevState->Event); KeReleaseSpinLock (&DevState->SpinLock, OldIrql); Timeout.QuadPart = DevState->Cancelled ? POP_ACTION_CANCEL_TIMEOUT : POP_ACTION_TIMEOUT; Timeout.QuadPart = Timeout.QuadPart * US2SEC * US2TIME * -1; Status = KeWaitForSingleObject ( &DevState->Event, Suspended, KernelMode, FALSE, &Timeout ); KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); // // No longer waiting // DevState->WaitAll = FALSE; DevState->WaitAny = FALSE; // // If this is a timeout, then dump all the pending irps // if (Status == STATUS_TIMEOUT) { for (Link = DevState->Head.Pending.Flink; Link != &DevState->Head.Pending; Link = Link->Flink) { PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Pending); InsertTailList (&DevState->Head.Abort, &PowerIrp->Abort); } KeReleaseSpinLock (&DevState->SpinLock, OldIrql); for (Link = DevState->Head.Abort.Flink; Link != &DevState->Head.Abort; Link = Link->Flink) { PowerIrp = CONTAINING_RECORD (Link, POP_DEVICE_POWER_IRP, Abort); PopDumpSystemIrp ("Waiting on", PowerIrp); } KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); InitializeListHead (&DevState->Head.Abort); } // // Check for completed irps // State = STATE_COMPLETE_IRPS; break; } } KeReleaseSpinLock (&DevState->SpinLock, OldIrql); } VOID PopSleepDeviceList ( IN PPOP_DEVICE_SYS_STATE DevState, IN PPO_NOTIFY_ORDER_LEVEL Level ) /*++ Routine Description: Sends Sx power irps to all devices in the supplied level Arguments: DevState - Supplies the devstate Level - Supplies the level to send power irps to Return Value: None. DevState->Status is set on error. --*/ { PPO_DEVICE_NOTIFY NotifyDevice; PLIST_ENTRY Link; KIRQL OldIrql; ASSERT(!DevState->Waking); ASSERT(IsListEmpty(&Level->Pending)); ASSERT(IsListEmpty(&Level->ReadyS0)); ASSERT(IsListEmpty(&Level->WaitS0)); // // Move any devices from the completed list back to their correct spots. // Link = Level->ReadyS0.Flink; while (Link != &Level->ReadyS0) { NotifyDevice = CONTAINING_RECORD (Link, PO_DEVICE_NOTIFY, Link); Link = NotifyDevice->Link.Flink; if (NotifyDevice->ChildCount) { InsertHeadList(&Level->WaitSleep, Link); } else { ASSERT(NotifyDevice->ActiveChild == 0); InsertHeadList(&Level->ReadySleep, Link); } } while (!IsListEmpty(&Level->Complete)) { Link = RemoveHeadList(&Level->Complete); NotifyDevice = CONTAINING_RECORD (Link, PO_DEVICE_NOTIFY, Link); if (NotifyDevice->ChildCount) { InsertHeadList(&Level->WaitSleep, Link); } else { ASSERT(NotifyDevice->ActiveChild == 0); InsertHeadList(&Level->ReadySleep, Link); } } ASSERT(!IsListEmpty(&Level->ReadySleep)); Level->ActiveCount = Level->DeviceCount; KeAcquireSpinLock(&DevState->SpinLock, &OldIrql); while ((Level->ActiveCount) && (NT_SUCCESS(DevState->Status))) { if (!IsListEmpty(&Level->ReadySleep)) { Link = RemoveHeadList(&Level->ReadySleep); InsertTailList(&Level->Pending, Link); KeReleaseSpinLock(&DevState->SpinLock, OldIrql); NotifyDevice = CONTAINING_RECORD (Link, PO_DEVICE_NOTIFY, Link); ASSERT(NotifyDevice->ActiveChild == 0); PopNotifyDevice(DevState, NotifyDevice); } else { if ((Level->ActiveCount) && (NT_SUCCESS(DevState->Status))) { // // No devices are ready to receive IRPs yet, so wait for // one of the pending IRPs to complete. // ASSERT(!IsListEmpty(&Level->Pending)); KeReleaseSpinLock(&DevState->SpinLock, OldIrql); PopWaitForSystemPowerIrp(DevState, FALSE); } } KeAcquireSpinLock(&DevState->SpinLock, &OldIrql); } KeReleaseSpinLock(&DevState->SpinLock, OldIrql); } VOID PopResetChildCount( IN PLIST_ENTRY ListHead ) /*++ Routine Description: Enumerates the notify structures in the supplied list and sets their active child count to be equal to the total child count. Arguments: ListHead - supplies the list head. Return Value: None --*/ { PPO_DEVICE_NOTIFY Notify; PLIST_ENTRY Link; Link = ListHead->Flink; while (Link != ListHead) { Notify = CONTAINING_RECORD (Link, PO_DEVICE_NOTIFY, Link); Link = Link->Flink; Notify->ActiveChild = Notify->ChildCount; } } VOID PopSetupListForWake( IN PPO_NOTIFY_ORDER_LEVEL Level, IN PLIST_ENTRY ListHead ) /*++ Routine Description: Moves all devices that have WakeNeeded=TRUE from the specified list to either the ReadyS0 or WaitS0 lists. Arguments: Level - Supplies the level ListHead - Supplies the list to be moved. Return Value: None --*/ { PPO_DEVICE_NOTIFY NotifyDevice; PPO_DEVICE_NOTIFY ParentNotify; PLIST_ENTRY Link; Link = ListHead->Flink; while (Link != ListHead) { NotifyDevice = CONTAINING_RECORD (Link, PO_DEVICE_NOTIFY, Link); Link = NotifyDevice->Link.Flink; if (NotifyDevice->WakeNeeded) { --Level->ActiveCount; RemoveEntryList(&NotifyDevice->Link); ParentNotify = IoGetPoNotifyParent(NotifyDevice); if ((ParentNotify==NULL) || (!ParentNotify->WakeNeeded)) { InsertTailList(&Level->ReadyS0, &NotifyDevice->Link); } else { InsertTailList(&Level->WaitS0, &NotifyDevice->Link); } } } } VOID PopWakeDeviceList( IN PPOP_DEVICE_SYS_STATE DevState, IN PPO_NOTIFY_ORDER_LEVEL Level ) /*++ Routine Description: Sends S0 power irps to all devices that need waking in the given order level. Arguments: DevState - Supplies the device state Level - supplies the level to send power irps to Return Value: None. DevState->Status is set on error. --*/ { PPO_DEVICE_NOTIFY NotifyDevice; PPO_DEVICE_NOTIFY ParentNotify; PLIST_ENTRY Link; KIRQL OldIrql; ASSERT(DevState->Waking); ASSERT(IsListEmpty(&Level->Pending)); ASSERT(IsListEmpty(&Level->WaitS0)); Level->ActiveCount = Level->DeviceCount; // // Run through all the devices and put everything that has // WakeNeeded=TRUE onto the wake list. // PopSetupListForWake(Level, &Level->WaitSleep); PopSetupListForWake(Level, &Level->ReadySleep); PopSetupListForWake(Level, &Level->Complete); ASSERT((Level->DeviceCount == 0) || (Level->ActiveCount == Level->DeviceCount) || !IsListEmpty(&Level->ReadyS0)); KeAcquireSpinLock(&DevState->SpinLock, &OldIrql); while (Level->ActiveCount < Level->DeviceCount) { if (!IsListEmpty(&Level->ReadyS0)) { Link = RemoveHeadList(&Level->ReadyS0); InsertTailList(&Level->Pending,Link); KeReleaseSpinLock(&DevState->SpinLock, OldIrql); NotifyDevice = CONTAINING_RECORD (Link, PO_DEVICE_NOTIFY, Link); // // Set the timer to go off if we are not done by the timeout period // if (PopSimulate & POP_WAKE_DEADMAN) { LARGE_INTEGER DueTime; DueTime.QuadPart = (LONGLONG)PopWakeTimer * -1 * 1000 * 1000 * 10; KeSetTimer(&PopWakeTimeoutTimer, DueTime, &PopWakeTimeoutDpc); } PopNotifyDevice(DevState, NotifyDevice); } else { // // No devices are ready to receive IRPs yet, so wait for // one of the pending IRPs to complete. // ASSERT(!IsListEmpty(&Level->Pending)); KeReleaseSpinLock(&DevState->SpinLock, OldIrql); PopWaitForSystemPowerIrp(DevState, FALSE); } KeAcquireSpinLock(&DevState->SpinLock, &OldIrql); } KeReleaseSpinLock(&DevState->SpinLock, OldIrql); ASSERT(Level->ActiveCount == Level->DeviceCount); } VOID PopLogNotifyDevice ( IN PDEVICE_OBJECT TargetDevice, IN OPTIONAL PPO_DEVICE_NOTIFY Notify, IN PIRP Irp ) /*++ Routine Description: This routine logs a Po device notification. It is a seperate function so that the local buffer does not eat stack space through the PoCallDriver call. Arguments: TargetDevice - Device IRP is being sent to. Notify - If present, supplies the power notify structure for the specified device This will only be present on Sx irps, not Dx irps. Irp - Pointer to the built Irp for PoCallDriver. Return Value: None. --*/ { UCHAR StackBuffer[256]; ULONG StackBufferSize; PPERFINFO_PO_NOTIFY_DEVICE LogEntry; ULONG MaxDeviceNameLength; ULONG DeviceNameLength; ULONG CopyLength; ULONG RemainingSize; ULONG LogEntrySize; PIO_STACK_LOCATION IrpSp; // // Initialize locals. // StackBufferSize = sizeof(StackBuffer); LogEntry = (PVOID) StackBuffer; IrpSp = IoGetNextIrpStackLocation(Irp); // // Stack buffer should be large enough to contain at least the fixed // part of the LogEntry structure. // if (StackBufferSize < sizeof(PERFINFO_PO_NOTIFY_DEVICE)) { ASSERT(FALSE); return; } // // Fill in the LogEntry fields. // LogEntry->Irp = Irp; LogEntry->DriverStart = TargetDevice->DriverObject->DriverStart; LogEntry->MajorFunction = IrpSp->MajorFunction; LogEntry->MinorFunction = IrpSp->MinorFunction; LogEntry->Type = IrpSp->Parameters.Power.Type; LogEntry->State = IrpSp->Parameters.Power.State; if (Notify) { LogEntry->OrderLevel = Notify->OrderLevel; if (Notify->DeviceName) { // // Determine what the maximum device name length (excluding NUL) we // can fit into our stack buffer. Note that PERFINFO_NOTIFY_DEVICE // has space for the terminating NUL character. // RemainingSize = StackBufferSize - sizeof(PERFINFO_PO_NOTIFY_DEVICE); MaxDeviceNameLength = RemainingSize / sizeof(WCHAR); // // Determine the length of the device name and adjust the copy length. // DeviceNameLength = wcslen(Notify->DeviceName); CopyLength = DeviceNameLength; if (CopyLength > MaxDeviceNameLength) { CopyLength = MaxDeviceNameLength; } // // Copy CopyLength characters from the end of the DeviceName. // This way if our buffer is not enough, we get a more distinct part // of the name. // wcscpy(LogEntry->DeviceName, Notify->DeviceName + DeviceNameLength - CopyLength); } else { // // There is no device name. // CopyLength = 0; LogEntry->DeviceName[CopyLength] = 0; } } else { LogEntry->OrderLevel = 0; CopyLength = 0; LogEntry->DeviceName[CopyLength] = 0; } // // Copied device name should be terminated: we had enough room for it. // ASSERT(LogEntry->DeviceName[CopyLength] == 0); // // Log the entry. // LogEntrySize = sizeof(PERFINFO_PO_NOTIFY_DEVICE); LogEntrySize += CopyLength * sizeof(WCHAR); PerfInfoLogBytes(PERFINFO_LOG_TYPE_PO_NOTIFY_DEVICE, LogEntry, LogEntrySize); // // We are done. // return; } VOID PopNotifyDevice ( IN PPOP_DEVICE_SYS_STATE DevState, IN PPO_DEVICE_NOTIFY Notify ) /*++ Routine Description: Arguments: Return Value: --*/ { PPOP_DEVICE_POWER_IRP PowerIrp; PSINGLE_LIST_ENTRY Entry; PIO_STACK_LOCATION IrpSp; PIRP Irp; ULONG SysCall; KIRQL OldIrql; PDEVICE_OBJECT *WarmEjectDevice; POWER_ACTION IrpAction; // // Set the SysCall state to match our notify current state // ASSERT(PopCurrentLevel == Notify->OrderLevel); SysCall = PO_CALL_SYSDEV_QUEUE; if (!(Notify->OrderLevel & PO_ORDER_PAGABLE)) { SysCall |= PO_CALL_NON_PAGED; } if (PopCallSystemState != SysCall) { PopLockWorkerQueue(&OldIrql); PopCallSystemState = SysCall; PopUnlockWorkerQueue(OldIrql); } // // Allocate an PowerIrp & Irp structures // PowerIrp = NULL; Irp = NULL; for (; ;) { Entry = PopEntryList(&DevState->Head.Free); if (Entry) { break; } PopWaitForSystemPowerIrp (DevState, FALSE); } PowerIrp = CONTAINING_RECORD(Entry, POP_DEVICE_POWER_IRP, Free); for (; ;) { Irp = IoAllocateIrp ((CHAR) Notify->TargetDevice->StackSize, FALSE); if (Irp) { break; } PopWaitForSystemPowerIrp (DevState, FALSE); } SPECIALIRP_WATERMARK_IRP(Irp, IRP_SYSTEM_RESTRICTED); if (!DevState->Waking) { // // If the device node list changed, then restart. This could have // happened when we dropped our list and then rebuilt it inbetween // queries for sleep states. // if (DevState->Order.DevNodeSequence != IoDeviceNodeTreeSequence) { PopRestartSetSystemState(); } // // If there's been some sort of error, then abort // if (!NT_SUCCESS(DevState->Status)) { PushEntryList (&DevState->Head.Free, &PowerIrp->Free); IoFreeIrp (Irp); return ; // abort } // // Mark notify as needing wake. // Notify->WakeNeeded = TRUE; } else { Notify->WakeNeeded = FALSE; } // // Put irp onto pending queue // PowerIrp->Irp = Irp; PowerIrp->Notify = Notify; ExInterlockedInsertTailList ( &DevState->Head.Pending, &PowerIrp->Pending, &DevState->SpinLock ); // // Setup irp // Irp->IoStatus.Status = STATUS_NOT_SUPPORTED ; Irp->IoStatus.Information = 0; IrpSp = IoGetNextIrpStackLocation(Irp); IrpSp->MajorFunction = IRP_MJ_POWER; IrpSp->MinorFunction = DevState->IrpMinor; IrpSp->Parameters.Power.SystemContext = 0; IrpSp->Parameters.Power.Type = SystemPowerState; IrpSp->Parameters.Power.State.SystemState = DevState->SystemState; ASSERT(PopAction.Action != PowerActionHibernate); WarmEjectDevice = DevState->Order.WarmEjectPdoPointer; // // We need to determine the appropriate power action to place in our IRP. // For instance, we send PowerActionWarmEject to the devnode being warm // ejected, and we convert our internal PowerActionSleep to // PowerActionHibernate if the sleep state is S4. // IrpAction = PopMapInternalActionToIrpAction ( PopAction.Action, DevState->SystemState, (BOOLEAN) (DevState->Waking || (*WarmEjectDevice != Notify->DeviceObject)) ); // // If we are sending a set power to the devnode to be warm ejected, // zero out the warm eject device field to signify we have handled to // requested operation. // if ((IrpAction == PowerActionWarmEject) && (*WarmEjectDevice == Notify->DeviceObject) && (DevState->IrpMinor == IRP_MN_SET_POWER)) { *WarmEjectDevice = NULL; } IrpSp->Parameters.Power.ShutdownType = IrpAction; IoSetCompletionRoutine (Irp, PopCompleteSystemPowerIrp, PowerIrp, TRUE, TRUE, TRUE); // // Log the call. // if (PERFINFO_IS_GROUP_ON(PERF_POWER)) { PopLogNotifyDevice(Notify->TargetDevice, Notify, Irp); } // // Give it to the driver, and continue // PoCallDriver (Notify->TargetDevice, Irp); } NTSTATUS PopCompleteSystemPowerIrp ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: IRP completion routine for system power irps. Takes the irp from the DevState pending queue and puts it on the DevState complete queue. Arguments: DeviceObect - The device object Irp - The IRP Context - Device power irp structure for this request Return Value: STATUS_MORE_PROCESSING_REQUIRED --*/ { PPOP_DEVICE_POWER_IRP PowerIrp; PPOP_DEVICE_SYS_STATE DevState; KIRQL OldIrql; BOOLEAN SetEvent; NTSTATUS Status; PIO_STACK_LOCATION IrpSp, IrpSp2; PPO_DEVICE_NOTIFY Notify; PPO_DEVICE_NOTIFY ParentNotify; PPO_NOTIFY_ORDER_LEVEL Order; PowerIrp = (PPOP_DEVICE_POWER_IRP) Context; DevState = PopAction.DevState; SetEvent = FALSE; // // Log the completion. // if (PERFINFO_IS_GROUP_ON(PERF_POWER)) { PERFINFO_PO_NOTIFY_DEVICE_COMPLETE LogEntry; LogEntry.Irp = Irp; LogEntry.Status = Irp->IoStatus.Status; PerfInfoLogBytes(PERFINFO_LOG_TYPE_PO_NOTIFY_DEVICE_COMPLETE, &LogEntry, sizeof(LogEntry)); } KeAcquireSpinLock (&DevState->SpinLock, &OldIrql); // // Move irp from pending queue to complete queue // RemoveEntryList (&PowerIrp->Pending); PowerIrp->Pending.Flink = NULL; InsertTailList (&DevState->Head.Complete, &PowerIrp->Complete); // // Move notify from pending queue to the appropriate queue // depending on whether we are sleeping or waking. // Notify=PowerIrp->Notify; ASSERT(Notify->OrderLevel == PopCurrentLevel); Order = &DevState->Order.OrderLevel[Notify->OrderLevel]; RemoveEntryList(&Notify->Link); InsertTailList(&Order->Complete, &Notify->Link); if (DevState->Waking) { ++Order->ActiveCount; IoMovePoNotifyChildren(Notify, &DevState->Order); } else { // // We will only decrement the parent's active count if the IRP was // completed successfully. Otherwise it is possible for a parent to // get put on the ReadySleep list even though its child has failed // the query/set irp. // if (NT_SUCCESS(Irp->IoStatus.Status) || DevState->IgnoreErrors) { --Order->ActiveCount; ParentNotify = IoGetPoNotifyParent(Notify); if (ParentNotify) { ASSERT(ParentNotify->ActiveChild > 0); if (--ParentNotify->ActiveChild == 0) { RemoveEntryList(&ParentNotify->Link); InsertTailList(&DevState->Order.OrderLevel[ParentNotify->OrderLevel].ReadySleep, &ParentNotify->Link); } } } } // // If there is a wait any, then kick event // If there is a wait all, then check for empty pending queue // if ((DevState->WaitAny) || (DevState->WaitAll && IsListEmpty(&DevState->Head.Pending))) { SetEvent = TRUE; } // // If the IRP is in error and it's the first such IRP start aborting // the current operation // if (!PopCheckSystemPowerIrpStatus(DevState, Irp, TRUE) && NT_SUCCESS(DevState->Status)) { // // We need to set the failed device here. If we are warm ejecting // however, the warm eject devnode will *legitimately* fail any queries // for S states it doesn't support. As we will be trying several Sx // states, the trick is to preserve any failed device that is *not* // warm eject devnode, and update failed device to the warm eject // devnode only if failed device is currently NULL. // if ((PopAction.Action != PowerActionWarmEject) || (DevState->FailedDevice == NULL) || (PowerIrp->Notify->DeviceObject != *DevState->Order.WarmEjectPdoPointer)) { DevState->FailedDevice = PowerIrp->Notify->DeviceObject; } DevState->Status = Irp->IoStatus.Status; SetEvent = TRUE; // wake to cancel pending irps } KeReleaseSpinLock (&DevState->SpinLock, OldIrql); if (SetEvent) { KeSetEvent (&DevState->Event, IO_NO_INCREMENT, FALSE); } return STATUS_MORE_PROCESSING_REQUIRED; } BOOLEAN PopCheckSystemPowerIrpStatus ( IN PPOP_DEVICE_SYS_STATE DevState, IN PIRP Irp, IN BOOLEAN AllowTestFailure ) // return FALSE if irp is some sort of unallowed error { NTSTATUS Status; Status = Irp->IoStatus.Status; // // If Status is sucess, then no problem // if (NT_SUCCESS(Status)) { return TRUE; } // // If errors are allowed, or it's a cancelled request no problem // if (DevState->IgnoreErrors || Status == STATUS_CANCELLED) { return TRUE; } // // Check to see if the error is that the driver doesn't implement the // request and if such a condition is allowed // if (Status == STATUS_NOT_SUPPORTED && DevState->IgnoreNotImplemented) { return TRUE; } // // For testing purposes, optionally let through unsupported device drivers // if (Status == STATUS_NOT_SUPPORTED && AllowTestFailure && (PopSimulate & POP_IGNORE_UNSUPPORTED_DRIVERS)) { return TRUE; } // // Some unexpected failure, treat it as an error // return FALSE; } VOID PopRestartSetSystemState ( VOID ) /*++ Routine Description: Aborts current system power state operation. Arguments: None Return Value: None --*/ { KIRQL OldIrql; KeAcquireSpinLock (&PopAction.DevState->SpinLock, &OldIrql); if (!PopAction.Shutdown && NT_SUCCESS(PopAction.DevState->Status)) { PopAction.DevState->Status = STATUS_CANCELLED; } KeReleaseSpinLock (&PopAction.DevState->SpinLock, OldIrql); KeSetEvent (&PopAction.DevState->Event, IO_NO_INCREMENT, FALSE); } VOID PopDumpSystemIrp ( IN PUCHAR Desc, IN PPOP_DEVICE_POWER_IRP PowerIrp ) { PUCHAR Testing; PUCHAR IrpType; PPO_DEVICE_NOTIFY Notify; Notify = PowerIrp->Notify; // // Dump errors to debugger // switch (PopAction.DevState->IrpMinor) { case IRP_MN_QUERY_POWER: IrpType = "QueryPower"; break; case IRP_MN_SET_POWER: IrpType = "SetPower"; break; default: IrpType = "?"; break; } DbgPrint ("%s: ", Desc); if (Notify->DriverName) { DbgPrint ("%ws ", Notify->DriverName); } else { DbgPrint ("%x ", Notify->TargetDevice->DriverObject); } if (Notify->DeviceName) { DbgPrint ("%ws ", Notify->DeviceName); } else { DbgPrint ("%x ", Notify->TargetDevice); } DbgPrint ("irp (%x) %s-%s status %x\n", PowerIrp->Irp, IrpType, PopSystemStateString(PopAction.DevState->SystemState), PowerIrp->Irp->IoStatus.Status ); } VOID PopWakeSystemTimeout( IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++ Routine Description: This routine is used to break into the kernel debugger if somebody is taking too long processing their S irps. Arguments: Return Value: None --*/ { try { DbgBreakPoint(); } except (EXCEPTION_EXECUTE_HANDLER) { ; } }