Skip to content

Commit caf5f52

Browse files
authored
Merge pull request #24 from hangzqcom/fix/QUD-1837-read-interval-timeout
fix(wdfserial): implement ReadIntervalTimeout inter-byte gap semantics [QUD-1837]
2 parents 782a6fb + 9e60a01 commit caf5f52

1 file changed

Lines changed: 146 additions & 2 deletions

File tree

src/windows/wdfserial/QCRD.c

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)