Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <oleavr@gmail.com>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>


authored by

Ole Andre Vadla Ravnas and committed by
Greg Kroah-Hartman
ad55d71a 11d54898

+144 -31
+20 -8
drivers/usb/core/generic.c
··· 25 25 return (n == 1 ? "" : "s"); 26 26 } 27 27 28 + static int is_rndis(struct usb_interface_descriptor *desc) 29 + { 30 + return desc->bInterfaceClass == USB_CLASS_COMM 31 + && desc->bInterfaceSubClass == 2 32 + && desc->bInterfaceProtocol == 0xff; 33 + } 34 + 35 + static int is_activesync(struct usb_interface_descriptor *desc) 36 + { 37 + return desc->bInterfaceClass == USB_CLASS_MISC 38 + && desc->bInterfaceSubClass == 1 39 + && desc->bInterfaceProtocol == 1; 40 + } 41 + 28 42 static int choose_configuration(struct usb_device *udev) 29 43 { 30 44 int i; ··· 101 87 continue; 102 88 } 103 89 104 - /* If the first config's first interface is COMM/2/0xff 105 - * (MSFT RNDIS), rule it out unless Linux has host-side 106 - * RNDIS support. */ 107 - if (i == 0 && desc 108 - && desc->bInterfaceClass == USB_CLASS_COMM 109 - && desc->bInterfaceSubClass == 2 110 - && desc->bInterfaceProtocol == 0xff) { 111 - #ifndef CONFIG_USB_NET_RNDIS_HOST 90 + /* When the first config's first interface is one of Microsoft's 91 + * pet nonstandard Ethernet-over-USB protocols, ignore it unless 92 + * this kernel has enabled the necessary host side driver. 93 + */ 94 + if (i == 0 && desc && (is_rndis(desc) || is_activesync(desc))) { 95 + #if !defined(CONFIG_USB_NET_RNDIS_HOST) && !defined(CONFIG_USB_NET_RNDIS_HOST_MODULE) 112 96 continue; 113 97 #else 114 98 best = c;
+4 -2
drivers/usb/net/Kconfig
··· 222 222 adapters marketed under the DeLOCK brand. 223 223 224 224 config USB_NET_RNDIS_HOST 225 - tristate "Host for RNDIS devices (EXPERIMENTAL)" 225 + tristate "Host for RNDIS and ActiveSync devices (EXPERIMENTAL)" 226 226 depends on USB_USBNET && EXPERIMENTAL 227 227 select USB_NET_CDCETHER 228 228 help 229 229 This option enables hosting "Remote NDIS" USB networking links, 230 230 as encouraged by Microsoft (instead of CDC Ethernet!) for use in 231 - various devices that may only support this protocol. 231 + various devices that may only support this protocol. A variant 232 + of this protocol (with even less public documentation) seems to 233 + be at the root of Microsoft's "ActiveSync" too. 232 234 233 235 Avoid using this protocol unless you have no better options. 234 236 The protocol specification is incomplete, and is controlled by
+58 -2
drivers/usb/net/cdc_ether.c
··· 1 1 /* 2 2 * CDC Ethernet based networking peripherals 3 3 * Copyright (C) 2003-2005 by David Brownell 4 + * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync) 4 5 * 5 6 * This program is free software; you can redistribute it and/or modify 6 7 * it under the terms of the GNU General Public License as published by ··· 35 34 36 35 #include "usbnet.h" 37 36 37 + 38 + #if defined(CONFIG_USB_NET_RNDIS_HOST) || defined(CONFIG_USB_NET_RNDIS_HOST_MODULE) 39 + 40 + static int is_rndis(struct usb_interface_descriptor *desc) 41 + { 42 + return desc->bInterfaceClass == USB_CLASS_COMM 43 + && desc->bInterfaceSubClass == 2 44 + && desc->bInterfaceProtocol == 0xff; 45 + } 46 + 47 + static int is_activesync(struct usb_interface_descriptor *desc) 48 + { 49 + return desc->bInterfaceClass == USB_CLASS_MISC 50 + && desc->bInterfaceSubClass == 1 51 + && desc->bInterfaceProtocol == 1; 52 + } 53 + 54 + #else 55 + 56 + #define is_rndis(desc) 0 57 + #define is_activesync(desc) 0 58 + 59 + #endif 38 60 39 61 /* 40 62 * probes control interface, claims data interface, collects the bulk ··· 95 71 /* this assumes that if there's a non-RNDIS vendor variant 96 72 * of cdc-acm, it'll fail RNDIS requests cleanly. 97 73 */ 98 - rndis = (intf->cur_altsetting->desc.bInterfaceProtocol == 0xff); 74 + rndis = is_rndis(&intf->cur_altsetting->desc) 75 + || is_activesync(&intf->cur_altsetting->desc); 99 76 100 77 memset(info, 0, sizeof *info); 101 78 info->control = intf; ··· 122 97 dev_dbg(&intf->dev, "CDC header len %u\n", 123 98 info->header->bLength); 124 99 goto bad_desc; 100 + } 101 + break; 102 + case USB_CDC_ACM_TYPE: 103 + /* paranoia: disambiguate a "real" vendor-specific 104 + * modem interface from an RNDIS non-modem. 105 + */ 106 + if (rndis) { 107 + struct usb_cdc_acm_descriptor *d; 108 + 109 + d = (void *) buf; 110 + if (d->bmCapabilities) { 111 + dev_dbg(&intf->dev, 112 + "ACM capabilities %02x, " 113 + "not really RNDIS?\n", 114 + d->bmCapabilities); 115 + goto bad_desc; 116 + } 125 117 } 126 118 break; 127 119 case USB_CDC_UNION_TYPE: ··· 213 171 buf += buf [0]; 214 172 } 215 173 216 - if (!info->header || !info->u || (!rndis && !info->ether)) { 174 + /* Microsoft ActiveSync based RNDIS devices lack the CDC descriptors, 175 + * so we'll hard-wire the interfaces and not check for descriptors. 176 + */ 177 + if (is_activesync(&intf->cur_altsetting->desc) && !info->u) { 178 + info->control = usb_ifnum_to_if(dev->udev, 0); 179 + info->data = usb_ifnum_to_if(dev->udev, 1); 180 + if (!info->control || !info->data) { 181 + dev_dbg(&intf->dev, 182 + "activesync: master #0/%p slave #1/%p\n", 183 + info->control, 184 + info->data); 185 + goto bad_desc; 186 + } 187 + 188 + } else if (!info->header || !info->u || (!rndis && !info->ether)) { 217 189 dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n", 218 190 info->header ? "" : "header ", 219 191 info->u ? "" : "union ",
+62 -19
drivers/usb/net/rndis_host.c
··· 49 49 * - In some cases, MS-Windows will emit undocumented requests; this 50 50 * matters more to peripheral implementations than host ones. 51 51 * 52 + * Moreover there's a no-open-specs variant of RNDIS called "ActiveSync". 53 + * 52 54 * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in 53 55 * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and 54 56 * currently rare) "Ethernet Emulation Model" (EEM). ··· 63 61 * - control-in: GET_ENCAPSULATED 64 62 * 65 63 * We'll try to ignore the RESPONSE_AVAILABLE notifications. 64 + * 65 + * REVISIT some RNDIS implementations seem to have curious issues still 66 + * to be resolved. 66 67 */ 67 68 struct rndis_msg_hdr { 68 69 __le32 msg_type; /* RNDIS_MSG_* */ ··· 76 71 // ... and more 77 72 } __attribute__ ((packed)); 78 73 79 - /* RNDIS defines this (absurdly huge) control timeout */ 80 - #define RNDIS_CONTROL_TIMEOUT_MS (10 * 1000) 74 + /* MS-Windows uses this strange size, but RNDIS spec says 1024 minimum */ 75 + #define CONTROL_BUFFER_SIZE 1025 76 + 77 + /* RNDIS defines an (absurdly huge) 10 second control timeout, 78 + * but ActiveSync seems to use a more usual 5 second timeout 79 + * (which matches the USB 2.0 spec). 80 + */ 81 + #define RNDIS_CONTROL_TIMEOUT_MS (5 * 1000) 81 82 82 83 83 84 #define ccpu2 __constant_cpu_to_le32 ··· 281 270 static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) 282 271 { 283 272 struct cdc_state *info = (void *) &dev->data; 273 + int master_ifnum; 284 274 int retval; 285 275 unsigned count; 286 276 __le32 rsp; ··· 291 279 * disconnect(): either serialize, or dispatch responses on xid 292 280 */ 293 281 294 - /* Issue the request; don't bother byteswapping our xid */ 282 + /* Issue the request; xid is unique, don't bother byteswapping it */ 295 283 if (likely(buf->msg_type != RNDIS_MSG_HALT 296 284 && buf->msg_type != RNDIS_MSG_RESET)) { 297 285 xid = dev->xid++; ··· 299 287 xid = dev->xid++; 300 288 buf->request_id = (__force __le32) xid; 301 289 } 290 + master_ifnum = info->control->cur_altsetting->desc.bInterfaceNumber; 302 291 retval = usb_control_msg(dev->udev, 303 292 usb_sndctrlpipe(dev->udev, 0), 304 293 USB_CDC_SEND_ENCAPSULATED_COMMAND, 305 294 USB_TYPE_CLASS | USB_RECIP_INTERFACE, 306 - 0, info->u->bMasterInterface0, 295 + 0, master_ifnum, 307 296 buf, le32_to_cpu(buf->msg_len), 308 297 RNDIS_CONTROL_TIMEOUT_MS); 309 298 if (unlikely(retval < 0 || xid == 0)) ··· 319 306 */ 320 307 rsp = buf->msg_type | RNDIS_MSG_COMPLETION; 321 308 for (count = 0; count < 10; count++) { 322 - memset(buf, 0, 1024); 309 + memset(buf, 0, CONTROL_BUFFER_SIZE); 323 310 retval = usb_control_msg(dev->udev, 324 311 usb_rcvctrlpipe(dev->udev, 0), 325 312 USB_CDC_GET_ENCAPSULATED_RESPONSE, 326 313 USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 327 - 0, info->u->bMasterInterface0, 328 - buf, 1024, 314 + 0, master_ifnum, 315 + buf, CONTROL_BUFFER_SIZE, 329 316 RNDIS_CONTROL_TIMEOUT_MS); 330 317 if (likely(retval >= 8)) { 331 318 msg_len = le32_to_cpu(buf->msg_len); ··· 363 350 usb_sndctrlpipe(dev->udev, 0), 364 351 USB_CDC_SEND_ENCAPSULATED_COMMAND, 365 352 USB_TYPE_CLASS | USB_RECIP_INTERFACE, 366 - 0, info->u->bMasterInterface0, 353 + 0, master_ifnum, 367 354 msg, sizeof *msg, 368 355 RNDIS_CONTROL_TIMEOUT_MS); 369 356 if (unlikely(retval < 0)) ··· 406 393 u32 tmp; 407 394 408 395 /* we can't rely on i/o from stack working, or stack allocation */ 409 - u.buf = kmalloc(1024, GFP_KERNEL); 396 + u.buf = kmalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL); 410 397 if (!u.buf) 411 398 return -ENOMEM; 412 399 retval = usbnet_generic_cdc_bind(dev, intf); 413 400 if (retval < 0) 414 401 goto fail; 415 402 416 - net->hard_header_len += sizeof (struct rndis_data_hdr); 417 - 418 - /* initialize; max transfer is 16KB at full speed */ 419 403 u.init->msg_type = RNDIS_MSG_INIT; 420 404 u.init->msg_len = ccpu2(sizeof *u.init); 421 405 u.init->major_version = ccpu2(1); 422 406 u.init->minor_version = ccpu2(0); 423 - u.init->max_transfer_size = ccpu2(net->mtu + net->hard_header_len); 424 407 408 + /* max transfer (in spec) is 0x4000 at full speed, but for 409 + * TX we'll stick to one Ethernet packet plus RNDIS framing. 410 + * For RX we handle drivers that zero-pad to end-of-packet. 411 + * Don't let userspace change these settings. 412 + */ 413 + net->hard_header_len += sizeof (struct rndis_data_hdr); 414 + dev->hard_mtu = net->mtu + net->hard_header_len; 415 + 416 + dev->rx_urb_size = dev->hard_mtu + (dev->maxpacket + 1); 417 + dev->rx_urb_size &= ~(dev->maxpacket - 1); 418 + u.init->max_transfer_size = cpu_to_le32(dev->rx_urb_size); 419 + 420 + net->change_mtu = NULL; 425 421 retval = rndis_command(dev, u.header); 426 422 if (unlikely(retval < 0)) { 427 423 /* it might not even be an RNDIS device!! */ 428 424 dev_err(&intf->dev, "RNDIS init failed, %d\n", retval); 425 + goto fail_and_release; 426 + } 427 + tmp = le32_to_cpu(u.init_c->max_transfer_size); 428 + if (tmp < dev->hard_mtu) { 429 + dev_err(&intf->dev, 430 + "dev can't take %u byte packets (max %u)\n", 431 + dev->hard_mtu, tmp); 429 432 goto fail_and_release; 430 433 } 431 - dev->hard_mtu = le32_to_cpu(u.init_c->max_transfer_size); 434 + 432 435 /* REVISIT: peripheral "alignment" request is ignored ... */ 433 - dev_dbg(&intf->dev, "hard mtu %u, align %d\n", dev->hard_mtu, 436 + dev_dbg(&intf->dev, 437 + "hard mtu %u (%u from dev), rx buflen %Zu, align %d\n", 438 + dev->hard_mtu, tmp, dev->rx_urb_size, 434 439 1 << le32_to_cpu(u.init_c->packet_alignment)); 435 440 436 - /* get designated host ethernet address */ 437 - memset(u.get, 0, sizeof *u.get); 441 + /* Get designated host ethernet address. 442 + * 443 + * Adding a payload exactly the same size as the expected response 444 + * payload is an evident requirement MSFT added for ActiveSync. 445 + * This undocumented (and nonsensical) issue was found by sniffing 446 + * protocol requests from the ActiveSync 4.1 Windows driver. 447 + */ 448 + memset(u.get, 0, sizeof *u.get + 48); 438 449 u.get->msg_type = RNDIS_MSG_QUERY; 439 - u.get->msg_len = ccpu2(sizeof *u.get); 450 + u.get->msg_len = ccpu2(sizeof *u.get + 48); 440 451 u.get->oid = OID_802_3_PERMANENT_ADDRESS; 452 + u.get->len = ccpu2(48); 453 + u.get->offset = ccpu2(20); 441 454 442 455 retval = rndis_command(dev, u.header); 443 456 if (unlikely(retval < 0)) { ··· 471 432 goto fail_and_release; 472 433 } 473 434 tmp = le32_to_cpu(u.get_c->offset); 474 - if (unlikely((tmp + 8) > (1024 - ETH_ALEN) 435 + if (unlikely((tmp + 8) > (CONTROL_BUFFER_SIZE - ETH_ALEN) 475 436 || u.get_c->len != ccpu2(ETH_ALEN))) { 476 437 dev_err(&intf->dev, "rndis ethaddr off %d len %d ?\n", 477 438 tmp, le32_to_cpu(u.get_c->len)); ··· 636 597 { 637 598 /* RNDIS is MSFT's un-official variant of CDC ACM */ 638 599 USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff), 600 + .driver_info = (unsigned long) &rndis_info, 601 + }, { 602 + /* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */ 603 + USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1), 639 604 .driver_info = (unsigned long) &rndis_info, 640 605 }, 641 606 { }, // END