@@ -890,9 +890,14 @@ static bool controlsNeedsResending = false;
890890static uint8_t mouseRetries = 0 ;
891891static bool mouseNeedsResending = false;
892892
893- // Try resending a report for maxDelay ms. Give up if it doesn't succeed by then.
894- // Once given up, stay given up until some statusOk is seen, so a full queue doesn't
895- // freeze for queueLength * maxDelay on replay.
893+ // Counts retry attempts and decides whether to keep trying. On success, resets
894+ // the counter and clears the give-up latch. On failure, increments the counter
895+ // and gives up once MAX_RETRIES is exceeded. Once given up, stay given up until
896+ // some statusOk is seen, so a full queue doesn't replay one report at a time.
897+ //
898+ // Pair with GetResendThrottleDelay() to compute the wait before the next retry.
899+ // MAX_RETRIES is sized so that the cumulative wait from GetResendThrottleDelay()
900+ // is ~1024 ms (1+2+4+8+16+32 + 30*32 = 1023).
896901bool ShouldResendReport (bool statusOk , uint8_t * counter ) {
897902 static bool givenUp = false;
898903
@@ -906,20 +911,30 @@ bool ShouldResendReport(bool statusOk, uint8_t* counter) {
906911 return false;
907912 }
908913
909- const uint16_t maxDelay = 1024 ; //ms
910- const uint8_t granularity = 16 ; //ms
911- uint8_t minimizedTime = Timer_GetCurrentTime () / granularity ;
914+ const uint8_t MAX_RETRIES = 36 ;
912915
913- if (* counter == 0 ) {
914- * counter = minimizedTime ;
915- return true;
916- } else if ((uint8_t )(minimizedTime - * counter ) < (maxDelay / granularity )) {
917- return true;
918- } else {
916+ (* counter )++ ;
917+ if (* counter > MAX_RETRIES ) {
919918 * counter = 0 ;
920919 givenUp = true;
921920 return false;
922921 }
922+ return true;
923+ }
924+
925+ // Exponential backoff for retry throttling: 1, 2, 4, 8, 16, 32 ms, then
926+ // capped at 32 ms. Keeps initial retries snappy (mouse keys don't stutter on
927+ // transient USB-busy) while still backing off on sustained failure.
928+ uint16_t GetResendThrottleDelay (uint8_t counter ) {
929+ if (counter == 0 ) {
930+ return 0 ;
931+ }
932+ const uint8_t MAX_SHIFT = 5 ; // 1 << 5 == 32 ms
933+ uint8_t shift = counter - 1 ;
934+ if (shift > MAX_SHIFT ) {
935+ shift = MAX_SHIFT ;
936+ }
937+ return (uint16_t )1 << shift ;
923938}
924939
925940static void clearMouseMovement (void ) {
@@ -995,7 +1010,7 @@ static void sendActiveReports(bool resending) {
9951010 //This is *not* asynchronously safe as long as multiple reports of different type can be sent at the same time.
9961011 //TODO: consider making it atomic, or lowering semaphore reset delay
9971012 keyboardNeedsResending = true;
998- retryThrottleTime = Timer_GetCurrentTime () + USB_RESEND_DELAY_MS ;
1013+ retryThrottleTime = Timer_GetCurrentTime () + GetResendThrottleDelay ( keyboardRetries ) ;
9991014 UsbReportUpdateSemaphore &= ~UsbReportUpdate_Keyboard ;
10001015 EventVector_Set (EventVector_ResendUsbReports );
10011016 } else {
@@ -1022,7 +1037,7 @@ static void sendActiveReports(bool resending) {
10221037 if (ShouldResendReport (ret == 0 , & controlsRetries )) {
10231038 reportRetry (ret );
10241039 controlsNeedsResending = true;
1025- retryThrottleTime = Timer_GetCurrentTime () + USB_RESEND_DELAY_MS ;
1040+ retryThrottleTime = Timer_GetCurrentTime () + GetResendThrottleDelay ( controlsRetries ) ;
10261041 UsbReportUpdateSemaphore &= ~UsbReportUpdate_Controls ;
10271042 EventVector_Set (EventVector_ResendUsbReports );
10281043 } else {
@@ -1048,7 +1063,7 @@ static void sendActiveReports(bool resending) {
10481063 if (ShouldResendReport (ret == 0 , & mouseRetries )) {
10491064 reportRetry (ret );
10501065 mouseNeedsResending = true;
1051- retryThrottleTime = Timer_GetCurrentTime () + USB_RESEND_DELAY_MS ;
1066+ retryThrottleTime = Timer_GetCurrentTime () + GetResendThrottleDelay ( mouseRetries ) ;
10521067 UsbReportUpdateSemaphore &= ~UsbReportUpdate_Mouse ;
10531068 EventVector_Set (EventVector_ResendUsbReports );
10541069 } else {
0 commit comments