/*++ Copyright (c) 1999, 2000 Microsoft Corporation Module Name: idle.c Abstract Author: Doron H. Environment: Kernel mode only Revision History: --*/ #ifdef ALLOC_PRAGMA #endif #include "pch.h" KSPIN_LOCK idleDeviceListSpinLock; LIST_ENTRY idleDeviceList; KTIMER idleTimer; KDPC idleTimerDpc; LONG numIdleDevices = 0; #define HID_IDLE_SCAN_INTERVAL 1 typedef struct _HID_IDLE_DEVICE_INFO { LIST_ENTRY entry; ULONG idleCount; ULONG idleTime; PDEVICE_OBJECT device; BOOLEAN tryAgain; } HID_IDLE_DEVICE_INFO, *PHID_IDLE_DEVICE_INFO; VOID HidpIdleTimerDpcProc( IN PKDPC Dpc, IN PDEVICE_OBJECT DeviceObject, IN PVOID Context1, IN PVOID Context2 ); NTSTATUS HidpRegisterDeviceForIdleDetection( PDEVICE_OBJECT DeviceObject, ULONG IdleTime, PULONG *IdleTimeout ) { PHID_IDLE_DEVICE_INFO info = NULL; KIRQL irql; PLIST_ENTRY entry = NULL; static BOOLEAN firstCall = TRUE; BOOLEAN freeInfo = FALSE; NTSTATUS status = STATUS_UNSUCCESSFUL; if (firstCall) { KeInitializeSpinLock(&idleDeviceListSpinLock); InitializeListHead(&idleDeviceList); KeInitializeTimerEx(&idleTimer, NotificationTimer); KeInitializeDpc(&idleTimerDpc, HidpIdleTimerDpcProc, NULL); firstCall = FALSE; } KeAcquireSpinLock(&idleDeviceListSpinLock, &irql); if (IdleTime == 0) { ASSERT(numIdleDevices >= 0); // // Remove the device from the list // for (entry = idleDeviceList.Flink; entry != &idleDeviceList; entry = entry->Flink) { info = CONTAINING_RECORD(entry, HID_IDLE_DEVICE_INFO, entry); if (info->device == DeviceObject) { DBGINFO(("Remove device idle on fdo 0x%x", DeviceObject)); numIdleDevices--; ObDereferenceObject(DeviceObject); RemoveEntryList(entry); status = STATUS_SUCCESS; ExFreePool(info); *IdleTimeout = BAD_POINTER; break; } } if (NT_SUCCESS(status)) { // // If there are no more idle devices we can stop the timer // if (IsListEmpty(&idleDeviceList)) { ASSERT(numIdleDevices == 0); DBGINFO(("Idle detection list empty. Stopping timer.")); KeCancelTimer(&idleTimer); } } } else { LARGE_INTEGER scanTime; BOOLEAN empty = FALSE; DBGINFO(("Register for device idle on fdo 0x%x", DeviceObject)); // // Check if we've already started this. // status = STATUS_SUCCESS; for (entry = idleDeviceList.Flink; entry != &idleDeviceList; entry = entry->Flink) { info = CONTAINING_RECORD(entry, HID_IDLE_DEVICE_INFO, entry); if (info->device == DeviceObject) { DBGWARN(("Device already registered for idle detection. Ignoring.")); ASSERT(*IdleTimeout == &(info->idleCount)); status = STATUS_UNSUCCESSFUL; } } if (NT_SUCCESS(status)) { info = (PHID_IDLE_DEVICE_INFO) ALLOCATEPOOL(NonPagedPool, sizeof(HID_IDLE_DEVICE_INFO)); if (info != NULL) { ObReferenceObject(DeviceObject); RtlZeroMemory(info, sizeof(HID_IDLE_DEVICE_INFO)); info->device = DeviceObject; info->idleTime = IdleTime; if (IsListEmpty(&idleDeviceList)) { empty = TRUE; } InsertTailList(&idleDeviceList, &info->entry); *IdleTimeout = &(info->idleCount); numIdleDevices++; if (empty) { DBGINFO(("Starting idle detection timer for first time.")); // // Turn on idle detection // scanTime = RtlConvertLongToLargeInteger(-10*1000*1000 * HID_IDLE_SCAN_INTERVAL); KeSetTimerEx(&idleTimer, scanTime, HID_IDLE_SCAN_INTERVAL*1000, // call wants milliseconds &idleTimerDpc); } } else { status = STATUS_INSUFFICIENT_RESOURCES; } } } KeReleaseSpinLock(&idleDeviceListSpinLock, irql); return status; } VOID HidpIdleTimerDpcProc( IN PKDPC Dpc, IN PDEVICE_OBJECT DeviceObject, IN PVOID Context1, IN PVOID Context2 ) { PLIST_ENTRY entry; PHID_IDLE_DEVICE_INFO info; ULONG oldCount; KIRQL irql1, irql2; BOOLEAN ok = FALSE; PFDO_EXTENSION fdoExt; LONG idleState; UNREFERENCED_PARAMETER(Context1); UNREFERENCED_PARAMETER(Context2); KeAcquireSpinLock(&idleDeviceListSpinLock, &irql1); entry = idleDeviceList.Flink; while (entry != &idleDeviceList) { info = CONTAINING_RECORD(entry, HID_IDLE_DEVICE_INFO, entry); fdoExt = &((PHIDCLASS_DEVICE_EXTENSION) info->device->DeviceExtension)->fdoExt; KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql2); oldCount = InterlockedIncrement(&info->idleCount); if (info->tryAgain || ((oldCount+1) == info->idleTime)) { PIO_WORKITEM item = IoAllocateWorkItem(info->device); if (item) { info->tryAgain = FALSE; SS_TRAP; KeResetEvent(&fdoExt->idleDoneEvent); ASSERT(fdoExt->idleState != IdleIrpSent); ASSERT(fdoExt->idleState != IdleCallbackReceived); ASSERT(fdoExt->idleState != IdleComplete); idleState = InterlockedCompareExchange(&fdoExt->idleState, IdleIrpSent, IdleWaiting); if (fdoExt->idleState == IdleIrpSent) { ok = TRUE; } else { // We shouldn't get here if we're disabled. ASSERT(idleState != IdleDisabled); DBGWARN(("Resetting timer to zero for fdo %x in state %x", info->device,fdoExt->idleState)); info->idleCount = 0; } if (ok) { IoQueueWorkItem(item, HidpIdleTimeWorker, DelayedWorkQueue, item); } else { IoFreeWorkItem(item); } } else { info->tryAgain = TRUE; } } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql2); entry = entry->Flink; } KeReleaseSpinLock(&idleDeviceListSpinLock, irql1); } NTSTATUS HidpIdleNotificationRequestComplete( PDEVICE_OBJECT DeviceObject, PIRP Irp, PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension ) { FDO_EXTENSION *fdoExt; PDO_EXTENSION *pdoExt; KIRQL irql; LONG prevIdleState = IdleWaiting; POWER_STATE powerState; NTSTATUS status = Irp->IoStatus.Status; ULONG count, i; PIRP delayedIrp; LIST_ENTRY dequeue, *entry; PIO_STACK_LOCATION stack; // // DeviceObject is NULL because we sent the irp // UNREFERENCED_PARAMETER(DeviceObject); fdoExt = &HidDeviceExtension->fdoExt; DBGVERBOSE(("Idle irp completed status 0x%x for fdo 0x%x", status, fdoExt->fdo)); // // Cancel any outstanding WW irp we queued up for the exclusive purpose // of selective suspend. // KeAcquireSpinLock(&fdoExt->collectionWaitWakeIrpQueueSpinLock, &irql); if (IsListEmpty(&fdoExt->collectionWaitWakeIrpQueue) && HidpIsWaitWakePending(fdoExt, FALSE)) { if (ISPTR(fdoExt->waitWakeIrp)) { DBGINFO(("Cancelling the WW irp that was queued for idle.")) IoCancelIrp(fdoExt->waitWakeIrp); } else { TRAP; } } KeReleaseSpinLock(&fdoExt->collectionWaitWakeIrpQueueSpinLock, irql); switch (status) { case STATUS_SUCCESS: // we successfully idled the device we are either now back in D0, // or will be very soon. KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql); if (fdoExt->devicePowerState == PowerDeviceD0) { prevIdleState = InterlockedCompareExchange(&fdoExt->idleState, IdleWaiting, IdleComplete); DBGASSERT(fdoExt->idleState == IdleWaiting, ("IdleCompletion, prev state not IdleWaiting, actually %x",prevIdleState), TRUE); if (ISPTR(fdoExt->idleTimeoutValue)) { InterlockedExchange(fdoExt->idleTimeoutValue, 0); } } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); break; case STATUS_INVALID_DEVICE_REQUEST: case STATUS_NOT_SUPPORTED: // the bus below does not support idle timeouts, forget about it DBGINFO(("Bus does not support idle. Removing for fdo %x", fdoExt->fdo)); // // Call to cancel idle notification. // ASSERT(fdoExt->idleState == IdleIrpSent); ASSERT(fdoExt->devicePowerState == PowerDeviceD0); fdoExt->idleState = IdleWaiting; HidpCancelIdleNotification(fdoExt, TRUE); KeSetEvent(&fdoExt->idleDoneEvent, 0, FALSE); break; // we cancelled the request case STATUS_CANCELLED: DBGINFO(("Idle Irp completed cancelled")); // transitioned into a power state where we could not idle out case STATUS_POWER_STATE_INVALID: // oops, there was already a request in the bus below us case STATUS_DEVICE_BUSY: default: // // We must reset ourselves. // KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql); DBGASSERT((fdoExt->idleState != IdleWaiting), ("Idle completion, previous state was already waiting."), FALSE); prevIdleState = fdoExt->idleState; if (prevIdleState == IdleIrpSent) { ASSERT(fdoExt->devicePowerState == PowerDeviceD0); fdoExt->idleCancelling = FALSE; if (ISPTR(fdoExt->idleTimeoutValue) && prevIdleState != IdleComplete) { InterlockedExchange(fdoExt->idleTimeoutValue, 0); } InterlockedExchange(&fdoExt->idleState, IdleWaiting); } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); if (prevIdleState == IdleComplete) { // // We now have to power up the stack. // DBGINFO(("Fully idled. Must power up stack.")) powerState.DeviceState = PowerDeviceD0; PoRequestPowerIrp(((PHIDCLASS_DEVICE_EXTENSION) fdoExt->fdo->DeviceExtension)->hidExt.PhysicalDeviceObject, IRP_MN_SET_POWER, powerState, HidpDelayedPowerPoRequestComplete, fdoExt, NULL); } else if (prevIdleState == IdleIrpSent) { // // Dequeue any enqueued irps and send them on their way. // This is for the case where we didn't make it to suspend, but // enqueued irps anyways. I.e. using mouse, set caps lock on // ps/2 keybd causing write to be sent to usb kbd. // if (fdoExt->devicePowerState == PowerDeviceD0) { for (i = 0; i < fdoExt->deviceRelations->Count; i++) { pdoExt = &((PHIDCLASS_DEVICE_EXTENSION) fdoExt->deviceRelations->Objects[i]->DeviceExtension)->pdoExt; // // Resend all power delayed IRPs // count = DequeueAllPdoPowerDelayedIrps(pdoExt, &dequeue); DBGVERBOSE(("dequeued %d requests\n", count)); while (!IsListEmpty(&dequeue)) { entry = RemoveHeadList(&dequeue); delayedIrp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry); stack = IoGetCurrentIrpStackLocation(delayedIrp); DBGINFO(("resending %x to pdo %x in idle completion.\n", delayedIrp, pdoExt->pdo)); pdoExt->pdo->DriverObject-> MajorFunction[stack->MajorFunction] (pdoExt->pdo, delayedIrp); } } } /* * We cancelled this IRP. * REGARDLESS of whether this IRP was actually completed by * the cancel routine or not * (i.e. regardless of the completion status) * set this event so that stuff can exit. * Don't touch the irp again. */ DBGINFO(("Set done event.")) KeSetEvent(&fdoExt->idleDoneEvent, 0, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; } break; } return STATUS_MORE_PROCESSING_REQUIRED; } VOID HidpIdleTimeWorker( PDEVICE_OBJECT DeviceObject, PIO_WORKITEM Item ) { FDO_EXTENSION *fdoExt; PIO_STACK_LOCATION stack; PIRP irp = NULL, irpToCancel = NULL; NTSTATUS status; KIRQL irql; fdoExt = &((PHIDCLASS_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->fdoExt; DBGINFO(("fdo 0x%x can idle out", fdoExt->fdo)); irp = fdoExt->idleNotificationRequest; ASSERT(ISPTR(irp)); if (ISPTR(irp)) { USHORT PacketSize; CCHAR StackSize; UCHAR AllocationFlags; // Did anyone forget to pull their cancel routine? ASSERT(irp->CancelRoutine == NULL) ; AllocationFlags = irp->AllocationFlags; StackSize = irp->StackCount; PacketSize = IoSizeOfIrp(StackSize); IoInitializeIrp(irp, PacketSize, StackSize); irp->AllocationFlags = AllocationFlags; irp->Cancel = FALSE; irp->IoStatus.Status = STATUS_NOT_SUPPORTED; stack = IoGetNextIrpStackLocation(irp); stack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; stack->Parameters.DeviceIoControl.IoControlCode = IOCTL_HID_SEND_IDLE_NOTIFICATION_REQUEST; stack->Parameters.DeviceIoControl.InputBufferLength = sizeof(fdoExt->idleCallbackInfo); stack->Parameters.DeviceIoControl.Type3InputBuffer = (PVOID) &(fdoExt->idleCallbackInfo); // // Hook a completion routine for when the device completes. // IoSetCompletionRoutine(irp, HidpIdleNotificationRequestComplete, DeviceObject->DeviceExtension, TRUE, TRUE, TRUE); // // The hub will fail this request if the hub doesn't support selective // suspend. By returning FALSE we remove ourselves from the // status = HidpCallDriver(fdoExt->fdo, irp); KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql); if (status == STATUS_PENDING && fdoExt->idleCancelling) { irpToCancel = irp; } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); if (irpToCancel) { IoCancelIrp(irpToCancel); } } IoFreeWorkItem(Item); } BOOLEAN HidpStartIdleTimeout( FDO_EXTENSION *fdoExt, BOOLEAN DeviceStart ) { DEVICE_POWER_STATE deviceWakeableState = PowerDeviceUnspecified; USHORT deviceUsagePage, deviceUsage; USHORT usagePage, usage; ULONG iList, iDesc, iPdo; HANDLE hKey; NTSTATUS status; ULONG enabled; ULONG length; UNICODE_STRING s; KEY_VALUE_PARTIAL_INFORMATION partial; PHID_IDLE_DEVICE_INFO info; PLIST_ENTRY entry = NULL; PULONG idleTimeoutAddress; if (fdoExt->idleState != IdleDisabled) { // // We're already registered for idle detection. // return TRUE; } // // If we can't wake the machine, forget about it // if (fdoExt->deviceCapabilities.SystemWake == PowerSystemUnspecified) { DBGVERBOSE(("Can't wake the system with these caps! Disabling SS.")); return FALSE; } // // If D1Latency, D2Latency, D3Latency are ever filled in, perhaps we should // let these values help us determine which low power state to go to // deviceWakeableState = fdoExt->deviceCapabilities.DeviceWake; DBGVERBOSE(("DeviceWakeableState is D%d", deviceWakeableState-1)); if (deviceWakeableState == PowerDeviceUnspecified) { DBGVERBOSE(("Due to devcaps, can't idle wake from any state! Disabling SS.")); return FALSE; } if (DeviceStart) { // // Open the registry and make sure that the // SelectiveSuspendEnabled value is set to 1. // // predispose to failure. fdoExt->idleEnabledInRegistry = FALSE; if (!NT_SUCCESS(IoOpenDeviceRegistryKey(fdoExt->collectionPdoExtensions[0]->hidExt.PhysicalDeviceObject, PLUGPLAY_REGKEY_DEVICE, STANDARD_RIGHTS_READ, &hKey))) { DBGVERBOSE(("Couldn't open device key to check for idle timeout value. Disabling SS.")); return FALSE; } RtlInitUnicodeString(&s, HIDCLASS_SELECTIVE_SUSPEND_ON); status = ZwQueryValueKey(hKey, &s, KeyValuePartialInformation, &partial, sizeof(KEY_VALUE_PARTIAL_INFORMATION), &length); if (!NT_SUCCESS(status)) { DBGVERBOSE(("ZwQueryValueKey failed for fdo %x. Default to SS turned on if enabled.", fdoExt->fdo)); fdoExt->idleEnabled = TRUE; } else if (!partial.Data[0]) { DBGINFO(("Selective suspend is not turned on for this device.")); fdoExt->idleEnabled = FALSE; } else { fdoExt->idleEnabled = TRUE; } RtlInitUnicodeString(&s, HIDCLASS_SELECTIVE_SUSPEND_ENABLED); status = ZwQueryValueKey(hKey, &s, KeyValuePartialInformation, &partial, sizeof(KEY_VALUE_PARTIAL_INFORMATION), &length); ZwClose(hKey); if (!NT_SUCCESS(status)) { DBGVERBOSE(("ZwQueryValueKey failed for fdo %x. Disabling SS.", fdoExt->fdo)); return FALSE; } DBGASSERT(partial.Type == REG_BINARY, ("Registry key wrong type"), FALSE); if (!partial.Data[0]) { DBGINFO(("Selective suspend is not enabled for this device in the hive. Disabling SS.")); return FALSE; } fdoExt->idleEnabledInRegistry = TRUE; status = IoWMIRegistrationControl(fdoExt->fdo, WMIREG_ACTION_REGISTER); ASSERT(NT_SUCCESS(status)); } if (!fdoExt->idleEnabledInRegistry || !fdoExt->idleEnabled) { return FALSE; } DBGVERBOSE(("There are %d PDOs on FDO 0x%x", fdoExt->deviceDesc.CollectionDescLength, fdoExt)); ASSERT(ISPTR(fdoExt->deviceRelations)); // // OK, we can selectively suspend this device. // Allocate and initialize everything, then register. // fdoExt->idleNotificationRequest = IoAllocateIrp(fdoExt->fdo->StackSize, FALSE); if (fdoExt->idleNotificationRequest == NULL) { DBGWARN(("Failed to allocate idle notification irp")) return FALSE; } status = HidpRegisterDeviceForIdleDetection(fdoExt->fdo, HID_DEFAULT_IDLE_TIME, &fdoExt->idleTimeoutValue); if (STATUS_SUCCESS == status) { // // We have successfully registered all device for idle detection, // send a WW irp down the FDO stack // fdoExt->idleState = IdleWaiting; return TRUE; } else { // // We're already registered? Or did the alloc fail? // DBGSUCCESS(status, TRUE); return FALSE; } } NTSTATUS HidpCheckIdleState( PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, PIRP Irp ) { KIRQL irql; LONG idleState; PFDO_EXTENSION fdoExt = &HidDeviceExtension->pdoExt.deviceFdoExt->fdoExt; NTSTATUS status = STATUS_SUCCESS; BOOLEAN cancelIdleIrp = FALSE; ASSERT(HidDeviceExtension->isClientPdo); KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql); if (fdoExt->idleState == IdleWaiting || fdoExt->idleState == IdleDisabled) { // // Done. // if (ISPTR(fdoExt->idleTimeoutValue) && fdoExt->idleState == IdleWaiting) { InterlockedExchange(fdoExt->idleTimeoutValue, 0); } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); return STATUS_SUCCESS; } DBGINFO(("CheckIdleState on fdo %x", fdoExt->fdo)) status = EnqueuePowerDelayedIrp(HidDeviceExtension, Irp); if (STATUS_PENDING != status) { KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); return status; } fdoExt->idleCancelling = TRUE; idleState = fdoExt->idleState; switch (idleState) { case IdleWaiting: // bugbug. // How'd this happen? We already tried this... TRAP; break; case IdleIrpSent: case IdleCallbackReceived: case IdleComplete: cancelIdleIrp = TRUE; break; case IdleDisabled: // // Shouldn't get here. // DBGERR(("Already disabled.")); } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); if (cancelIdleIrp) { IoCancelIrp(fdoExt->idleNotificationRequest); } return status; } VOID HidpSetDeviceBusy(PFDO_EXTENSION fdoExt) { KIRQL irql; BOOLEAN cancelIdleIrp = FALSE; LONG idleState; KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql); if (fdoExt->idleState == IdleWaiting || fdoExt->idleState == IdleDisabled || fdoExt->idleCancelling) { if (ISPTR(fdoExt->idleTimeoutValue) && fdoExt->idleState == IdleWaiting) { InterlockedExchange(fdoExt->idleTimeoutValue, 0); fdoExt->idleCancelling = FALSE; } // // Done. // KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); return; } fdoExt->idleCancelling = TRUE; DBGVERBOSE(("HidpSetDeviceBusy on fdo %x", fdoExt->fdo)) idleState = fdoExt->idleState; switch (idleState) { case IdleWaiting: // bugbug. // How'd this happen? We already tried this... TRAP; break; case IdleIrpSent: case IdleCallbackReceived: case IdleComplete: cancelIdleIrp = TRUE; break; case IdleDisabled: // // Shouldn't get here. // DBGERR(("Already disabled.")); } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); if (cancelIdleIrp) { IoCancelIrp(fdoExt->idleNotificationRequest); } } VOID HidpCancelIdleNotification( PFDO_EXTENSION fdoExt, BOOLEAN removing // Whether this is happening on a remove device ) { KIRQL irql; BOOLEAN cancelIdleIrp = FALSE; LONG idleState; NTSTATUS status; DBGVERBOSE(("Cancelling idle notification for fdo 0x%x", fdoExt->fdo)); status = HidpRegisterDeviceForIdleDetection(fdoExt->fdo, 0, &fdoExt->idleTimeoutValue); KeAcquireSpinLock(&fdoExt->idleNotificationSpinLock, &irql); InterlockedCompareExchange(&fdoExt->idleState, IdleDisabled, IdleWaiting); if (fdoExt->idleState == IdleDisabled) { DBGVERBOSE(("Was waiting or already disabled. Exitting.")) // // Done. // KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); return; } fdoExt->idleCancelling = TRUE; idleState = fdoExt->idleState; DBGINFO(("Wait routine...")) switch (idleState) { case IdleWaiting: // How'd this happen? We already tried this... TRAP; break; case IdleIrpSent: case IdleCallbackReceived: // FUlly idled. case IdleComplete: cancelIdleIrp = TRUE; break; case IdleDisabled: // // Shouldn't get here. // TRAP; } KeReleaseSpinLock(&fdoExt->idleNotificationSpinLock, irql); if (cancelIdleIrp) { // Don't need to check the return status of IoCancel, since we'll // be waiting for the idleDoneEvent. IoCancelIrp(fdoExt->idleNotificationRequest); } if (removing) { DBGINFO(("Removing fdo %x. Must wait", fdoExt->fdo)) /* * Cancelling the IRP causes a lower driver to * complete it (either in a cancel routine or when * the driver checks Irp->Cancel just before queueing it). * Wait for the IRP to actually get cancelled. */ KeWaitForSingleObject( &fdoExt->idleDoneEvent, Executive, // wait reason KernelMode, FALSE, // not alertable NULL ); // no timeout } DBGINFO(("Done cancelling idle notification on fdo %x", fdoExt->fdo)) idleState = InterlockedExchange(&fdoExt->idleState, IdleDisabled); ASSERT(fdoExt->idleState == IdleDisabled); }