summaryrefslogtreecommitdiff
path: root/SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch
diff options
context:
space:
mode:
Diffstat (limited to 'SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch')
-rw-r--r--SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch258
1 files changed, 258 insertions, 0 deletions
diff --git a/SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch b/SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch
new file mode 100644
index 0000000..ecd0304
--- /dev/null
+++ b/SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch
@@ -0,0 +1,258 @@
+From 498e88ae626be4f523063c8a7027b4b02eca31d2 Mon Sep 17 00:00:00 2001
+From: GloriousEggroll <gloriouseggroll@gmail.com>
+Date: Tue, 17 Jan 2023 12:08:46 -0700
+Subject: [PATCH] Allow to set custom USB pollrate for specific devices like
+ so: usbcore.interrupt_interval_override=045e:00db:16,1bcf:0005:1
+
+---
+ .../admin-guide/kernel-parameters.txt | 8 +
+ drivers/usb/core/config.c | 170 +++++++++++++++++-
+ drivers/usb/core/usb.c | 1 +
+ drivers/usb/core/usb.h | 1 +
+ 4 files changed, 179 insertions(+), 1 deletion(-)
+
+diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
+index dbd26fde4..c9b8b80af 100644
+--- a/Documentation/admin-guide/kernel-parameters.txt
++++ b/Documentation/admin-guide/kernel-parameters.txt
+@@ -6552,6 +6552,14 @@
+ delay after resetting its port);
+ Example: quirks=0781:5580:bk,0a5c:5834:gij
+
++ usbcore.interrupt_interval_override=
++ [USB] A list of USB devices for which a different polling
++ interval than the default shall be used on all interrupt-type
++ endpoints. The format is VendorID:ProductID:interval, with
++ the vendor and product ids specified hexadecimally, and the
++ interval decimally in milliseconds.
++ Example: interrupt_interval_override=045e:00db:16,1bcf:0005:2
++
+ usbhid.mousepoll=
+ [USBHID] The interval which mice are to be polled at.
+
+diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
+index 48bc8a481..84bd550ad 100644
+--- a/drivers/usb/core/config.c
++++ b/drivers/usb/core/config.c
+@@ -19,6 +19,149 @@
+ #define USB_MAXCONFIG 8 /* Arbitrary limit */
+
+
++/* A struct associated with the interrupt_interval_override module parameter, representing
++ an user's choice to force a specific interrupt interval upon all interrupt endpoints of
++ a certain device. */
++struct interrupt_interval_override {
++ /* The vendor ID of the device of which the interrupt interval shall be overridden */
++ u16 vendor;
++ /* The product ID of the device of which the interrupt interval shall be overridden */
++ u16 product;
++ /* The new interval measured in milliseconds that shall be given to all endpoints of type interrupt on said device */
++ unsigned int interval;
++};
++
++static DEFINE_MUTEX(interrupt_interval_override_mutex);
++static char interrupt_interval_override_param[128];
++static struct interrupt_interval_override *interrupt_interval_override_list = NULL;
++static size_t interrupt_interval_override_count = 0;
++
++static int interrupt_interval_override_param_set(const char *value, const struct kernel_param *kp)
++{
++ const char *p;
++ unsigned short vendor, product;
++ unsigned int interval;
++ struct interrupt_interval_override* list;
++ struct interrupt_interval_override param;
++ size_t count, max_count, i, len;
++ int err, res;
++
++ mutex_lock(&interrupt_interval_override_mutex);
++
++ if (!value || !*value) {
++ /* Unset the current variable. */
++ kfree(interrupt_interval_override_list);
++ interrupt_interval_override_list = NULL;
++ interrupt_interval_override_count = 0;
++ param_set_copystring(value, kp); /* Does not fail: the empty string is short enough to fit. */
++ mutex_unlock(&interrupt_interval_override_mutex);
++ return 0;
++ }
++
++ /* Compute an upper bound on the amount of entries we need. */
++ for (max_count = 1, i = 0; value[i]; i++) {
++ if (value[i] == ',')
++ max_count++;
++ }
++
++ /* Ensure we can allocate enough memory before overwriting the global variables. */
++ list = kcalloc(max_count,
++ sizeof(struct interrupt_interval_override),
++ GFP_KERNEL);
++
++ if (!list) {
++ mutex_unlock(&interrupt_interval_override_mutex);
++ return -ENOMEM;
++ }
++
++ err = param_set_copystring(value, kp);
++ if (err) {
++ kfree(list);
++ mutex_unlock(&interrupt_interval_override_mutex);
++ return err;
++ }
++
++ /* Parse the parameter. Example of a valid parameter: 045e:00db:16,1bcf:0005:2 */
++ for (count = 0, p = (const char*)value; p && *p;) {
++ res = sscanf(p, "%hx:%hx:%d%zn", &vendor, &product, &interval, &len);
++
++ /* Check whether all variables (vendor, product, interval, len) were assigned.
++ %zn does not increase the assignment count, so we need to check for value 3 instead of 4.
++ %zn does not consume input either, so setting len shouldn't fail if interval has been properly set. */
++ if (res != 3) {
++ pr_warn("Error while parsing USB interrupt interval override parameter %s.\n", value);
++ break;
++ }
++
++ param.vendor = (u16)vendor;
++ param.product = (u16)product;
++ param.interval = interval;
++ list[count++] = param;
++
++ p += len;
++ if (*p == ',' && *(p+1) != '\0') {
++ p++;
++ continue;
++ } else if(*p == '\0' || (*p == '\n' && *(p+1) == '\0')) {
++ break;
++ } else {
++ pr_warn("Error while parsing USB interrupt interval override parameter %s.\n", value);
++ break;
++ }
++ }
++
++ /* Overwrite the global variables with the local ones. */
++ kfree(interrupt_interval_override_list);
++ interrupt_interval_override_list = list;
++ interrupt_interval_override_count = count;
++ mutex_unlock(&interrupt_interval_override_mutex);
++ return 0;
++}
++
++static const struct kernel_param_ops interrupt_interval_override_param_ops = {
++ .set = interrupt_interval_override_param_set,
++ .get = param_get_string,
++};
++
++static struct kparam_string interrupt_interval_override_param_string = {
++ .maxlen = sizeof(interrupt_interval_override_param),
++ .string = interrupt_interval_override_param,
++};
++
++device_param_cb(interrupt_interval_override,
++ &interrupt_interval_override_param_ops,
++ &interrupt_interval_override_param_string,
++ 0644);
++MODULE_PARM_DESC(interrupt_interval_override,
++ "Override the polling interval of all interrupt-type endpoints of a specific USB"
++ " device by specifying interrupt_interval_override=vendorID:productID:interval.");
++
++/* Given an USB device, this checks whether the user has specified they want to override the interrupt
++ polling interval on all interrupt-type endpoints of said device.
++
++ This function returns the user-desired amount of milliseconds between interrupts on said endpoint.
++ If this function returns zero, the device-requested interrupt interval should be used. */
++static unsigned int usb_check_interrupt_interval_override(struct usb_device* udev)
++{
++ size_t i;
++ unsigned int res;
++ u16 vendor = le16_to_cpu(udev->descriptor.idVendor);
++ u16 product = le16_to_cpu(udev->descriptor.idProduct);
++
++ mutex_lock(&interrupt_interval_override_mutex);
++ for (i = 0; i < interrupt_interval_override_count; i++) {
++ if (interrupt_interval_override_list[i].vendor == vendor
++ && interrupt_interval_override_list[i].product == product) {
++
++ res = interrupt_interval_override_list[i].interval;
++ mutex_unlock(&interrupt_interval_override_mutex);
++ return res;
++ }
++ }
++ mutex_unlock(&interrupt_interval_override_mutex);
++ return 0;
++}
++
+ static inline const char *plural(int n)
+ {
+ return (n == 1 ? "" : "s");
+@@ -261,7 +404,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno,
+ struct usb_endpoint_descriptor *d;
+ struct usb_host_endpoint *endpoint;
+ int n, i, j, retval;
+- unsigned int maxp;
++ unsigned int maxp, ival;
+ const unsigned short *maxpacket_maxes;
+
+ d = (struct usb_endpoint_descriptor *) buffer;
+@@ -386,6 +529,23 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno,
+ endpoint->desc.bInterval = n;
+ }
+
++ /* Override the interrupt polling interval if a module parameter tells us to do so. */
++ if (usb_endpoint_xfer_int(d)) {
++ ival = usb_check_interrupt_interval_override(udev);
++ if (ival > 0) {
++ switch (udev->speed) {
++ case USB_SPEED_SUPER_PLUS:
++ case USB_SPEED_SUPER:
++ case USB_SPEED_HIGH:
++ endpoint->desc.bInterval = fls(ival) + 3;
++ break;
++ default: /* USB_SPEED_FULL or _LOW */
++ endpoint->desc.bInterval = ival;
++ break;
++ }
++ }
++ }
++
+ /* Some buggy low-speed devices have Bulk endpoints, which is
+ * explicitly forbidden by the USB spec. In an attempt to make
+ * them usable, we will try treating them as Interrupt endpoints.
+@@ -1092,3 +1252,11 @@ int usb_get_bos_descriptor(struct usb_device *dev)
+ usb_release_bos_descriptor(dev);
+ return ret;
+ }
++
++void usb_release_interrupt_interval_override_list(void)
++{
++ mutex_lock(&interrupt_interval_override_mutex);
++ kfree(interrupt_interval_override_list);
++ interrupt_interval_override_list = NULL;
++ mutex_unlock(&interrupt_interval_override_mutex);
++}
+diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
+index 11b15d7b3..ec52c6322 100644
+--- a/drivers/usb/core/usb.c
++++ b/drivers/usb/core/usb.c
+@@ -1066,6 +1066,7 @@ static void __exit usb_exit(void)
+ return;
+
+ usb_release_quirk_list();
++ usb_release_interrupt_interval_override_list();
+ usb_deregister_device_driver(&usb_generic_driver);
+ usb_major_cleanup();
+ usb_deregister(&usbfs_driver);
+diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
+index 82538daac..b6faa897c 100644
+--- a/drivers/usb/core/usb.h
++++ b/drivers/usb/core/usb.h
+@@ -37,6 +37,7 @@ extern void usb_authorize_interface(struct usb_interface *);
+ extern void usb_detect_quirks(struct usb_device *udev);
+ extern void usb_detect_interface_quirks(struct usb_device *udev);
+ extern void usb_release_quirk_list(void);
++extern void usb_release_interrupt_interval_override_list(void);
+ extern bool usb_endpoint_is_ignored(struct usb_device *udev,
+ struct usb_host_interface *intf,
+ struct usb_endpoint_descriptor *epd);
+--
+2.39.0
+