Subject: Re: [RFC PATCH] qmi_wwan: set correct altsetting for Gobi 1K devices

From: Bjørn Mork <[email-masked]>
Date: Wed, 13 Mar 2013 08:13:40 +0100

Bjørn Mork <[email-masked]> writes:

>
> Risking that I am trying to be too smart again. .. But I really think
> we can fix this in a more generic way by making the probe behave more
> like it did before. How about just letting it continue without erring
> out until it has called usbnet_get_endpoints? That should work without
> needing any quirks.

I was thinking about something like this patch, dropping both the
unnecessary verification of bNumEndpoints and of the CDC functional
descriptors.  If we find a CDC Union, then fine - we use it to locate
the data interface. If we find a CDC Ether, then fine - we use it to set
the MAC address.

This should end up calling usbnet_get_endpoints (first thing
qmi_wwan_register_subdriver does), and succeed if any data interface
altsetting have the necessary endpoints.

There really are few reasons to do any strict verification step in the
probe in this driver, because there really are no usable markers on the
functions. That's why the driver does explicit interface matching in
the first place.  So it makes sense to allow the probe to succeed if
collecting the 3 required endpoints is at all possible.

It will of course succeed on a number of unsupported interfaces if
dynamical IDs are used.  But with nothing to differentiate a QMI/wwan
function from other functions, there is really nothing we can do to
prevent that. If we are to support dynamic IDs, then there will be false
positives.  The user can manually unbind the driver from unwanted
matches in this case.

Only build tested for now. I will test on the devices I have before
submitting, if this works for you:

diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index efb5c7c..1cfa80c 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -139,18 +139,11 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
 
 	BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) < sizeof(struct qmi_wwan_state)));
 
-	/* control and data is shared? */
-	if (intf->cur_altsetting->desc.bNumEndpoints == 3) {
-		info->control = intf;
-		info->data = intf;
-		goto shared;
-	}
-
-	/* else require a single interrupt status endpoint on control intf */
-	if (intf->cur_altsetting->desc.bNumEndpoints != 1)
-		goto err;
+	/* set up initial state */
+	info->control = intf;
+	info->data = intf;
 
-	/* and a number of CDC descriptors */
+	/* some vendors add CDC descriptors */
 	while (len > 3) {
 		struct usb_descriptor_header *h = (void *)buf;
 
@@ -207,25 +200,17 @@ next_desc:
 		buf += h->bLength;
 	}
 
-	/* did we find all the required ones? */
-	if (!(found & (1 << USB_CDC_HEADER_TYPE)) ||
-	    !(found & (1 << USB_CDC_UNION_TYPE))) {
-		dev_err(&intf->dev, "CDC functional descriptors missing\n");
-		goto err;
-	}
-
-	/* verify CDC Union */
-	if (desc->bInterfaceNumber != cdc_union->bMasterInterface0) {
-		dev_err(&intf->dev, "bogus CDC Union: master=%u\n", cdc_union->bMasterInterface0);
-		goto err;
-	}
-
-	/* need to save these for unbind */
-	info->control = intf;
-	info->data = usb_ifnum_to_if(dev->udev,	cdc_union->bSlaveInterface0);
-	if (!info->data) {
-		dev_err(&intf->dev, "bogus CDC Union: slave=%u\n", cdc_union->bSlaveInterface0);
-		goto err;
+	/* using two interfaces if we found a CDC Union */
+	if (cdc_union) {
+		if (desc->bInterfaceNumber != cdc_union->bMasterInterface0) {
+			dev_err(&intf->dev, "bogus CDC Union: master=%u\n", cdc_union->bMasterInterface0);
+			goto err;
+		}
+		info->data = usb_ifnum_to_if(dev->udev,	cdc_union->bSlaveInterface0);
+		if (!info->data) {
+			dev_err(&intf->dev, "bogus CDC Union: slave=%u\n", cdc_union->bSlaveInterface0);
+			goto err;
+		}
 	}
 
 	/* errors aren't fatal - we can live with the dynamic address */
@@ -234,12 +219,13 @@ next_desc:
 		usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress);
 	}
 
-	/* claim data interface and set it up */
-	status = usb_driver_claim_interface(driver, info->data, dev);
-	if (status < 0)
-		goto err;
+	/* claim separate data interface? */
+	if (info->control != info->data) {
+		status = usb_driver_claim_interface(driver, info->data, dev);
+		if (status < 0)
+			goto err;
+	}
 
-shared:
 	status = qmi_wwan_register_subdriver(dev);
 	if (status < 0 && info->control != info->data) {
 		usb_set_intfdata(info->data, NULL);