You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
857 lines
28 KiB
857 lines
28 KiB
/*++
|
|
|
|
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);
|
|
}
|
|
|