Skip to content

Commit

Permalink
HID: nintendo: fix rumble rate limiter
Browse files Browse the repository at this point in the history
It's been discovered that BT controller disconnect events correlate to
erratic input report timestamp deltas.

In experimentation, it's been found that ensuring that multiple
timestamp deltas are consistent prior to transmitting a rumble packet
drastically reduces the occurence rate of BT disconnects.

Alter the joycon_enforce_subcmd_rate() function to use this new
approach.

Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com>
Reviewed-by: Silvan Jegen <s.jegen@gmail.com
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Signed-off-by: Alexandre Frade <kernel@xanmod.org>
  • Loading branch information
DanielOgorchock authored and xanmod committed May 24, 2023
1 parent f08f9c8 commit 6960926
Showing 1 changed file with 69 additions and 6 deletions.
75 changes: 69 additions & 6 deletions drivers/hid/hid-nintendo.c
Expand Up @@ -433,7 +433,9 @@ struct joycon_ctlr {
u8 usb_ack_match;
u8 subcmd_ack_match;
bool received_input_report;
unsigned int last_input_report_msecs;
unsigned int last_subcmd_sent_msecs;
unsigned int consecutive_valid_report_deltas;

/* factory calibration data */
struct joycon_stick_cal left_stick_cal_x;
Expand Down Expand Up @@ -543,19 +545,54 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
* Sending subcommands and/or rumble data at too high a rate can cause bluetooth
* controller disconnections.
*/
#define JC_INPUT_REPORT_MIN_DELTA 8
#define JC_INPUT_REPORT_MAX_DELTA 17
#define JC_SUBCMD_TX_OFFSET_MS 4
#define JC_SUBCMD_VALID_DELTA_REQ 3
#define JC_SUBCMD_RATE_MAX_ATTEMPTS 500
#define JC_SUBCMD_RATE_LIMITER_USB_MS 20
#define JC_SUBCMD_RATE_LIMITER_BT_MS 60
#define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS)
static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
{
static const unsigned int max_subcmd_rate_ms = 25;
unsigned int current_ms = jiffies_to_msecs(jiffies);
unsigned int delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
unsigned int current_ms;
unsigned long subcmd_delta;
int consecutive_valid_deltas = 0;
int attempts = 0;
unsigned long flags;

if (unlikely(ctlr->ctlr_state != JOYCON_CTLR_STATE_READ))
return;

while (delta_ms < max_subcmd_rate_ms &&
ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
do {
joycon_wait_for_input_report(ctlr);
current_ms = jiffies_to_msecs(jiffies);
delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
subcmd_delta = current_ms - ctlr->last_subcmd_sent_msecs;

spin_lock_irqsave(&ctlr->lock, flags);
consecutive_valid_deltas = ctlr->consecutive_valid_report_deltas;
spin_unlock_irqrestore(&ctlr->lock, flags);

attempts++;
} while ((consecutive_valid_deltas < JC_SUBCMD_VALID_DELTA_REQ ||
subcmd_delta < JC_SUBCMD_RATE_LIMITER_MS(ctlr)) &&
ctlr->ctlr_state == JOYCON_CTLR_STATE_READ &&
attempts < JC_SUBCMD_RATE_MAX_ATTEMPTS);

if (attempts >= JC_SUBCMD_RATE_MAX_ATTEMPTS) {
hid_warn(ctlr->hdev, "%s: exceeded max attempts", __func__);
return;
}

ctlr->last_subcmd_sent_msecs = current_ms;

/*
* Wait a short time after receiving an input report before
* transmitting. This should reduce odds of a TX coinciding with an RX.
* Minimizing concurrent BT traffic with the controller seems to lower
* the rate of disconnections.
*/
msleep(JC_SUBCMD_TX_OFFSET_MS);
}

static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
Expand Down Expand Up @@ -1223,6 +1260,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
u8 tmp;
u32 btns;
unsigned long msecs = jiffies_to_msecs(jiffies);
unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs;

spin_lock_irqsave(&ctlr->lock, flags);
if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report &&
Expand Down Expand Up @@ -1364,6 +1402,31 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,

input_sync(dev);

spin_lock_irqsave(&ctlr->lock, flags);
ctlr->last_input_report_msecs = msecs;
/*
* Was this input report a reasonable time delta compared to the prior
* report? We use this information to decide when a safe time is to send
* rumble packets or subcommand packets.
*/
if (report_delta_ms >= JC_INPUT_REPORT_MIN_DELTA &&
report_delta_ms <= JC_INPUT_REPORT_MAX_DELTA) {
if (ctlr->consecutive_valid_report_deltas < JC_SUBCMD_VALID_DELTA_REQ)
ctlr->consecutive_valid_report_deltas++;
} else {
ctlr->consecutive_valid_report_deltas = 0;
}
/*
* Our consecutive valid report tracking is only relevant for
* bluetooth-connected controllers. For USB devices, we're beholden to
* USB's underlying polling rate anyway. Always set to the consecutive
* delta requirement.
*/
if (ctlr->hdev->bus == BUS_USB)
ctlr->consecutive_valid_report_deltas = JC_SUBCMD_VALID_DELTA_REQ;

spin_unlock_irqrestore(&ctlr->lock, flags);

/*
* Immediately after receiving a report is the most reliable time to
* send a subcommand to the controller. Wake any subcommand senders
Expand Down

0 comments on commit 6960926

Please sign in to comment.