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.
502 lines
20 KiB
502 lines
20 KiB
/*++
|
|
|
|
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 = 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 = 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);
|
|
|
|
|
|
|
|
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 (isStopped) {
|
|
status = EnqueuePolledReadIrp(classCollection, Irp);
|
|
} else if (fileExtension->isOpportunisticPolledDeviceReader &&
|
|
!classCollection->polledDataIsStale &&
|
|
(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, FALSE)
|
|
} 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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))
|
|
asyncCompleteContext->workItem = IoAllocateWorkItem(pdoExt->pdo);
|
|
|
|
asyncCompleteContext->sig = ASYNC_COMPLETE_CONTEXT_SIG;
|
|
asyncCompleteContext->irp = Irp;
|
|
|
|
/*
|
|
* Indicate that the irp has been queued
|
|
*/
|
|
IoMarkIrpPending(asyncCompleteContext->irp);
|
|
|
|
IoQueueWorkItem(asyncCompleteContext->workItem,
|
|
WorkItemCallback_CompleteIrpAsynchronously,
|
|
DelayedWorkQueue,
|
|
asyncCompleteContext);
|
|
|
|
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(PDEVICE_OBJECT DevObj,
|
|
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))
|
|
|
|
IoCompleteRequest(asyncCompleteContext->irp, IO_NO_INCREMENT);
|
|
|
|
IoFreeWorkItem(asyncCompleteContext->workItem);
|
|
ExFreePool(asyncCompleteContext);
|
|
}
|