From 498e88ae626be4f523063c8a7027b4b02eca31d2 Mon Sep 17 00:00:00 2001 From: GloriousEggroll 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