/*************************************************************************** Copyright (c) 1998 Microsoft Corporation Module Name: READ.C Abstract: Routines that perform read functionality Environment: kernel mode only Notes: THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. Copyright (c) 1998 Microsoft Corporation. All Rights Reserved. Revision History: 9/25/98 : created Authors: Louis J. Giliberto, Jr. ****************************************************************************/ #include #include #include #include #include #include #include #include #ifdef WMI_SUPPORT #include #include #include #endif #include "usbser.h" #include "utils.h" #include "debugwdm.h" // // PAGEUSBS is keyed off of UsbSer_Read, so UsbSer_Read must // remain in PAGEUSBS for things to work properly // #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGEUSBS, UsbSerCancelCurrentRead) #pragma alloc_text(PAGEUSBS, UsbSer_Read) #pragma alloc_text(PAGEUSBS, UsbSerStartRead) #pragma alloc_text(PAGEUSBS, UsbSerGrabReadFromRx) #pragma alloc_text(PAGEUSBS, UsbSerReadTimeout) #pragma alloc_text(PAGEUSBS, UsbSerIntervalReadTimeout) #endif // ALLOC_PRAGMA /************************************************************************/ /* UsbSer_Read */ /************************************************************************/ /* */ /* Routine Description: */ /* */ /* Process the IRPs sent to this device for Read calls */ /* */ /* Arguments: */ /* */ /* DeviceObject - pointer to a device object */ /* Irp - pointer to an I/O Request Packet */ /* */ /* Return Value: */ /* */ /* NTSTATUS */ /* */ /************************************************************************/ NTSTATUS UsbSer_Read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS NtStatus = STATUS_SUCCESS; PIO_STACK_LOCATION IrpStack; PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension; USBSER_LOCKED_PAGED_CODE(); DEBUG_LOG_PATH("enter UsbSer_Read"); UsbSerSerialDump(USBSERTRACERD, (">UsbSer_Read(%08X)\n", Irp)); // set return values to something known Irp->IoStatus.Information = 0; IrpStack = IoGetCurrentIrpStackLocation(Irp); DEBUG_TRACE2(("Read (%08X)\n", IrpStack->Parameters.Read.Length)); UsbSerSerialDump(USBSERTRACE, ("UsbSer_Read Irp: %08X (%08X)\n", Irp, IrpStack->Parameters.Read.Length)); // make entry in IRP history table DEBUG_LOG_IRP_HIST(DeviceObject, Irp, IrpStack->MajorFunction, Irp->AssociatedIrp.SystemBuffer, IrpStack->Parameters.Read.Length); if (IrpStack->Parameters.Read.Length != 0) { NtStatus = UsbSerStartOrQueue(DeviceExtension, Irp, &DeviceExtension->ReadQueue, &DeviceExtension->CurrentReadIrp, UsbSerStartRead); } else { Irp->IoStatus.Status = NtStatus = STATUS_SUCCESS; Irp->IoStatus.Information = 0; CompleteIO(DeviceObject, Irp, IrpStack->MajorFunction, Irp->AssociatedIrp.SystemBuffer, Irp->IoStatus.Information); } // log an error if we got one DEBUG_LOG_ERROR(NtStatus); DEBUG_LOG_PATH("exit UsbSer_Read"); DEBUG_TRACE3(("status (%08X)\n", NtStatus)); UsbSerSerialDump(USBSERTRACERD, ("UsbSerStartRead\n")); do { pReadIrp = PDevExt->CurrentReadIrp; readLen = IoGetCurrentIrpStackLocation(pReadIrp)->Parameters.Read.Length; PDevExt->NumberNeededForRead = readLen; DEBUG_TRACE3(("Start Reading %08X\n", PDevExt->NumberNeededForRead)); useTotalTimer = FALSE; returnWithWhatsPresent = FALSE; os2ssreturn = FALSE; crunchDownToOne = FALSE; useIntervalTimer = FALSE; // // Always initialize the timer objects so that the // completion code can tell when it attempts to // cancel the timers whether the timers had ever // been Set. // ACQUIRE_SPINLOCK(PDevExt, &PDevExt->ControlLock, &controlIrql); timeoutsForIrp = PDevExt->Timeouts; PDevExt->CountOnLastRead = 0; RELEASE_SPINLOCK(PDevExt, &PDevExt->ControlLock, controlIrql); // // Calculate the interval timeout for the read // if (timeoutsForIrp.ReadIntervalTimeout && (timeoutsForIrp.ReadIntervalTimeout != MAXULONG)) { useIntervalTimer = TRUE; PDevExt->IntervalTime.QuadPart = UInt32x32To64(timeoutsForIrp.ReadIntervalTimeout, 10000); if (PDevExt->IntervalTime.QuadPart >= PDevExt->CutOverAmount.QuadPart) { PDevExt->IntervalTimeToUse = &PDevExt->LongIntervalAmount; } else { PDevExt->IntervalTimeToUse = &PDevExt->ShortIntervalAmount; } } if (timeoutsForIrp.ReadIntervalTimeout == MAXULONG) { // // We need to do special return quickly stuff here. // // 1) If both constant and multiplier are // 0 then we return immediately with whatever // we've got, even if it was zero. // // 2) If constant and multiplier are not MAXULONG // then return immediately if any characters // are present, but if nothing is there, then // use the timeouts as specified. // // 3) If multiplier is MAXULONG then do as in // "2" but return when the first character // arrives. // if (!timeoutsForIrp.ReadTotalTimeoutConstant && !timeoutsForIrp.ReadTotalTimeoutMultiplier) { returnWithWhatsPresent = TRUE; } else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG) && (timeoutsForIrp.ReadTotalTimeoutMultiplier != MAXULONG)) { useTotalTimer = TRUE; os2ssreturn = TRUE; multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier; constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; } else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG) && (timeoutsForIrp.ReadTotalTimeoutMultiplier == MAXULONG)) { useTotalTimer = TRUE; os2ssreturn = TRUE; crunchDownToOne = TRUE; multiplierVal = 0; constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; } } else { // // If both the multiplier and the constant are // zero then don't do any total timeout processing. // if (timeoutsForIrp.ReadTotalTimeoutMultiplier || timeoutsForIrp.ReadTotalTimeoutConstant) { // // We have some timer values to calculate // useTotalTimer = TRUE; multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier; constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; } } if (useTotalTimer) { totalTime.QuadPart = ((LONGLONG)(UInt32x32To64(PDevExt->NumberNeededForRead, multiplierVal) + constantVal)) * -10000; } if (PDevExt->CharsInReadBuff) { charsRead = GetData(PDevExt, ((PUCHAR)(pReadIrp->AssociatedIrp.SystemBuffer)) + readLen - PDevExt->NumberNeededForRead, PDevExt->NumberNeededForRead, &pReadIrp->IoStatus.Information); } else { charsRead = 0; } // // See if this read is complete // if (returnWithWhatsPresent || (PDevExt->NumberNeededForRead == 0) || (os2ssreturn && pReadIrp->IoStatus.Information)) { #if DBG if (UsbSerSerialDebugLevel & USBSERDUMPRD) { ULONG i; ULONG count; if (PDevExt->CurrentReadIrp->IoStatus.Status == STATUS_SUCCESS) { count = (ULONG)PDevExt->CurrentReadIrp->IoStatus.Information; } else { count = 0; } DbgPrint("RD3: A(%08X) G(%08X) I(%08X)\n", IoGetCurrentIrpStackLocation(PDevExt->CurrentReadIrp) ->Parameters.Read.Length, count, PDevExt->CurrentReadIrp); for (i = 0; i < count; i++) { DbgPrint("%02x ", *(((PUCHAR)PDevExt->CurrentReadIrp ->AssociatedIrp.SystemBuffer) + i) & 0xFF); } if (i == 0) { DbgPrint("NULL (%08X)\n", PDevExt->CurrentReadIrp ->IoStatus.Status); } DbgPrint("\n\n"); } #endif // // Update the amount of chars left in the ring buffer // pReadIrp->IoStatus.Status = STATUS_SUCCESS; if (!setFirstStatus) { firstStatus = STATUS_SUCCESS; setFirstStatus = TRUE; } } else { // // The irp may be given to the buffering routine // USBSER_INIT_REFERENCE(pReadIrp); ACQUIRE_CANCEL_SPINLOCK(PDevExt, &oldIrql); // // Check to see if it needs to be cancelled // if (pReadIrp->Cancel) { RELEASE_CANCEL_SPINLOCK(PDevExt, oldIrql); pReadIrp->IoStatus.Status = STATUS_CANCELLED; pReadIrp->IoStatus.Information = 0; if (!setFirstStatus) { setFirstStatus = TRUE; firstStatus = STATUS_CANCELLED; } UsbSerGetNextIrp(&PDevExt->CurrentReadIrp, &PDevExt->ReadQueue, &newIrp, TRUE, PDevExt); continue; } else { // // If we are supposed to crunch the read down to // one character, then update the read length // in the irp and truncate the number needed for // read down to one. Note that if we are doing // this crunching, then the information must be // zero (or we would have completed above) and // the number needed for the read must still be /// equal to the read length. // if (crunchDownToOne) { PDevExt->NumberNeededForRead = 1; IoGetCurrentIrpStackLocation(pReadIrp)->Parameters.Read.Length = 1; } USBSER_SET_REFERENCE(pReadIrp, USBSER_REF_RXBUFFER); USBSER_SET_REFERENCE(pReadIrp, USBSER_REF_CANCEL); if (useTotalTimer) { USBSER_SET_REFERENCE(pReadIrp, USBSER_REF_TOTAL_TIMER); KeSetTimer(&PDevExt->ReadRequestTotalTimer, totalTime, &PDevExt->TotalReadTimeoutDpc); } if (useIntervalTimer) { USBSER_SET_REFERENCE(pReadIrp, USBSER_REF_INT_TIMER); KeQuerySystemTime(&PDevExt->LastReadTime); KeSetTimer(&PDevExt->ReadRequestIntervalTimer, *PDevExt->IntervalTimeToUse, &PDevExt->IntervalReadTimeoutDpc); } // // Mark IRP as cancellable // IoSetCancelRoutine(pReadIrp, UsbSerCancelCurrentRead); IoMarkIrpPending(pReadIrp); RELEASE_CANCEL_SPINLOCK(PDevExt, oldIrql); if (!setFirstStatus) { firstStatus = STATUS_PENDING; } } DEBUG_LOG_PATH("Exit UsbSerStartRead (1)\n"); UsbSerSerialDump(USBSERTRACERD, ("CurrentReadIrp, &PDevExt->ReadQueue, &newIrp, TRUE, PDevExt); } while (newIrp != NULL); DEBUG_LOG_PATH("Exit UsbSerStartRead (2)\n"); UsbSerSerialDump(USBSERTRACERD, ("CurrentReadIrp, USBSER_REF_RXBUFFER); UsbSerSerialDump(USBSERTRACERD, ("Exit UsbSerGrabReadFromRx\n")); return FALSE; } VOID UsbSerCancelCurrentRead(IN PDEVICE_OBJECT PDevObj, IN PIRP PIrp) /*++ Routine Description: This routine is used to cancel the current read. Arguments: PDevObj - Pointer to the device object for this device PIrp - Pointer to the IRP to be canceled. Return Value: None. --*/ { PDEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension; USBSER_ALWAYS_LOCKED_CODE(); UsbSerSerialDump(USBSERTRACEOTH, (">UsbSerCancelCurrentRead(%08X)\n", PIrp)); // // We set this to indicate to the interval timer // that the read has encountered a cancel. // // Recall that the interval timer dpc can be lurking in some // DPC queue. // pDevExt->CountOnLastRead = SERIAL_COMPLETE_READ_CANCEL; // // HACKHACK // UsbSerGrabReadFromRx(pDevExt); UsbSerTryToCompleteCurrent(pDevExt, PIrp->CancelIrql, STATUS_CANCELLED, &pDevExt->CurrentReadIrp, &pDevExt->ReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, UsbSerStartRead, UsbSerGetNextIrp, USBSER_REF_CANCEL, TRUE); UsbSerSerialDump(USBSERTRACEOTH, ("UsbSerReadTimeout\n")); ACQUIRE_CANCEL_SPINLOCK(pDevExt, &oldIrql); // // We set this to indicate to the interval timer // that the read has completed due to total timeout. // // Recall that the interval timer dpc can be lurking in some // DPC queue. // pDevExt->CountOnLastRead = SERIAL_COMPLETE_READ_TOTAL; // // HACKHACK // UsbSerGrabReadFromRx(pDevExt); UsbSerTryToCompleteCurrent(pDevExt, oldIrql, STATUS_TIMEOUT, &pDevExt->CurrentReadIrp, &pDevExt->ReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, UsbSerStartRead, UsbSerGetNextIrp, USBSER_REF_TOTAL_TIMER, TRUE); UsbSerSerialDump(USBSERTRACETM, ("UsbSerIntervalReadTimeout ")); ACQUIRE_CANCEL_SPINLOCK(pDevExt, &oldIrql); if (pDevExt->CountOnLastRead == SERIAL_COMPLETE_READ_TOTAL) { UsbSerSerialDump(USBSERTRACETM, ("SERIAL_COMPLETE_READ_TOTAL\n")); // // This value is only set by the total // timer to indicate that it has fired. // If so, then we should simply try to complete. // // // HACKHACK // ACQUIRE_SPINLOCK(pDevExt, &pDevExt->ControlLock, &oldControlIrql); UsbSerGrabReadFromRx(pDevExt); pDevExt->CountOnLastRead = 0; RELEASE_SPINLOCK(pDevExt, &pDevExt->ControlLock, oldControlIrql); UsbSerTryToCompleteCurrent(pDevExt, oldIrql, STATUS_TIMEOUT, &pDevExt->CurrentReadIrp, &pDevExt->ReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, UsbSerStartRead, UsbSerGetNextIrp, USBSER_REF_INT_TIMER, TRUE); } else if (pDevExt->CountOnLastRead == SERIAL_COMPLETE_READ_COMPLETE) { UsbSerSerialDump(USBSERTRACETM, ("SERIAL_COMPLETE_READ_COMPLETE\n")); // // This value is only set by the regular // completion routine. // // If so, then we should simply try to complete. // // // HACKHACK // ACQUIRE_SPINLOCK(pDevExt, &pDevExt->ControlLock, &oldControlIrql); UsbSerGrabReadFromRx(pDevExt); pDevExt->CountOnLastRead = 0; RELEASE_SPINLOCK(pDevExt, &pDevExt->ControlLock, oldControlIrql); UsbSerTryToCompleteCurrent(pDevExt, oldIrql, STATUS_SUCCESS, &pDevExt->CurrentReadIrp, &pDevExt->ReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, UsbSerStartRead, UsbSerGetNextIrp, USBSER_REF_INT_TIMER, TRUE); } else if (pDevExt->CountOnLastRead == SERIAL_COMPLETE_READ_CANCEL) { UsbSerSerialDump(USBSERTRACETM, ("SERIAL_COMPLETE_READ_CANCEL\n")); // // This value is only set by the cancel // read routine. // // If so, then we should simply try to complete. // // // HACKHACK // ACQUIRE_SPINLOCK(pDevExt, &pDevExt->ControlLock, &oldControlIrql); UsbSerGrabReadFromRx(pDevExt); pDevExt->CountOnLastRead = 0; RELEASE_SPINLOCK(pDevExt, &pDevExt->ControlLock, oldControlIrql); UsbSerTryToCompleteCurrent(pDevExt, oldIrql, STATUS_CANCELLED, &pDevExt->CurrentReadIrp, &pDevExt->ReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, UsbSerStartRead, UsbSerGetNextIrp, USBSER_REF_INT_TIMER, TRUE); } else if (pDevExt->CountOnLastRead || pDevExt->ReadByIsr) { // // Something has happened since we last came here. We // check to see if the ISR has read in any more characters. // If it did then we should update the isr's read count // and resubmit the timer. // if (pDevExt->ReadByIsr) { UsbSerSerialDump(USBSERTRACETM, ("ReadByIsr\n")); pDevExt->CountOnLastRead = pDevExt->ReadByIsr; pDevExt->ReadByIsr = 0; // // Save off the "last" time something was read. // As we come back to this routine we will compare // the current time to the "last" time. If the // difference is ever larger then the interval // requested by the user, then time out the request. // KeQuerySystemTime(&pDevExt->LastReadTime); KeSetTimer(&pDevExt->ReadRequestIntervalTimer, *pDevExt->IntervalTimeToUse, &pDevExt->IntervalReadTimeoutDpc); RELEASE_CANCEL_SPINLOCK(pDevExt, oldIrql); } else { // // Take the difference between the current time // and the last time we had characters and // see if it is greater then the interval time. // if it is, then time out the request. Otherwise // go away again for a while. // // // No characters read in the interval time. Kill // this read. // LARGE_INTEGER currentTime; UsbSerSerialDump(USBSERTRACETM, ("TIMEOUT\n")); KeQuerySystemTime(¤tTime); if ((currentTime.QuadPart - pDevExt->LastReadTime.QuadPart) >= pDevExt->IntervalTime.QuadPart) { pDevExt->CountOnLastRead = pDevExt->ReadByIsr = 0; // // HACKHACK // ACQUIRE_SPINLOCK(pDevExt, &pDevExt->ControlLock, &oldControlIrql); UsbSerGrabReadFromRx(pDevExt); RELEASE_SPINLOCK(pDevExt, &pDevExt->ControlLock, oldControlIrql); UsbSerTryToCompleteCurrent(pDevExt, oldIrql, STATUS_TIMEOUT, &pDevExt->CurrentReadIrp, &pDevExt->ReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, UsbSerStartRead, UsbSerGetNextIrp, USBSER_REF_INT_TIMER, TRUE); } else { KeSetTimer(&pDevExt->ReadRequestIntervalTimer, *pDevExt->IntervalTimeToUse, &pDevExt->IntervalReadTimeoutDpc); RELEASE_CANCEL_SPINLOCK(pDevExt, oldIrql); } } } else { // // Timer doesn't really start until the first character. // So we should simply resubmit ourselves. // UsbSerSerialDump(USBSERTRACETM, ("-\n")); KeSetTimer(&pDevExt->ReadRequestIntervalTimer, *pDevExt->IntervalTimeToUse, &pDevExt->IntervalReadTimeoutDpc); RELEASE_CANCEL_SPINLOCK(pDevExt, oldIrql); } UsbSerSerialDump(USBSERTRACETM, ("