Web lists-archives.com

[PATCH] usb: core: Add "quirks" parameter for usbcore




Trying quirks in usbcore needs to rebuild the driver or the entire
kernel if it's builtin. It can save a lot of time if usbcore has similar
ability like "usbhid.quirks=" and "usb-storage.quirks=".

Rename the original quirk detection function to "static" as we introduce
this new "dynamic" function.

Now users can use "usbcore.quirks=" as short term workaround before the
next kernel release.

This is inspired by usbhid and usb-storage.

Signed-off-by: Kai-Heng Feng <kai.heng.feng@xxxxxxxxxxxxx>
---
 Documentation/admin-guide/kernel-parameters.txt |  55 +++++++++++++
 drivers/usb/core/quirks.c                       | 103 ++++++++++++++++++++++--
 include/linux/usb/quirks.h                      |   2 +
 3 files changed, 154 insertions(+), 6 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 6571fbfdb2a1..f1cb31aa25e3 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -4302,6 +4302,61 @@
 
 	usbcore.nousb	[USB] Disable the USB subsystem
 
+	usbcore.quirks=
+			[USB] A list of quirks entries to supplement or
+			override the built-in usb core quirk list.  List
+			entries are separated by commas.  Each entry has
+			the form VID:PID:Flags where VID and PID are Vendor
+			and Product ID values (4-digit hex numbers) and
+			Flags is a set of characters, each corresponding
+			to a common usb core quirk flag as follows:
+				a = USB_QUIRK_STRING_FETCH_255 (string
+					descriptors must not be fetched using
+					a 255-byte read);
+				b = USB_QUIRK_RESET_RESUME (device can't resume
+					correctly so reset it instead);
+				c = USB_QUIRK_NO_SET_INTF (device can't handle
+					Set-Interface requests);
+				d = USB_QUIRK_CONFIG_INTF_STRINGS (device can't
+					handle its Configuration or Interface
+					strings);
+				e = USB_QUIRK_RESET (device can't be reset
+					(e.g morph devices), don't use reset);
+				f = USB_QUIRK_HONOR_BNUMINTERFACES (device has
+					more interface descriptions than the
+					bNumInterfaces count, and can't handle
+					talking to these interfaces);
+				g = USB_QUIRK_DELAY_INIT (device needs a pause
+					during initialization, after we read
+					the device descriptor);
+				h = USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL (For
+					high speed and super speed interrupt
+					endpoints, the USB 2.0 and USB 3.0 spec
+					require the interval in microframes (1
+					microframe = 125 microseconds) to be
+					calculated as interval = 2 ^
+					(bInterval-1).
+					Devices with this quirk report their
+					bInterval as the result of this
+					calculation instead of the exponent
+					variable used in the calculation);
+				i = USB_QUIRK_DEVICE_QUALIFIER (device can't
+					handle device_qualifier descriptor
+					requests);
+				j = USB_QUIRK_IGNORE_REMOTE_WAKEUP (device
+					generates spurious wakeup, ignore
+					remote wakeup capability);
+				k = USB_QUIRK_NO_LPM (device can't handle Link
+					Power Management);
+				l = USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL
+					(Device reports its bInterval as linear
+					frames instead of the USB 2.0
+					calculation);
+				m = USB_QUIRK_DISCONNECT_SUSPEND (Device needs
+					to be disconnected before suspend to
+					prevent spurious wakeup)
+			Example: quirks=0781:5580:bk,0a5c:5834:gij
+
 	usbhid.mousepoll=
 			[USBHID] The interval which mice are to be polled at.
 
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index f1dbab6f798f..0fd31aa2baa1 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -11,6 +11,12 @@
 #include <linux/usb/hcd.h>
 #include "usb.h"
 
+/* Quirks specified at module load time */
+static char *quirks_param[MAX_USB_BOOT_QUIRKS];
+module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
+MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying
+			   quirks=vendorID:productID:quirks");
+
 /* Lists of quirky USB devices, split in device quirks and interface quirks.
  * Device quirks are applied at the very beginning of the enumeration process,
  * right after reading the device descriptor. They can thus only match on device
@@ -310,8 +316,8 @@ static int usb_amd_resume_quirk(struct usb_device *udev)
 	return 0;
 }
 
-static u32 __usb_detect_quirks(struct usb_device *udev,
-			       const struct usb_device_id *id)
+static u32 usb_detect_static_quirks(struct usb_device *udev,
+				    const struct usb_device_id *id)
 {
 	u32 quirks = 0;
 
@@ -329,23 +335,108 @@ static u32 __usb_detect_quirks(struct usb_device *udev,
 	return quirks;
 }
 
+/* Works only for digits and letters, but small and fast */
+#define TOLOWER(x) ((x) | 0x20)
+
+static u32 usb_detect_dynamic_quirks(struct usb_device *udev)
+{
+	u16 dev_vid = le16_to_cpu(udev->descriptor.idVendor);
+	u16 dev_pid = le16_to_cpu(udev->descriptor.idProduct);
+	u16 quirk_vid, quirk_pid;
+	char quirks[32];
+	char *p;
+	u32 flags = 0;
+	int n, m;
+
+	for (n = 0; n < MAX_USB_BOOT_QUIRKS && quirks_param[n]; n++) {
+		m = sscanf(quirks_param[n], "%hx:%hx:%s",
+			   &quirk_vid, &quirk_pid, quirks);
+		if (m != 3)
+			pr_warn("Could not parse USB quirk module param %s\n",
+				quirks_param[n]);
+
+		if (quirk_vid == dev_vid && quirk_pid == dev_pid)
+			break;
+	}
+	if (n == MAX_USB_BOOT_QUIRKS || !quirks_param[n])
+		return 0;
+
+	p = quirks;
+
+	/* Collect the flags */
+	for (; *p; p++) {
+		switch (TOLOWER(*p)) {
+		case 'a':
+			flags |= USB_QUIRK_STRING_FETCH_255;
+			break;
+		case 'b':
+			flags |= USB_QUIRK_RESET_RESUME;
+			break;
+		case 'c':
+			flags |= USB_QUIRK_NO_SET_INTF;
+			break;
+		case 'd':
+			flags |= USB_QUIRK_CONFIG_INTF_STRINGS;
+			break;
+		case 'e':
+			flags |= USB_QUIRK_RESET;
+			break;
+		case 'f':
+			flags |= USB_QUIRK_HONOR_BNUMINTERFACES;
+			break;
+		case 'g':
+			flags |= USB_QUIRK_DELAY_INIT;
+			break;
+		case 'h':
+			flags |= USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL;
+			break;
+		case 'i':
+			flags |= USB_QUIRK_DEVICE_QUALIFIER;
+			break;
+		case 'j':
+			flags |= USB_QUIRK_IGNORE_REMOTE_WAKEUP;
+			break;
+		case 'k':
+			flags |= USB_QUIRK_NO_LPM;
+			break;
+		case 'l':
+			flags |= USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL;
+			break;
+		case 'm':
+			flags |= USB_QUIRK_DISCONNECT_SUSPEND;
+			break;
+		/* Ignore unrecognized flag characters */
+		}
+	}
+
+	return flags;
+}
+
 /*
  * Detect any quirks the device has, and do any housekeeping for it if needed.
  */
 void usb_detect_quirks(struct usb_device *udev)
 {
-	udev->quirks = __usb_detect_quirks(udev, usb_quirk_list);
+	udev->quirks = usb_detect_dynamic_quirks(udev);
+
+	if (udev->quirks) {
+		dev_dbg(&udev->dev, "Dynamic USB quirks for this device: %x\n",
+			udev->quirks);
+		return;
+	}
+
+	udev->quirks = usb_detect_static_quirks(udev, usb_quirk_list);
 
 	/*
 	 * Pixart-based mice would trigger remote wakeup issue on AMD
 	 * Yangtze chipset, so set them as RESET_RESUME flag.
 	 */
 	if (usb_amd_resume_quirk(udev))
-		udev->quirks |= __usb_detect_quirks(udev,
+		udev->quirks |= usb_detect_static_quirks(udev,
 				usb_amd_resume_quirk_list);
 
 	if (udev->quirks)
-		dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
+		dev_dbg(&udev->dev, "Static USB quirks for this device: %x\n",
 			udev->quirks);
 
 #ifdef CONFIG_USB_DEFAULT_PERSIST
@@ -362,7 +453,7 @@ void usb_detect_interface_quirks(struct usb_device *udev)
 {
 	u32 quirks;
 
-	quirks = __usb_detect_quirks(udev, usb_interface_quirk_list);
+	quirks = usb_detect_static_quirks(udev, usb_interface_quirk_list);
 	if (quirks == 0)
 		return;
 
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index f1fcec2fd5f8..40254f2fafb2 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -8,6 +8,8 @@
 #ifndef __LINUX_USB_QUIRKS_H
 #define __LINUX_USB_QUIRKS_H
 
+#define MAX_USB_BOOT_QUIRKS 4
+
 /* string descriptors must not be fetched using a 255-byte read */
 #define USB_QUIRK_STRING_FETCH_255		BIT(0)
 
-- 
2.14.1