Hi,

newer laptops can have more than one camera.  For instance the Lenovo
X395 ships with a normal camera and an infrared camera.  These cameras
are behind a single USB device and their "functions" are separated
using interface association descriptors.  This means that there are
USB descriptors that tell us which USB interfaces belong to a single
function.  Note that the interface association descriptor is always
before its interfaces.

Unfortunately uvideo(4) currently can't handle that.  It goes through
all interfaces looking for VC or VS headers and realizes... there are
too many!  Because it doesn't know where to look, and when to stop.
Thus, it reports

uvideo0: too many VC_HEADERs!

Also since it claims all interfaces of the device, only one uvideo(4)
attaches and not two.

To fix this first of all when we attach, we should look for the first
unclaimed video class interface.  Then, go through all descriptors and
look for the function we belong to (using the interface association
descriptor).  Then, claim all the interfaces belonging to that function
and leave the rest.  This allows the second uvideo(4) to attach.

uvideo(4) contains more code that goes through every USB descriptor
looking for information.  In all these functions we should restrict it
so that it only looks inside its function's descriptors.  Thus, skip all
descriptors until we find our first interface and only then start
parsing.  Once we cross the function by reaching the next interface
association descriptor break out, because there's nothing left for us.

With this, and another small diff that follows, I can use the normal
camera on my Lenovo X395.

Patrick

diff --git a/sys/dev/usb/uvideo.c b/sys/dev/usb/uvideo.c
index 0c382b39071..c990c899c45 100644
--- a/sys/dev/usb/uvideo.c
+++ b/sys/dev/usb/uvideo.c
@@ -61,8 +61,8 @@ int uvideo_debug = 1;
 struct uvideo_softc {
        struct device                            sc_dev;
        struct usbd_device                      *sc_udev;
+       int                                      sc_iface;
        int                                      sc_nifaces;
-       struct usbd_interface                   **sc_ifaces;
 
        struct device                           *sc_videodev;
 
@@ -500,25 +500,56 @@ uvideo_attach(struct device *parent, struct device *self, 
void *aux)
 {
        struct uvideo_softc *sc = (struct uvideo_softc *)self;
        struct usb_attach_arg *uaa = aux;
+       usb_interface_assoc_descriptor_t *iad;
        usb_interface_descriptor_t *id;
+       const usb_descriptor_t *desc;
+       struct usbd_desc_iter iter;
        int i;
 
        sc->sc_udev = uaa->device;
-       sc->sc_nifaces = uaa->nifaces;
-       /*
-        * Claim all video interfaces.  Interfaces must be claimed during
-        * attach, during attach hooks is too late.
-        */
-       for (i = 0; i < sc->sc_nifaces; i++) {
+
+       /* Find the first unclaimed video interface. */
+       for (i = 0; i < uaa->nifaces; i++) {
                if (usbd_iface_claimed(sc->sc_udev, i))
                        continue;
                id = usbd_get_interface_descriptor(&sc->sc_udev->ifaces[i]);
                if (id == NULL)
                        continue;
                if (id->bInterfaceClass == UICLASS_VIDEO)
-                       usbd_claim_iface(sc->sc_udev, i);
+                       break;
+       }
+       KASSERT(i != uaa->nifaces);
+
+       /* Find out which interface association we belong to. */
+       usbd_desc_iter_init(sc->sc_udev, &iter);
+       desc = usbd_desc_iter_next(&iter);
+       while (desc) {
+               if (desc->bDescriptorType != UDESC_IFACE_ASSOC) {
+                       desc = usbd_desc_iter_next(&iter);
+                       continue;
+               }
+               iad = (usb_interface_assoc_descriptor_t *)desc;
+               if (i >= iad->bFirstInterface &&
+                   i < iad->bFirstInterface + iad->bInterfaceCount)
+                       break;
+               desc = usbd_desc_iter_next(&iter);
+       }
+       KASSERT(desc != NULL);
+
+       /*
+        * Claim all interfaces of our association.  Interfaces must be
+        * claimed during attach, during attach hooks is too late.
+        */
+       for (i = iad->bFirstInterface;
+           i < iad->bFirstInterface + iad->bInterfaceCount; i++) {
+               KASSERT(!usbd_iface_claimed(sc->sc_udev, i));
+               usbd_claim_iface(sc->sc_udev, i);
        }
 
+       /* Remember our association by saving the first interface. */
+       sc->sc_iface = iad->bFirstInterface;
+       sc->sc_nifaces = iad->bInterfaceCount;
+
        /* maybe the device has quirks */
        sc->sc_quirk = uvideo_lookup(uaa->vendor, uaa->product);
 
@@ -612,6 +643,7 @@ uvideo_vc_parse_desc(struct uvideo_softc *sc)
 {
        struct usbd_desc_iter iter;
        const usb_descriptor_t *desc;
+       usb_interface_descriptor_t *id;
        int vc_header_found;
        usbd_status error;
 
@@ -622,6 +654,18 @@ uvideo_vc_parse_desc(struct uvideo_softc *sc)
        usbd_desc_iter_init(sc->sc_udev, &iter);
        desc = usbd_desc_iter_next(&iter);
        while (desc) {
+               /* Skip all interfaces until we found our first. */
+               if (desc->bDescriptorType == UDESC_INTERFACE) {
+                       id = (usb_interface_descriptor_t *)desc;
+                       if (id->bInterfaceNumber == sc->sc_iface)
+                               break;
+               }
+               desc = usbd_desc_iter_next(&iter);
+       }
+       while (desc) {
+               /* Crossed device function boundary. */
+               if (desc->bDescriptorType == UDESC_IFACE_ASSOC)
+                       break;
                if (desc->bDescriptorType != UDESC_CS_INTERFACE) {
                        desc = usbd_desc_iter_next(&iter);
                        continue;
@@ -818,6 +862,18 @@ uvideo_vs_parse_desc(struct uvideo_softc *sc, 
usb_config_descriptor_t *cdesc)
        usbd_desc_iter_init(sc->sc_udev, &iter);
        desc = usbd_desc_iter_next(&iter);
        while (desc) {
+               /* Skip all interfaces until we found our first. */
+               if (desc->bDescriptorType == UDESC_INTERFACE) {
+                       id = (usb_interface_descriptor_t *)desc;
+                       if (id->bInterfaceNumber == sc->sc_iface)
+                               break;
+               }
+               desc = usbd_desc_iter_next(&iter);
+       }
+       while (desc) {
+               /* Crossed device function boundary. */
+               if (desc->bDescriptorType == UDESC_IFACE_ASSOC)
+                       break;
                if (desc->bDescriptorType != UDESC_CS_INTERFACE) {
                        desc = usbd_desc_iter_next(&iter);
                        continue;
@@ -902,12 +958,25 @@ uvideo_vs_parse_desc_format(struct uvideo_softc *sc)
 {
        struct usbd_desc_iter iter;
        const usb_descriptor_t *desc;
+       usb_interface_descriptor_t *id;
 
        DPRINTF(1, "%s: %s\n", DEVNAME(sc), __func__);
 
        usbd_desc_iter_init(sc->sc_udev, &iter);
        desc = usbd_desc_iter_next(&iter);
        while (desc) {
+               /* Skip all interfaces until we found our first. */
+               if (desc->bDescriptorType == UDESC_INTERFACE) {
+                       id = (usb_interface_descriptor_t *)desc;
+                       if (id->bInterfaceNumber == sc->sc_iface)
+                               break;
+               }
+               desc = usbd_desc_iter_next(&iter);
+       }
+       while (desc) {
+               /* Crossed device function boundary. */
+               if (desc->bDescriptorType == UDESC_IFACE_ASSOC)
+                       break;
                if (desc->bDescriptorType != UDESC_CS_INTERFACE) {
                        desc = usbd_desc_iter_next(&iter);
                        continue;
@@ -1040,6 +1109,7 @@ uvideo_vs_parse_desc_frame(struct uvideo_softc *sc)
 {
        struct usbd_desc_iter iter;
        const usb_descriptor_t *desc;
+       usb_interface_descriptor_t *id;
        usbd_status error;
 
        DPRINTF(1, "%s: %s\n", DEVNAME(sc), __func__);
@@ -1047,6 +1117,18 @@ uvideo_vs_parse_desc_frame(struct uvideo_softc *sc)
        usbd_desc_iter_init(sc->sc_udev, &iter);
        desc = usbd_desc_iter_next(&iter);
        while (desc) {
+               /* Skip all interfaces until we found our first. */
+               if (desc->bDescriptorType == UDESC_INTERFACE) {
+                       id = (usb_interface_descriptor_t *)desc;
+                       if (id->bInterfaceNumber == sc->sc_iface)
+                               break;
+               }
+               desc = usbd_desc_iter_next(&iter);
+       }
+       while (desc) {
+               /* Crossed device function boundary. */
+               if (desc->bDescriptorType == UDESC_IFACE_ASSOC)
+                       break;
                if (desc->bDescriptorType == UDESC_CS_INTERFACE &&
                    desc->bLength > sizeof(struct usb_video_frame_desc) &&
                    (desc->bDescriptorSubtype == UDESCSUB_VS_FRAME_MJPEG ||
@@ -1134,6 +1216,18 @@ uvideo_vs_parse_desc_alt(struct uvideo_softc *sc, int 
vs_nr, int iface, int numa
        usbd_desc_iter_init(sc->sc_udev, &iter);
        desc = usbd_desc_iter_next(&iter);
        while (desc) {
+               /* Skip all interfaces until we found our first. */
+               if (desc->bDescriptorType == UDESC_INTERFACE) {
+                       id = (usb_interface_descriptor_t *)desc;
+                       if (id->bInterfaceNumber == sc->sc_iface)
+                               break;
+               }
+               desc = usbd_desc_iter_next(&iter);
+       }
+       while (desc) {
+               /* Crossed device function boundary. */
+               if (desc->bDescriptorType == UDESC_IFACE_ASSOC)
+                       break;
                /* find video stream interface */
                if (desc->bDescriptorType != UDESC_INTERFACE)
                        goto next;
@@ -1204,6 +1298,18 @@ uvideo_vs_set_alt(struct uvideo_softc *sc, struct 
usbd_interface *ifaceh,
        usbd_desc_iter_init(sc->sc_udev, &iter);
        desc = usbd_desc_iter_next(&iter);
        while (desc) {
+               /* Skip all interfaces until we found our first. */
+               if (desc->bDescriptorType == UDESC_INTERFACE) {
+                       id = (usb_interface_descriptor_t *)desc;
+                       if (id->bInterfaceNumber == sc->sc_iface)
+                               break;
+               }
+               desc = usbd_desc_iter_next(&iter);
+       }
+       while (desc) {
+               /* Crossed device function boundary. */
+               if (desc->bDescriptorType == UDESC_IFACE_ASSOC)
+                       break;
                /* find video stream interface */
                if (desc->bDescriptorType != UDESC_INTERFACE)
                        goto next;

Reply via email to