aboutsummaryrefslogtreecommitdiff
path: root/SOURCES/steam-deck.patch
diff options
context:
space:
mode:
Diffstat (limited to 'SOURCES/steam-deck.patch')
-rw-r--r--SOURCES/steam-deck.patch1654
1 files changed, 1654 insertions, 0 deletions
diff --git a/SOURCES/steam-deck.patch b/SOURCES/steam-deck.patch
index e8570a0..696f642 100644
--- a/SOURCES/steam-deck.patch
+++ b/SOURCES/steam-deck.patch
@@ -919,3 +919,1657 @@ index fab9e9460bd4..9d0a5471b181 100644
if (IS_ERR(hwmon)) {
dev_err(dev, "Failed to register HWMON device");
return PTR_ERR(hwmon);
+From b899859fe49cccda9e8739d29d883dbd6dd057f3 Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Thu, 30 Jun 2022 18:42:10 -0700
+Subject: [PATCH 01/10] USB: gadget: f_hid: Add Get-Feature report
+
+While the HID gadget implementation has been sufficient for devices that only
+use INTERRUPT transfers, the USB HID standard includes provisions for Set- and
+Get-Feature report CONTROL transfers that go over endpoint 0. These were
+previously impossible with the existing implementation, and would either send
+an empty reply, or stall out.
+
+As the feature is a standard part of USB HID, it stands to reason that devices
+would use it, and that the HID gadget should support it. This patch adds
+support for (polled) device-to-host Get-Feature reports through a new ioctl
+interface to the hidg class dev nodes.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+(cherry picked from commit 8437fa3861c7198a3e286f393c8637c4fc08d2bc)
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+---
+ drivers/usb/gadget/function/f_hid.c | 121 ++++++++++++++++++++++++++--
+ include/uapi/linux/usb/g_hid.h | 38 +++++++++
+ include/uapi/linux/usb/gadgetfs.h | 2 +-
+ 3 files changed, 154 insertions(+), 7 deletions(-)
+ create mode 100644 include/uapi/linux/usb/g_hid.h
+
+diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
+index ea85e2c701a15..6fec92b5a0bd9 100644
+--- a/drivers/usb/gadget/function/f_hid.c
++++ b/drivers/usb/gadget/function/f_hid.c
+@@ -16,6 +16,7 @@
+ #include <linux/wait.h>
+ #include <linux/sched.h>
+ #include <linux/usb/g_hid.h>
++#include <uapi/linux/usb/g_hid.h>
+
+ #include "u_f.h"
+ #include "u_hid.h"
+@@ -75,6 +76,13 @@ struct f_hidg {
+ wait_queue_head_t write_queue;
+ struct usb_request *req;
+
++ /* get report */
++ struct usb_request *get_req;
++ struct usb_hidg_report get_report;
++ spinlock_t get_spinlock;
++ bool get_pending;
++ wait_queue_head_t get_queue;
++
+ struct device dev;
+ struct cdev cdev;
+ struct usb_function func;
+@@ -523,6 +531,64 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
+ return status;
+ }
+
++
++static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer)
++{
++ struct f_hidg *hidg = file->private_data;
++ struct usb_composite_dev *cdev = hidg->func.config->cdev;
++
++ int status = 0;
++ unsigned long flags;
++
++ spin_lock_irqsave(&hidg->get_spinlock, flags);
++
++#define GET_REPORT_COND (!hidg->get_pending)
++
++ while (!GET_REPORT_COND) {
++ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
++
++ if (file->f_flags & O_NONBLOCK)
++ return -EAGAIN;
++
++ if (wait_event_interruptible_exclusive(hidg->get_queue,
++ GET_REPORT_COND))
++ return -ERESTARTSYS;
++
++ spin_lock_irqsave(&hidg->get_spinlock, flags);
++ if (!hidg->get_pending) {
++ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
++ return -EINVAL;
++ }
++ }
++
++ hidg->get_pending = true;
++ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
++
++ status = copy_from_user(&hidg->get_report, buffer,
++ sizeof(struct usb_hidg_report));
++ if (status != 0) {
++ ERROR(cdev, "copy_from_user error\n");
++ status = -EINVAL;
++ }
++
++ spin_lock_irqsave(&hidg->get_spinlock, flags);
++ hidg->get_pending = false;
++ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
++
++ wake_up(&hidg->get_queue);
++ return status;
++}
++
++static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg)
++{
++ switch (code) {
++ case GADGET_HID_WRITE_GET_REPORT:
++ return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg);
++ default:
++ return -ENOTTY;
++ }
++}
++
+ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
+ {
+ struct f_hidg *hidg = file->private_data;
+@@ -548,6 +614,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
+ #undef WRITE_COND
+ #undef READ_COND_SSREPORT
+ #undef READ_COND_INTOUT
++#undef GET_REPORT_COND
+
+ static int f_hidg_release(struct inode *inode, struct file *fd)
+ {
+@@ -640,6 +707,10 @@ static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req)
+ wake_up(&hidg->read_queue);
+ }
+
++static void hidg_get_report_complete(struct usb_ep *ep, struct usb_request *req)
++{
++}
++
+ static int hidg_setup(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+ {
+@@ -647,6 +718,8 @@ static int hidg_setup(struct usb_function *f,
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int status = 0;
++ unsigned long flags;
++ bool do_wake = false;
+ __u16 value, length;
+
+ value = __le16_to_cpu(ctrl->wValue);
+@@ -659,14 +732,29 @@ static int hidg_setup(struct usb_function *f,
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_GET_REPORT):
+- VDBG(cdev, "get_report\n");
++ VDBG(cdev, "get_report | wLength=%d\n", ctrl->wLength);
+
+- /* send an empty report */
+- length = min_t(unsigned, length, hidg->report_length);
+- memset(req->buf, 0x0, length);
++ req = hidg->get_req;
++ req->zero = 0;
++ req->length = min_t(unsigned, length, hidg->report_length);
++ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
++ if (status < 0) {
++ ERROR(cdev, "usb_ep_queue error on get_report %d\n",
++ status);
+
+- goto respond;
+- break;
++ spin_lock_irqsave(&hidg->get_spinlock, flags);
++ if (hidg->get_pending) {
++ hidg->get_pending = false;
++ do_wake = true;
++ }
++ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
++
++ if (do_wake) {
++ wake_up(&hidg->get_queue);
++ }
++ }
++
++ return status;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_GET_PROTOCOL):
+@@ -800,6 +888,14 @@ static void hidg_disable(struct usb_function *f)
+
+ hidg->req = NULL;
+ spin_unlock_irqrestore(&hidg->write_spinlock, flags);
++
++ spin_lock_irqsave(&hidg->get_spinlock, flags);
++ if (!hidg->get_pending) {
++ usb_ep_free_request(f->config->cdev->gadget->ep0, hidg->get_req);
++ hidg->get_pending = true;
++ }
++ hidg->get_req = NULL;
++ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
+ }
+
+ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+@@ -908,6 +1004,7 @@ static const struct file_operations f_hidg_fops = {
+ .write = f_hidg_write,
+ .read = f_hidg_read,
+ .poll = f_hidg_poll,
++ .unlocked_ioctl = f_hidg_ioctl,
+ .llseek = noop_llseek,
+ };
+
+@@ -918,6 +1015,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
+ struct usb_string *us;
+ int status;
+
++ hidg->get_req = usb_ep_alloc_request(c->cdev->gadget->ep0, GFP_ATOMIC);
++ if (!hidg->get_req)
++ return -ENOMEM;
++ hidg->get_req->buf = hidg->get_report.data;
++ hidg->get_req->zero = 0;
++ hidg->get_req->complete = hidg_get_report_complete;
++ hidg->get_req->context = hidg;
++
+ /* maybe allocate device-global string IDs, and patch descriptors */
+ us = usb_gstrings_attach(c->cdev, ct_func_strings,
+ ARRAY_SIZE(ct_func_string_defs));
+@@ -1003,8 +1108,10 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
+ hidg->write_pending = 1;
+ hidg->req = NULL;
+ spin_lock_init(&hidg->read_spinlock);
++ spin_lock_init(&hidg->get_spinlock);
+ init_waitqueue_head(&hidg->write_queue);
+ init_waitqueue_head(&hidg->read_queue);
++ init_waitqueue_head(&hidg->get_queue);
+ INIT_LIST_HEAD(&hidg->completed_out_req);
+
+ /* create char device */
+@@ -1021,6 +1128,8 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
+ if (hidg->req != NULL)
+ free_ep_req(hidg->in_ep, hidg->req);
+
++ usb_ep_free_request(c->cdev->gadget->ep0, hidg->get_req);
++
+ return status;
+ }
+
+diff --git a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h
+new file mode 100644
+index 0000000000000..c6068b4863543
+--- /dev/null
++++ b/include/uapi/linux/usb/g_hid.h
+@@ -0,0 +1,38 @@
++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
++/*
++ * g_hid.h -- Header file for USB HID gadget driver
++ *
++ * Copyright (C) 2022 Valve Software
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++ */
++
++#ifndef __UAPI_LINUX_USB_G_HID_H
++#define __UAPI_LINUX_USB_G_HID_H
++
++#include <linux/types.h>
++
++struct usb_hidg_report {
++ __u16 length;
++ __u8 data[512];
++};
++
++/* The 'g' code is also used by gadgetfs and hid gadget ioctl requests.
++ * Don't add any colliding codes to either driver, and keep
++ * them in unique ranges (size 0x20 for now).
++ */
++#define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report)
++
++#endif /* __UAPI_LINUX_USB_G_HID_H */
+diff --git a/include/uapi/linux/usb/gadgetfs.h b/include/uapi/linux/usb/gadgetfs.h
+index 835473910a498..9754822b2a409 100644
+--- a/include/uapi/linux/usb/gadgetfs.h
++++ b/include/uapi/linux/usb/gadgetfs.h
+@@ -62,7 +62,7 @@ struct usb_gadgetfs_event {
+ };
+
+
+-/* The 'g' code is also used by printer gadget ioctl requests.
++/* The 'g' code is also used by printer and hid gadget ioctl requests.
+ * Don't add any colliding codes to either driver, and keep
+ * them in unique ranges (size 0x20 for now).
+ */
+--
+2.41.0
+
+
+From 20ebaf7b44ff03078cf53e43306d6c5a3d0613e6 Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Thu, 30 Jun 2022 18:43:10 -0700
+Subject: [PATCH 02/10] USB: gadget: f_hid: Add Set-Feature report
+
+While the HID gadget implementation has been sufficient for devices that only
+use INTERRUPT transfers, the USB HID standard includes provisions for Set- and
+Get-Feature report CONTROL transfers that go over endpoint 0. These were
+previously impossible with the existing implementation, and would either send
+an empty reply, or stall out.
+
+As the feature is a standard part of USB HID, it stands to reason that devices
+would use it, and that the HID gadget should support it. This patch adds
+support for host-to-device Set-Feature reports through a new ioctl
+interface to the hidg class dev nodes.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+(cherry picked from commit 3d82be0ec3aa3b947d9c927d7b06c433de15be8b)
+Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+---
+ drivers/usb/gadget/function/f_hid.c | 110 ++++++++++++++++++++++++++--
+ include/uapi/linux/usb/g_hid.h | 24 +-----
+ 2 files changed, 106 insertions(+), 28 deletions(-)
+
+diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
+index 6fec92b5a0bd9..172cba91aded1 100644
+--- a/drivers/usb/gadget/function/f_hid.c
++++ b/drivers/usb/gadget/function/f_hid.c
+@@ -76,6 +76,11 @@ struct f_hidg {
+ wait_queue_head_t write_queue;
+ struct usb_request *req;
+
++ /* set report */
++ struct list_head completed_set_req;
++ spinlock_t set_spinlock;
++ wait_queue_head_t set_queue;
++
+ /* get report */
+ struct usb_request *get_req;
+ struct usb_hidg_report get_report;
+@@ -531,6 +536,54 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
+ return status;
+ }
+
++static int f_hidg_set_report(struct file *file, struct usb_hidg_report __user *buffer)
++{
++ struct f_hidg *hidg = file->private_data;
++ struct f_hidg_req_list *list;
++ struct usb_request *req;
++ unsigned long flags;
++ unsigned short length;
++ int status;
++
++ spin_lock_irqsave(&hidg->set_spinlock, flags);
++
++#define SET_REPORT_COND (!list_empty(&hidg->completed_set_req))
++
++ /* wait for at least one buffer to complete */
++ while (!SET_REPORT_COND) {
++ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
++ if (file->f_flags & O_NONBLOCK)
++ return -EAGAIN;
++
++ if (wait_event_interruptible(hidg->set_queue, SET_REPORT_COND))
++ return -ERESTARTSYS;
++
++ spin_lock_irqsave(&hidg->set_spinlock, flags);
++ }
++
++ /* pick the first one */
++ list = list_first_entry(&hidg->completed_set_req,
++ struct f_hidg_req_list, list);
++
++ /*
++ * Remove this from list to protect it from being free()
++ * while host disables our function
++ */
++ list_del(&list->list);
++
++ req = list->req;
++ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
++
++ /* copy to user outside spinlock */
++ length = min_t(unsigned short, sizeof(buffer->data), req->actual);
++ status = copy_to_user(&buffer->length, &length, sizeof(buffer->length));
++ if (!status) {
++ status = copy_to_user(&buffer->data, req->buf, length);
++ }
++ kfree(list);
++ free_ep_req(hidg->func.config->cdev->gadget->ep0, req);
++ return status;
++}
+
+ static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer)
+ {
+@@ -582,6 +635,8 @@ static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *b
+ static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg)
+ {
+ switch (code) {
++ case GADGET_HID_READ_SET_REPORT:
++ return f_hidg_set_report(file, (struct usb_hidg_report __user *)arg);
+ case GADGET_HID_WRITE_GET_REPORT:
+ return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg);
+ default:
+@@ -596,6 +651,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
+
+ poll_wait(file, &hidg->read_queue, wait);
+ poll_wait(file, &hidg->write_queue, wait);
++ poll_wait(file, &hidg->set_queue, wait);
+
+ if (WRITE_COND)
+ ret |= EPOLLOUT | EPOLLWRNORM;
+@@ -608,12 +664,16 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
+ ret |= EPOLLIN | EPOLLRDNORM;
+ }
+
++ if (SET_REPORT_COND)
++ ret |= EPOLLPRI;
++
+ return ret;
+ }
+
+ #undef WRITE_COND
+ #undef READ_COND_SSREPORT
+ #undef READ_COND_INTOUT
++#undef SET_REPORT_COND
+ #undef GET_REPORT_COND
+
+ static int f_hidg_release(struct inode *inode, struct file *fd)
+@@ -658,11 +718,19 @@ static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req)
+
+ req_list->req = req;
+
+- spin_lock_irqsave(&hidg->read_spinlock, flags);
+- list_add_tail(&req_list->list, &hidg->completed_out_req);
+- spin_unlock_irqrestore(&hidg->read_spinlock, flags);
++ if (ep == cdev->gadget->ep0) {
++ spin_lock_irqsave(&hidg->set_spinlock, flags);
++ list_add_tail(&req_list->list, &hidg->completed_set_req);
++ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
+
+- wake_up(&hidg->read_queue);
++ wake_up(&hidg->set_queue);
++ } else {
++ spin_lock_irqsave(&hidg->read_spinlock, flags);
++ list_add_tail(&req_list->list, &hidg->completed_out_req);
++ spin_unlock_irqrestore(&hidg->read_spinlock, flags);
++
++ wake_up(&hidg->read_queue);
++ }
+ break;
+ default:
+ ERROR(cdev, "Set report failed %d\n", req->status);
+@@ -775,12 +843,27 @@ static int hidg_setup(struct usb_function *f,
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_SET_REPORT):
+ VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength);
+- if (hidg->use_out_ep)
++ if (!hidg->use_out_ep) {
++ req->complete = hidg_ssreport_complete;
++ req->context = hidg;
++ goto respond;
++ }
++ if (!length)
+ goto stall;
+- req->complete = hidg_ssreport_complete;
++ req = alloc_ep_req(cdev->gadget->ep0, GFP_ATOMIC);
++ if (!req)
++ return -ENOMEM;
++ req->complete = hidg_intout_complete;
+ req->context = hidg;
+- goto respond;
+- break;
++ req->zero = 0;
++ req->length = length;
++ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
++ if (status < 0) {
++ ERROR(cdev, "usb_ep_queue error on set_report %d\n", status);
++ free_ep_req(cdev->gadget->ep0, req);
++ }
++
++ return status;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
+ | HID_REQ_SET_PROTOCOL):
+@@ -880,6 +963,14 @@ static void hidg_disable(struct usb_function *f)
+ spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+ }
+
++ spin_lock_irqsave(&hidg->set_spinlock, flags);
++ list_for_each_entry_safe(list, next, &hidg->completed_set_req, list) {
++ free_ep_req(f->config->cdev->gadget->ep0, list->req);
++ list_del(&list->list);
++ kfree(list);
++ }
++ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
++
+ spin_lock_irqsave(&hidg->write_spinlock, flags);
+ if (!hidg->write_pending) {
+ free_ep_req(hidg->in_ep, hidg->req);
+@@ -1108,11 +1199,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
+ hidg->write_pending = 1;
+ hidg->req = NULL;
+ spin_lock_init(&hidg->read_spinlock);
++ spin_lock_init(&hidg->set_spinlock);
+ spin_lock_init(&hidg->get_spinlock);
+ init_waitqueue_head(&hidg->write_queue);
+ init_waitqueue_head(&hidg->read_queue);
++ init_waitqueue_head(&hidg->set_queue);
+ init_waitqueue_head(&hidg->get_queue);
+ INIT_LIST_HEAD(&hidg->completed_out_req);
++ INIT_LIST_HEAD(&hidg->completed_set_req);
+
+ /* create char device */
+ cdev_init(&hidg->cdev, &f_hidg_fops);
+diff --git a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h
+index c6068b4863543..54814c2c68d60 100644
+--- a/include/uapi/linux/usb/g_hid.h
++++ b/include/uapi/linux/usb/g_hid.h
+@@ -1,38 +1,22 @@
+ /* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+-/*
+- * g_hid.h -- Header file for USB HID gadget driver
+- *
+- * Copyright (C) 2022 Valve Software
+- *
+- * This program is free software; you can redistribute it and/or modify
+- * it under the terms of the GNU General Public License as published by
+- * the Free Software Foundation; either version 2 of the License, or
+- * (at your option) any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * You should have received a copy of the GNU General Public License
+- * along with this program; if not, write to the Free Software
+- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+- */
+
+ #ifndef __UAPI_LINUX_USB_G_HID_H
+ #define __UAPI_LINUX_USB_G_HID_H
+
+ #include <linux/types.h>
+
++#define HIDG_REPORT_SIZE_MAX 64
++
+ struct usb_hidg_report {
+ __u16 length;
+- __u8 data[512];
++ __u8 data[HIDG_REPORT_SIZE_MAX];
+ };
+
+ /* The 'g' code is also used by gadgetfs and hid gadget ioctl requests.
+ * Don't add any colliding codes to either driver, and keep
+ * them in unique ranges (size 0x20 for now).
+ */
++#define GADGET_HID_READ_SET_REPORT _IOR('g', 0x41, struct usb_hidg_report)
+ #define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report)
+
+ #endif /* __UAPI_LINUX_USB_G_HID_H */
+--
+2.41.0
+
+
+From 146e98d6f595e3a4e6c09a00ee9ec2d48a6703cb Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Tue, 29 Nov 2022 18:32:58 -0800
+Subject: [PATCH 03/10] HID: hid-steam: Update list of identifiers from SDL
+
+SDL includes a list of settings (registers), reports (cmds), and various other
+identifiers that were provided by Valve. This commit imports a significant
+chunk of that list as well as updating the guessed names and replacing a
+handful of magic constants. It also replaces bitmask definitions that used hex
+with the BIT macro.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+---
+ drivers/hid/hid-steam.c | 156 +++++++++++++++++++++++++++++++---------
+ 1 file changed, 121 insertions(+), 35 deletions(-)
+
+diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
+index b110818fc9458..39a9bf3b7f77d 100644
+--- a/drivers/hid/hid-steam.c
++++ b/drivers/hid/hid-steam.c
+@@ -71,7 +71,7 @@ static LIST_HEAD(steam_devices);
+
+ /*
+ * Commands that can be sent in a feature report.
+- * Thanks to Valve for some valuable hints.
++ * Thanks to Valve and SDL for some valuable hints.
+ */
+ #define STEAM_CMD_SET_MAPPINGS 0x80
+ #define STEAM_CMD_CLEAR_MAPPINGS 0x81
+@@ -80,27 +80,98 @@ static LIST_HEAD(steam_devices);
+ #define STEAM_CMD_GET_ATTRIB_LABEL 0x84
+ #define STEAM_CMD_DEFAULT_MAPPINGS 0x85
+ #define STEAM_CMD_FACTORY_RESET 0x86
+-#define STEAM_CMD_WRITE_REGISTER 0x87
++#define STEAM_CMD_SET_REGISTER 0x87
+ #define STEAM_CMD_CLEAR_REGISTER 0x88
+-#define STEAM_CMD_READ_REGISTER 0x89
++#define STEAM_CMD_GET_REGISTER 0x89
+ #define STEAM_CMD_GET_REGISTER_LABEL 0x8a
+ #define STEAM_CMD_GET_REGISTER_MAX 0x8b
+ #define STEAM_CMD_GET_REGISTER_DEFAULT 0x8c
+ #define STEAM_CMD_SET_MODE 0x8d
+-#define STEAM_CMD_DEFAULT_MOUSE 0x8e
+-#define STEAM_CMD_FORCEFEEDBAK 0x8f
+-#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4
+-#define STEAM_CMD_GET_SERIAL 0xae
++#define STEAM_CMD_DEFAULT_REGISTER 0x8e
++#define STEAM_CMD_HAPTIC_PULSE 0x8f
++#define STEAM_CMD_TURN_OFF_CONTROLLER 0x9f
++#define STEAM_CMD_GET_DEVICE_IFNO 0xa1
++#define STEAM_CMD_CALIBRATE_TRACKPADS 0xa7
++#define STEAM_CMD_SET_SERIAL 0xa9
++#define STEAM_CMD_GET_TRACKPAD_CALIB 0xaa
++#define STEAM_CMD_GET_TRACKPAD_FACTORY_CALIB 0xab
++#define STEAM_CMD_GET_TRACKPAD_RAW_DATA 0xac
++#define STEAM_CMD_ENABLE_PAIRING 0xad
++#define STEAM_CMD_GET_STRING_ATTRIB 0xae
++#define STEAM_CMD_RADIO_ERASE_RECORDS 0xaf
++#define STEAM_CMD_RADIO_WRITE_RECORD 0xb0
++#define STEAM_CMD_SET_DONGLE_SETTING 0xb1
++#define STEAM_CMD_DONGLE_DISCONNECT_DEV 0xb2
++#define STEAM_CMD_DONGLE_COMMIT_DEV 0xb3
++#define STEAM_CMD_DONGLE_GET_STATE 0xb4
++#define STEAM_CMD_CALIBRATE_GYRO 0xb5
++#define STEAM_CMD_PLAY_AUDIO 0xb6
++#define STEAM_CMD_AUDIO_UPDATE_START 0xb7
++#define STEAM_CMD_AUDIO_UPDATE_DATA 0xb8
++#define STEAM_CMD_AUDIO_UPDATE_COMPLETE 0xb9
++#define STEAM_CMD_GET_CHIPID 0xba
++#define STEAM_CMD_CALIBRATE_JOYSTICK 0xbf
++#define STEAM_CMD_CALIBRATE_TRIGGERS 0xc0
++#define STEAM_CMD_SET_AUDIO_MAPPING 0xc1
++#define STEAM_CMD_CHECK_GYRO_FW_LOAD 0xc2
++#define STEAM_CMD_CALIBRATE_ANALOG 0xc3
++#define STEAM_CMD_DONGLE_GET_CONN_SLOTS 0xc4
++#define STEAM_CMD_HAPTIC_CMD 0xea
+ #define STEAM_CMD_HAPTIC_RUMBLE 0xeb
+
+ /* Some useful register ids */
+-#define STEAM_REG_LPAD_MODE 0x07
+-#define STEAM_REG_RPAD_MODE 0x08
+-#define STEAM_REG_RPAD_MARGIN 0x18
+-#define STEAM_REG_LED 0x2d
+-#define STEAM_REG_GYRO_MODE 0x30
+-#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34
+-#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35
++#define STEAM_REG_MOUSE_SENSITIVITY 0x00
++#define STEAM_REG_MOUSE_ACCELERATION 0x01
++#define STEAM_REG_TRACKBALL_ROTATION_ANGLE 0x02
++#define STEAM_REG_HAPTIC_INTENSITY 0x03
++#define STEAM_REG_LEFT_GAMEPAD_STICK_ENABLED 0x04
++#define STEAM_REG_RIGHT_GAMEPAD_STICK_ENABLED 0x05
++#define STEAM_REG_USB_DEBUG_MODE 0x06
++#define STEAM_REG_LEFT_TRACKPAD_MODE 0x07
++#define STEAM_REG_RIGHT_TRACKPAD_MODE 0x08
++#define STEAM_REG_MOUSE_POINTER_ENABLED 0x09
++#define STEAM_REG_DPAD_DEADZONE 0x0a
++#define STEAM_REG_MINIMUM_MOMENTUM_VEL 0x0b
++#define STEAM_REG_MOMENTUM_DECAY_AMOUNT 0x0c
++#define STEAM_REG_PAD_REL_MODE_TICKS_PER_PIXEL 0x0d
++#define STEAM_REG_HAPTIC_INCREMENT 0x0e
++#define STEAM_REG_DPAD_ANGLE_SIN 0x0f
++#define STEAM_REG_DPAD_ANGLE_COS 0x10
++#define STEAM_REG_MOMENTUM_VERTICAL_DIVISOR 0x11
++#define STEAM_REG_MOMENTUM_MAXIMUM_VELOCITY 0x12
++#define STEAM_REG_TRACKPAD_Z_ON 0x13
++#define STEAM_REG_TRACKPAD_Z_OFF 0x14
++#define STEAM_REG_SENSITIVY_SCALE_AMOUNT 0x15
++#define STEAM_REG_LEFT_TRACKPAD_SECONDARY_MODE 0x16
++#define STEAM_REG_RIGHT_TRACKPAD_SECONDARY_MODE 0x17
++#define STEAM_REG_SMOOTH_ABSOLUTE_MOUSE 0x18
++#define STEAM_REG_STEAMBUTTON_POWEROFF_TIME 0x19
++#define STEAM_REG_TRACKPAD_OUTER_RADIUS 0x1b
++#define STEAM_REG_TRACKPAD_Z_ON_LEFT 0x1c
++#define STEAM_REG_TRACKPAD_Z_OFF_LEFT 0x1d
++#define STEAM_REG_TRACKPAD_OUTER_SPIN_VEL 0x1e
++#define STEAM_REG_TRACKPAD_OUTER_SPIN_RADIUS 0x1f
++#define STEAM_REG_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY 0x20
++#define STEAM_REG_TRACKPAD_RELATIVE_MODE_DEADZONE 0x21
++#define STEAM_REG_TRACKPAD_RELATIVE_MODE_MAX_VEL 0x22
++#define STEAM_REG_TRACKPAD_RELATIVE_MODE_INVERT_Y 0x23
++#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED 0x24
++#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD 0x25
++#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_COUNT 0x26
++#define STEAM_REG_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION 0x27
++#define STEAM_REG_RADIAL_MODE_ANGLE 0x28
++#define STEAM_REG_HAPTIC_INTENSITY_MOUSE_MODE 0x29
++#define STEAM_REG_LEFT_DPAD_REQUIRES_CLICK 0x2a
++#define STEAM_REG_RIGHT_DPAD_REQUIRES_CLICK 0x2b
++#define STEAM_REG_LED_BASELINE_BRIGHTNESS 0x2c
++#define STEAM_REG_LED_USER_BRIGHTNESS 0x2d
++#define STEAM_REG_ENABLE_RAW_JOYSTICK 0x2e
++#define STEAM_REG_ENABLE_FAST_SCAN 0x2f
++#define STEAM_REG_GYRO_MODE 0x30
++#define STEAM_REG_WIRELESS_PACKET_VERSION 0x31
++#define STEAM_REG_SLEEP_INACTIVITY_TIMEOUT 0x32
++#define STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE 0x34
++#define STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE 0x35
+
+ /* Raw event identifiers */
+ #define STEAM_EV_INPUT_DATA 0x01
+@@ -108,13 +179,28 @@ static LIST_HEAD(steam_devices);
+ #define STEAM_EV_BATTERY 0x04
+ #define STEAM_EV_DECK_INPUT_DATA 0x09
+
++/* String attribute idenitifiers */
++#define STEAM_ATTRIB_STR_BOARD_SERIAL 0x00
++#define STEAM_ATTRIB_STR_UNIT_SERIAL 0x01
++
+ /* Values for GYRO_MODE (bitmask) */
+-#define STEAM_GYRO_MODE_OFF 0x0000
+-#define STEAM_GYRO_MODE_STEERING 0x0001
+-#define STEAM_GYRO_MODE_TILT 0x0002
+-#define STEAM_GYRO_MODE_SEND_ORIENTATION 0x0004
+-#define STEAM_GYRO_MODE_SEND_RAW_ACCEL 0x0008
+-#define STEAM_GYRO_MODE_SEND_RAW_GYRO 0x0010
++#define STEAM_GYRO_MODE_OFF 0
++#define STEAM_GYRO_MODE_STEERING BIT(0)
++#define STEAM_GYRO_MODE_TILT BIT(1)
++#define STEAM_GYRO_MODE_SEND_ORIENTATION BIT(2)
++#define STEAM_GYRO_MODE_SEND_RAW_ACCEL BIT(3)
++#define STEAM_GYRO_MODE_SEND_RAW_GYRO BIT(4)
++
++/* Trackpad modes */
++#define STEAM_TRACKPAD_ABSOLUTE_MOUSE 0x00
++#define STEAM_TRACKPAD_RELATIVE_MOUSE 0x01
++#define STEAM_TRACKPAD_DPAD_FOUR_WAY_DISCRETE 0x02
++#define STEAM_TRACKPAD_DPAD_FOUR_WAY_OVERLAP 0x03
++#define STEAM_TRACKPAD_DPAD_EIGHT_WAY 0x04
++#define STEAM_TRACKPAD_RADIAL_MODE 0x05
++#define STEAM_TRACKPAD_ABSOLUTE_DPAD 0x06
++#define STEAM_TRACKPAD_NONE 0x07
++#define STEAM_TRACKPAD_GESTURE_KEYBOARD 0x08
+
+ /* Other random constants */
+ #define STEAM_SERIAL_LEN 10
+@@ -232,7 +318,7 @@ static int steam_write_registers(struct steam_device *steam,
+ /* Send: 0x87 len (reg valLo valHi)* */
+ u8 reg;
+ u16 val;
+- u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
++ u8 cmd[64] = {STEAM_CMD_SET_REGISTER, 0x00};
+ int ret;
+ va_list args;
+
+@@ -268,7 +354,7 @@ static int steam_get_serial(struct steam_device *steam)
+ * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
+ */
+ int ret;
+- u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01};
++ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL};
+ u8 reply[3 + STEAM_SERIAL_LEN + 1];
+
+ ret = steam_send_report(steam, cmd, sizeof(cmd));
+@@ -277,7 +363,7 @@ static int steam_get_serial(struct steam_device *steam)
+ ret = steam_recv_report(steam, reply, sizeof(reply));
+ if (ret < 0)
+ return ret;
+- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != 0x01)
++ if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL)
+ return -EIO;
+ reply[3 + STEAM_SERIAL_LEN] = 0;
+ strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
+@@ -291,7 +377,7 @@ static int steam_get_serial(struct steam_device *steam)
+ */
+ static inline int steam_request_conn_status(struct steam_device *steam)
+ {
+- return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
++ return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
+ }
+
+ static inline int steam_haptic_rumble(struct steam_device *steam,
+@@ -339,9 +425,9 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ /* enable esc, enter, cursors */
+ steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
+ /* enable mouse */
+- steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MOUSE);
++ steam_send_report_byte(steam, STEAM_CMD_DEFAULT_REGISTER);
+ steam_write_registers(steam,
+- STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
++ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x01, /* enable smooth */
+ 0);
+
+ cancel_delayed_work_sync(&steam->heartbeat);
+@@ -351,11 +437,11 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+
+ if (steam->quirks & STEAM_QUIRK_DECK) {
+ steam_write_registers(steam,
+- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
+- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
+- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
+- STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+- STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
++ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */
++ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
++ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
++ STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
++ STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+ 0);
+ /*
+ * The Steam Deck has a watchdog that automatically enables
+@@ -365,9 +451,9 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
+ } else {
+ steam_write_registers(steam,
+- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
+- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
+- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
++ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */
++ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
++ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
+ 0);
+ }
+ }
+@@ -747,7 +833,7 @@ static void steam_lizard_mode_heartbeat(struct work_struct *work)
+ if (!steam->client_opened && steam->client_hdev) {
+ steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
+ steam_write_registers(steam,
+- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
++ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
+ 0);
+ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
+ }
+--
+2.41.0
+
+
+From 4b1dd1ebfd2d3f123212e1296d304909e5b3a406 Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Wed, 16 Nov 2022 19:54:26 -0800
+Subject: [PATCH 04/10] HID: hid-steam: Add gamepad-only mode switched to by
+ holding options
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+---
+ drivers/hid/hid-steam.c | 72 +++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 72 insertions(+)
+
+diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
+index 39a9bf3b7f77d..0620046b142ef 100644
+--- a/drivers/hid/hid-steam.c
++++ b/drivers/hid/hid-steam.c
+@@ -202,6 +202,11 @@ static LIST_HEAD(steam_devices);
+ #define STEAM_TRACKPAD_NONE 0x07
+ #define STEAM_TRACKPAD_GESTURE_KEYBOARD 0x08
+
++/* Pad identifiers for the deck */
++#define STEAM_PAD_LEFT 0
++#define STEAM_PAD_RIGHT 1
++#define STEAM_PAD_BOTH 2
++
+ /* Other random constants */
+ #define STEAM_SERIAL_LEN 10
+
+@@ -221,6 +226,9 @@ struct steam_device {
+ u8 battery_charge;
+ u16 voltage;
+ struct delayed_work heartbeat;
++ struct delayed_work mode_switch;
++ bool did_mode_switch;
++ bool gamepad_mode;
+ struct work_struct rumble_work;
+ u16 rumble_left;
+ u16 rumble_right;
+@@ -380,6 +388,33 @@ static inline int steam_request_conn_status(struct steam_device *steam)
+ return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
+ }
+
++/*
++ * Send a haptic pulse to the trackpads
++ * Duration and interval are measured in microseconds, count is the number
++ * of pulses to send for duration time with interval microseconds between them
++ * and gain is measured in decibels, ranging from -24 to +6
++ */
++static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
++ u16 duration, u16 interval, u16 count, u8 gain)
++{
++ u8 report[10] = {STEAM_CMD_HAPTIC_PULSE, 8};
++
++ /* Left and right are swapped on this report for legacy reasons */
++ if (pad < STEAM_PAD_BOTH)
++ pad ^= 1;
++
++ report[2] = pad;
++ report[3] = duration & 0xFF;
++ report[4] = duration >> 8;
++ report[5] = interval & 0xFF;
++ report[6] = interval >> 8;
++ report[7] = count & 0xFF;
++ report[8] = count >> 8;
++ report[9] = gain;
++
++ return steam_send_report(steam, report, sizeof(report));
++}
++
+ static inline int steam_haptic_rumble(struct steam_device *steam,
+ u16 intensity, u16 left_speed, u16 right_speed,
+ u8 left_gain, u8 right_gain)
+@@ -421,6 +456,9 @@ static int steam_play_effect(struct input_dev *dev, void *data,
+
+ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ {
++ if (steam->gamepad_mode)
++ enable = false;
++
+ if (enable) {
+ /* enable esc, enter, cursors */
+ steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
+@@ -805,6 +843,29 @@ static void steam_work_connect_cb(struct work_struct *work)
+ }
+ }
+
++static void steam_mode_switch_cb(struct work_struct *work)
++{
++ struct steam_device *steam = container_of(to_delayed_work(work),
++ struct steam_device, mode_switch);
++ steam->gamepad_mode = !steam->gamepad_mode;
++ if (!lizard_mode)
++ return;
++
++ mutex_lock(&steam->mutex);
++ if (steam->gamepad_mode)
++ steam_set_lizard_mode(steam, false);
++ else if (!steam->client_opened)
++ steam_set_lizard_mode(steam, lizard_mode);
++ mutex_unlock(&steam->mutex);
++
++ steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
++ if (steam->gamepad_mode) {
++ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0);
++ } else {
++ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0);
++ }
++}
++
+ static bool steam_is_valve_interface(struct hid_device *hdev)
+ {
+ struct hid_report_enum *rep_enum;
+@@ -977,6 +1038,7 @@ static int steam_probe(struct hid_device *hdev,
+ mutex_init(&steam->mutex);
+ steam->quirks = id->driver_data;
+ INIT_WORK(&steam->work_connect, steam_work_connect_cb);
++ INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
+ INIT_LIST_HEAD(&steam->list);
+ INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
+ INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
+@@ -1036,6 +1098,7 @@ static int steam_probe(struct hid_device *hdev,
+ client_hdev_fail:
+ cancel_work_sync(&steam->work_connect);
+ cancel_delayed_work_sync(&steam->heartbeat);
++ cancel_delayed_work_sync(&steam->mode_switch);
+ cancel_work_sync(&steam->rumble_work);
+ steam_alloc_fail:
+ hid_err(hdev, "%s: failed with error %d\n",
+@@ -1059,6 +1122,7 @@ static void steam_remove(struct hid_device *hdev)
+ cancel_delayed_work_sync(&steam->heartbeat);
+ mutex_unlock(&steam->mutex);
+ cancel_work_sync(&steam->work_connect);
++ cancel_delayed_work_sync(&steam->mode_switch);
+ if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+ hid_info(hdev, "Steam wireless receiver disconnected");
+ }
+@@ -1393,6 +1457,14 @@ static void steam_do_deck_input_event(struct steam_device *steam,
+ input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2)));
+
+ input_sync(input);
++
++ if (!(b9 & BIT(6)) && steam->did_mode_switch) {
++ steam->did_mode_switch = false;
++ cancel_delayed_work_sync(&steam->mode_switch);
++ } else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) {
++ steam->did_mode_switch = true;
++ schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100);
++ }
+ }
+
+ /*
+--
+2.41.0
+
+
+From 187582492c359d56865759f120214cfe6fa4ed50 Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Mon, 8 May 2023 20:24:56 -0700
+Subject: [PATCH 05/10] HID: hid-steam: Clean up locking
+
+This cleans up the locking logic so that the spinlock is consistently used for
+access to a small handful of struct variables, and the mutex is exclusively and
+consistently used for ensuring that mutliple threads aren't trying to
+send/receive reports at the same time. Previously, only some report
+transactions were guarded by this mutex, potentially breaking atomicity. The
+mutex has been renamed to reflect this usage.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+---
+ drivers/hid/hid-steam.c | 148 ++++++++++++++++++++++++----------------
+ 1 file changed, 90 insertions(+), 58 deletions(-)
+
+diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
+index 0620046b142ef..845ca71b8bd3a 100644
+--- a/drivers/hid/hid-steam.c
++++ b/drivers/hid/hid-steam.c
+@@ -214,7 +214,7 @@ struct steam_device {
+ struct list_head list;
+ spinlock_t lock;
+ struct hid_device *hdev, *client_hdev;
+- struct mutex mutex;
++ struct mutex report_mutex;
+ bool client_opened;
+ struct input_dev __rcu *input;
+ unsigned long quirks;
+@@ -361,21 +361,26 @@ static int steam_get_serial(struct steam_device *steam)
+ * Send: 0xae 0x15 0x01
+ * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
+ */
+- int ret;
++ int ret = 0;
+ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL};
+ u8 reply[3 + STEAM_SERIAL_LEN + 1];
+
++ mutex_lock(&steam->report_mutex);
+ ret = steam_send_report(steam, cmd, sizeof(cmd));
+ if (ret < 0)
+- return ret;
++ goto out;
+ ret = steam_recv_report(steam, reply, sizeof(reply));
+ if (ret < 0)
+- return ret;
+- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL)
+- return -EIO;
++ goto out;
++ if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) {
++ ret = -EIO;
++ goto out;
++ }
+ reply[3 + STEAM_SERIAL_LEN] = 0;
+ strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
+- return 0;
++out:
++ mutex_unlock(&steam->report_mutex);
++ return ret;
+ }
+
+ /*
+@@ -385,7 +390,11 @@ static int steam_get_serial(struct steam_device *steam)
+ */
+ static inline int steam_request_conn_status(struct steam_device *steam)
+ {
+- return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
++ int ret;
++ mutex_lock(&steam->report_mutex);
++ ret = steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
++ mutex_unlock(&steam->report_mutex);
++ return ret;
+ }
+
+ /*
+@@ -397,6 +406,7 @@ static inline int steam_request_conn_status(struct steam_device *steam)
+ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
+ u16 duration, u16 interval, u16 count, u8 gain)
+ {
++ int ret;
+ u8 report[10] = {STEAM_CMD_HAPTIC_PULSE, 8};
+
+ /* Left and right are swapped on this report for legacy reasons */
+@@ -412,13 +422,17 @@ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
+ report[8] = count >> 8;
+ report[9] = gain;
+
+- return steam_send_report(steam, report, sizeof(report));
++ mutex_lock(&steam->report_mutex);
++ ret = steam_send_report(steam, report, sizeof(report));
++ mutex_unlock(&steam->report_mutex);
++ return ret;
+ }
+
+ static inline int steam_haptic_rumble(struct steam_device *steam,
+ u16 intensity, u16 left_speed, u16 right_speed,
+ u8 left_gain, u8 right_gain)
+ {
++ int ret;
+ u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
+
+ report[3] = intensity & 0xFF;
+@@ -430,7 +444,10 @@ static inline int steam_haptic_rumble(struct steam_device *steam,
+ report[9] = left_gain;
+ report[10] = right_gain;
+
+- return steam_send_report(steam, report, sizeof(report));
++ mutex_lock(&steam->report_mutex);
++ ret = steam_send_report(steam, report, sizeof(report));
++ mutex_unlock(&steam->report_mutex);
++ return ret;
+ }
+
+ static void steam_haptic_rumble_cb(struct work_struct *work)
+@@ -460,6 +477,7 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ enable = false;
+
+ if (enable) {
++ mutex_lock(&steam->report_mutex);
+ /* enable esc, enter, cursors */
+ steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
+ /* enable mouse */
+@@ -467,9 +485,11 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ steam_write_registers(steam,
+ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x01, /* enable smooth */
+ 0);
++ mutex_unlock(&steam->report_mutex);
+
+ cancel_delayed_work_sync(&steam->heartbeat);
+ } else {
++ mutex_lock(&steam->report_mutex);
+ /* disable esc, enter, cursor */
+ steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
+
+@@ -481,18 +501,19 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+ STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+ 0);
++ mutex_unlock(&steam->report_mutex);
+ /*
+ * The Steam Deck has a watchdog that automatically enables
+ * lizard mode if it doesn't see any traffic for too long
+ */
+- if (!work_busy(&steam->heartbeat.work))
+- schedule_delayed_work(&steam->heartbeat, 5 * HZ);
++ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
+ } else {
+ steam_write_registers(steam,
+ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */
+ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
+ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
+ 0);
++ mutex_unlock(&steam->report_mutex);
+ }
+ }
+ }
+@@ -500,22 +521,29 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+ static int steam_input_open(struct input_dev *dev)
+ {
+ struct steam_device *steam = input_get_drvdata(dev);
++ unsigned long flags;
++ bool set_lizard_mode;
+
+- mutex_lock(&steam->mutex);
+- if (!steam->client_opened && lizard_mode)
++ spin_lock_irqsave(&steam->lock, flags);
++ set_lizard_mode = !steam->client_opened && lizard_mode;
++ spin_unlock_irqrestore(&steam->lock, flags);
++ if (set_lizard_mode)
+ steam_set_lizard_mode(steam, false);
+- mutex_unlock(&steam->mutex);
++
+ return 0;
+ }
+
+ static void steam_input_close(struct input_dev *dev)
+ {
+ struct steam_device *steam = input_get_drvdata(dev);
++ unsigned long flags;
++ bool set_lizard_mode;
+
+- mutex_lock(&steam->mutex);
+- if (!steam->client_opened && lizard_mode)
++ spin_lock_irqsave(&steam->lock, flags);
++ set_lizard_mode = !steam->client_opened && lizard_mode;
++ spin_unlock_irqrestore(&steam->lock, flags);
++ if (set_lizard_mode)
+ steam_set_lizard_mode(steam, true);
+- mutex_unlock(&steam->mutex);
+ }
+
+ static enum power_supply_property steam_battery_props[] = {
+@@ -760,6 +788,7 @@ static int steam_register(struct steam_device *steam)
+ {
+ int ret;
+ bool client_opened;
++ unsigned long flags;
+
+ /*
+ * This function can be called several times in a row with the
+@@ -772,11 +801,9 @@ static int steam_register(struct steam_device *steam)
+ * Unlikely, but getting the serial could fail, and it is not so
+ * important, so make up a serial number and go on.
+ */
+- mutex_lock(&steam->mutex);
+ if (steam_get_serial(steam) < 0)
+ strscpy(steam->serial_no, "XXXXXXXXXX",
+ sizeof(steam->serial_no));
+- mutex_unlock(&steam->mutex);
+
+ hid_info(steam->hdev, "Steam Controller '%s' connected",
+ steam->serial_no);
+@@ -791,11 +818,11 @@ static int steam_register(struct steam_device *steam)
+ mutex_unlock(&steam_devices_lock);
+ }
+
+- mutex_lock(&steam->mutex);
++ spin_lock_irqsave(&steam->lock, flags);
+ client_opened = steam->client_opened;
++ spin_unlock_irqrestore(&steam->lock, flags);
+ if (!client_opened)
+ steam_set_lizard_mode(steam, lizard_mode);
+- mutex_unlock(&steam->mutex);
+
+ if (!client_opened)
+ ret = steam_input_register(steam);
+@@ -847,16 +874,21 @@ static void steam_mode_switch_cb(struct work_struct *work)
+ {
+ struct steam_device *steam = container_of(to_delayed_work(work),
+ struct steam_device, mode_switch);
++ unsigned long flags;
++ bool client_opened;
+ steam->gamepad_mode = !steam->gamepad_mode;
+ if (!lizard_mode)
+ return;
+
+- mutex_lock(&steam->mutex);
+ if (steam->gamepad_mode)
+ steam_set_lizard_mode(steam, false);
+- else if (!steam->client_opened)
+- steam_set_lizard_mode(steam, lizard_mode);
+- mutex_unlock(&steam->mutex);
++ else {
++ spin_lock_irqsave(&steam->lock, flags);
++ client_opened = steam->client_opened;
++ spin_unlock_irqrestore(&steam->lock, flags);
++ if (!client_opened)
++ steam_set_lizard_mode(steam, lizard_mode);
++ }
+
+ steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
+ if (steam->gamepad_mode) {
+@@ -889,16 +921,21 @@ static void steam_lizard_mode_heartbeat(struct work_struct *work)
+ {
+ struct steam_device *steam = container_of(work, struct steam_device,
+ heartbeat.work);
++ bool client_opened;
++ unsigned long flags;
+
+- mutex_lock(&steam->mutex);
+- if (!steam->client_opened && steam->client_hdev) {
++ spin_lock_irqsave(&steam->lock, flags);
++ client_opened = steam->client_opened;
++ spin_unlock_irqrestore(&steam->lock, flags);
++ if (!client_opened) {
++ mutex_lock(&steam->report_mutex);
+ steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
+ steam_write_registers(steam,
+ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
+ 0);
++ mutex_unlock(&steam->report_mutex);
+ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
+ }
+- mutex_unlock(&steam->mutex);
+ }
+
+ static int steam_client_ll_parse(struct hid_device *hdev)
+@@ -921,10 +958,11 @@ static void steam_client_ll_stop(struct hid_device *hdev)
+ static int steam_client_ll_open(struct hid_device *hdev)
+ {
+ struct steam_device *steam = hdev->driver_data;
++ unsigned long flags;
+
+- mutex_lock(&steam->mutex);
++ spin_lock_irqsave(&steam->lock, flags);
+ steam->client_opened = true;
+- mutex_unlock(&steam->mutex);
++ spin_unlock_irqrestore(&steam->lock, flags);
+
+ steam_input_unregister(steam);
+
+@@ -939,14 +977,12 @@ static void steam_client_ll_close(struct hid_device *hdev)
+ bool connected;
+
+ spin_lock_irqsave(&steam->lock, flags);
+- connected = steam->connected;
++ steam->client_opened = false;
++ connected = steam->connected && !steam->client_opened;
+ spin_unlock_irqrestore(&steam->lock, flags);
+
+- mutex_lock(&steam->mutex);
+- steam->client_opened = false;
+ if (connected)
+ steam_set_lizard_mode(steam, lizard_mode);
+- mutex_unlock(&steam->mutex);
+
+ if (connected)
+ steam_input_register(steam);
+@@ -1035,7 +1071,7 @@ static int steam_probe(struct hid_device *hdev,
+ steam->hdev = hdev;
+ hid_set_drvdata(hdev, steam);
+ spin_lock_init(&steam->lock);
+- mutex_init(&steam->mutex);
++ mutex_init(&steam->report_mutex);
+ steam->quirks = id->driver_data;
+ INIT_WORK(&steam->work_connect, steam_work_connect_cb);
+ INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
+@@ -1043,13 +1079,6 @@ static int steam_probe(struct hid_device *hdev,
+ INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
+ INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
+
+- steam->client_hdev = steam_create_client_hid(hdev);
+- if (IS_ERR(steam->client_hdev)) {
+- ret = PTR_ERR(steam->client_hdev);
+- goto client_hdev_fail;
+- }
+- steam->client_hdev->driver_data = steam;
+-
+ /*
+ * With the real steam controller interface, do not connect hidraw.
+ * Instead, create the client_hid and connect that.
+@@ -1058,10 +1087,6 @@ static int steam_probe(struct hid_device *hdev,
+ if (ret)
+ goto hid_hw_start_fail;
+
+- ret = hid_add_device(steam->client_hdev);
+- if (ret)
+- goto client_hdev_add_fail;
+-
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev,
+@@ -1087,15 +1112,26 @@ static int steam_probe(struct hid_device *hdev,
+ }
+ }
+
++ steam->client_hdev = steam_create_client_hid(hdev);
++ if (IS_ERR(steam->client_hdev)) {
++ ret = PTR_ERR(steam->client_hdev);
++ goto client_hdev_fail;
++ }
++ steam->client_hdev->driver_data = steam;
++
++ ret = hid_add_device(steam->client_hdev);
++ if (ret)
++ goto client_hdev_add_fail;
++
+ return 0;
+
+-input_register_fail:
+-hid_hw_open_fail:
+ client_hdev_add_fail:
+ hid_hw_stop(hdev);
+-hid_hw_start_fail:
+- hid_destroy_device(steam->client_hdev);
+ client_hdev_fail:
++ hid_destroy_device(steam->client_hdev);
++input_register_fail:
++hid_hw_open_fail:
++hid_hw_start_fail:
+ cancel_work_sync(&steam->work_connect);
+ cancel_delayed_work_sync(&steam->heartbeat);
+ cancel_delayed_work_sync(&steam->mode_switch);
+@@ -1115,14 +1151,12 @@ static void steam_remove(struct hid_device *hdev)
+ return;
+ }
+
++ cancel_delayed_work_sync(&steam->heartbeat);
++ cancel_delayed_work_sync(&steam->mode_switch);
++ cancel_work_sync(&steam->work_connect);
+ hid_destroy_device(steam->client_hdev);
+- mutex_lock(&steam->mutex);
+ steam->client_hdev = NULL;
+ steam->client_opened = false;
+- cancel_delayed_work_sync(&steam->heartbeat);
+- mutex_unlock(&steam->mutex);
+- cancel_work_sync(&steam->work_connect);
+- cancel_delayed_work_sync(&steam->mode_switch);
+ if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+ hid_info(hdev, "Steam wireless receiver disconnected");
+ }
+@@ -1597,10 +1631,8 @@ static int steam_param_set_lizard_mode(const char *val,
+
+ mutex_lock(&steam_devices_lock);
+ list_for_each_entry(steam, &steam_devices, list) {
+- mutex_lock(&steam->mutex);
+ if (!steam->client_opened)
+ steam_set_lizard_mode(steam, lizard_mode);
+- mutex_unlock(&steam->mutex);
+ }
+ mutex_unlock(&steam_devices_lock);
+ return 0;
+--
+2.41.0
+
+
+From d4490c88bed06b4c18af4a6029d67374df5218e1 Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Wed, 10 May 2023 17:27:12 -0700
+Subject: [PATCH 06/10] HID: hid-steam: Make client_opened a counter
+
+The client_opened variable was used to track if the hidraw was opened by any
+clients to silence keyboard/mouse events while opened. However, there was no
+counting of how many clients were opened, so opening two at the same time and
+then closing one would fool the driver into thinking it had no remaining opened
+clients.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+---
+ drivers/hid/hid-steam.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
+index 845ca71b8bd3a..0c2fe51b29bc1 100644
+--- a/drivers/hid/hid-steam.c
++++ b/drivers/hid/hid-steam.c
+@@ -215,7 +215,7 @@ struct steam_device {
+ spinlock_t lock;
+ struct hid_device *hdev, *client_hdev;
+ struct mutex report_mutex;
+- bool client_opened;
++ unsigned long client_opened;
+ struct input_dev __rcu *input;
+ unsigned long quirks;
+ struct work_struct work_connect;
+@@ -787,7 +787,7 @@ static void steam_battery_unregister(struct steam_device *steam)
+ static int steam_register(struct steam_device *steam)
+ {
+ int ret;
+- bool client_opened;
++ unsigned long client_opened;
+ unsigned long flags;
+
+ /*
+@@ -961,7 +961,7 @@ static int steam_client_ll_open(struct hid_device *hdev)
+ unsigned long flags;
+
+ spin_lock_irqsave(&steam->lock, flags);
+- steam->client_opened = true;
++ steam->client_opened++;
+ spin_unlock_irqrestore(&steam->lock, flags);
+
+ steam_input_unregister(steam);
+@@ -977,7 +977,7 @@ static void steam_client_ll_close(struct hid_device *hdev)
+ bool connected;
+
+ spin_lock_irqsave(&steam->lock, flags);
+- steam->client_opened = false;
++ steam->client_opened--;
+ connected = steam->connected && !steam->client_opened;
+ spin_unlock_irqrestore(&steam->lock, flags);
+
+@@ -1156,7 +1156,7 @@ static void steam_remove(struct hid_device *hdev)
+ cancel_work_sync(&steam->work_connect);
+ hid_destroy_device(steam->client_hdev);
+ steam->client_hdev = NULL;
+- steam->client_opened = false;
++ steam->client_opened = 0;
+ if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+ hid_info(hdev, "Steam wireless receiver disconnected");
+ }
+--
+2.41.0
+
+
+From 58a8667b251984ecc85a503c5dec3fc8f98028ff Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Thu, 18 May 2023 18:00:35 -0700
+Subject: [PATCH 07/10] HID: hid-steam: Better handling of serial number length
+
+The second byte of the GET_STRING_ATTRIB report is a length, so we should set
+the size of the buffer to be the size we're actually requesting, and only
+reject the reply if the length out is nonsensical.
+
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+---
+ drivers/hid/hid-steam.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
+index 0c2fe51b29bc1..92e3e1052fa42 100644
+--- a/drivers/hid/hid-steam.c
++++ b/drivers/hid/hid-steam.c
+@@ -208,7 +208,7 @@ static LIST_HEAD(steam_devices);
+ #define STEAM_PAD_BOTH 2
+
+ /* Other random constants */
+-#define STEAM_SERIAL_LEN 10
++#define STEAM_SERIAL_LEN 0x15
+
+ struct steam_device {
+ struct list_head list;
+@@ -359,10 +359,10 @@ static int steam_get_serial(struct steam_device *steam)
+ {
+ /*
+ * Send: 0xae 0x15 0x01
+- * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
++ * Recv: 0xae 0x15 0x01 serialnumber
+ */
+ int ret = 0;
+- u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL};
++ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, sizeof(steam->serial_no), STEAM_ATTRIB_STR_UNIT_SERIAL};
+ u8 reply[3 + STEAM_SERIAL_LEN + 1];
+
+ mutex_lock(&steam->report_mutex);
+@@ -372,12 +372,12 @@ static int steam_get_serial(struct steam_device *steam)
+ ret = steam_recv_report(steam, reply, sizeof(reply));
+ if (ret < 0)
+ goto out;
+- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) {
++ if (reply[0] != 0xae || reply[1] < 1 || reply[1] > sizeof(steam->serial_no) || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) {
+ ret = -EIO;
+ goto out;
+ }
+ reply[3 + STEAM_SERIAL_LEN] = 0;
+- strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
++ strscpy(steam->serial_no, reply + 3, reply[1]);
+ out:
+ mutex_unlock(&steam->report_mutex);
+ return ret;
+--
+2.41.0
+
+
+From 7460867bd78651a6187ac44c73d1be653c09973b Mon Sep 17 00:00:00 2001
+From: Vicki Pfau <vi@endrift.com>
+Date: Fri, 24 Mar 2023 10:42:27 -0700
+Subject: [PATCH 08/10] Input: xpad - fix support for some third-party
+ controllers
+
+Some third-party controllers, such as the HORPIAD FPS for Nintendo Switch and
+Gamesir-G3w, require a specific packet that the first-party XInput driver sends
+before it will start sending reports. It's not currently known what this packet
+does, but since the first-party driver always sends it's unlikely that this
+could cause issues with existing controllers.
+
+Co-authored-by: Andrey Smirnov <andrew.smirnov@gmail.com>
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+Link: https://lore.kernel.org/r/20230324040446.3487725-3-vi@endrift.com
+Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
+---
+ drivers/input/joystick/xpad.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
+index cdb193317c3b6..fc680b45f936e 100644
+--- a/drivers/input/joystick/xpad.c
++++ b/drivers/input/joystick/xpad.c
+@@ -264,6 +264,7 @@ static const struct xpad_device {
+ { 0x0f0d, 0x0067, "HORIPAD ONE", 0, XTYPE_XBOXONE },
+ { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
+ { 0x0f0d, 0x00c5, "Hori Fighting Commander ONE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
++ { 0x0f0d, 0x00dc, "HORIPAD FPS for Nintendo Switch", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
+ { 0x0f30, 0x010b, "Philips Recoil", 0, XTYPE_XBOX },
+ { 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX },
+ { 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX },
+@@ -1720,6 +1721,27 @@ static int xpad_start_input(struct usb_xpad *xpad)
+ return error;
+ }
+ }
++ if (xpad->xtype == XTYPE_XBOX360) {
++ /*
++ * Some third-party controllers Xbox 360-style controllers
++ * require this message to finish initialization.
++ */
++ u8 dummy[20];
++
++ error = usb_control_msg_recv(xpad->udev, 0,
++ /* bRequest */ 0x01,
++ /* bmRequestType */
++ USB_TYPE_VENDOR | USB_DIR_IN |
++ USB_RECIP_INTERFACE,
++ /* wValue */ 0x100,
++ /* wIndex */ 0x00,
++ dummy, sizeof(dummy),
++ 25, GFP_KERNEL);
++ if (error)
++ dev_warn(&xpad->dev->dev,
++ "unable to receive magic message: %d\n",
++ error);
++ }
+
+ return 0;
+ }
+--
+2.41.0
+
+
+From 469ab7efd0383f60e83c086347526273ed1d1a33 Mon Sep 17 00:00:00 2001
+From: Timothee Besset <ttimo@valvesoftware.com>
+Date: Mon, 22 May 2023 20:25:57 -0500
+Subject: [PATCH 09/10] Input: xpad - Add GameSir VID for Xbox One controllers
+
+Co-authored-by: Sam Lantinga <saml@valvesoftware.com>
+Signed-off-by: Sam Lantinga <slouken@libsdl.org>
+Signed-off-by: Vicki Pfau <vi@endrift.com>
+---
+ drivers/input/joystick/xpad.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
+index fc680b45f936e..bb2f69faa2a81 100644
+--- a/drivers/input/joystick/xpad.c
++++ b/drivers/input/joystick/xpad.c
+@@ -500,6 +500,7 @@ static const struct usb_device_id xpad_table[] = {
+ XPAD_XBOX360_VENDOR(0x2f24), /* GameSir controllers */
+ XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */
+ XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */
++ XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */
+ { }
+ };
+
+--
+2.41.0
+
+
+From 4fd74c574f8554056facabd4e36e5e397f2e6b98 Mon Sep 17 00:00:00 2001
+From: Jonathan Frederick <doublej472@gmail.com>
+Date: Fri, 7 Jul 2023 15:11:33 -0700
+Subject: [PATCH 10/10] Input: xpad - add GameSir T4 Kaleid Controller support
+
+Add VID and PID to the xpad_device table to allow driver
+to use the GameSir T4 Kaleid Controller, which is
+XTYPE_XBOX360 compatible in xinput mode.
+
+Signed-off-by: Jonathan Frederick <doublej472@gmail.com>
+Link: https://lore.kernel.org/r/ZKeKSbP3faIPv5jB@dbj-hp-flip
+Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
+---
+ drivers/input/joystick/xpad.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
+index bb2f69faa2a81..ede380551e55c 100644
+--- a/drivers/input/joystick/xpad.c
++++ b/drivers/input/joystick/xpad.c
+@@ -366,6 +366,7 @@ static const struct xpad_device {
+ { 0x31e3, 0x1300, "Wooting 60HE (AVR)", 0, XTYPE_XBOX360 },
+ { 0x31e3, 0x1310, "Wooting 60HE (ARM)", 0, XTYPE_XBOX360 },
+ { 0x3285, 0x0607, "Nacon GC-100", 0, XTYPE_XBOX360 },
++ { 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 },
+ { 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX },
+ { 0xffff, 0xffff, "Chinese-made Xbox Controller", 0, XTYPE_XBOX },
+ { 0x0000, 0x0000, "Generic X-Box pad", 0, XTYPE_UNKNOWN }
+@@ -500,6 +501,7 @@ static const struct usb_device_id xpad_table[] = {
+ XPAD_XBOX360_VENDOR(0x2f24), /* GameSir controllers */
+ XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */
+ XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */
++ XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */
+ XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */
+ { }
+ };
+--
+2.41.0
+
+