Great work!  I found some time to test the revised driver this
evening, and I came up with the following proposed changes:

1) ActiveSync RNDIS devices don't have the USB_CDC_HEADER, so I had to
change the ActiveSync-specific checks in usbnet_generic_cdc_bind to
ignore cdc_state.info. Tested and works just fine (it's not used
anywhere in the code as far as I can tell).

Patch (attached): linux-2.6.19-git-cdc_ether-rndis-ignore-header.patch

2) It turns out there aren't any batched IN packets as I said earlier.
After debugging this issue further I discovered that RNDIS_MSG_INIT's
MaxTransferSize, sent by the host, is the value that
usbnet.rx_urb_size should be set to. If this is set to something lower
the device will (obviously) prepare larger URB payloads than what we
expect, and thus we'll fetch partial ones. This results in horrible
performance and makes the device stop responding after attempting a
few transfers.

Patch (attached): linux-2.6.19-git-rndis_host-rx_usb_size-fix.patch

3) To better mimic the Windows driver's behavior I changed
CONTROL_BUFFER_SIZE from 1024 to 1025 bytes. It's highly unlikely that
any devices actually discriminate on this, it just makes for less
differences when comparing the two drivers' traffic.  I also lowered
the control timeout from 10 to 5 seconds, though on second thought it
would probably make more sense to have the driver set this depending
on whether it's an ActiveSync device or not (RNDIS 1.0 vs RNDIS
post-1.0).
I also removed the list of curious issues from one of the comments as
it's not currently known exactly what causes those other devices to
not work, as I've proven myself wrong on every single one of my
earlier assumptions.
After writing a userspace network driver, usb-rndis-ng [1], for the
ActiveSync RNDIS devices, not doing anything special at all, one of
the devices not working with rndis_host just worked (without correct
peripheral alignment, keepalives, using the status endpoint, etc.).
This leads me to believe that it's a timing-issue -- I'll try to find
some time one day to look at this in detail, using my cheezy hardware
USB sniffer to compare the traffic.

Ole André

[1]: http://synce.svn.sourceforge.net/viewvc/synce/trunk/usb-rndis-ng/


On 12/13/06, David Brownell <[EMAIL PROTECTED]> wrote:
From: Ole Andre Vadla Ravnas <[EMAIL PROTECTED]>

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.)

Signed-off-by: Ole Andre Vadla Ravnaas <[EMAIL PROTECTED]>

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

Signed-off-by: David Brownell <[EMAIL PROTECTED]>
---
This is a refresh of a patch last seen around 2.6.17 or so.  Ole, if
this more or less works (ignoring improvements in your version),
please ack ... I'm not sure this is ready for 2.6.20 but it might be.


Index: g26/drivers/usb/net/cdc_ether.c
===================================================================
--- g26.orig/drivers/usb/net/cdc_ether.c        2006-12-07 23:00:34.000000000 
-0800
+++ g26/drivers/usb/net/cdc_ether.c     2006-12-12 15:49:00.000000000 -0800
@@ -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
@@ -35,6 +36,29 @@
 #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 usbne
        /* 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 usbne
                                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,22 @@ 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->header || !info->control || !info->data) {
+                       dev_dbg(&intf->dev,
+                               "activesync: header %p master #0/%p slave 
#1/%p\n",
+                               info->header,
+                               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: g26/drivers/usb/net/rndis_host.c
===================================================================
--- g26.orig/drivers/usb/net/rndis_host.c       2006-12-07 23:00:34.000000000 
-0800
+++ g26/drivers/usb/net/rndis_host.c    2006-12-12 16:25:07.000000000 -0800
@@ -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,13 @@
  *  - control-in:  GET_ENCAPSULATED
  *
  * We'll try to ignore the RESPONSE_AVAILABLE notifications.
+ *
+ * REVISIT some RNDIS implementations seem to have curious issues which
+ * make them really want to:
+ *   - be driven by RESPONSE_AVAILABLE notifications, not polling;
+ *   - get frequent keepalives;
+ *   - batched IN packets (throughput improves, maybe they can't queue)
+ *   - have the host side pre-align OUT packets for them.
  */
 struct rndis_msg_hdr {
        __le32  msg_type;                       /* RNDIS_MSG_* */
@@ -71,7 +80,11 @@ struct rndis_msg_hdr {
        // ... and more
 } __attribute__ ((packed));

-/* RNDIS defines this (absurdly huge) control timeout */
+#define        CONTROL_BUFFER_SIZE             1024
+
+/* RNDIS defines this (absurdly huge) control timeout,
+ * but ActiveSync seems to use a more usual 5 second timeout.
+ */
 #define        RNDIS_CONTROL_TIMEOUT_MS        (10 * 1000)


@@ -270,6 +283,7 @@ static void rndis_status(struct usbnet *
 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 +293,7 @@ static int rndis_command(struct usbnet *
         * 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 +301,12 @@ static int rndis_command(struct usbnet *
                        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 +321,13 @@ static int rndis_command(struct usbnet *
         */
        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 +365,7 @@ static int rndis_command(struct usbnet *
                                        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))
@@ -392,7 +407,7 @@ static int rndis_bind(struct usbnet *dev
        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);
@@ -422,11 +437,19 @@ fail:
        dev_dbg(&intf->dev, "hard mtu %u, align %d\n", dev->hard_mtu,
                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)) {
@@ -434,7 +457,7 @@ fail:
                goto fail;
        }
        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));
@@ -593,6 +616,10 @@ static const struct usb_device_id  produc
        /* 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
 };
Index: g26/drivers/usb/net/Kconfig
===================================================================
--- g26.orig/drivers/usb/net/Kconfig    2006-11-06 13:08:45.000000000 -0800
+++ g26/drivers/usb/net/Kconfig 2006-12-12 15:23:13.000000000 -0800
@@ -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: g26/drivers/usb/core/generic.c
===================================================================
--- g26.orig/drivers/usb/core/generic.c 2006-10-05 19:43:54.000000000 -0700
+++ g26/drivers/usb/core/generic.c      2006-12-12 15:33:07.000000000 -0800
@@ -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,13 +101,11 @@ static int choose_configuration(struct u
                        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) {
+               /* 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))) {
 #ifndef CONFIG_USB_NET_RNDIS_HOST
                        continue;
 #else

--- usb-rndis-lite-orig/cdc_ether.c	2006-12-13 20:59:05.000000000 +0100
+++ usb-rndis-lite/cdc_ether.c	2006-12-13 21:25:53.000000000 +0100
@@ -219,10 +219,9 @@
 	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->header || !info->control || !info->data) {
+		if (!info->control || !info->data) {
 			dev_dbg(&intf->dev,
-				"activesync: header %p master #0/%p slave #1/%p\n",
-				info->header,
+				"activesync: master #0/%p slave #1/%p\n",
 				info->control,
 				info->data);
 			goto bad_desc;
--- usb-rndis-lite-orig/rndis_host.c	2006-12-13 21:33:51.000000000 +0100
+++ usb-rndis-lite/rndis_host.c	2006-12-13 22:35:03.000000000 +0100
@@ -411,13 +411,14 @@
 		goto done;
 
 	net->hard_header_len += sizeof (struct rndis_data_hdr);
+	dev->rx_urb_size = (dev->udev->speed == USB_SPEED_FULL) ? 16384 : 8192;
 
 	/* 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);
+	u.init->max_transfer_size = ccpu2(dev->rx_urb_size);
 
 	retval = rndis_command(dev, u.header);
 	if (unlikely(retval < 0)) {
--- usb-rndis-lite-orig/rndis_host.c	2006-12-13 20:52:40.000000000 +0100
+++ usb-rndis-lite/rndis_host.c	2006-12-13 21:32:39.000000000 +0100
@@ -64,12 +64,8 @@
  *
  * We'll try to ignore the RESPONSE_AVAILABLE notifications.
  *
- * REVISIT some RNDIS implementations seem to have curious issues which
- * make them really want to:
- *   - be driven by RESPONSE_AVAILABLE notifications, not polling;
- *   - get frequent keepalives;
- *   - batched IN packets (throughput improves, maybe they can't queue)
- *   - have the host side pre-align OUT packets for them.
+ * REVISIT some RNDIS implementations seem to have curious issues still
+ * to be resolved.
  */
 struct rndis_msg_hdr {
 	__le32	msg_type;			/* RNDIS_MSG_* */
@@ -80,12 +76,12 @@
 	// ... and more
 } __attribute__ ((packed));
 
-#define	CONTROL_BUFFER_SIZE		1024
+#define	CONTROL_BUFFER_SIZE		1025
 
 /* RNDIS defines this (absurdly huge) control timeout,
  * but ActiveSync seems to use a more usual 5 second timeout.
  */
-#define	RNDIS_CONTROL_TIMEOUT_MS	(10 * 1000)
+#define	RNDIS_CONTROL_TIMEOUT_MS	(5 * 1000)
 
 
 #define ccpu2 __constant_cpu_to_le32
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to