On 17/06/16(Fri) 22:22, Mark Kettenis wrote:
> As reported earlier, umb(4) currently doesn't attach to devices that
> implement both NCM 1.0 and MBIM, such as the Sierra Wireless EM7345
> that is found in some thinkpads.
>
> The diff below fixes this. It revamps the way we look up interface
> descriptors quite a bit. I removed the unused code for matching
> devices based on vendor and product ids. That code got a bit in my
> way. It should be possible to bring that back if needed.
>
> With this fix, the EM7345 attaches as:
>
> umb0 at uhub0 port 4 configuration 1 interface 0 "Sierra Wireless Inc. Sierra
> Wi
> reless EM7345 4G LTE" rev 2.00/17.29 addr 2
> umb0: ignoring invalid segment size 1500
> umb0: vers 1.0
> umodem0 at uhub0 port 4 configuration 1 interface 2 "Sierra Wireless Inc.
> Sierra Wireless EM7345 4G LTE" rev 2.00/17.29 addr 2
> umodem0: data interface 3, has no CM over data, has break
> umodem0: status change notification available
> ucom0 at umodem0
>
> Note that it still attaches as umodem(4) as well. That is actually a
> good thing since it allows me to read out the GPS though that interface.
>
> I believe this code should work on all devices that are properly MBIM
> compliant. But of course vendors are notoriously sloppy in providing
> the right usb interface descriptors for their devices. So testing is
> welcome. If you run into issues, please provide lsusb -v output.
Any test report? This looks good to me.
> Index: if_umb.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/usb/if_umb.c,v
> retrieving revision 1.1
> diff -u -p -r1.1 if_umb.c
> --- if_umb.c 15 Jun 2016 19:39:34 -0000 1.1
> +++ if_umb.c 17 Jun 2016 20:08:05 -0000
> @@ -204,48 +204,35 @@ const struct cfattach umb_ca = {
>
> int umb_delay = 4000;
>
> -/*
> - * Normally, MBIM devices are detected by their interface class and subclass.
> - * But for some models that have multiple configurations, it is better to
> - * match by vendor and product id so that we can select the desired
> - * configuration ourselves.
> - *
> - * OTOH, some devices identifiy themself als an MBIM device but fail to speak
> - * the MBIM protocol.
> - */
> -struct umb_products {
> - struct usb_devno dev;
> - int confno;
> -};
> -const struct umb_products umb_devs[] = {
> - /*
> - * Add devices here to force them to attach as umb.
> - * Format: { { VID, PID }, CONFIGNO }
> - */
> -};
> -
> -#define umb_lookup(vid, pid) \
> - ((const struct umb_products *)usb_lookup(umb_devs, vid, pid))
> -
> int
> umb_match(struct device *parent, void *match, void *aux)
> {
> struct usb_attach_arg *uaa = aux;
> usb_interface_descriptor_t *id;
>
> - if (umb_lookup(uaa->vendor, uaa->product) != NULL)
> - return UMATCH_VENDOR_PRODUCT;
> if (!uaa->iface)
> return UMATCH_NONE;
> if ((id = usbd_get_interface_descriptor(uaa->iface)) == NULL)
> return UMATCH_NONE;
> - if (id->bInterfaceClass != UICLASS_CDC ||
> - id->bInterfaceSubClass !=
> - UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL ||
> - id->bNumEndpoints != 1)
> +
> + /*
> + * If this function implements NCM, check if alternate setting
> + * 1 implements MBIM.
> + */
> + if (id->bInterfaceClass == UICLASS_CDC &&
> + id->bInterfaceSubClass ==
> + UISUBCLASS_NETWORK_CONTROL_MODEL)
> + id = usbd_find_idesc(uaa->device->cdesc, uaa->ifaceno, 1);
> + if (id == NULL)
> return UMATCH_NONE;
>
> - return UMATCH_DEVCLASS_DEVSUBCLASS;
> + if (id->bInterfaceClass == UICLASS_CDC &&
> + id->bInterfaceSubClass ==
> + UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL &&
> + id->bInterfaceProtocol == 0)
> + return UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO;
> +
> + return UMATCH_NONE;
> }
>
> void
> @@ -257,45 +244,54 @@ umb_attach(struct device *parent, struct
> struct usbd_desc_iter iter;
> const usb_descriptor_t *desc;
> int v;
> + struct usb_cdc_union_descriptor *ud;
> struct mbim_descriptor *md;
> int i;
> - struct usbd_interface *ctrl_iface = NULL;
> int ctrl_ep;
> - uint8_t data_ifaceno;
> usb_interface_descriptor_t *id;
> usb_config_descriptor_t *cd;
> usb_endpoint_descriptor_t *ed;
> + usb_interface_assoc_descriptor_t *ad;
> + int current_ifaceno = -1;
> + int data_ifaceno = -1;
> int altnum;
> int s;
> struct ifnet *ifp;
> int hard_mtu;
>
> sc->sc_udev = uaa->device;
> + sc->sc_ctrl_ifaceno = uaa->ifaceno;
>
> - if (uaa->configno < 0) {
> - /*
> - * In case the device was matched by VID/PID instead of
> - * InterfaceClass/InterfaceSubClass, we have to pick the
> - * correct configuration ourself.
> - */
> - uaa->configno = umb_lookup(uaa->vendor, uaa->product)->confno;
> - DPRINTF("%s: switching to config #%d\n", DEVNAM(sc),
> - uaa->configno);
> - status = usbd_set_config_no(sc->sc_udev, uaa->configno, 1);
> - if (status) {
> - printf("%s: failed to switch to config #%d: %s\n",
> - DEVNAM(sc), uaa->configno, usbd_errstr(status));
> - goto fail;
> - }
> - }
> -
> + /*
> + * Some MBIM hardware does not provide the mandatory CDC Union
> + * Descriptor, so we also look at matching Interface
> + * Association Descriptors to find out the MBIM Data Interface
> + * number.
> + */
> sc->sc_ver_maj = sc->sc_ver_min = -1;
> - usbd_desc_iter_init(sc->sc_udev, &iter);
> hard_mtu = MBIM_MAXSEGSZ_MINVAL;
> + usbd_desc_iter_init(sc->sc_udev, &iter);
> while ((desc = usbd_desc_iter_next(&iter))) {
> + if (desc->bDescriptorType == UDESC_IFACE_ASSOC) {
> + ad = (usb_interface_assoc_descriptor_t *)desc;
> + if (ad->bFirstInterface == uaa->ifaceno &&
> + ad->bInterfaceCount > 1)
> + data_ifaceno = uaa->ifaceno + 1;
> + }
> + if (desc->bDescriptorType == UDESC_INTERFACE) {
> + id = (usb_interface_descriptor_t *)desc;
> + current_ifaceno = id->bInterfaceNumber;
> + continue;
> + }
> + if (current_ifaceno != uaa->ifaceno)
> + continue;
> if (desc->bDescriptorType != UDESC_CS_INTERFACE)
> continue;
> switch (desc->bDescriptorSubtype) {
> + case UDESCSUB_CDC_UNION:
> + ud = (struct usb_cdc_union_descriptor *)desc;
> + data_ifaceno = ud->bSlaveInterface[0];
> + break;
> case UDESCSUB_MBIM:
> md = (struct mbim_descriptor *)desc;
> v = UGETW(md->bcdMBIMVersion);
> @@ -332,39 +328,29 @@ umb_attach(struct device *parent, struct
> goto fail;
> }
>
> - for (i = 0; i < sc->sc_udev->cdesc->bNumInterface; 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_CDC &&
> - id->bInterfaceSubClass ==
> - UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL) {
> - ctrl_iface = &sc->sc_udev->ifaces[i];
> - sc->sc_ctrl_ifaceno = id->bInterfaceNumber;
> - usbd_claim_iface(sc->sc_udev, i);
> - } else if (id->bInterfaceClass == UICLASS_CDC_DATA &&
> - id->bInterfaceSubClass == UISUBCLASS_DATA &&
> - id->bInterfaceProtocol == UIPROTO_DATA_MBIM) {
> - sc->sc_data_iface = &sc->sc_udev->ifaces[i];
> - data_ifaceno = id->bInterfaceNumber;
> - usbd_claim_iface(sc->sc_udev, i);
> - }
> - }
> - if (ctrl_iface == NULL) {
> - printf("%s: no control interface found\n", DEVNAM(sc));
> - goto fail;
> - }
> - if (sc->sc_data_iface == NULL) {
> + if (data_ifaceno < 0 || data_ifaceno >= uaa->nifaces) {
> printf("%s: no data interface found\n", DEVNAM(sc));
> goto fail;
> }
> + sc->sc_data_iface = uaa->ifaces[data_ifaceno];
>
> - id = usbd_get_interface_descriptor(ctrl_iface);
> + usbd_claim_iface(sc->sc_udev, uaa->ifaceno);
> + usbd_claim_iface(sc->sc_udev, data_ifaceno);
> +
> + /*
> + * If this is a combined NCM/MBIM function, switch to
> + * alternate setting one to enable MBIM.
> + */
> + id = usbd_get_interface_descriptor(uaa->iface);
> + if (id->bInterfaceClass == UICLASS_CDC &&
> + id->bInterfaceSubClass ==
> + UISUBCLASS_NETWORK_CONTROL_MODEL)
> + usbd_set_interface(uaa->iface, 1);
> +
> + id = usbd_get_interface_descriptor(uaa->iface);
> ctrl_ep = -1;
> for (i = 0; i < id->bNumEndpoints && ctrl_ep == -1; i++) {
> - ed = usbd_interface2endpoint_descriptor(ctrl_iface, i);
> + ed = usbd_interface2endpoint_descriptor(uaa->iface, i);
> if (ed == NULL)
> break;
> if (UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT &&
> @@ -376,23 +362,38 @@ umb_attach(struct device *parent, struct
> goto fail;
> }
>
> + /*
> + * For the MBIM Data Interface, select the appropriate
> + * alternate setting by looking for a matching descriptor that
> + * has two endpoints.
> + */
> cd = usbd_get_config_descriptor(sc->sc_udev);
> - id = usbd_get_interface_descriptor(sc->sc_data_iface);
> - altnum = usbd_get_no_alts(cd, id->bInterfaceNumber);
> - if (MBIM_INTERFACE_ALTSETTING >= altnum) {
> - printf("%s: missing alt setting %d for interface #%d\n",
> - DEVNAM(sc), MBIM_INTERFACE_ALTSETTING, data_ifaceno);
> + altnum = usbd_get_no_alts(cd, data_ifaceno);
> + for (i = 0; i < altnum; i++) {
> + id = usbd_find_idesc(cd, data_ifaceno, i);
> + if (id == NULL)
> + continue;
> + if (id->bInterfaceClass == UICLASS_CDC_DATA &&
> + id->bInterfaceSubClass == UISUBCLASS_DATA &&
> + id->bInterfaceProtocol == UIPROTO_DATA_MBIM &&
> + id->bNumEndpoints == 2)
> + break;
> + }
> + if (i == altnum || id == NULL) {
> + printf("%s: missing alt setting for interface #%d\n",
> + DEVNAM(sc), data_ifaceno);
> goto fail;
> }
> - sc->sc_rx_ep = sc->sc_tx_ep = -1;
> - if ((status = usbd_set_interface(sc->sc_data_iface,
> - MBIM_INTERFACE_ALTSETTING))) {
> + status = usbd_set_interface(sc->sc_data_iface, i);
> + if (status) {
> printf("%s: select alt setting %d for interface #%d "
> - "failed: %s\n", DEVNAM(sc), MBIM_INTERFACE_ALTSETTING,
> - data_ifaceno, usbd_errstr(status));
> + "failed: %s\n", DEVNAM(sc), i, data_ifaceno,
> + usbd_errstr(status));
> goto fail;
> }
> +
> id = usbd_get_interface_descriptor(sc->sc_data_iface);
> + sc->sc_rx_ep = sc->sc_tx_ep = -1;
> for (i = 0; i < id->bNumEndpoints; i++) {
> if ((ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface,
> i)) == NULL)
> @@ -420,7 +421,7 @@ umb_attach(struct device *parent, struct
> USB_TASK_TYPE_GENERIC);
> timeout_set(&sc->sc_statechg_timer, umb_statechg_timeout, sc);
>
> - if (usbd_open_pipe_intr(ctrl_iface, ctrl_ep, USBD_SHORT_XFER_OK,
> + if (usbd_open_pipe_intr(uaa->iface, ctrl_ep, USBD_SHORT_XFER_OK,
> &sc->sc_ctrl_pipe, sc, &sc->sc_intr_msg, sizeof (sc->sc_intr_msg),
> umb_intr, USBD_DEFAULT_INTERVAL)) {
> printf("%s: failed to open control pipe\n", DEVNAM(sc));
>