/*++ Copyright (c) 1996 Microsoft Corporation Module Name: polled.c Abstract Read handling routines Author: Ervin P. Environment: Kernel mode only Revision History: --*/ #include "pch.h" /* ******************************************************************************** * CompleteQueuedIrpsForPolled ******************************************************************************** * * Complete all waiting client reads with the given report value. * * Note: report is a 'cooked' report (i.e. it already has the report id added). * */ VOID CompleteQueuedIrpsForPolled( FDO_EXTENSION *fdoExt, ULONG collectionNum, PUCHAR report, ULONG reportLen, NTSTATUS status) { PHIDCLASS_COLLECTION hidCollection; hidCollection = GetHidclassCollection(fdoExt, collectionNum); if (hidCollection){ PLIST_ENTRY listEntry; LIST_ENTRY irpsToComplete; PIRP irp; ULONG actualLen; /* * Note: In order to avoid an infinite loop with a client that * resubmits the read each in his completion routine, * we must build a separate list of IRPs to be completed * while holding the spinlock continuously. */ InitializeListHead(&irpsToComplete); if (hidCollection->secureReadMode) { while (irp = DequeuePolledReadSystemIrp(hidCollection)){ InsertTailList(&irpsToComplete, &irp->Tail.Overlay.ListEntry); } } else { while (irp = DequeuePolledReadIrp(hidCollection)){ InsertTailList(&irpsToComplete, &irp->Tail.Overlay.ListEntry); } } while (!IsListEmpty(&irpsToComplete)){ PIO_STACK_LOCATION stackPtr; PHIDCLASS_FILE_EXTENSION fileExtension; listEntry = RemoveHeadList(&irpsToComplete); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); stackPtr = IoGetCurrentIrpStackLocation(irp); ASSERT(stackPtr); fileExtension = (PHIDCLASS_FILE_EXTENSION)stackPtr->FileObject->FsContext; ASSERT(fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG); actualLen = 0; if (NT_SUCCESS(status)){ PUCHAR callerBuf; callerBuf = HidpGetSystemAddressForMdlSafe(irp->MdlAddress); if (callerBuf && (stackPtr->Parameters.Read.Length >= reportLen)){ RtlCopyMemory(callerBuf, report, reportLen); irp->IoStatus.Information = actualLen = reportLen; } else { status = STATUS_INVALID_USER_BUFFER; } } DBG_RECORD_READ(irp, actualLen, (ULONG)report[0], TRUE) irp->IoStatus.Status = status; IoCompleteRequest(irp, IO_KEYBOARD_INCREMENT); } } else { TRAP; } } /* ******************************************************************************** * HidpPolledReadComplete ******************************************************************************** * * Note: the context passed to this callback is the PDO extension for * the collection which initiated this read; however, the returned * report may be for another collection. */ NTSTATUS HidpPolledReadComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) { PDO_EXTENSION *pdoExt = (PDO_EXTENSION *)Context; FDO_EXTENSION *fdoExt = &pdoExt->deviceFdoExt->fdoExt; PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp); ULONG reportId; PHIDP_REPORT_IDS reportIdent; DBG_COMMON_ENTRY() ASSERT(pdoExt->deviceFdoExt->Signature == HID_DEVICE_EXTENSION_SIG); ASSERT(ISPTR(Irp->UserBuffer)); InterlockedIncrement(&fdoExt->outstandingRequests); if (fdoExt->deviceDesc.ReportIDs[0].ReportID == 0) { /* * We previously incremented the UserBuffer to knock off the report id, * so restore it now and set the default report id. */ *(PUCHAR)(--(PUCHAR)Irp->UserBuffer) = (UCHAR)0; if (NT_SUCCESS(Irp->IoStatus.Status)){ Irp->IoStatus.Information++; } } /* * WHETHER OR NOT THE CALL SUCCEEDED, * we'll complete the waiting client read IRPs with * the result of this read. */ reportId = (ULONG)(*(PUCHAR)Irp->UserBuffer); reportIdent = GetReportIdentifier(fdoExt, reportId); if (reportIdent){ ULONG collectionNum = reportIdent->CollectionNumber; PHIDCLASS_COLLECTION hidpCollection = GetHidclassCollection(fdoExt, collectionNum); PHIDP_COLLECTION_DESC hidCollectionDesc = GetCollectionDesc(fdoExt, collectionNum); if (hidpCollection && hidCollectionDesc){ ULONG reportLen = (ULONG)Irp->IoStatus.Information; ASSERT((reportLen == hidCollectionDesc->InputLength) || !NT_SUCCESS(Irp->IoStatus.Status)); if (NT_SUCCESS(Irp->IoStatus.Status)){ KIRQL oldIrql; /* * If this report contains a power-button event, alert the system. */ CheckReportPowerEvent( fdoExt, hidpCollection, Irp->UserBuffer, reportLen); /* * Save this report for "opportunistic" polled device * readers who want a result right away. * Use the polledDeviceReadQueueSpinLock to protect * the savedPolledReportBuf. */ if (hidpCollection->secureReadMode) { hidpCollection->polledDataIsStale = TRUE; } else { KeAcquireSpinLock(&hidpCollection->polledDeviceReadQueueSpinLock, &oldIrql); ASSERT(reportLen <= fdoExt->maxReportSize+1); RtlCopyMemory(hidpCollection->savedPolledReportBuf, Irp->UserBuffer, reportLen); hidpCollection->savedPolledReportLen = reportLen; hidpCollection->polledDataIsStale = FALSE; KeReleaseSpinLock(&hidpCollection->polledDeviceReadQueueSpinLock, oldIrql); } } /* * Copy this report for all queued read IRPs on this polled device. * Do this AFTER updating the savedPolledReport information * because many clients will issue a read again immediately * from the completion routine. */ CompleteQueuedIrpsForPolled( fdoExt, collectionNum, Irp->UserBuffer, reportLen, Irp->IoStatus.Status); } else { TRAP; } } else { TRAP; } /* * This is an IRP we created to poll the device. * Free the buffer we allocated for the read. */ ExFreePool(Irp->UserBuffer); IoFreeIrp(Irp); /* * MUST return STATUS_MORE_PROCESSING_REQUIRED here or * NTKERN will touch the IRP. */ DBG_COMMON_EXIT() return STATUS_MORE_PROCESSING_REQUIRED; } /* ******************************************************************************** * HidpPolledReadComplete_TimerDriven ******************************************************************************** * */ NTSTATUS HidpPolledReadComplete_TimerDriven(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) { PDO_EXTENSION *pdoExt = (PDO_EXTENSION *)Context; FDO_EXTENSION *fdoExt = &pdoExt->deviceFdoExt->fdoExt; NTSTATUS status; /* * Call the actual completion routine. */ status = HidpPolledReadComplete(DeviceObject, Irp, Context); /* * Reset the timer of the collection which initiated this read, * (which may be different than the collection that returned the report). */ if (pdoExt->state == COLLECTION_STATE_RUNNING){ PHIDCLASS_COLLECTION originatorCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum); if (originatorCollection){ LARGE_INTEGER timeout; timeout.HighPart = -1; timeout.LowPart = -(LONG)(originatorCollection->PollInterval_msec*10000); KeSetTimer( &originatorCollection->polledDeviceTimer, timeout, &originatorCollection->polledDeviceTimerDPC); } else { TRAP; } } return status; } /* * ReadPolledDevice * * Issue a read to the polled device on behalf of the * top-level collection indicated by pdoExt. * (Note that because we keep separate polling loops for * each collection, we do reads on behalf of specific collections). * */ BOOLEAN ReadPolledDevice(PDO_EXTENSION *pdoExt, BOOLEAN isTimerDrivenRead) { BOOLEAN didPollDevice = FALSE; FDO_EXTENSION *fdoExt; PHIDP_COLLECTION_DESC hidCollectionDesc; fdoExt = &pdoExt->deviceFdoExt->fdoExt; hidCollectionDesc = GetCollectionDesc(fdoExt, pdoExt->collectionNum); if (hidCollectionDesc){ PIRP irp = IoAllocateIrp(fdoExt->fdo->StackSize, FALSE); if (irp){ /* * We cannot issue a read on a specific collection. * But we'll allocate a buffer just large enough for a report * on the collection we want. * Note that hidCollectionDesc->InputLength includes * the report id byte, which we may have to prepend ourselves. */ ULONG reportLen = hidCollectionDesc->InputLength; irp->UserBuffer = ALLOCATEPOOL(NonPagedPool, reportLen); if (irp->UserBuffer){ PIO_COMPLETION_ROUTINE completionRoutine; PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(irp); ASSERT(nextStack); if (fdoExt->deviceDesc.ReportIDs[0].ReportID == 0) { /* * This device has only one report type, * so the minidriver will not include the 1-byte report id * (which is implicitly zero). * However, we still need to return a 'cooked' report, * with the report id, to the user; so bump the buffer * we pass down to make room for the report id. */ *(((PUCHAR)irp->UserBuffer)++) = (UCHAR)0; reportLen--; } nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_HID_READ_REPORT; nextStack->Parameters.DeviceIoControl.OutputBufferLength = reportLen; irp->IoStatus.Status = STATUS_NOT_SUPPORTED; completionRoutine = (isTimerDrivenRead) ? HidpPolledReadComplete_TimerDriven : HidpPolledReadComplete; IoSetCompletionRoutine( irp, completionRoutine, (PVOID)pdoExt, // context TRUE, TRUE, TRUE ); InterlockedDecrement(&fdoExt->outstandingRequests); HidpCallDriver(fdoExt->fdo, irp); didPollDevice = TRUE; } } } else { ASSERT(hidCollectionDesc); } return didPollDevice; } /* ******************************************************************************** * HidpPolledTimerDpc ******************************************************************************** * * * */ VOID HidpPolledTimerDpc( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) { PDO_EXTENSION *pdoExt = (PDO_EXTENSION *)DeferredContext; FDO_EXTENSION *fdoExt = &pdoExt->deviceFdoExt->fdoExt; ASSERT(pdoExt->deviceFdoExt->Signature == HID_DEVICE_EXTENSION_SIG); if (pdoExt->state == COLLECTION_STATE_RUNNING){ PHIDCLASS_COLLECTION hidCollection; hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum); if (hidCollection){ KIRQL oldIrql; BOOLEAN haveReadIrpsQueued; BOOLEAN didPollDevice = FALSE; /* * If there are reads pending on this collection, * issue a read to the device. * * Note: we have no control over which collection we are reading. * This read may end up returning a report for a different * collection! That's ok, since a report for this collection * will eventually be returned. */ KeAcquireSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, &oldIrql); haveReadIrpsQueued = !IsListEmpty(&hidCollection->polledDeviceReadQueue); KeReleaseSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, oldIrql); if (haveReadIrpsQueued){ didPollDevice = ReadPolledDevice(pdoExt, TRUE); } else { /* * The timer period has expired, so any saved reports * are now stale. */ hidCollection->polledDataIsStale = TRUE; } /* * If we actually polled the device, we'll reset the timer in the * completion routine; otherwise, we do it here. */ if (!didPollDevice){ LARGE_INTEGER timeout; timeout.HighPart = -1; timeout.LowPart = -(LONG)(hidCollection->PollInterval_msec*10000); KeSetTimer( &hidCollection->polledDeviceTimer, timeout, &hidCollection->polledDeviceTimerDPC); } } else { TRAP; } } } /* ******************************************************************************** * StartPollingLoop ******************************************************************************** * * Start a polling loop for a particular collection. * */ BOOLEAN StartPollingLoop( FDO_EXTENSION *fdoExt, PHIDCLASS_COLLECTION hidCollection, BOOLEAN freshQueue) { ULONG ctnIndex = hidCollection->CollectionIndex; LARGE_INTEGER timeout; KIRQL oldIrql; if (freshQueue){ InitializeListHead(&hidCollection->polledDeviceReadQueue); KeInitializeSpinLock(&hidCollection->polledDeviceReadQueueSpinLock); KeInitializeTimer(&hidCollection->polledDeviceTimer); } /* * Use polledDeviceReadQueueSpinLock to protect the timer structures as well * as the queue. */ KeAcquireSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, &oldIrql); KeInitializeDpc( &hidCollection->polledDeviceTimerDPC, HidpPolledTimerDpc, &fdoExt->collectionPdoExtensions[ctnIndex]->pdoExt); KeReleaseSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, oldIrql); timeout.HighPart = -1; timeout.LowPart = -(LONG)(hidCollection->PollInterval_msec*10000); KeSetTimer( &hidCollection->polledDeviceTimer, timeout, &hidCollection->polledDeviceTimerDPC); return TRUE; } /* ******************************************************************************** * StopPollingLoop ******************************************************************************** * * * */ VOID StopPollingLoop(PHIDCLASS_COLLECTION hidCollection, BOOLEAN flushQueue) { KIRQL oldIrql; /* * Use polledDeviceReadQueueSpinLock to protect the timer structures as well * as the queue. */ KeAcquireSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, &oldIrql); KeCancelTimer(&hidCollection->polledDeviceTimer); KeInitializeTimer(&hidCollection->polledDeviceTimer); KeReleaseSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, oldIrql); /* * Fail all the queued IRPs. */ if (flushQueue){ PIRP irp; LIST_ENTRY irpsToComplete; /* * Move the IRPs to a temporary queue first so they don't get requeued * on the completion thread and cause us to loop forever. */ InitializeListHead(&irpsToComplete); while (irp = DequeuePolledReadIrp(hidCollection)){ InsertTailList(&irpsToComplete, &irp->Tail.Overlay.ListEntry); } while (!IsListEmpty(&irpsToComplete)){ PLIST_ENTRY listEntry = RemoveHeadList(&irpsToComplete); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); DBG_RECORD_READ(irp, 0, 0, TRUE) irp->IoStatus.Status = STATUS_DEVICE_NOT_CONNECTED; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); } } } /* ******************************************************************************** * PolledReadCancelRoutine ******************************************************************************** * * We need to set an IRP's cancel routine to non-NULL before * we queue it; so just use a pointer this NULL function. * */ VOID PolledReadCancelRoutine(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension; FDO_EXTENSION *fdoExt; PHIDCLASS_COLLECTION hidCollection; ULONG collectionIndex; KIRQL oldIrql; IoReleaseCancelSpinLock(Irp->CancelIrql); ASSERT(hidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG); ASSERT(hidDeviceExtension->isClientPdo); fdoExt = &hidDeviceExtension->pdoExt.deviceFdoExt->fdoExt; collectionIndex = hidDeviceExtension->pdoExt.collectionIndex; hidCollection = &fdoExt->classCollectionArray[collectionIndex]; KeAcquireSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, &oldIrql); RemoveEntryList(&Irp->Tail.Overlay.ListEntry); ASSERT(hidCollection->numPendingReads > 0); hidCollection->numPendingReads--; KeReleaseSpinLock(&hidCollection->polledDeviceReadQueueSpinLock, oldIrql); Irp->IoStatus.Status = STATUS_CANCELLED; DBG_RECORD_READ(Irp, 0, 0, TRUE) IoCompleteRequest(Irp, IO_NO_INCREMENT); } NTSTATUS EnqueuePolledReadIrp(PHIDCLASS_COLLECTION collection, PIRP Irp) { NTSTATUS status; KIRQL oldIrql; PDRIVER_CANCEL oldCancelRoutine; KeAcquireSpinLock(&collection->polledDeviceReadQueueSpinLock, &oldIrql); /* * Must set a cancel routine before * checking the Cancel flag. */ oldCancelRoutine = IoSetCancelRoutine(Irp, PolledReadCancelRoutine); ASSERT(!oldCancelRoutine); /* * Make sure this Irp wasn't just cancelled. * Note that there is NO RACE CONDITION here * because we are holding the fileExtension lock. */ if (Irp->Cancel){ /* * This IRP was cancelled. */ oldCancelRoutine = IoSetCancelRoutine(Irp, NULL); if (oldCancelRoutine){ /* * The cancel routine was NOT called. * Return error so that caller completes the IRP. */ DBG_RECORD_READ(Irp, IoGetCurrentIrpStackLocation(Irp)->Parameters.Read.Length, 0, TRUE) ASSERT(oldCancelRoutine == PolledReadCancelRoutine); status = STATUS_CANCELLED; } else { /* * The cancel routine was called. * As soon as we drop the spinlock it will dequeue * and complete the IRP. * Initialize the IRP's listEntry so that the dequeue * doesn't cause corruption. * Then don't touch the irp. */ InitializeListHead(&Irp->Tail.Overlay.ListEntry); collection->numPendingReads++; // because cancel routine will decrement IoMarkIrpPending(Irp); status = STATUS_PENDING; } } else { DBG_RECORD_READ(Irp, IoGetCurrentIrpStackLocation(Irp)->Parameters.Read.Length, 0, FALSE) /* * There are no reports waiting. * Queue this irp onto the file extension's list of pending irps. */ InsertTailList(&collection->polledDeviceReadQueue, &Irp->Tail.Overlay.ListEntry); collection->numPendingReads++; IoMarkIrpPending(Irp); status = STATUS_PENDING; } KeReleaseSpinLock(&collection->polledDeviceReadQueueSpinLock, oldIrql); DBGSUCCESS(status, TRUE) return status; } PIRP DequeuePolledReadSystemIrp(PHIDCLASS_COLLECTION collection) { KIRQL oldIrql; PIRP irp = NULL; PLIST_ENTRY listEntry; PHIDCLASS_FILE_EXTENSION fileExtension; PFILE_OBJECT fileObject; PIO_STACK_LOCATION irpSp; KeAcquireSpinLock(&collection->polledDeviceReadQueueSpinLock, &oldIrql); listEntry = &collection->polledDeviceReadQueue; while (!irp && ((listEntry = listEntry->Flink) != &collection->polledDeviceReadQueue)) { PDRIVER_CANCEL oldCancelRoutine; irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); irpSp = IoGetCurrentIrpStackLocation(irp); fileObject = irpSp->FileObject; fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext; if (!fileExtension->isSecureOpen) { irp = NULL; continue; } RemoveEntryList(listEntry); oldCancelRoutine = IoSetCancelRoutine(irp, NULL); if (oldCancelRoutine){ ASSERT(oldCancelRoutine == PolledReadCancelRoutine); ASSERT(collection->numPendingReads > 0); collection->numPendingReads--; } else { /* * IRP was cancelled and cancel routine was called. * As soon as we drop the spinlock, * the cancel routine will dequeue and complete this IRP. * Initialize the IRP's listEntry so that the dequeue doesn't cause corruption. * Then, don't touch the IRP. */ ASSERT(irp->Cancel); InitializeListHead(&irp->Tail.Overlay.ListEntry); irp = NULL; } } KeReleaseSpinLock(&collection->polledDeviceReadQueueSpinLock, oldIrql); return irp; } PIRP DequeuePolledReadIrp(PHIDCLASS_COLLECTION collection) { KIRQL oldIrql; PIRP irp = NULL; KeAcquireSpinLock(&collection->polledDeviceReadQueueSpinLock, &oldIrql); while (!irp && !IsListEmpty(&collection->polledDeviceReadQueue)){ PDRIVER_CANCEL oldCancelRoutine; PLIST_ENTRY listEntry = RemoveHeadList(&collection->polledDeviceReadQueue); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); oldCancelRoutine = IoSetCancelRoutine(irp, NULL); if (oldCancelRoutine){ ASSERT(oldCancelRoutine == PolledReadCancelRoutine); ASSERT(collection->numPendingReads > 0); collection->numPendingReads--; } else { /* * IRP was cancelled and cancel routine was called. * As soon as we drop the spinlock, * the cancel routine will dequeue and complete this IRP. * Initialize the IRP's listEntry so that the dequeue doesn't cause corruption. * Then, don't touch the IRP. */ ASSERT(irp->Cancel); InitializeListHead(&irp->Tail.Overlay.ListEntry); irp = NULL; } } KeReleaseSpinLock(&collection->polledDeviceReadQueueSpinLock, oldIrql); return irp; }