From: Ole Andre Vadla Ravnas Date: Fri, 15 Dec 2006 00:01:28 +0000 (-0800) Subject: rndis_host learns ActiveSync basics X-Git-Tag: v2.6.21-rc2~42^2~9^2~52 X-Git-Url: http://pilppa.com/gitweb/?a=commitdiff_plain;h=ad55d71a3d4401f44b4ddee1412283c99eedd05c;p=linux-2.6-omap-h63xx.git rndis_host learns ActiveSync basics 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 Signed-off-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index ebb20ff7ac5..b531a4fd30c 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -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; diff --git a/drivers/usb/net/Kconfig b/drivers/usb/net/Kconfig index e081836014a..a2b94ef512b 100644 --- a/drivers/usb/net/Kconfig +++ b/drivers/usb/net/Kconfig @@ -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 diff --git a/drivers/usb/net/cdc_ether.c b/drivers/usb/net/cdc_ether.c index 44a91547146..e5cdafa258d 100644 --- a/drivers/usb/net/cdc_ether.c +++ b/drivers/usb/net/cdc_ether.c @@ -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 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 ", diff --git a/drivers/usb/net/rndis_host.c b/drivers/usb/net/rndis_host.c index a322a16d9cf..be888d2d813 100644 --- a/drivers/usb/net/rndis_host.c +++ b/drivers/usb/net/rndis_host.c @@ -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 };