summaryrefslogtreecommitdiffstats
path: root/drivers/hid/hid-nintendo.c
diff options
context:
space:
mode:
authorDaniel J. Ogorchock <djogorchock@gmail.com>2021-09-11 13:36:38 -0400
committerJiri Kosina <jkosina@suse.cz>2021-10-27 10:05:52 +0200
commite93363f716a23e61b46adfefbc3d01e99c240b5d (patch)
tree11d5161ec55310bef6770b168f96187a9dc5966c /drivers/hid/hid-nintendo.c
parent4c048f6b2c25ff0fc0a7bdedc285ab8fe97df2fc (diff)
downloadlinux-e93363f716a23e61b46adfefbc3d01e99c240b5d.tar.bz2
HID: nintendo: ratelimit subcommands and rumble
It has been found that sending subcommands and rumble data packets at too great a rate can result in controller disconnects. This patch limits the rate of subcommands/rumble to once every 25 milliseconds. Similar to sending subcommands, it is more reliable to send the rumble data packets immediately after we've received an input report from the controller. This results in far fewer bluetooth disconnects for the controller. Signed-off-by: Daniel J. Ogorchock <djogorchock@gmail.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/hid-nintendo.c')
-rw-r--r--drivers/hid/hid-nintendo.c69
1 files changed, 49 insertions, 20 deletions
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 2351e1d8c8e0..ae3cd8ca9fa7 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -2,7 +2,7 @@
/*
* HID driver for Nintendo Switch Joy-Cons and Pro Controllers
*
- * Copyright (c) 2019-2020 Daniel J. Ogorchock <djogorchock@gmail.com>
+ * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
*
* The following resources/projects were referenced for this driver:
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
@@ -431,6 +431,7 @@ struct joycon_ctlr {
u8 usb_ack_match;
u8 subcmd_ack_match;
bool received_input_report;
+ unsigned int last_subcmd_sent_msecs;
/* factory calibration data */
struct joycon_stick_cal left_stick_cal_x;
@@ -510,6 +511,50 @@ static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
return ret;
}
+static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
+{
+ int ret;
+
+ /*
+ * If we are in the proper reporting mode, wait for an input
+ * report prior to sending the subcommand. This improves
+ * reliability considerably.
+ */
+ if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctlr->lock, flags);
+ ctlr->received_input_report = false;
+ spin_unlock_irqrestore(&ctlr->lock, flags);
+ ret = wait_event_timeout(ctlr->wait,
+ ctlr->received_input_report,
+ HZ / 4);
+ /* We will still proceed, even with a timeout here */
+ if (!ret)
+ hid_warn(ctlr->hdev,
+ "timeout waiting for input report\n");
+ }
+}
+
+/*
+ * Sending subcommands and/or rumble data at too high a rate can cause bluetooth
+ * controller disconnections.
+ */
+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;
+
+ while (delta_ms < max_subcmd_rate_ms &&
+ ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
+ joycon_wait_for_input_report(ctlr);
+ current_ms = jiffies_to_msecs(jiffies);
+ delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
+ }
+ ctlr->last_subcmd_sent_msecs = current_ms;
+}
+
static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
u32 timeout)
{
@@ -521,25 +566,7 @@ static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
* doing one retry after a timeout appears to always work.
*/
while (tries--) {
- /*
- * If we are in the proper reporting mode, wait for an input
- * report prior to sending the subcommand. This improves
- * reliability considerably.
- */
- if (ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
- unsigned long flags;
-
- spin_lock_irqsave(&ctlr->lock, flags);
- ctlr->received_input_report = false;
- spin_unlock_irqrestore(&ctlr->lock, flags);
- ret = wait_event_timeout(ctlr->wait,
- ctlr->received_input_report,
- HZ / 4);
- /* We will still proceed, even with a timeout here */
- if (!ret)
- hid_warn(ctlr->hdev,
- "timeout waiting for input report\n");
- }
+ joycon_enforce_subcmd_rate(ctlr);
ret = __joycon_hid_send(ctlr->hdev, data, len);
if (ret < 0) {
@@ -1359,6 +1386,8 @@ static int joycon_send_rumble_data(struct joycon_ctlr *ctlr)
if (++ctlr->subcmd_num > 0xF)
ctlr->subcmd_num = 0;
+ joycon_enforce_subcmd_rate(ctlr);
+
ret = __joycon_hid_send(ctlr->hdev, (u8 *)&rumble_output,
sizeof(rumble_output));
return ret;