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. Also, the quirk parameter can XOR the builtin
quirks for debugging purpose.

This is inspired by usbhid and usb-storage.

Signed-off-by: Kai-Heng Feng <kai.heng.f...@canonical.com>
---
v4: Use kmalloc() to reduce memory usage.
    Remove tolower() so we can use capital character in the future.
    
v3: Stop overridding static quirks.
    Use XOR for dynamic quirks.
    Save parsed quirk as a list to avoid parsing quirk table everytime.

v2: Use in-kernel tolower() function.

 Documentation/admin-guide/kernel-parameters.txt |  55 ++++++++
 drivers/usb/core/quirks.c                       | 160 +++++++++++++++++++++++-
 drivers/usb/core/usb.c                          |   1 +
 drivers/usb/core/usb.h                          |   1 +
 4 files changed, 212 insertions(+), 5 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt 
b/Documentation/admin-guide/kernel-parameters.txt
index 1d1d53f85ddd..70a7398c20e2 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -4368,6 +4368,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 4024926c1d68..b15e80e414c8 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -11,6 +11,23 @@
 #include <linux/usb/hcd.h>
 #include "usb.h"
 
+static char quirks_param[128];
+module_param_string(quirks, quirks_param, sizeof(quirks_param), 0644);
+MODULE_PARM_DESC(quirks, "Add/modify USB quirks by specifying 
quirks=vendorID:productID:quirks");
+
+static char quirks_param_orig[128];
+
+static LIST_HEAD(quirk_list);
+
+static struct mutex quirk_mutex;
+
+struct quirk_entry {
+       u16 vid;
+       u16 pid;
+       u32 flags;
+       struct list_head list;
+};
+
 /* 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
@@ -305,6 +322,105 @@ static bool usb_match_any_interface(struct usb_device 
*udev,
        return false;
 }
 
+static int usb_build_quirk_list(const char *quirks_param)
+{
+       char *param, *p, *field;
+       u16 vid, pid;
+       u32 flags;
+       struct quirk_entry *quirk;
+
+       usb_release_quirk_list();
+
+       param = kmalloc(strlen(quirks_param), GFP_KERNEL);
+       if (!param)
+               return -ENOMEM;
+
+       strcpy(param, quirks_param);
+       p = param;
+       while (p && *p) {
+               /* Each entry consists of VID:PID:flags */
+               field = strsep(&p, ":");
+               if (!field)
+                       break;
+
+               if (kstrtou16(field, 16, &vid))
+                       break;
+
+               field = strsep(&p, ":");
+               if (!field)
+                       break;
+
+               if (kstrtou16(field, 16, &pid))
+                       break;
+
+               field = strsep(&p, ",");
+               if (!field)
+                       break;
+
+               /* Collect the flags */
+               for (flags = 0; *field; field++) {
+                       switch (*field) {
+                       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 */
+                       }
+               }
+
+               quirk = kmalloc(sizeof(struct quirk_entry), GFP_KERNEL);
+               if (!quirk) {
+                       kfree(param);
+                       return -ENOMEM;
+               }
+
+               quirk->vid = vid;
+               quirk->pid = pid;
+               quirk->flags = flags;
+
+               list_add(&quirk->list, &quirk_list);
+       }
+
+       kfree(param);
+
+       return 0;
+}
+
 static int usb_amd_resume_quirk(struct usb_device *udev)
 {
        struct usb_hcd *hcd;
@@ -317,8 +433,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;
 
@@ -336,21 +452,45 @@ static u32 __usb_detect_quirks(struct usb_device *udev,
        return quirks;
 }
 
+static u32 usb_detect_dynamic_quirks(struct usb_device *udev)
+{
+       u16 vid = le16_to_cpu(udev->descriptor.idVendor);
+       u16 pid = le16_to_cpu(udev->descriptor.idProduct);
+       struct quirk_entry *quirk;
+
+       mutex_lock(&quirk_mutex);
+       if (strcmp(quirks_param, quirks_param_orig) != 0) {
+               strcpy(quirks_param_orig, quirks_param);
+               if (usb_build_quirk_list(quirks_param))
+                       dev_warn(&udev->dev, "failed to build quirk list");
+       }
+       mutex_unlock(&quirk_mutex);
+
+       list_for_each_entry(quirk, &quirk_list, list) {
+               if (vid == quirk->vid && pid == quirk->pid)
+                       return quirk->flags;
+       }
+
+       return 0;
+}
+
 /*
  * 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_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);
 
+       udev->quirks ^= usb_detect_dynamic_quirks(udev);
+
        if (udev->quirks)
                dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
                        udev->quirks);
@@ -369,7 +509,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;
 
@@ -377,3 +517,13 @@ void usb_detect_interface_quirks(struct usb_device *udev)
                quirks);
        udev->quirks |= quirks;
 }
+
+void usb_release_quirk_list(void)
+{
+       struct quirk_entry *quirk, *tmp;
+
+       list_for_each_entry_safe(quirk, tmp, &quirk_list, list) {
+               list_del(&quirk->list);
+               kfree(quirk);
+       }
+}
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 2f5fbc56a9dd..0adb6345ff2e 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -1259,6 +1259,7 @@ static void __exit usb_exit(void)
        if (usb_disabled())
                return;
 
+       usb_release_quirk_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 149cc7480971..546a2219454b 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -36,6 +36,7 @@ extern void usb_deauthorize_interface(struct usb_interface *);
 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 int usb_remove_device(struct usb_device *udev);
 
 extern int usb_get_device_descriptor(struct usb_device *dev,
-- 
2.15.1

Reply via email to