/*++ 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) { // // 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 { InterlockedIncrement(&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)) 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; LockFileExtension(FileExtension, &oldIrql); /* * 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; readIrpToSatisfy->IoStatus.Status = result; } } 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, FALSE) 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 irql; BOOLEAN startRead; DBG_COMMON_ENTRY() DBGLOG_INTSTART() // // Track the number of outstanding requests to this device. // ASSERT(fdoExt->outstandingRequests > 0 ); InterlockedDecrement(&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 (NT_SUCCESS(Irp->IoStatus.Status)){ /* * 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). */ if (ISPTR(fdoExt->collectionPdoExtensions) && ISPTR(fdoExt->collectionPdoExtensions[collection->CollectionIndex])) { 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 { DBGVERBOSE(("Report dropped because collection-PDO doesn't exist")) } } 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)) KeSetEvent(&pingPong->pumpDoneEvent, 0, FALSE); } else { if (NT_SUCCESS(Irp->IoStatus.Status)){ 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 InterlockedDecrement(&pingPong->weAreCancelling); 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); }