aboutsummaryrefslogtreecommitdiff
path: root/SOURCES/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch
blob: 88631aea64dd870e4d9231632b13770d670af945 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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
@@ -6971,6 +6971,14 @@
 					request from 5000 ms to 500 ms);
 			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