/*++ Copyright (c) 1996 Microsoft Corporation Module Name: read.c Abstract Read handling routines Author: Ervin P. Environment: Kernel mode only Revision History: --*/ #include "pch.h" /* ******************************************************************************** * HidpCancelReadIrp ******************************************************************************** * * If a queued read Irp gets cancelled by the user, * this function removes it from our pending-read list. * */ VOID HidpCancelReadIrp(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension; FDO_EXTENSION *fdoExt; PHIDCLASS_COLLECTION collection; ULONG collectionIndex; KIRQL oldIrql; PIO_STACK_LOCATION irpSp; PHIDCLASS_FILE_EXTENSION fileExtension; ASSERT(hidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG); ASSERT(hidDeviceExtension->isClientPdo); fdoExt = &hidDeviceExtension->pdoExt.deviceFdoExt->fdoExt; collectionIndex = hidDeviceExtension->pdoExt.collectionIndex; collection = &fdoExt->classCollectionArray[collectionIndex]; irpSp = IoGetCurrentIrpStackLocation(Irp); ASSERT(irpSp->FileObject->Type == IO_TYPE_FILE); fileExtension = (PHIDCLASS_FILE_EXTENSION)irpSp->FileObject->FsContext; IoReleaseCancelSpinLock(Irp->CancelIrql); LockFileExtension(fileExtension, &oldIrql); RemoveEntryList(&Irp->Tail.Overlay.ListEntry); DBG_RECORD_READ(Irp, 0, 0, TRUE); ASSERT(collection->numPendingReads > 0); collection->numPendingReads--; UnlockFileExtension(fileExtension, oldIrql); Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(Irp, IO_NO_INCREMENT); } NTSTATUS EnqueueInterruptReadIrp( PHIDCLASS_COLLECTION collection, PHIDCLASS_FILE_EXTENSION fileExtension, PIRP Irp) { NTSTATUS status; PDRIVER_CANCEL oldCancelRoutine; RUNNING_DISPATCH(); /* * Must set a cancel routine before * checking the Cancel flag. */ oldCancelRoutine = IoSetCancelRoutine(Irp, HidpCancelReadIrp); 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. */ ASSERT(oldCancelRoutine == HidpCancelReadIrp); 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 = Irp->IoStatus.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(&fileExtension->PendingIrpList, &Irp->Tail.Overlay.ListEntry); collection->numPendingReads++; IoMarkIrpPending(Irp); status = Irp->IoStatus.Status = STATUS_PENDING; } return status; } PIRP DequeueInterruptReadIrp( PHIDCLASS_COLLECTION collection, PHIDCLASS_FILE_EXTENSION fileExtension) { PIRP irp = NULL; RUNNING_DISPATCH(); while (!irp && !IsListEmpty(&fileExtension->PendingIrpList)){ PDRIVER_CANCEL oldCancelRoutine; PLIST_ENTRY listEntry = RemoveHeadList(&fileExtension->PendingIrpList); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); oldCancelRoutine = IoSetCancelRoutine(irp, NULL); if (oldCancelRoutine){ ASSERT(oldCancelRoutine == HidpCancelReadIrp); 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; } } return irp; } /* ******************************************************************************** * HidpIrpMajorRead ******************************************************************************** * * Note: this function should not be pageable because * reads can come in at dispatch level. * */ NTSTATUS HidpIrpMajorRead(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp) { NTSTATUS status = STATUS_SUCCESS; FDO_EXTENSION *fdoExt; PDO_EXTENSION *pdoExt; PIO_STACK_LOCATION irpSp; PHIDCLASS_FILE_EXTENSION fileExtension; KIRQL oldIrql; ASSERT(HidDeviceExtension->isClientPdo); pdoExt = &HidDeviceExtension->pdoExt; fdoExt = &pdoExt->deviceFdoExt->fdoExt; irpSp = IoGetCurrentIrpStackLocation(Irp); /* * Get our file extension. */ if (!irpSp->FileObject || (irpSp->FileObject && !irpSp->FileObject->FsContext)) { DBGWARN(("Attempted read with no file extension")) Irp->IoStatus.Status = status = STATUS_PRIVILEGE_NOT_HELD; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; } ASSERT(irpSp->FileObject->Type == IO_TYPE_FILE); fileExtension = (PHIDCLASS_FILE_EXTENSION)irpSp->FileObject->FsContext; ASSERT(fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG); /* * Check security. * The open must have been succeeded BY THIS DRIVER and * (if this is a read on a keyboard or mouse) * the client must be a kernel driver. */ if (fileExtension->SecurityCheck && fileExtension->haveReadPrivilege){ if (((fdoExt->state == DEVICE_STATE_START_SUCCESS) || (fdoExt->state == DEVICE_STATE_STOPPING) || (fdoExt->state == DEVICE_STATE_STOPPED)) && ((pdoExt->state == COLLECTION_STATE_RUNNING) || (pdoExt->state == COLLECTION_STATE_STOPPING) || (pdoExt->state == COLLECTION_STATE_STOPPED))){ ULONG collectionNum; PHIDCLASS_COLLECTION classCollection; PHIDP_COLLECTION_DESC collectionDesc; // // ISSUE: Is this safe to stop a polled collection like this? // interrupt driver collections have a restore read pump at power up // to D0, but I don't see any for polled collections...? // BOOLEAN isStopped = ((fdoExt->state == DEVICE_STATE_STOPPED) || (fdoExt->state == DEVICE_STATE_STOPPING) || (pdoExt->state == COLLECTION_STATE_STOPPING) || (pdoExt->state == COLLECTION_STATE_STOPPED)); Irp->IoStatus.Information = 0; /* * Get our collection and collection description. */ collectionNum = HidDeviceExtension->pdoExt.collectionNum; classCollection = GetHidclassCollection(fdoExt, collectionNum); collectionDesc = GetCollectionDesc(fdoExt, collectionNum); if (classCollection && collectionDesc){ /* * Make sure the caller's read buffer is large enough to read at least one report. */ if (irpSp->Parameters.Read.Length >= collectionDesc->InputLength){ /* * We know we're going to try to transfer something into the caller's * buffer, so get the global address. This will also serve to create * a mapped system address in the MDL if necessary. */ if (classCollection->hidCollectionInfo.Polled){ /* * This is a POLLED collection. */ #if DBG if (fileExtension->isOpportunisticPolledDeviceReader && fileExtension->nowCompletingIrpForOpportunisticReader){ DBGWARN(("'Opportunistic' reader issuing read in completion routine")) } #endif if (isStopped){ status = EnqueuePolledReadIrp(classCollection, Irp); } else if (fileExtension->isOpportunisticPolledDeviceReader && !classCollection->polledDataIsStale && !fileExtension->nowCompletingIrpForOpportunisticReader && (irpSp->Parameters.Read.Length >= classCollection->savedPolledReportLen)){ PUCHAR callersBuffer; callersBuffer = HidpGetSystemAddressForMdlSafe(Irp->MdlAddress); if (callersBuffer) { ULONG userReportLength; /* * Use the polledDeviceReadQueueSpinLock to protect * the savedPolledReportBuf. */ KeAcquireSpinLock(&classCollection->polledDeviceReadQueueSpinLock, &oldIrql); /* * This is an "opportunistic" reader who * wants a result right away. * We have a recent report, * so just copy the last saved report. */ RtlCopyMemory( callersBuffer, classCollection->savedPolledReportBuf, classCollection->savedPolledReportLen); Irp->IoStatus.Information = userReportLength = classCollection->savedPolledReportLen; KeReleaseSpinLock(&classCollection->polledDeviceReadQueueSpinLock, oldIrql); DBG_RECORD_READ(Irp, userReportLength, (ULONG)callersBuffer[0], TRUE) status = STATUS_SUCCESS; } else { status = STATUS_INVALID_USER_BUFFER; } } else { status = EnqueuePolledReadIrp(classCollection, Irp); /* * If this is an "opportunistic" polled * device reader, and we queued the irp, * make the read happen right away. * Make sure ALL SPINLOCKS ARE RELEASED * before we call out of the driver. */ if (NT_SUCCESS(status) && fileExtension->isOpportunisticPolledDeviceReader){ ReadPolledDevice(pdoExt, FALSE); } } } else { /* * This is an ordinary NON-POLLED collection. * We either: * 1. Satisfy this read with a queued report * or * 2. Queue this read IRP and satisfy it in the future * when a report comes in (on one of the ping-pong IRPs). */ // // We only stop interrupt devices when we power down. // if (fdoExt->devicePowerState != PowerDeviceD0) { DBGINFO(("read report received in low power")); } isStopped |= (fdoExt->devicePowerState != PowerDeviceD0); LockFileExtension(fileExtension, &oldIrql); if (isStopped){ status = EnqueueInterruptReadIrp(classCollection, fileExtension, Irp); } else { ULONG userBufferRemaining = irpSp->Parameters.Read.Length; PUCHAR callersBuffer; callersBuffer = HidpGetSystemAddressForMdlSafe(Irp->MdlAddress); if (callersBuffer) { PUCHAR nextReportBuffer = callersBuffer; /* * There are some reports waiting. * * Spin in this loop, filling up the caller's buffer with reports, * until either the buffer fills up or we run out of reports. */ ULONG reportsReturned = 0; status = STATUS_SUCCESS; while (userBufferRemaining > 0){ PHIDCLASS_REPORT reportExtension; ULONG reportSize = userBufferRemaining; reportExtension = DequeueInterruptReport(fileExtension, userBufferRemaining); if (reportExtension){ status = HidpCopyInputReportToUser( fileExtension, reportExtension->UnparsedReport, &reportSize, nextReportBuffer); /* * Whether we succeeded or failed, free this report. * (If we failed, there may be something wrong with * the report, so we'll just throw it away). */ ExFreePool(reportExtension); if (NT_SUCCESS(status)){ reportsReturned++; nextReportBuffer += reportSize; ASSERT(reportSize <= userBufferRemaining); userBufferRemaining -= reportSize; } else { DBGSUCCESS(status, TRUE) break; } } else { break; } } if (NT_SUCCESS(status)){ if (!reportsReturned) { /* * No reports are ready. So queue the read IRP. */ status = EnqueueInterruptReadIrp(classCollection, fileExtension, Irp); } else { /* * We've succesfully copied something into the user's buffer, * calculate how much we've copied and return in the irp. */ Irp->IoStatus.Information = (ULONG)(nextReportBuffer - callersBuffer); DBG_RECORD_READ(Irp, (ULONG)Irp->IoStatus.Information, (ULONG)callersBuffer[0], TRUE) } } } else { status = STATUS_INVALID_USER_BUFFER; } } UnlockFileExtension(fileExtension, oldIrql); } } else { status = STATUS_INVALID_BUFFER_SIZE; } } else { status = STATUS_DEVICE_NOT_CONNECTED; } DBGSUCCESS(status, TRUE) } else { /* * This can legitimately happen. * The device was disconnected between the client's open and read; * or between a read-complete and the next read. */ status = STATUS_DEVICE_NOT_CONNECTED; } } else { DBGWARN(("HidpIrpMajorRead: user-mode client does not have read privilege")) status = STATUS_PRIVILEGE_NOT_HELD; } /* * If we satisfied the read Irp (did not queue it), * then complete it here. */ if (status != STATUS_PENDING){ ULONG insideReadCompleteCount; Irp->IoStatus.Status = status; insideReadCompleteCount = InterlockedIncrement(&fileExtension->insideReadCompleteCount); if (insideReadCompleteCount <= INSIDE_READCOMPLETE_MAX){ IoCompleteRequest(Irp, IO_KEYBOARD_INCREMENT); } else { /* * All these nested reads are _probably_ occuring on the same thread, * and we are going to run out of stack and crash if we keep completing * synchronously. So return pending for this IRP and schedule a workItem * to complete it asynchronously, just to give the stack a chance to unwind. */ ASYNC_COMPLETE_CONTEXT *asyncCompleteContext = ALLOCATEPOOL(NonPagedPool, sizeof(ASYNC_COMPLETE_CONTEXT)); if (asyncCompleteContext){ ASSERT(!Irp->CancelRoutine); DBGWARN(("HidpIrpMajorRead: CLIENT IS LOOPING ON READ COMPLETION -- scheduling workItem to complete IRP %ph (status=%xh) asynchronously", Irp, status)) ExInitializeWorkItem( &asyncCompleteContext->workItem, WorkItemCallback_CompleteIrpAsynchronously, asyncCompleteContext); asyncCompleteContext->sig = ASYNC_COMPLETE_CONTEXT_SIG; asyncCompleteContext->irp = Irp; asyncCompleteContext->devObj = pdoExt->pdo; ObReferenceObject(pdoExt->pdo); ExQueueWorkItem(&asyncCompleteContext->workItem, DelayedWorkQueue); status = STATUS_PENDING; } else { DBGERR(("HidpIrpMajorRead: completeIrpWorkItem alloc failed")) IoCompleteRequest(Irp, IO_NO_INCREMENT); } } InterlockedDecrement(&fileExtension->insideReadCompleteCount); } DBGSUCCESS(status, FALSE) return status; } VOID WorkItemCallback_CompleteIrpAsynchronously(PVOID context) { ASYNC_COMPLETE_CONTEXT *asyncCompleteContext = context; ASSERT(asyncCompleteContext->sig == ASYNC_COMPLETE_CONTEXT_SIG); DBGVERBOSE(("WorkItemCallback_CompleteIrpAsynchronously: completing irp %ph with status %xh.", asyncCompleteContext->irp, asyncCompleteContext->irp->IoStatus.Status)) /* * Indicate that the irp may be completing */ IoMarkIrpPending(asyncCompleteContext->irp); IoCompleteRequest(asyncCompleteContext->irp, IO_NO_INCREMENT); ObDereferenceObject(asyncCompleteContext->devObj); ExFreePool(asyncCompleteContext); }