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.pinch...@ideasonboard.com>
---
 drivers/usb/core/driver.c |   38 +++++++++++-------
 drivers/usb/core/hub.c    |    2 +
 drivers/usb/core/quirks.c |   93 ++++++++++++++++++++++++++++++++++----------
 drivers/usb/core/usb.h    |    4 ++
 4 files changed, 101 insertions(+), 36 deletions(-)

diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index f536aeb..5be5eba 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -606,22 +606,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, and protocol should never be
         * checked for a match if the device class is Vendor Specific,
         * unless the match record specifies the Vendor ID. */
@@ -646,6 +634,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);
 
 /**
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 8fb4849..c1fa6b2 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2170,6 +2170,8 @@ int usb_new_device(struct usb_device *udev)
        udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
                        (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
 
+       usb_detect_interface_quirks(udev);
+
        /* Tell the world! */
        announce_device(udev);
 
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 32d3adc..cbd15d1 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -15,17 +15,22 @@
 #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;
+}
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 5c5c538..7297ebb 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -24,6 +24,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,
@@ -35,6 +36,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);
-- 
1.7.8.6

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to