1045 lines
36 KiB
C
1045 lines
36 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pingpong.c
|
|
|
|
Abstract
|
|
|
|
Interrupt style collections like to always have a read pending in case
|
|
something happens. This file contains routines to keep IRPs down
|
|
in the miniport, and to complete client reads (if a client read IRP is
|
|
pending) or queue them (if not).
|
|
|
|
Author:
|
|
|
|
Ervin P.
|
|
|
|
Environment:
|
|
|
|
Kernel mode only
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, HidpInitializePingPongIrps)
|
|
#pragma alloc_text(PAGE, HidpReallocPingPongIrps)
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpInitializePingPongIrps
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpInitializePingPongIrps(FDO_EXTENSION *fdoExtension)
|
|
{
|
|
NTSTATUS result = STATUS_SUCCESS;
|
|
ULONG i;
|
|
CCHAR numIrpStackLocations;
|
|
|
|
PAGED_CODE();
|
|
|
|
/*
|
|
* Note that our functional device object normally requires FDO->StackSize stack
|
|
* locations; but these IRPs will only be sent to the minidriver, so we need one less.
|
|
*
|
|
* THIS MEANS THAT WE SHOULD NEVER TOUCH OUR OWN STACK LOCATION (we don't have one!)
|
|
*/
|
|
numIrpStackLocations = fdoExtension->fdo->StackSize - 1;
|
|
|
|
|
|
//
|
|
// Next determine the size of each input HID report. There
|
|
// must be at least one collection of type interrupt, or we wouldn't
|
|
// need the ping-pong stuff at all and therefore wouldn't be here.
|
|
//
|
|
|
|
ASSERT(fdoExtension->maxReportSize > 0);
|
|
ASSERT(fdoExtension->numPingPongs > 0);
|
|
|
|
fdoExtension->pingPongs = ALLOCATEPOOL(NonPagedPool, fdoExtension->numPingPongs*sizeof(HIDCLASS_PINGPONG));
|
|
if (fdoExtension->pingPongs){
|
|
ULONG reportBufferSize = fdoExtension->maxReportSize;
|
|
|
|
RtlZeroMemory(fdoExtension->pingPongs, fdoExtension->numPingPongs*sizeof(HIDCLASS_PINGPONG));
|
|
|
|
#if DBG
|
|
// reserve space for guard word
|
|
reportBufferSize += sizeof(ULONG);
|
|
#endif
|
|
|
|
|
|
for (i = 0; i < fdoExtension->numPingPongs; i++){
|
|
|
|
fdoExtension->pingPongs[i].myFdoExt = fdoExtension;
|
|
fdoExtension->pingPongs[i].weAreCancelling = 0;
|
|
fdoExtension->pingPongs[i].sig = PINGPONG_SIG;
|
|
|
|
/*
|
|
* Initialize backoff timeout to 1 second (in neg 100-nsec units)
|
|
*/
|
|
fdoExtension->pingPongs[i].backoffTimerPeriod.HighPart = -1;
|
|
fdoExtension->pingPongs[i].backoffTimerPeriod.LowPart = -10000000;
|
|
KeInitializeTimer(&fdoExtension->pingPongs[i].backoffTimer);
|
|
KeInitializeDpc(&fdoExtension->pingPongs[i].backoffTimerDPC,
|
|
HidpPingpongBackoffTimerDpc,
|
|
&fdoExtension->pingPongs[i]);
|
|
|
|
fdoExtension->pingPongs[i].reportBuffer = ALLOCATEPOOL(NonPagedPool, reportBufferSize);
|
|
if (fdoExtension->pingPongs[i].reportBuffer){
|
|
PIRP irp;
|
|
|
|
#if DBG
|
|
#ifdef _X86_
|
|
// this sets off alignment problems on Alpha
|
|
// place guard word
|
|
*(PULONG)(&fdoExtension->pingPongs[i].reportBuffer[fdoExtension->maxReportSize]) = HIDCLASS_REPORT_BUFFER_GUARD;
|
|
#endif
|
|
#endif
|
|
|
|
irp = IoAllocateIrp(numIrpStackLocations, FALSE);
|
|
if (irp){
|
|
/*
|
|
* Point the ping-pong IRP's UserBuffer to the corresponding
|
|
* ping-pong object's report buffer.
|
|
*/
|
|
irp->UserBuffer = fdoExtension->pingPongs[i].reportBuffer;
|
|
fdoExtension->pingPongs[i].irp = irp;
|
|
KeInitializeEvent(&fdoExtension->pingPongs[i].sentEvent,
|
|
NotificationEvent,
|
|
TRUE); // Set to signaled
|
|
KeInitializeEvent(&fdoExtension->pingPongs[i].pumpDoneEvent,
|
|
NotificationEvent,
|
|
TRUE); // Set to signaled
|
|
}
|
|
else {
|
|
result = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
result = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
result = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
DBGSUCCESS(result, TRUE)
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpReallocPingPongIrps
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpReallocPingPongIrps(FDO_EXTENSION *fdoExtension, ULONG newNumBufs)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (fdoExtension->driverExt->DevicesArePolled){
|
|
/*
|
|
* Polled devices don't _HAVE_ ping-pong IRPs.
|
|
*/
|
|
DBGERR(("Minidriver devices polled fdo %x.", fdoExtension))
|
|
fdoExtension->numPingPongs = 0;
|
|
fdoExtension->pingPongs = BAD_POINTER;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else if (newNumBufs < MIN_PINGPONG_IRPS){
|
|
DBGERR(("newNumBufs < MIN_PINGPONG_IRPS!"))
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
else {
|
|
|
|
DestroyPingPongs(fdoExtension);
|
|
|
|
if (HidpSetMaxReportSize(fdoExtension)){
|
|
|
|
/*
|
|
* Initialize and restart the new ping-pong IRPs.
|
|
* If we can't allocate the desired number of buffers,
|
|
* keep reducing until we get some.
|
|
*/
|
|
do {
|
|
fdoExtension->numPingPongs = newNumBufs;
|
|
status = HidpInitializePingPongIrps(fdoExtension);
|
|
newNumBufs /= 2;
|
|
} while (!NT_SUCCESS(status) && (newNumBufs >= MIN_PINGPONG_IRPS));
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
/*
|
|
* The device will no longer function !!!
|
|
*/
|
|
TRAP;
|
|
fdoExtension->numPingPongs = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
DBGSUCCESS(status, TRUE)
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpSubmitInterruptRead
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpSubmitInterruptRead(
|
|
IN FDO_EXTENSION *fdoExt,
|
|
HIDCLASS_PINGPONG *pingPong,
|
|
BOOLEAN *irpSent)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION irpSp;
|
|
KIRQL oldIrql;
|
|
BOOLEAN proceed;
|
|
LONG oldInterlock;
|
|
PIRP irp = pingPong->irp;
|
|
|
|
ASSERT(irp);
|
|
|
|
*irpSent = FALSE;
|
|
|
|
while (1) {
|
|
if (NT_SUCCESS(status)) {
|
|
HidpSetDeviceBusy(fdoExt);
|
|
|
|
oldInterlock = InterlockedExchange(&pingPong->ReadInterlock,
|
|
PINGPONG_START_READ);
|
|
ASSERT(oldInterlock == PINGPONG_END_READ);
|
|
|
|
irp->Cancel = FALSE;
|
|
irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
|
|
|
|
irpSp = IoGetNextIrpStackLocation(irp);
|
|
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
|
|
irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_HID_READ_REPORT;
|
|
irpSp->Parameters.DeviceIoControl.OutputBufferLength = fdoExt->maxReportSize;
|
|
|
|
/*
|
|
* Indicate interrupt collection (default).
|
|
* We use .InputBufferLength for this
|
|
*/
|
|
irpSp->Parameters.DeviceIoControl.InputBufferLength = 0;
|
|
|
|
ASSERT(irp->UserBuffer == pingPong->reportBuffer);
|
|
#ifdef _X86_
|
|
// this sets off alignment problems on Alpha
|
|
ASSERT(*(PULONG)(&pingPong->reportBuffer[fdoExt->maxReportSize]) == HIDCLASS_REPORT_BUFFER_GUARD);
|
|
#endif
|
|
|
|
/*
|
|
* Set the completion, passing the FDO extension as context.
|
|
*/
|
|
IoSetCompletionRoutine( irp,
|
|
HidpInterruptReadComplete,
|
|
(PVOID)fdoExt,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
|
|
/*
|
|
* Send down the read IRP.
|
|
*/
|
|
KeResetEvent(&pingPong->sentEvent);
|
|
if (pingPong->weAreCancelling) {
|
|
InterlockedDecrement(&pingPong->weAreCancelling);
|
|
//
|
|
// Ordering of the next two instructions is crucial, since
|
|
// CancelPingPongs will exit after pumpDoneEvent is set, and the
|
|
// pingPongs could be deleted after that.
|
|
//
|
|
DBGVERBOSE(("Pingpong %x cancelled in submit before sending\n", pingPong))
|
|
KeSetEvent (&pingPong->sentEvent, 0, FALSE);
|
|
KeSetEvent(&pingPong->pumpDoneEvent, 0, FALSE);
|
|
status = STATUS_CANCELLED;
|
|
break;
|
|
} else {
|
|
fdoExt->outstandingRequests++;
|
|
DBGVERBOSE(("Sending pingpong %x from Submit\n", pingPong))
|
|
status = HidpCallDriver(fdoExt->fdo, irp);
|
|
KeSetEvent (&pingPong->sentEvent, 0, FALSE);
|
|
*irpSent = TRUE;
|
|
}
|
|
|
|
if (PINGPONG_IMMEDIATE_READ != InterlockedExchange(&pingPong->ReadInterlock,
|
|
PINGPONG_END_READ)) {
|
|
//
|
|
// The read is asynch, will call SubmitInterruptRead from the
|
|
// completion routine
|
|
//
|
|
DBGVERBOSE(("read is pending\n"))
|
|
break;
|
|
} else {
|
|
//
|
|
// The read was synchronous (probably bytes in the buffer). The
|
|
// completion routine will not call SubmitInterruptRead, so we
|
|
// just loop here. This is to prevent us from running out of stack
|
|
// space if always call StartRead from the completion routine
|
|
//
|
|
status = irp->IoStatus.Status;
|
|
DBGVERBOSE(("read is looping with status %x\n", status))
|
|
}
|
|
} else {
|
|
if (pingPong->weAreCancelling ){
|
|
|
|
// We are stopping the read pump.
|
|
// set this event and stop resending the pingpong IRP.
|
|
DBGVERBOSE(("We are cancelling bit set for pingpong %x\n", pingPong))
|
|
InterlockedDecrement(&pingPong->weAreCancelling);
|
|
KeSetEvent(&pingPong->pumpDoneEvent, 0, FALSE);
|
|
} else {
|
|
/*
|
|
* The device returned error.
|
|
* In order to support slightly-broken devices which
|
|
* "hiccup" occasionally, we implement a back-off timer
|
|
* algorithm; this way, the device gets a second chance,
|
|
* but if it spits back error each time, this doesn't
|
|
* eat up all the available CPU.
|
|
*/
|
|
DBGVERBOSE(("Queuing backoff timer on pingpong %x\n", pingPong))
|
|
ASSERT((LONG)pingPong->backoffTimerPeriod.HighPart == -1);
|
|
ASSERT((LONG)pingPong->backoffTimerPeriod.LowPart < 0);
|
|
KeSetTimer( &pingPong->backoffTimer,
|
|
pingPong->backoffTimerPeriod,
|
|
&pingPong->backoffTimerDPC);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
DBGSUCCESS(status, FALSE)
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpProcessInterruptReport
|
|
********************************************************************************
|
|
*
|
|
* Take the new interrupt read report and either:
|
|
* 1. If there is a pending read IRP, use it to satisfy that read IRP
|
|
* and complete the read IRP
|
|
*
|
|
* or
|
|
*
|
|
* 2. If there is no pending read IRP,
|
|
* queue the report for a future read.
|
|
*
|
|
*/
|
|
NTSTATUS HidpProcessInterruptReport(
|
|
PHIDCLASS_COLLECTION collection,
|
|
PHIDCLASS_FILE_EXTENSION FileExtension,
|
|
PUCHAR Report,
|
|
ULONG ReportLength,
|
|
PIRP *irpToComplete
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
NTSTATUS result;
|
|
PIRP readIrpToSatisfy;
|
|
BOOLEAN calledBlueScreenFunc = FALSE;
|
|
|
|
|
|
LockFileExtension(FileExtension, &oldIrql);
|
|
|
|
if (FileExtension->BlueScreenData.BluescreenFunction &&
|
|
*(FileExtension->BlueScreenData.IsBluescreenTime) ) {
|
|
|
|
(*FileExtension->BlueScreenData.BluescreenFunction)(
|
|
FileExtension->BlueScreenData.Context,
|
|
Report
|
|
);
|
|
calledBlueScreenFunc = TRUE;
|
|
|
|
readIrpToSatisfy = NULL;
|
|
result = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (!calledBlueScreenFunc){
|
|
|
|
/*
|
|
* Dequeue the next interrupt read.
|
|
*/
|
|
readIrpToSatisfy = DequeueInterruptReadIrp(collection, FileExtension);
|
|
|
|
if (readIrpToSatisfy){
|
|
/*
|
|
* We have dequeued a pended read IRP
|
|
* which we will complete with this report.
|
|
*/
|
|
ULONG userReportLength;
|
|
PCHAR pDest;
|
|
PIO_STACK_LOCATION irpSp;
|
|
NTSTATUS status;
|
|
|
|
ASSERT(IsListEmpty(&FileExtension->ReportList));
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(readIrpToSatisfy);
|
|
pDest = HidpGetSystemAddressForMdlSafe(readIrpToSatisfy->MdlAddress);
|
|
if(pDest) {
|
|
userReportLength = irpSp->Parameters.Read.Length;
|
|
|
|
status = HidpCopyInputReportToUser( FileExtension,
|
|
Report,
|
|
&userReportLength,
|
|
pDest);
|
|
DBGASSERT(NT_SUCCESS(status),
|
|
("HidpCopyInputReportToUser returned status = %x", status),
|
|
TRUE)
|
|
|
|
readIrpToSatisfy->IoStatus.Status = status;
|
|
readIrpToSatisfy->IoStatus.Information = userReportLength;
|
|
|
|
DBG_RECORD_READ(readIrpToSatisfy, userReportLength, (ULONG)Report[0], TRUE)
|
|
|
|
result = status;
|
|
}
|
|
else {
|
|
result = STATUS_INVALID_USER_BUFFER;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* We don't have any pending read IRPs.
|
|
* So queue this report for the next read.
|
|
*/
|
|
|
|
PHIDCLASS_REPORT report;
|
|
ULONG reportSize;
|
|
|
|
reportSize = FIELD_OFFSET(HIDCLASS_REPORT, UnparsedReport) + ReportLength;
|
|
report = ALLOCATEPOOL(NonPagedPool, reportSize);
|
|
if (report){
|
|
report->reportLength = ReportLength;
|
|
RtlCopyMemory(report->UnparsedReport, Report, ReportLength);
|
|
EnqueueInterruptReport(FileExtension, report);
|
|
result = STATUS_PENDING;
|
|
}
|
|
else {
|
|
result = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
}
|
|
|
|
UnlockFileExtension(FileExtension, oldIrql);
|
|
|
|
/*
|
|
* This function is called with the fileExtensionsList spinlock held.
|
|
* So we can't complete the IRP here. Pass it back to the caller and it'll
|
|
* be completed as soon as we drop all the spinlocks.
|
|
*/
|
|
*irpToComplete = readIrpToSatisfy;
|
|
|
|
DBGSUCCESS(result, TRUE)
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpDistributeInterruptReport
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
VOID HidpDistributeInterruptReport(
|
|
IN PHIDCLASS_COLLECTION hidclassCollection,
|
|
PUCHAR Report,
|
|
ULONG ReportLength
|
|
)
|
|
{
|
|
PLIST_ENTRY listEntry;
|
|
KIRQL oldIrql;
|
|
LIST_ENTRY irpsToComplete;
|
|
ULONG secureReadMode;
|
|
|
|
#if DBG
|
|
ULONG numRecipients = 0;
|
|
ULONG numPending = 0;
|
|
ULONG numFailed = 0;
|
|
#endif
|
|
|
|
InitializeListHead(&irpsToComplete);
|
|
|
|
KeAcquireSpinLock(&hidclassCollection->FileExtensionListSpinLock, &oldIrql);
|
|
|
|
listEntry = &hidclassCollection->FileExtensionList;
|
|
secureReadMode = hidclassCollection->secureReadMode;
|
|
|
|
while ((listEntry = listEntry->Flink) != &hidclassCollection->FileExtensionList){
|
|
PIRP irpToComplete;
|
|
PHIDCLASS_FILE_EXTENSION fileExtension = CONTAINING_RECORD(listEntry, HIDCLASS_FILE_EXTENSION, FileList);
|
|
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// This is to enforce security for devices such as a digitizer on a
|
|
// tablet PC at the logon screen
|
|
//
|
|
if (secureReadMode && !fileExtension->isSecureOpen) {
|
|
continue;
|
|
}
|
|
|
|
#if DBG
|
|
status =
|
|
#endif
|
|
|
|
HidpProcessInterruptReport(hidclassCollection, fileExtension, Report, ReportLength, &irpToComplete);
|
|
|
|
if (irpToComplete){
|
|
InsertTailList(&irpsToComplete, &irpToComplete->Tail.Overlay.ListEntry);
|
|
}
|
|
|
|
#if DBG
|
|
if (status == STATUS_SUCCESS){
|
|
}
|
|
else if (status == STATUS_PENDING){
|
|
numPending++;
|
|
}
|
|
else {
|
|
DBGSUCCESS(status, FALSE)
|
|
numFailed++;
|
|
}
|
|
numRecipients++;
|
|
#endif
|
|
}
|
|
|
|
DBG_LOG_REPORT(hidclassCollection->CollectionNumber, numRecipients, numPending, numFailed, Report, ReportLength)
|
|
|
|
KeReleaseSpinLock(&hidclassCollection->FileExtensionListSpinLock, oldIrql);
|
|
|
|
/*
|
|
* Now that we've dropped all the spinlocks, complete all the dequeued read IRPs.
|
|
*/
|
|
while (!IsListEmpty(&irpsToComplete)){
|
|
PIRP irp;
|
|
PLIST_ENTRY listEntry = RemoveHeadList(&irpsToComplete);
|
|
irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
|
|
IoCompleteRequest(irp, IO_KEYBOARD_INCREMENT);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* GetPingPongFromIrp
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
HIDCLASS_PINGPONG *GetPingPongFromIrp(FDO_EXTENSION *fdoExt, PIRP irp)
|
|
{
|
|
HIDCLASS_PINGPONG *pingPong = NULL;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < fdoExt->numPingPongs; i++){
|
|
if (fdoExt->pingPongs[i].irp == irp){
|
|
pingPong = &fdoExt->pingPongs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(pingPong);
|
|
return pingPong;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpInterruptReadComplete
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpInterruptReadComplete(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Context
|
|
)
|
|
{
|
|
FDO_EXTENSION *fdoExt = (FDO_EXTENSION *)Context;
|
|
HIDCLASS_PINGPONG *pingPong;
|
|
KIRQL oldIrql;
|
|
BOOLEAN startRead;
|
|
|
|
DBG_COMMON_ENTRY()
|
|
|
|
DBGLOG_INTSTART()
|
|
|
|
//
|
|
// Track the number of outstanding requests to this device.
|
|
//
|
|
ASSERT(fdoExt->outstandingRequests > 0 );
|
|
fdoExt->outstandingRequests--;
|
|
|
|
pingPong = GetPingPongFromIrp(fdoExt, Irp);
|
|
|
|
if (!pingPong) {
|
|
//
|
|
// Something is terribly wrong, but do nothing. Hopefully
|
|
// just exiting will clear up this pimple.
|
|
//
|
|
DBGERR(("A pingPong structure could not be found!!! Have this looked at!"))
|
|
goto InterruptReadCompleteExit;
|
|
}
|
|
|
|
//
|
|
// If ReadInterlock is == START_READ, this func has been completed
|
|
// synchronously. Place IMMEDIATE_READ into the interlock to signify this
|
|
// situation; this will notify StartRead to loop when IoCallDriver returns.
|
|
// Otherwise, we have been completed async and it is safe to call StartRead()
|
|
//
|
|
startRead =
|
|
(PINGPONG_START_READ !=
|
|
InterlockedCompareExchange(&pingPong->ReadInterlock,
|
|
PINGPONG_IMMEDIATE_READ,
|
|
PINGPONG_START_READ));
|
|
|
|
|
|
/*
|
|
* Take appropriate action based on the completion code of this pingpong irp.
|
|
*/
|
|
if (Irp->IoStatus.Status == STATUS_SUCCESS){
|
|
|
|
/*
|
|
* We've read one or more input reports.
|
|
* They are sitting consecutively in Irp->UserBuffer.
|
|
*/
|
|
PUCHAR reportStart = Irp->UserBuffer;
|
|
LONG bytesRemaining = (LONG)Irp->IoStatus.Information;
|
|
|
|
DBGASSERT(bytesRemaining > 0, ("BAD HARDWARE. Device returned zero bytes. If this happens repeatedly, remove device."), FALSE);
|
|
|
|
/*
|
|
* Deliver each report separately.
|
|
*/
|
|
while (bytesRemaining > 0){
|
|
UCHAR reportId;
|
|
PHIDP_REPORT_IDS reportIdentifier;
|
|
|
|
/*
|
|
* If the first report ID is 0, then there is only one report id
|
|
* and it is known implicitly by the device, so it is not included
|
|
* in the reports sent to or from the device.
|
|
* Otherwise, there are multiple report ids and the report id is the
|
|
* first byte of the report.
|
|
*/
|
|
if (fdoExt->deviceDesc.ReportIDs[0].ReportID == 0){
|
|
/*
|
|
* This device has only a single input report ID, so call it report id 0;
|
|
*/
|
|
reportId = 0;
|
|
}
|
|
else {
|
|
/*
|
|
* This device has multiple input report IDs, so each report
|
|
* begins with a UCHAR report ID.
|
|
*/
|
|
reportId = *reportStart;
|
|
DBGASSERT(reportId,
|
|
("Bad Hardware. Not returning a report id although it has multiple ids."),
|
|
FALSE) // Bad hardware, bug 354829.
|
|
reportStart += sizeof(UCHAR);
|
|
bytesRemaining--;
|
|
}
|
|
|
|
|
|
/*
|
|
* Extract the report identifier with the given id from the HID device extension.
|
|
*/
|
|
reportIdentifier = GetReportIdentifier(fdoExt, reportId);
|
|
|
|
if (reportIdentifier){
|
|
LONG reportDataLen = (reportId ?
|
|
reportIdentifier->InputLength-1 :
|
|
reportIdentifier->InputLength);
|
|
|
|
if ((reportDataLen > 0) && (reportDataLen <= bytesRemaining)){
|
|
|
|
PHIDCLASS_COLLECTION collection;
|
|
PHIDP_COLLECTION_DESC hidCollectionDesc;
|
|
|
|
/*
|
|
* This report represents the state of some collection on the device.
|
|
* Find that collection.
|
|
*/
|
|
collection = GetHidclassCollection( fdoExt,
|
|
reportIdentifier->CollectionNumber);
|
|
hidCollectionDesc = GetCollectionDesc( fdoExt,
|
|
reportIdentifier->CollectionNumber);
|
|
if (collection && hidCollectionDesc){
|
|
PDO_EXTENSION *pdoExt;
|
|
|
|
/*
|
|
* The collection's inputLength is the size of the
|
|
* largest report (including report id); so it should
|
|
* be at least as big as this one.
|
|
*/
|
|
ASSERT(hidCollectionDesc->InputLength >= reportDataLen+1);
|
|
|
|
/*
|
|
* Make sure that the PDO for this collection has gotten
|
|
* START_DEVICE before returning anything for it.
|
|
* (collection-PDOs can get REMOVE_DEVICE/START_DEVICE intermittently).
|
|
*/
|
|
pdoExt = &fdoExt->collectionPdoExtensions[collection->CollectionIndex]->pdoExt;
|
|
ASSERT(ISPTR(pdoExt));
|
|
if (pdoExt->state == COLLECTION_STATE_RUNNING){
|
|
/*
|
|
* "Cook" the report
|
|
* (if it doesn't already have a report id byte, add one).
|
|
*/
|
|
ASSERT(ISPTR(collection->cookedInterruptReportBuf));
|
|
collection->cookedInterruptReportBuf[0] = reportId;
|
|
RtlCopyMemory( collection->cookedInterruptReportBuf+1,
|
|
reportStart,
|
|
reportDataLen);
|
|
|
|
/*
|
|
* If this report contains a power-button event, alert this system.
|
|
*/
|
|
CheckReportPowerEvent( fdoExt,
|
|
collection,
|
|
collection->cookedInterruptReportBuf,
|
|
hidCollectionDesc->InputLength);
|
|
|
|
/*
|
|
* Distribute the report to all of the open file objects on this collection.
|
|
*/
|
|
HidpDistributeInterruptReport(collection,
|
|
collection->cookedInterruptReportBuf,
|
|
hidCollectionDesc->InputLength);
|
|
}
|
|
else {
|
|
DBGVERBOSE(("Report dropped because collection-PDO not started (pdoExt->state = %d).", pdoExt->state))
|
|
}
|
|
}
|
|
else {
|
|
// PDO hasn't been initialized yet. Throw away data.
|
|
DBGVERBOSE(("Report dropped because collection-PDO not initialized."))
|
|
|
|
// TRAP;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
DBGASSERT(reportDataLen > 0, ("Device returning report id with zero-length input report as part of input data."), FALSE)
|
|
if (reportDataLen > bytesRemaining) {
|
|
DBGVERBOSE(("Device has corrupt input report"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Move to the next report in the buffer.
|
|
*/
|
|
bytesRemaining -= reportDataLen;
|
|
reportStart += reportDataLen;
|
|
}
|
|
else {
|
|
//
|
|
// We have thrown away data because we couldn't find a report
|
|
// identifier corresponding to this data that we've been
|
|
// returned. Bad hardware, bug 354829.
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The read succeeded.
|
|
* Reset the backoff timer stuff (for when reads fail)
|
|
* and re-submit this ping-pong IRP.
|
|
*/
|
|
pingPong->backoffTimerPeriod.HighPart = -1;
|
|
pingPong->backoffTimerPeriod.LowPart = -10000000;
|
|
}
|
|
|
|
//
|
|
// Business as usual.
|
|
//
|
|
if (startRead) {
|
|
if (pingPong->weAreCancelling ){
|
|
|
|
// We are stopping the read pump.
|
|
// Set this event and stop resending the pingpong IRP.
|
|
DBGVERBOSE(("We are cancelling bit set for pingpong %x\n", pingPong))
|
|
InterlockedDecrement(&pingPong->weAreCancelling);
|
|
KeSetEvent(&pingPong->pumpDoneEvent, 0, FALSE);
|
|
} else {
|
|
if (Irp->IoStatus.Status == STATUS_SUCCESS){
|
|
BOOLEAN irpSent;
|
|
DBGVERBOSE(("Submitting pingpong %x from completion routine\n", pingPong))
|
|
HidpSubmitInterruptRead(fdoExt, pingPong, &irpSent);
|
|
} else {
|
|
/*
|
|
* The device returned error.
|
|
* In order to support slightly-broken devices which
|
|
* "hiccup" occasionally, we implement a back-off timer
|
|
* algorithm; this way, the device gets a second chance,
|
|
* but if it spits back error each time, this doesn't
|
|
* eat up all the available CPU.
|
|
*/
|
|
#if DBG
|
|
if (dbgTrapOnHiccup){
|
|
DBGERR(("Device 'hiccuped' (status=%xh); setting backoff timer (fdoExt=%ph)...", Irp->IoStatus.Status, fdoExt))
|
|
}
|
|
#endif
|
|
DBGVERBOSE(("Device returned error %x on pingpong %x\n", Irp->IoStatus.Status, pingPong))
|
|
ASSERT((LONG)pingPong->backoffTimerPeriod.HighPart == -1);
|
|
ASSERT((LONG)pingPong->backoffTimerPeriod.LowPart < 0);
|
|
KeSetTimer( &pingPong->backoffTimer,
|
|
pingPong->backoffTimerPeriod,
|
|
&pingPong->backoffTimerDPC);
|
|
}
|
|
}
|
|
}
|
|
|
|
InterruptReadCompleteExit:
|
|
DBGLOG_INTEND()
|
|
DBG_COMMON_EXIT()
|
|
|
|
/*
|
|
* ALWAYS return STATUS_MORE_PROCESSING_REQUIRED;
|
|
* otherwise, the irp is required to have a thread.
|
|
*/
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpStartAllPingPongs
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpStartAllPingPongs(FDO_EXTENSION *fdoExt)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
ULONG i;
|
|
|
|
ASSERT(fdoExt->numPingPongs > 0);
|
|
|
|
for (i = 0; i < fdoExt->numPingPongs; i++){
|
|
BOOLEAN irpSent;
|
|
|
|
// Different threads may be trying to start this pump at the
|
|
// same time due to idle notification. Must only start once.
|
|
if (fdoExt->pingPongs[i].pumpDoneEvent.Header.SignalState) {
|
|
fdoExt->pingPongs[i].ReadInterlock = PINGPONG_END_READ;
|
|
KeResetEvent(&fdoExt->pingPongs[i].pumpDoneEvent);
|
|
DBGVERBOSE(("Starting pingpong %x from HidpStartAllPingPongs\n", &fdoExt->pingPongs[i]))
|
|
status = HidpSubmitInterruptRead(fdoExt, &fdoExt->pingPongs[i], &irpSent);
|
|
if (!NT_SUCCESS(status)){
|
|
if (irpSent){
|
|
DBGWARN(("Initial read failed with status %xh.", status))
|
|
#if DBG
|
|
if (dbgTrapOnHiccup){
|
|
DBGERR(("Device 'hiccuped' ?? (fdoExt=%ph).", fdoExt))
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* We'll let the back-off logic in the completion
|
|
* routine deal with this.
|
|
*/
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
DBGERR(("Initial read failed, irp not sent, status = %xh.", status))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (status == STATUS_PENDING){
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
DBGSUCCESS(status, TRUE)
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* CancelAllPingPongIrps
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
VOID CancelAllPingPongIrps(FDO_EXTENSION *fdoExt)
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < fdoExt->numPingPongs; i++){
|
|
HIDCLASS_PINGPONG *pingPong = &fdoExt->pingPongs[i];
|
|
|
|
DBGVERBOSE(("Cancelling pingpong %x\n", pingPong))
|
|
ASSERT(pingPong->sig == PINGPONG_SIG);
|
|
ASSERT(!pingPong->weAreCancelling);
|
|
|
|
//
|
|
// The order of the following instructions is crucial. We must set
|
|
// the weAreCancelling bit before waiting on the sentEvent, and the
|
|
// last thing that we should wait on is the pumpDoneEvent, which
|
|
// indicates that the read loop has finished all reads and will never
|
|
// run again.
|
|
//
|
|
// Note that we don't need spinlocks to guard since we only have two
|
|
// threads touching pingpong structures; the read pump thread and the
|
|
// pnp thread. PNP irps are synchronous, so those are safe. Using the
|
|
// weAreCancelling bit and the two events, sentEvent and pumpDoneEvent,
|
|
// the pnp irps are synchronized with the pnp routines. This insures
|
|
// that this cancel routine doesn't exit until the read pump has
|
|
// signalled the pumpDoneEvent and exited, hence the pingpong
|
|
// structures aren't ripped out from underneath it.
|
|
//
|
|
// If we have a backoff timer queued, it will eventually fire and
|
|
// call the submitinterruptread routine to restart reads. This will
|
|
// exit eventually, because we have set the weAreCancelling bit.
|
|
//
|
|
InterlockedIncrement(&pingPong->weAreCancelling);
|
|
|
|
{
|
|
/*
|
|
* Synchronize with the irp's completion routine.
|
|
*/
|
|
#if DBG
|
|
UCHAR beforeIrql = KeGetCurrentIrql();
|
|
UCHAR afterIrql;
|
|
PVOID cancelRoutine = (PVOID)pingPong->irp->CancelRoutine;
|
|
#endif
|
|
|
|
KeWaitForSingleObject(&pingPong->sentEvent,
|
|
Executive, // wait reason
|
|
KernelMode,
|
|
FALSE, // not alertable
|
|
NULL ); // no timeout
|
|
DBGVERBOSE(("Pingpong sent event set for pingpong %x\n", pingPong))
|
|
IoCancelIrp(pingPong->irp);
|
|
|
|
#if DBG
|
|
afterIrql = KeGetCurrentIrql();
|
|
if (afterIrql != beforeIrql){
|
|
DBGERR(("CancelAllPingPongIrps: cancel routine at %ph changed irql from %d to %d.", cancelRoutine, beforeIrql, afterIrql))
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* 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( &pingPong->pumpDoneEvent,
|
|
Executive, // wait reason
|
|
KernelMode,
|
|
FALSE, // not alertable
|
|
NULL ); // no timeout
|
|
DBGVERBOSE(("Pingpong pump done event set for %x\n", pingPong))
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* DestroyPingPongs
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
VOID DestroyPingPongs(FDO_EXTENSION *fdoExt)
|
|
{
|
|
if (ISPTR(fdoExt->pingPongs)){
|
|
ULONG i;
|
|
|
|
CancelAllPingPongIrps(fdoExt);
|
|
|
|
for (i = 0; i < fdoExt->numPingPongs; i++){
|
|
IoFreeIrp(fdoExt->pingPongs[i].irp);
|
|
ExFreePool(fdoExt->pingPongs[i].reportBuffer);
|
|
#if DBG
|
|
fdoExt->pingPongs[i].sig = 0xDEADBEEF;
|
|
#endif
|
|
}
|
|
|
|
ExFreePool(fdoExt->pingPongs);
|
|
fdoExt->pingPongs = BAD_POINTER;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpPingpongBackoffTimerDpc
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
VOID HidpPingpongBackoffTimerDpc(
|
|
IN PKDPC Dpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2
|
|
)
|
|
{
|
|
HIDCLASS_PINGPONG *pingPong = (HIDCLASS_PINGPONG *)DeferredContext;
|
|
BOOLEAN irpSent;
|
|
|
|
ASSERT(pingPong->sig == PINGPONG_SIG);
|
|
|
|
/*
|
|
* Increase the back-off time by 1 second, up to a max of 5 secs
|
|
* (in negative 100-nanosecond units).
|
|
*/
|
|
ASSERT((LONG)pingPong->backoffTimerPeriod.HighPart == -1);
|
|
ASSERT((LONG)pingPong->backoffTimerPeriod.LowPart < 0);
|
|
|
|
if ((LONG)pingPong->backoffTimerPeriod.LowPart > -50000000){
|
|
(LONG)pingPong->backoffTimerPeriod.LowPart -= 10000000;
|
|
}
|
|
|
|
DBGVERBOSE(("Submitting Pingpong %x from backoff\n", pingPong))
|
|
//
|
|
// If we are being removed, or the CancelAllPingPongIrps has been called,
|
|
// this call will take care of things.
|
|
//
|
|
HidpSubmitInterruptRead(pingPong->myFdoExt, pingPong, &irpSent);
|
|
}
|
|
|