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;