net: cdc_ncm/cdc_mbim: rework probing of NCM/MBIM functions
authorBjørn Mork <bjorn@mork.no>
Sun, 11 May 2014 08:47:15 +0000 (10:47 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 13 May 2014 21:46:09 +0000 (17:46 -0400)
The NCM class match in the cdc_mbim driver is confusing and
cause unexpected behaviour. The USB core guarantees that a
USB interface is in altsetting 0 when probing starts. This
means that devices implementing a NCM 1.0 backwards
compatible MBIM function (a "NCM/MBIM function") always hit
the NCM entry in the cdc_mbim driver match table. Such
functions will never match any of the MBIM entries.

This causes unexpeced behaviour for cases where the NCM and
MBIM entries are differet, which is currently the case for
all except Ericsson devices.

Improve the probing of NCM/MBIM functions by looking up the
device again in the cdc_mbim match table after switching to
the MBIM identity.

The shared altsetting selection is updated to better
accommodate the new probing logic, returning the preferred
altsetting for the control interface instead of the data
interface. The control interface altsetting update is moved
to the cdc_mbim driver. It is never necessary to change the
control interface altsetting for NCM.

Cc: Greg Suarez <gsuarez@smithmicro.com>
Reported by: Yu-an Shih <yshih@nvidia.com>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/cdc_mbim.c
drivers/net/usb/cdc_ncm.c
include/linux/usb/cdc_ncm.h

index 80d2771..bc23273 100644 (file)
@@ -107,15 +107,54 @@ static const struct net_device_ops cdc_mbim_netdev_ops = {
        .ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid,
 };
 
+/* Change the control interface altsetting and update the .driver_info
+ * pointer if the matching entry after changing class codes points to
+ * a different struct
+ */
+static int cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt)
+{
+       struct usb_driver *driver = to_usb_driver(intf->dev.driver);
+       const struct usb_device_id *id;
+       struct driver_info *info;
+       int ret;
+
+       ret = usb_set_interface(dev->udev,
+                               intf->cur_altsetting->desc.bInterfaceNumber,
+                               alt);
+       if (ret)
+               return ret;
+
+       id = usb_match_id(intf, driver->id_table);
+       if (!id)
+               return -ENODEV;
+
+       info = (struct driver_info *)id->driver_info;
+       if (info != dev->driver_info) {
+               dev_dbg(&intf->dev, "driver_info updated to '%s'\n",
+                       info->description);
+               dev->driver_info = info;
+       }
+       return 0;
+}
+
 static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
 {
        struct cdc_ncm_ctx *ctx;
        struct usb_driver *subdriver = ERR_PTR(-ENODEV);
        int ret = -ENODEV;
-       u8 data_altsetting = cdc_ncm_select_altsetting(dev, intf);
+       u8 data_altsetting = 1;
        struct cdc_mbim_state *info = (void *)&dev->data;
 
-       /* Probably NCM, defer for cdc_ncm_bind */
+       /* should we change control altsetting on a NCM/MBIM function? */
+       if (cdc_ncm_select_altsetting(intf) == CDC_NCM_COMM_ALTSETTING_MBIM) {
+               data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM;
+               ret = cdc_mbim_set_ctrlalt(dev, intf, CDC_NCM_COMM_ALTSETTING_MBIM);
+               if (ret)
+                       goto err;
+               ret = -ENODEV;
+       }
+
+       /* we will hit this for NCM/MBIM functions if prefer_mbim is false */
        if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
                goto err;
 
index 9a2bd11..d23bca5 100644 (file)
@@ -541,10 +541,10 @@ void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
 }
 EXPORT_SYMBOL_GPL(cdc_ncm_unbind);
 
-/* Select the MBIM altsetting iff it is preferred and available,
- * returning the number of the corresponding data interface altsetting
+/* Return the number of the MBIM control interface altsetting iff it
+ * is preferred and available,
  */
-u8 cdc_ncm_select_altsetting(struct usbnet *dev, struct usb_interface *intf)
+u8 cdc_ncm_select_altsetting(struct usb_interface *intf)
 {
        struct usb_host_interface *alt;
 
@@ -563,15 +563,15 @@ u8 cdc_ncm_select_altsetting(struct usbnet *dev, struct usb_interface *intf)
         *   the rules given in section 6 (USB Device Model) of this
         *   specification."
         */
-       if (prefer_mbim && intf->num_altsetting == 2) {
+       if (intf->num_altsetting < 2)
+               return intf->cur_altsetting->desc.bAlternateSetting;
+
+       if (prefer_mbim) {
                alt = usb_altnum_to_altsetting(intf, CDC_NCM_COMM_ALTSETTING_MBIM);
-               if (alt && cdc_ncm_comm_intf_is_mbim(alt) &&
-                   !usb_set_interface(dev->udev,
-                                      intf->cur_altsetting->desc.bInterfaceNumber,
-                                      CDC_NCM_COMM_ALTSETTING_MBIM))
-                       return CDC_NCM_DATA_ALTSETTING_MBIM;
+               if (alt && cdc_ncm_comm_intf_is_mbim(alt))
+                       return CDC_NCM_COMM_ALTSETTING_MBIM;
        }
-       return CDC_NCM_DATA_ALTSETTING_NCM;
+       return CDC_NCM_COMM_ALTSETTING_NCM;
 }
 EXPORT_SYMBOL_GPL(cdc_ncm_select_altsetting);
 
@@ -580,12 +580,11 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
        int ret;
 
        /* MBIM backwards compatible function? */
-       cdc_ncm_select_altsetting(dev, intf);
-       if (cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
+       if (cdc_ncm_select_altsetting(intf) != CDC_NCM_COMM_ALTSETTING_NCM)
                return -ENODEV;
 
-       /* NCM data altsetting is always 1 */
-       ret = cdc_ncm_bind_common(dev, intf, 1);
+       /* The NCM data altsetting is fixed */
+       ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM);
 
        /*
         * We should get an event when network connection is "connected" or
index 44b38b9..55b6fee 100644 (file)
@@ -121,7 +121,7 @@ struct cdc_ncm_ctx {
        u16 connected;
 };
 
-u8 cdc_ncm_select_altsetting(struct usbnet *dev, struct usb_interface *intf);
+u8 cdc_ncm_select_altsetting(struct usb_interface *intf);
 int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting);
 void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf);
 struct sk_buff *cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign);