@@ -541,6 +541,26 @@ void QCRD_ReadRequestHandlerThread
541541 {
542542 KeSetEvent (& pDevContext -> ReadRequestArriveEvent , IO_NO_INCREMENT , FALSE);
543543 }
544+
545+ // QUD-1837: Re-arm ReadIntervalTimeout timer when new data arrives
546+ // and a request is pending in the timeout queue. This resets the
547+ // inter-byte gap timer so the request completes only after the
548+ // device stops sending data for ReadIntervalTimeout milliseconds.
549+ if (pDevContext -> ReadTimeout .bUseReadInterval &&
550+ !QCUTIL_IsIoQueueEmpty (pDevContext -> TimeoutReadQueue ) &&
551+ pDevContext -> Timeouts .ReadIntervalTimeout > 0 )
552+ {
553+ LARGE_INTEGER riTimeoutValue ;
554+ riTimeoutValue .QuadPart = -10000LL * (LONGLONG )pDevContext -> Timeouts .ReadIntervalTimeout ;
555+ KeSetTimer (& pDevContext -> ReadTimer , riTimeoutValue , & pDevContext -> ReadTimeoutDpc );
556+ QCSER_DbgPrint
557+ (
558+ QCSER_DBG_MASK_READ ,
559+ QCSER_DBG_LEVEL_DETAIL ,
560+ ("<%ws> RIRP: QCRD_ReadRequestHandlerThread re-armed ReadIntervalTimeout timer (%lu ms)\n" ,
561+ pDevContext -> PortName , pDevContext -> Timeouts .ReadIntervalTimeout )
562+ );
563+ }
544564 break ;
545565 }
546566 case READ_THREAD_REQUEST_ARRIVE_EVENT :
@@ -554,8 +574,82 @@ void QCRD_ReadRequestHandlerThread
554574 KeClearEvent (& pDevContext -> ReadRequestArriveEvent );
555575 if (bDeviceOpened && bDeviceAwaken )
556576 {
577+ // QUD-1837: When ReadIntervalTimeout is configured (cases 9/10),
578+ // do NOT drain the ring buffer into the pending request. The
579+ // request must stay in TimeoutReadQueue and only complete when
580+ // the inter-byte gap timer (ReadIntervalTimeout) expires.
581+ // COMPLETION_EVENT keeps the timer re-armed while data streams in;
582+ // REQUEST_TIMEOUT_EVENT delivers buffered data when the gap occurs.
583+ BOOLEAN useReadInterval = (pDevContext -> ReadTimeout .bUseReadInterval &&
584+ pDevContext -> Timeouts .ReadIntervalTimeout > 0 &&
585+ pDevContext -> Timeouts .ReadIntervalTimeout != MAXULONG );
586+
587+ if (useReadInterval )
588+ {
589+ // If a new request is sitting in ReadQueue, move it to
590+ // TimeoutReadQueue and arm the RI timer so it waits for
591+ // the inter-byte gap instead of completing immediately.
592+ if (QCUTIL_IsIoQueueEmpty (pDevContext -> TimeoutReadQueue ))
593+ {
594+ status = WdfIoQueueRetrieveNextRequest (pDevContext -> ReadQueue , & pendingTimeoutRequest );
595+ if (NT_SUCCESS (status ) && pendingTimeoutRequest != NULL )
596+ {
597+ status = WdfRequestForwardToIoQueue (pendingTimeoutRequest , pDevContext -> TimeoutReadQueue );
598+ if (!NT_SUCCESS (status ))
599+ {
600+ WdfRequestComplete (pendingTimeoutRequest , status );
601+ QCSER_DbgPrint
602+ (
603+ QCSER_DBG_MASK_READ ,
604+ QCSER_DBG_LEVEL_ERROR ,
605+ ("<%ws> RIRP: QCRD_ReadRequestHandlerThread RI forward to timeout queue FAILED request: 0x%p, status: 0x%x\n" ,
606+ pDevContext -> PortName , pendingTimeoutRequest , status )
607+ );
608+ }
609+ else
610+ {
611+ QCSER_DbgPrint
612+ (
613+ QCSER_DBG_MASK_READ ,
614+ QCSER_DBG_LEVEL_DETAIL ,
615+ ("<%ws> RIRP: QCRD_ReadRequestHandlerThread RI forwarded request to timeout queue: 0x%p\n" ,
616+ pDevContext -> PortName , pendingTimeoutRequest )
617+ );
618+ }
619+ pendingTimeoutRequest = NULL ;
620+ }
621+ }
622+
623+ // Arm / re-arm the ReadIntervalTimeout inter-byte gap timer
624+ // ONLY if data has already arrived in the ring buffer. Per
625+ // MSDN, ReadIntervalTimeout is an inter-byte gap timer and
626+ // must not start counting until after the first byte arrives.
627+ // If no data is buffered yet, ReadFile must block until
628+ // data arrives; COMPLETION_EVENT will arm the timer on
629+ // first data arrival.
630+ if (!QCUTIL_IsIoQueueEmpty (pDevContext -> TimeoutReadQueue ) &&
631+ QCUTIL_RingBufferBytesUsed (rxBuffer ) > 0 )
632+ {
633+ LARGE_INTEGER riTimeoutValue ;
634+ riTimeoutValue .QuadPart = -10000LL * (LONGLONG )pDevContext -> Timeouts .ReadIntervalTimeout ;
635+ KeCancelTimer (& pDevContext -> ReadTimer );
636+ KeClearEvent (& pDevContext -> ReadRequestTimeoutEvent );
637+ KeSetTimer (& pDevContext -> ReadTimer , riTimeoutValue , & pDevContext -> ReadTimeoutDpc );
638+ QCSER_DbgPrint
639+ (
640+ QCSER_DBG_MASK_READ ,
641+ QCSER_DBG_LEVEL_DETAIL ,
642+ ("<%ws> RIRP: QCRD_ReadRequestHandlerThread RI arm inter-byte timer (%lu ms), ring bytes: %llu\n" ,
643+ pDevContext -> PortName , pDevContext -> Timeouts .ReadIntervalTimeout , QCUTIL_RingBufferBytesUsed (rxBuffer ))
644+ );
645+ }
646+
647+ // Intentionally skip the ring-buffer drain loop; data
648+ // will be delivered by REQUEST_TIMEOUT_EVENT when the
649+ // inter-byte gap timer expires.
650+ }
557651 // serve the pending application requests
558- if (QCUTIL_RingBufferBytesUsed (rxBuffer ) == 0 )
652+ else if (QCUTIL_RingBufferBytesUsed (rxBuffer ) == 0 )
559653 {
560654 QCSER_DbgPrint
561655 (
@@ -877,7 +971,57 @@ void QCRD_ReadRequestHandlerThread
877971 QCSER_DBG_LEVEL_TRACE ,
878972 ("<%ws> RIRP: QCRD_ReadRequestHandlerThread READ_THREAD_REQUEST_TIMEOUT_EVENT triggered\n" , pDevContext -> PortName )
879973 );
880- QCUTIL_IoQueuePopAndComplete (pDevContext -> TimeoutReadQueue , STATUS_TIMEOUT , 0 );
974+
975+ // QUD-1837: When ReadIntervalTimeout fires, deliver whatever data
976+ // is available in the ring buffer as a partial read with STATUS_SUCCESS,
977+ // rather than completing with STATUS_TIMEOUT and 0 bytes.
978+ if (pDevContext -> ReadTimeout .bUseReadInterval &&
979+ QCUTIL_RingBufferBytesUsed (rxBuffer ) > 0 )
980+ {
981+ WDFREQUEST timeoutRequest = NULL ;
982+ status = WdfIoQueueRetrieveNextRequest (pDevContext -> TimeoutReadQueue , & timeoutRequest );
983+ if (NT_SUCCESS (status ) && timeoutRequest != NULL )
984+ {
985+ WDF_REQUEST_PARAMETERS riRequestParam ;
986+ WDF_REQUEST_PARAMETERS_INIT (& riRequestParam );
987+ WdfRequestGetParameters (timeoutRequest , & riRequestParam );
988+
989+ size_t riAvailable = QCUTIL_RingBufferBytesUsed (rxBuffer );
990+ size_t riRequested = riRequestParam .Parameters .Read .Length ;
991+ size_t riBytesCopied = 0 ;
992+ PUCHAR riOutputBuffer = NULL ;
993+
994+ WdfRequestRetrieveOutputBuffer (timeoutRequest , riRequested , & riOutputBuffer , NULL );
995+ status = QCUTIL_RingBufferRead (rxBuffer , riOutputBuffer , riRequested , & riBytesCopied );
996+ if (NT_SUCCESS (status ) && riBytesCopied > 0 )
997+ {
998+ WdfRequestCompleteWithInformation (timeoutRequest , STATUS_SUCCESS , riBytesCopied );
999+ QCSER_DbgPrint
1000+ (
1001+ QCSER_DBG_MASK_READ ,
1002+ QCSER_DBG_LEVEL_DETAIL ,
1003+ ("<%ws> RIRP: QCRD_ReadRequestHandlerThread ReadIntervalTimeout partial read completed, bytes: %llu (requested: %llu, available: %llu)\n" ,
1004+ pDevContext -> PortName , riBytesCopied , riRequested , riAvailable )
1005+ );
1006+ }
1007+ else
1008+ {
1009+ WdfRequestCompleteWithInformation (timeoutRequest , STATUS_TIMEOUT , 0 );
1010+ QCSER_DbgPrint
1011+ (
1012+ QCSER_DBG_MASK_READ ,
1013+ QCSER_DBG_LEVEL_ERROR ,
1014+ ("<%ws> RIRP: QCRD_ReadRequestHandlerThread ReadIntervalTimeout ring buffer read FAILED status: 0x%x\n" ,
1015+ pDevContext -> PortName , status )
1016+ );
1017+ }
1018+ pDevContext -> AmountInInQueue = QCUTIL_RingBufferBytesUsed (rxBuffer );
1019+ }
1020+ }
1021+ else
1022+ {
1023+ QCUTIL_IoQueuePopAndComplete (pDevContext -> TimeoutReadQueue , STATUS_TIMEOUT , 0 );
1024+ }
8811025 break ;
8821026 }
8831027 case READ_THREAD_SCAN_WAIT_MASK_EVENT :
0 commit comments