258 lines
9.2 KiB
Diff
258 lines
9.2 KiB
Diff
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
|