usb: Add quirk detection based on interface information
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Thu, 19 Jul 2012 10:39:13 +0000 (12:39 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 19 Jul 2012 22:44:58 +0000 (15:44 -0700)
When a whole class of devices (possibly from a specific vendor, or
across multiple vendors) require a quirk, explictly listing all devices
in the class make the quirks table unnecessarily large. Fix this by
allowing matching devices based on interface information.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/driver.c
drivers/usb/core/hub.c
drivers/usb/core/quirks.c
drivers/usb/core/usb.h

index 69781016a266816af9feab64ad91d56d2001e1f4..445455a4429bef2f9a26cff9a3aa88ac4629c1ac 100644 (file)
@@ -607,22 +607,10 @@ int usb_match_device(struct usb_device *dev, const struct usb_device_id *id)
 }
 
 /* returns 0 if no match, 1 if match */
-int usb_match_one_id(struct usb_interface *interface,
-                    const struct usb_device_id *id)
+int usb_match_one_id_intf(struct usb_device *dev,
+                         struct usb_host_interface *intf,
+                         const struct usb_device_id *id)
 {
-       struct usb_host_interface *intf;
-       struct usb_device *dev;
-
-       /* proc_connectinfo in devio.c may call us with id == NULL. */
-       if (id == NULL)
-               return 0;
-
-       intf = interface->cur_altsetting;
-       dev = interface_to_usbdev(interface);
-
-       if (!usb_match_device(dev, id))
-               return 0;
-
        /* The interface class, subclass, protocol and number should never be
         * checked for a match if the device class is Vendor Specific,
         * unless the match record specifies the Vendor ID. */
@@ -652,6 +640,26 @@ int usb_match_one_id(struct usb_interface *interface,
 
        return 1;
 }
+
+/* returns 0 if no match, 1 if match */
+int usb_match_one_id(struct usb_interface *interface,
+                    const struct usb_device_id *id)
+{
+       struct usb_host_interface *intf;
+       struct usb_device *dev;
+
+       /* proc_connectinfo in devio.c may call us with id == NULL. */
+       if (id == NULL)
+               return 0;
+
+       intf = interface->cur_altsetting;
+       dev = interface_to_usbdev(interface);
+
+       if (!usb_match_device(dev, id))
+               return 0;
+
+       return usb_match_one_id_intf(dev, intf, id);
+}
 EXPORT_SYMBOL_GPL(usb_match_one_id);
 
 /**
index 540f20bf9e22119c4085c91a0151bc5b174010b8..821126eb81762ee2b57c971eec611c1e9a4e7ef6 100644 (file)
@@ -2069,7 +2069,7 @@ static int usb_enumerate_device(struct usb_device *udev)
                if (err < 0) {
                        dev_err(&udev->dev, "can't read configurations, error %d\n",
                                err);
-                       goto fail;
+                       return err;
                }
        }
        if (udev->wusb == 1 && udev->authorized == 0) {
@@ -2085,8 +2085,12 @@ static int usb_enumerate_device(struct usb_device *udev)
                udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);
        }
        err = usb_enumerate_device_otg(udev);
-fail:
-       return err;
+       if (err < 0)
+               return err;
+
+       usb_detect_interface_quirks(udev);
+
+       return 0;
 }
 
 static void set_usb_port_removable(struct usb_device *udev)
index 32d3adc315f5357c01b137a725bba3e7d18fe1da..cbd15d1d25d348f57022fad3d38fedb898eeb91e 100644 (file)
 #include <linux/usb/quirks.h>
 #include "usb.h"
 
-/* List of quirky USB devices.  Please keep this list ordered by:
+/* 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
+ * information.
+ *
+ * Interface quirks are applied after reading all the configuration descriptors.
+ * They can match on both device and interface information.
+ *
+ * Note that the DELAY_INIT and HONOR_BNUMINTERFACES quirks do not make sense as
+ * interface quirks, as they only influence the enumeration process which is run
+ * before processing the interface quirks.
+ *
+ * Please keep the lists ordered by:
  *     1) Vendor ID
  *     2) Product ID
  *     3) Class ID
- *
- * as we want specific devices to be overridden first, and only after that, any
- * class specific quirks.
- *
- * Right now the logic aborts if it finds a valid device in the table, we might
- * want to change that in the future if it turns out that a whole class of
- * devices is broken...
  */
 static const struct usb_device_id usb_quirk_list[] = {
        /* CBM - Flash disk */
@@ -156,16 +161,53 @@ static const struct usb_device_id usb_quirk_list[] = {
        { }  /* terminating entry must be last */
 };
 
-static const struct usb_device_id *find_id(struct usb_device *udev)
+static const struct usb_device_id usb_interface_quirk_list[] = {
+       { }  /* terminating entry must be last */
+};
+
+static bool usb_match_any_interface(struct usb_device *udev,
+                                   const struct usb_device_id *id)
+{
+       unsigned int i;
+
+       for (i = 0; i < udev->descriptor.bNumConfigurations; ++i) {
+               struct usb_host_config *cfg = &udev->config[i];
+               unsigned int j;
+
+               for (j = 0; j < cfg->desc.bNumInterfaces; ++j) {
+                       struct usb_interface_cache *cache;
+                       struct usb_host_interface *intf;
+
+                       cache = cfg->intf_cache[j];
+                       if (cache->num_altsetting == 0)
+                               continue;
+
+                       intf = &cache->altsetting[0];
+                       if (usb_match_one_id_intf(udev, intf, id))
+                               return true;
+               }
+       }
+
+       return false;
+}
+
+static u32 __usb_detect_quirks(struct usb_device *udev,
+                              const struct usb_device_id *id)
 {
-       const struct usb_device_id *id = usb_quirk_list;
+       u32 quirks = 0;
 
-       for (; id->idVendor || id->bDeviceClass || id->bInterfaceClass ||
-                       id->driver_info; id++) {
-               if (usb_match_device(udev, id))
-                       return id;
+       for (; id->match_flags; id++) {
+               if (!usb_match_device(udev, id))
+                       continue;
+
+               if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_INFO) &&
+                   !usb_match_any_interface(udev, id))
+                       continue;
+
+               quirks |= (u32)(id->driver_info);
        }
-       return NULL;
+
+       return quirks;
 }
 
 /*
@@ -173,14 +215,10 @@ static const struct usb_device_id *find_id(struct usb_device *udev)
  */
 void usb_detect_quirks(struct usb_device *udev)
 {
-       const struct usb_device_id *id = usb_quirk_list;
-
-       id = find_id(udev);
-       if (id)
-               udev->quirks = (u32)(id->driver_info);
+       udev->quirks = __usb_detect_quirks(udev, usb_quirk_list);
        if (udev->quirks)
                dev_dbg(&udev->dev, "USB quirks for this device: %x\n",
-                               udev->quirks);
+                       udev->quirks);
 
        /* For the present, all devices default to USB-PERSIST enabled */
 #if 0          /* was: #ifdef CONFIG_PM */
@@ -197,3 +235,16 @@ void usb_detect_quirks(struct usb_device *udev)
                udev->persist_enabled = 1;
 #endif /* CONFIG_PM */
 }
+
+void usb_detect_interface_quirks(struct usb_device *udev)
+{
+       u32 quirks;
+
+       quirks = __usb_detect_quirks(udev, usb_interface_quirk_list);
+       if (quirks == 0)
+               return;
+
+       dev_dbg(&udev->dev, "USB interface quirks for this device: %x\n",
+               quirks);
+       udev->quirks |= quirks;
+}
index 67875a89cfa1f170c3ecd86bc8e99bc895e39612..acb103c5c391190e83ee66e3d837f80ad14f72e8 100644 (file)
@@ -26,6 +26,7 @@ extern void usb_disable_device(struct usb_device *dev, int skip_ep0);
 extern int usb_deauthorize_device(struct usb_device *);
 extern int usb_authorize_device(struct usb_device *);
 extern void usb_detect_quirks(struct usb_device *udev);
+extern void usb_detect_interface_quirks(struct usb_device *udev);
 extern int usb_remove_device(struct usb_device *udev);
 
 extern int usb_get_device_descriptor(struct usb_device *dev,
@@ -37,6 +38,9 @@ extern int usb_set_configuration(struct usb_device *dev, int configuration);
 extern int usb_choose_configuration(struct usb_device *udev);
 
 extern void usb_kick_khubd(struct usb_device *dev);
+extern int usb_match_one_id_intf(struct usb_device *dev,
+                                struct usb_host_interface *intf,
+                                const struct usb_device_id *id);
 extern int usb_match_device(struct usb_device *dev,
                            const struct usb_device_id *id);
 extern void usb_forced_unbind_intf(struct usb_interface *intf);