rndis_host learns ActiveSync basics
authorOle Andre Vadla Ravnas <oleavr@gmail.com>
Fri, 15 Dec 2006 00:01:28 +0000 (16:01 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 7 Feb 2007 23:44:33 +0000 (15:44 -0800)
Windows Mobile 5 based devices described as supporting "ActiveSync":

 - Speak RNDIS but lack the CDC and union descriptors.  This patch
   updates the cdc ethernet code to fake ACM descriptors we need.

 - Require RNDIS_MSG_QUERY messages to include a buffer of the size the
   response should generate.  This patch updates the rndis host code to
   pass this will-be-ignored data.

The resulting RNDIS host code has been reported to work with several
WM5 based devices.

(Note that a fancier patch is available at synce.sf.net.)

Some bugfixes, affecting not just ActiveSync:
    (a) when cleaning up after RNDS init fails, scrub the second interface
just like cdc_ether does, so disconnect won't oops.
    (b) handle peripherals that use the pad-to-end-of-packet option; some
devices can't talk to us if that option doesn't work.
    (c) when choosing configurations, don't forget about an RNDIS config
just because the RNDIS driver is dynamically linked.

Cleanup, streamlining, bugfixes, Kconfig, and matching hub driver update.
Also for paranoia's sake, refuse to talk to something that looks like a
real modem instead of RNDIS.

Signed-off-by: Ole Andre Vadla Ravnaas <oleavr@gmail.com>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/generic.c
drivers/usb/net/Kconfig
drivers/usb/net/cdc_ether.c
drivers/usb/net/rndis_host.c

index ebb20ff7ac583b41ace5b1ed8c2201fe35667f28..b531a4fd30c228b47fda66611fa304c75657eea2 100644 (file)
@@ -25,6 +25,20 @@ static inline const char *plural(int n)
        return (n == 1 ? "" : "s");
 }
 
+static int is_rndis(struct usb_interface_descriptor *desc)
+{
+       return desc->bInterfaceClass == USB_CLASS_COMM
+               && desc->bInterfaceSubClass == 2
+               && desc->bInterfaceProtocol == 0xff;
+}
+
+static int is_activesync(struct usb_interface_descriptor *desc)
+{
+       return desc->bInterfaceClass == USB_CLASS_MISC
+               && desc->bInterfaceSubClass == 1
+               && desc->bInterfaceProtocol == 1;
+}
+
 static int choose_configuration(struct usb_device *udev)
 {
        int i;
@@ -87,14 +101,12 @@ static int choose_configuration(struct usb_device *udev)
                        continue;
                }
 
-               /* If the first config's first interface is COMM/2/0xff
-                * (MSFT RNDIS), rule it out unless Linux has host-side
-                * RNDIS support. */
-               if (i == 0 && desc
-                               && desc->bInterfaceClass == USB_CLASS_COMM
-                               && desc->bInterfaceSubClass == 2
-                               && desc->bInterfaceProtocol == 0xff) {
-#ifndef CONFIG_USB_NET_RNDIS_HOST
+               /* When the first config's first interface is one of Microsoft's
+                * pet nonstandard Ethernet-over-USB protocols, ignore it unless
+                * this kernel has enabled the necessary host side driver.
+                */
+               if (i == 0 && desc && (is_rndis(desc) || is_activesync(desc))) {
+#if !defined(CONFIG_USB_NET_RNDIS_HOST) && !defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)
                        continue;
 #else
                        best = c;
index e081836014aca5a65172c4de7437a07ce37a8fe3..a2b94ef512bce95551add149d0edc0ec9f7acd67 100644 (file)
@@ -222,13 +222,15 @@ config USB_NET_MCS7830
          adapters marketed under the DeLOCK brand.
 
 config USB_NET_RNDIS_HOST
-       tristate "Host for RNDIS devices (EXPERIMENTAL)"
+       tristate "Host for RNDIS and ActiveSync devices (EXPERIMENTAL)"
        depends on USB_USBNET && EXPERIMENTAL
        select USB_NET_CDCETHER
        help
          This option enables hosting "Remote NDIS" USB networking links,
          as encouraged by Microsoft (instead of CDC Ethernet!) for use in
-         various devices that may only support this protocol.
+         various devices that may only support this protocol.  A variant
+         of this protocol (with even less public documentation) seems to
+         be at the root of Microsoft's "ActiveSync" too.
 
          Avoid using this protocol unless you have no better options.
          The protocol specification is incomplete, and is controlled by
index 44a91547146e21958df2196ea174a7a90413a395..e5cdafa258dd5e8955a3e2449fe5f605d9567fc7 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * CDC Ethernet based networking peripherals
  * Copyright (C) 2003-2005 by David Brownell
+ * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync)
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include "usbnet.h"
 
 
+#if defined(CONFIG_USB_NET_RNDIS_HOST) || defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)
+
+static int is_rndis(struct usb_interface_descriptor *desc)
+{
+       return desc->bInterfaceClass == USB_CLASS_COMM
+               && desc->bInterfaceSubClass == 2
+               && desc->bInterfaceProtocol == 0xff;
+}
+
+static int is_activesync(struct usb_interface_descriptor *desc)
+{
+       return desc->bInterfaceClass == USB_CLASS_MISC
+               && desc->bInterfaceSubClass == 1
+               && desc->bInterfaceProtocol == 1;
+}
+
+#else
+
+#define is_rndis(desc)         0
+#define is_activesync(desc)    0
+
+#endif
+
 /*
  * probes control interface, claims data interface, collects the bulk
  * endpoints, activates data interface (if needed), maybe sets MTU.
@@ -71,7 +95,8 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
        /* this assumes that if there's a non-RNDIS vendor variant
         * of cdc-acm, it'll fail RNDIS requests cleanly.
         */
-       rndis = (intf->cur_altsetting->desc.bInterfaceProtocol == 0xff);
+       rndis = is_rndis(&intf->cur_altsetting->desc)
+               || is_activesync(&intf->cur_altsetting->desc);
 
        memset(info, 0, sizeof *info);
        info->control = intf;
@@ -99,6 +124,23 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
                                goto bad_desc;
                        }
                        break;
+               case USB_CDC_ACM_TYPE:
+                       /* paranoia:  disambiguate a "real" vendor-specific
+                        * modem interface from an RNDIS non-modem.
+                        */
+                       if (rndis) {
+                               struct usb_cdc_acm_descriptor *d;
+
+                               d = (void *) buf;
+                               if (d->bmCapabilities) {
+                                       dev_dbg(&intf->dev,
+                                               "ACM capabilities %02x, "
+                                               "not really RNDIS?\n",
+                                               d->bmCapabilities);
+                                       goto bad_desc;
+                               }
+                       }
+                       break;
                case USB_CDC_UNION_TYPE:
                        if (info->u) {
                                dev_dbg(&intf->dev, "extra CDC union\n");
@@ -171,7 +213,21 @@ next_desc:
                buf += buf [0];
        }
 
-       if (!info->header || !info->u || (!rndis && !info->ether)) {
+       /* Microsoft ActiveSync based RNDIS devices lack the CDC descriptors,
+        * so we'll hard-wire the interfaces and not check for descriptors.
+        */
+       if (is_activesync(&intf->cur_altsetting->desc) && !info->u) {
+               info->control = usb_ifnum_to_if(dev->udev, 0);
+               info->data = usb_ifnum_to_if(dev->udev, 1);
+               if (!info->control || !info->data) {
+                       dev_dbg(&intf->dev,
+                               "activesync: master #0/%p slave #1/%p\n",
+                               info->control,
+                               info->data);
+                       goto bad_desc;
+               }
+
+       } else if (!info->header || !info->u || (!rndis && !info->ether)) {
                dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n",
                        info->header ? "" : "header ",
                        info->u ? "" : "union ",
index a322a16d9cf8a52f0548dc2845a1b47534196801..be888d2d813ca4293d5b74a816fcbc40915728ca 100644 (file)
@@ -49,6 +49,8 @@
  *    - In some cases, MS-Windows will emit undocumented requests; this
  *     matters more to peripheral implementations than host ones.
  *
+ * Moreover there's a no-open-specs variant of RNDIS called "ActiveSync".
+ *
  * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in
  * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and
  * currently rare) "Ethernet Emulation Model" (EEM).
@@ -61,6 +63,9 @@
  *  - control-in:  GET_ENCAPSULATED
  *
  * We'll try to ignore the RESPONSE_AVAILABLE notifications.
+ *
+ * REVISIT some RNDIS implementations seem to have curious issues still
+ * to be resolved.
  */
 struct rndis_msg_hdr {
        __le32  msg_type;                       /* RNDIS_MSG_* */
@@ -71,8 +76,14 @@ struct rndis_msg_hdr {
        // ... and more
 } __attribute__ ((packed));
 
-/* RNDIS defines this (absurdly huge) control timeout */
-#define        RNDIS_CONTROL_TIMEOUT_MS        (10 * 1000)
+/* MS-Windows uses this strange size, but RNDIS spec says 1024 minimum */
+#define        CONTROL_BUFFER_SIZE             1025
+
+/* RNDIS defines an (absurdly huge) 10 second control timeout,
+ * but ActiveSync seems to use a more usual 5 second timeout
+ * (which matches the USB 2.0 spec).
+ */
+#define        RNDIS_CONTROL_TIMEOUT_MS        (5 * 1000)
 
 
 #define ccpu2 __constant_cpu_to_le32
@@ -270,6 +281,7 @@ static void rndis_status(struct usbnet *dev, struct urb *urb)
 static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf)
 {
        struct cdc_state        *info = (void *) &dev->data;
+       int                     master_ifnum;
        int                     retval;
        unsigned                count;
        __le32                  rsp;
@@ -279,7 +291,7 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf)
         * disconnect(): either serialize, or dispatch responses on xid
         */
 
-       /* Issue the request; don't bother byteswapping our xid */
+       /* Issue the request; xid is unique, don't bother byteswapping it */
        if (likely(buf->msg_type != RNDIS_MSG_HALT
                        && buf->msg_type != RNDIS_MSG_RESET)) {
                xid = dev->xid++;
@@ -287,11 +299,12 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf)
                        xid = dev->xid++;
                buf->request_id = (__force __le32) xid;
        }
+       master_ifnum = info->control->cur_altsetting->desc.bInterfaceNumber;
        retval = usb_control_msg(dev->udev,
                usb_sndctrlpipe(dev->udev, 0),
                USB_CDC_SEND_ENCAPSULATED_COMMAND,
                USB_TYPE_CLASS | USB_RECIP_INTERFACE,
-               0, info->u->bMasterInterface0,
+               0, master_ifnum,
                buf, le32_to_cpu(buf->msg_len),
                RNDIS_CONTROL_TIMEOUT_MS);
        if (unlikely(retval < 0 || xid == 0))
@@ -306,13 +319,13 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf)
         */
        rsp = buf->msg_type | RNDIS_MSG_COMPLETION;
        for (count = 0; count < 10; count++) {
-               memset(buf, 0, 1024);
+               memset(buf, 0, CONTROL_BUFFER_SIZE);
                retval = usb_control_msg(dev->udev,
                        usb_rcvctrlpipe(dev->udev, 0),
                        USB_CDC_GET_ENCAPSULATED_RESPONSE,
                        USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
-                       0, info->u->bMasterInterface0,
-                       buf, 1024,
+                       0, master_ifnum,
+                       buf, CONTROL_BUFFER_SIZE,
                        RNDIS_CONTROL_TIMEOUT_MS);
                if (likely(retval >= 8)) {
                        msg_len = le32_to_cpu(buf->msg_len);
@@ -350,7 +363,7 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf)
                                        usb_sndctrlpipe(dev->udev, 0),
                                        USB_CDC_SEND_ENCAPSULATED_COMMAND,
                                        USB_TYPE_CLASS | USB_RECIP_INTERFACE,
-                                       0, info->u->bMasterInterface0,
+                                       0, master_ifnum,
                                        msg, sizeof *msg,
                                        RNDIS_CONTROL_TIMEOUT_MS);
                                if (unlikely(retval < 0))
@@ -393,38 +406,64 @@ static int rndis_bind(struct usbnet *dev, struct usb_interface *intf)
        u32                     tmp;
 
        /* we can't rely on i/o from stack working, or stack allocation */
-       u.buf = kmalloc(1024, GFP_KERNEL);
+       u.buf = kmalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL);
        if (!u.buf)
                return -ENOMEM;
        retval = usbnet_generic_cdc_bind(dev, intf);
        if (retval < 0)
                goto fail;
 
-       net->hard_header_len += sizeof (struct rndis_data_hdr);
-
-       /* initialize; max transfer is 16KB at full speed */
        u.init->msg_type = RNDIS_MSG_INIT;
        u.init->msg_len = ccpu2(sizeof *u.init);
        u.init->major_version = ccpu2(1);
        u.init->minor_version = ccpu2(0);
-       u.init->max_transfer_size = ccpu2(net->mtu + net->hard_header_len);
 
+       /* max transfer (in spec) is 0x4000 at full speed, but for
+        * TX we'll stick to one Ethernet packet plus RNDIS framing.
+        * For RX we handle drivers that zero-pad to end-of-packet.
+        * Don't let userspace change these settings.
+        */
+       net->hard_header_len += sizeof (struct rndis_data_hdr);
+       dev->hard_mtu = net->mtu + net->hard_header_len;
+
+       dev->rx_urb_size = dev->hard_mtu + (dev->maxpacket + 1);
+       dev->rx_urb_size &= ~(dev->maxpacket - 1);
+       u.init->max_transfer_size = cpu_to_le32(dev->rx_urb_size);
+
+       net->change_mtu = NULL;
        retval = rndis_command(dev, u.header);
        if (unlikely(retval < 0)) {
                /* it might not even be an RNDIS device!! */
                dev_err(&intf->dev, "RNDIS init failed, %d\n", retval);
+               goto fail_and_release;
+       }
+       tmp = le32_to_cpu(u.init_c->max_transfer_size);
+       if (tmp < dev->hard_mtu) {
+               dev_err(&intf->dev,
+                       "dev can't take %u byte packets (max %u)\n",
+                       dev->hard_mtu, tmp);
                goto fail_and_release;
        }
-       dev->hard_mtu = le32_to_cpu(u.init_c->max_transfer_size);
+
        /* REVISIT:  peripheral "alignment" request is ignored ... */
-       dev_dbg(&intf->dev, "hard mtu %u, align %d\n", dev->hard_mtu,
+       dev_dbg(&intf->dev,
+               "hard mtu %u (%u from dev), rx buflen %Zu, align %d\n",
+               dev->hard_mtu, tmp, dev->rx_urb_size,
                1 << le32_to_cpu(u.init_c->packet_alignment));
 
-       /* get designated host ethernet address */
-       memset(u.get, 0, sizeof *u.get);
+       /* Get designated host ethernet address.
+        *
+        * Adding a payload exactly the same size as the expected response
+        * payload is an evident requirement MSFT added for ActiveSync.
+        * This undocumented (and nonsensical) issue was found by sniffing
+        * protocol requests from the ActiveSync 4.1 Windows driver.
+        */
+       memset(u.get, 0, sizeof *u.get + 48);
        u.get->msg_type = RNDIS_MSG_QUERY;
-       u.get->msg_len = ccpu2(sizeof *u.get);
+       u.get->msg_len = ccpu2(sizeof *u.get + 48);
        u.get->oid = OID_802_3_PERMANENT_ADDRESS;
+       u.get->len = ccpu2(48);
+       u.get->offset = ccpu2(20);
 
        retval = rndis_command(dev, u.header);
        if (unlikely(retval < 0)) {
@@ -432,7 +471,7 @@ static int rndis_bind(struct usbnet *dev, struct usb_interface *intf)
                goto fail_and_release;
        }
        tmp = le32_to_cpu(u.get_c->offset);
-       if (unlikely((tmp + 8) > (1024 - ETH_ALEN)
+       if (unlikely((tmp + 8) > (CONTROL_BUFFER_SIZE - ETH_ALEN)
                        || u.get_c->len != ccpu2(ETH_ALEN))) {
                dev_err(&intf->dev, "rndis ethaddr off %d len %d ?\n",
                        tmp, le32_to_cpu(u.get_c->len));
@@ -598,6 +637,10 @@ static const struct usb_device_id  products [] = {
        /* RNDIS is MSFT's un-official variant of CDC ACM */
        USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
        .driver_info = (unsigned long) &rndis_info,
+}, {
+       /* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */
+       USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1),
+       .driver_info = (unsigned long) &rndis_info,
 },
        { },            // END
 };