windows-nt/Source/XPSP1/NT/drivers/wdm/input/hidclass/read.c

531 lines
20 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
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);
}