Skip to content

Commit c5873e8

Browse files
authored
Merge pull request #1559 from UltimateHackingKeyboard/fix/usb-retry-exponential-backoff
Use exponential backoff for USB report retry throttle
2 parents e261d84 + 65b821d commit c5873e8

3 files changed

Lines changed: 33 additions & 15 deletions

File tree

device/src/messenger.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ static void processSyncablePropertyDongle(device_id_t src, const uint8_t* data,
242242
#if DEVICE_IS_UHK_DONGLE
243243
uint8_t retryCounter = 0;
244244
while (ShouldResendReport(ret == 0, &retryCounter)) {
245+
uint16_t delay = GetResendThrottleDelay(retryCounter);
246+
k_sleep(K_MSEC(delay));
245247
k_sem_take(&dongleUsbSem, K_MSEC(128));
246248
ret = sendDongleReport(propertyId, message);
247249
}

right/src/usb_report_updater.c

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -890,9 +890,14 @@ static bool controlsNeedsResending = false;
890890
static uint8_t mouseRetries = 0;
891891
static 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).
896901
bool 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

925940
static 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 {

right/src/usb_report_updater.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,6 @@
7070

7171
hid_keyboard_report_t* GetInactiveKeyboardReport(void);
7272
bool ShouldResendReport(bool statusOk, uint8_t* counter);
73+
uint16_t GetResendThrottleDelay(uint8_t counter);
7374

7475
#endif

0 commit comments

Comments
 (0)