diff options
author | Ole Andre Vadla Ravnas <oleavr@gmail.com> | 2006-12-14 19:01:28 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-02-07 18:44:33 -0500 |
commit | ad55d71a3d4401f44b4ddee1412283c99eedd05c (patch) | |
tree | a416fe386d740506b01667d47466e6708f8a2388 /drivers/usb/net/rndis_host.c | |
parent | 11d5489873facd395653a4ee14669751bfe9bab5 (diff) |
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>
Diffstat (limited to 'drivers/usb/net/rndis_host.c')
-rw-r--r-- | drivers/usb/net/rndis_host.c | 81 |
1 files changed, 62 insertions, 19 deletions
diff --git a/drivers/usb/net/rndis_host.c b/drivers/usb/net/rndis_host.c index a322a16d9cf8..be888d2d813c 100644 --- a/drivers/usb/net/rndis_host.c +++ b/drivers/usb/net/rndis_host.c | |||
@@ -49,6 +49,8 @@ | |||
49 | * - In some cases, MS-Windows will emit undocumented requests; this | 49 | * - In some cases, MS-Windows will emit undocumented requests; this |
50 | * matters more to peripheral implementations than host ones. | 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 | * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in | 54 | * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in |
53 | * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and | 55 | * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and |
54 | * currently rare) "Ethernet Emulation Model" (EEM). | 56 | * currently rare) "Ethernet Emulation Model" (EEM). |
@@ -61,6 +63,9 @@ | |||
61 | * - control-in: GET_ENCAPSULATED | 63 | * - control-in: GET_ENCAPSULATED |
62 | * | 64 | * |
63 | * We'll try to ignore the RESPONSE_AVAILABLE notifications. | 65 | * We'll try to ignore the RESPONSE_AVAILABLE notifications. |
66 | * | ||
67 | * REVISIT some RNDIS implementations seem to have curious issues still | ||
68 | * to be resolved. | ||
64 | */ | 69 | */ |
65 | struct rndis_msg_hdr { | 70 | struct rndis_msg_hdr { |
66 | __le32 msg_type; /* RNDIS_MSG_* */ | 71 | __le32 msg_type; /* RNDIS_MSG_* */ |
@@ -71,8 +76,14 @@ struct rndis_msg_hdr { | |||
71 | // ... and more | 76 | // ... and more |
72 | } __attribute__ ((packed)); | 77 | } __attribute__ ((packed)); |
73 | 78 | ||
74 | /* RNDIS defines this (absurdly huge) control timeout */ | 79 | /* MS-Windows uses this strange size, but RNDIS spec says 1024 minimum */ |
75 | #define RNDIS_CONTROL_TIMEOUT_MS (10 * 1000) | 80 | #define CONTROL_BUFFER_SIZE 1025 |
81 | |||
82 | /* RNDIS defines an (absurdly huge) 10 second control timeout, | ||
83 | * but ActiveSync seems to use a more usual 5 second timeout | ||
84 | * (which matches the USB 2.0 spec). | ||
85 | */ | ||
86 | #define RNDIS_CONTROL_TIMEOUT_MS (5 * 1000) | ||
76 | 87 | ||
77 | 88 | ||
78 | #define ccpu2 __constant_cpu_to_le32 | 89 | #define ccpu2 __constant_cpu_to_le32 |
@@ -270,6 +281,7 @@ static void rndis_status(struct usbnet *dev, struct urb *urb) | |||
270 | static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) | 281 | static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) |
271 | { | 282 | { |
272 | struct cdc_state *info = (void *) &dev->data; | 283 | struct cdc_state *info = (void *) &dev->data; |
284 | int master_ifnum; | ||
273 | int retval; | 285 | int retval; |
274 | unsigned count; | 286 | unsigned count; |
275 | __le32 rsp; | 287 | __le32 rsp; |
@@ -279,7 +291,7 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) | |||
279 | * disconnect(): either serialize, or dispatch responses on xid | 291 | * disconnect(): either serialize, or dispatch responses on xid |
280 | */ | 292 | */ |
281 | 293 | ||
282 | /* Issue the request; don't bother byteswapping our xid */ | 294 | /* Issue the request; xid is unique, don't bother byteswapping it */ |
283 | if (likely(buf->msg_type != RNDIS_MSG_HALT | 295 | if (likely(buf->msg_type != RNDIS_MSG_HALT |
284 | && buf->msg_type != RNDIS_MSG_RESET)) { | 296 | && buf->msg_type != RNDIS_MSG_RESET)) { |
285 | xid = dev->xid++; | 297 | xid = dev->xid++; |
@@ -287,11 +299,12 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) | |||
287 | xid = dev->xid++; | 299 | xid = dev->xid++; |
288 | buf->request_id = (__force __le32) xid; | 300 | buf->request_id = (__force __le32) xid; |
289 | } | 301 | } |
302 | master_ifnum = info->control->cur_altsetting->desc.bInterfaceNumber; | ||
290 | retval = usb_control_msg(dev->udev, | 303 | retval = usb_control_msg(dev->udev, |
291 | usb_sndctrlpipe(dev->udev, 0), | 304 | usb_sndctrlpipe(dev->udev, 0), |
292 | USB_CDC_SEND_ENCAPSULATED_COMMAND, | 305 | USB_CDC_SEND_ENCAPSULATED_COMMAND, |
293 | USB_TYPE_CLASS | USB_RECIP_INTERFACE, | 306 | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
294 | 0, info->u->bMasterInterface0, | 307 | 0, master_ifnum, |
295 | buf, le32_to_cpu(buf->msg_len), | 308 | buf, le32_to_cpu(buf->msg_len), |
296 | RNDIS_CONTROL_TIMEOUT_MS); | 309 | RNDIS_CONTROL_TIMEOUT_MS); |
297 | if (unlikely(retval < 0 || xid == 0)) | 310 | if (unlikely(retval < 0 || xid == 0)) |
@@ -306,13 +319,13 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) | |||
306 | */ | 319 | */ |
307 | rsp = buf->msg_type | RNDIS_MSG_COMPLETION; | 320 | rsp = buf->msg_type | RNDIS_MSG_COMPLETION; |
308 | for (count = 0; count < 10; count++) { | 321 | for (count = 0; count < 10; count++) { |
309 | memset(buf, 0, 1024); | 322 | memset(buf, 0, CONTROL_BUFFER_SIZE); |
310 | retval = usb_control_msg(dev->udev, | 323 | retval = usb_control_msg(dev->udev, |
311 | usb_rcvctrlpipe(dev->udev, 0), | 324 | usb_rcvctrlpipe(dev->udev, 0), |
312 | USB_CDC_GET_ENCAPSULATED_RESPONSE, | 325 | USB_CDC_GET_ENCAPSULATED_RESPONSE, |
313 | USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, | 326 | USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
314 | 0, info->u->bMasterInterface0, | 327 | 0, master_ifnum, |
315 | buf, 1024, | 328 | buf, CONTROL_BUFFER_SIZE, |
316 | RNDIS_CONTROL_TIMEOUT_MS); | 329 | RNDIS_CONTROL_TIMEOUT_MS); |
317 | if (likely(retval >= 8)) { | 330 | if (likely(retval >= 8)) { |
318 | msg_len = le32_to_cpu(buf->msg_len); | 331 | msg_len = le32_to_cpu(buf->msg_len); |
@@ -350,7 +363,7 @@ static int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf) | |||
350 | usb_sndctrlpipe(dev->udev, 0), | 363 | usb_sndctrlpipe(dev->udev, 0), |
351 | USB_CDC_SEND_ENCAPSULATED_COMMAND, | 364 | USB_CDC_SEND_ENCAPSULATED_COMMAND, |
352 | USB_TYPE_CLASS | USB_RECIP_INTERFACE, | 365 | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
353 | 0, info->u->bMasterInterface0, | 366 | 0, master_ifnum, |
354 | msg, sizeof *msg, | 367 | msg, sizeof *msg, |
355 | RNDIS_CONTROL_TIMEOUT_MS); | 368 | RNDIS_CONTROL_TIMEOUT_MS); |
356 | if (unlikely(retval < 0)) | 369 | if (unlikely(retval < 0)) |
@@ -393,38 +406,64 @@ static int rndis_bind(struct usbnet *dev, struct usb_interface *intf) | |||
393 | u32 tmp; | 406 | u32 tmp; |
394 | 407 | ||
395 | /* we can't rely on i/o from stack working, or stack allocation */ | 408 | /* we can't rely on i/o from stack working, or stack allocation */ |
396 | u.buf = kmalloc(1024, GFP_KERNEL); | 409 | u.buf = kmalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL); |
397 | if (!u.buf) | 410 | if (!u.buf) |
398 | return -ENOMEM; | 411 | return -ENOMEM; |
399 | retval = usbnet_generic_cdc_bind(dev, intf); | 412 | retval = usbnet_generic_cdc_bind(dev, intf); |
400 | if (retval < 0) | 413 | if (retval < 0) |
401 | goto fail; | 414 | goto fail; |
402 | 415 | ||
403 | net->hard_header_len += sizeof (struct rndis_data_hdr); | ||
404 | |||
405 | /* initialize; max transfer is 16KB at full speed */ | ||
406 | u.init->msg_type = RNDIS_MSG_INIT; | 416 | u.init->msg_type = RNDIS_MSG_INIT; |
407 | u.init->msg_len = ccpu2(sizeof *u.init); | 417 | u.init->msg_len = ccpu2(sizeof *u.init); |
408 | u.init->major_version = ccpu2(1); | 418 | u.init->major_version = ccpu2(1); |
409 | u.init->minor_version = ccpu2(0); | 419 | u.init->minor_version = ccpu2(0); |
410 | u.init->max_transfer_size = ccpu2(net->mtu + net->hard_header_len); | ||
411 | 420 | ||
421 | /* max transfer (in spec) is 0x4000 at full speed, but for | ||
422 | * TX we'll stick to one Ethernet packet plus RNDIS framing. | ||
423 | * For RX we handle drivers that zero-pad to end-of-packet. | ||
424 | * Don't let userspace change these settings. | ||
425 | */ | ||
426 | net->hard_header_len += sizeof (struct rndis_data_hdr); | ||
427 | dev->hard_mtu = net->mtu + net->hard_header_len; | ||
428 | |||
429 | dev->rx_urb_size = dev->hard_mtu + (dev->maxpacket + 1); | ||
430 | dev->rx_urb_size &= ~(dev->maxpacket - 1); | ||
431 | u.init->max_transfer_size = cpu_to_le32(dev->rx_urb_size); | ||
432 | |||
433 | net->change_mtu = NULL; | ||
412 | retval = rndis_command(dev, u.header); | 434 | retval = rndis_command(dev, u.header); |
413 | if (unlikely(retval < 0)) { | 435 | if (unlikely(retval < 0)) { |
414 | /* it might not even be an RNDIS device!! */ | 436 | /* it might not even be an RNDIS device!! */ |
415 | dev_err(&intf->dev, "RNDIS init failed, %d\n", retval); | 437 | dev_err(&intf->dev, "RNDIS init failed, %d\n", retval); |
438 | goto fail_and_release; | ||
439 | } | ||
440 | tmp = le32_to_cpu(u.init_c->max_transfer_size); | ||
441 | if (tmp < dev->hard_mtu) { | ||
442 | dev_err(&intf->dev, | ||
443 | "dev can't take %u byte packets (max %u)\n", | ||
444 | dev->hard_mtu, tmp); | ||
416 | goto fail_and_release; | 445 | goto fail_and_release; |
417 | } | 446 | } |
418 | dev->hard_mtu = le32_to_cpu(u.init_c->max_transfer_size); | 447 | |
419 | /* REVISIT: peripheral "alignment" request is ignored ... */ | 448 | /* REVISIT: peripheral "alignment" request is ignored ... */ |
420 | dev_dbg(&intf->dev, "hard mtu %u, align %d\n", dev->hard_mtu, | 449 | dev_dbg(&intf->dev, |
450 | "hard mtu %u (%u from dev), rx buflen %Zu, align %d\n", | ||
451 | dev->hard_mtu, tmp, dev->rx_urb_size, | ||
421 | 1 << le32_to_cpu(u.init_c->packet_alignment)); | 452 | 1 << le32_to_cpu(u.init_c->packet_alignment)); |
422 | 453 | ||
423 | /* get designated host ethernet address */ | 454 | /* Get designated host ethernet address. |
424 | memset(u.get, 0, sizeof *u.get); | 455 | * |
456 | * Adding a payload exactly the same size as the expected response | ||
457 | * payload is an evident requirement MSFT added for ActiveSync. | ||
458 | * This undocumented (and nonsensical) issue was found by sniffing | ||
459 | * protocol requests from the ActiveSync 4.1 Windows driver. | ||
460 | */ | ||
461 | memset(u.get, 0, sizeof *u.get + 48); | ||
425 | u.get->msg_type = RNDIS_MSG_QUERY; | 462 | u.get->msg_type = RNDIS_MSG_QUERY; |
426 | u.get->msg_len = ccpu2(sizeof *u.get); | 463 | u.get->msg_len = ccpu2(sizeof *u.get + 48); |
427 | u.get->oid = OID_802_3_PERMANENT_ADDRESS; | 464 | u.get->oid = OID_802_3_PERMANENT_ADDRESS; |
465 | u.get->len = ccpu2(48); | ||
466 | u.get->offset = ccpu2(20); | ||
428 | 467 | ||
429 | retval = rndis_command(dev, u.header); | 468 | retval = rndis_command(dev, u.header); |
430 | if (unlikely(retval < 0)) { | 469 | if (unlikely(retval < 0)) { |
@@ -432,7 +471,7 @@ static int rndis_bind(struct usbnet *dev, struct usb_interface *intf) | |||
432 | goto fail_and_release; | 471 | goto fail_and_release; |
433 | } | 472 | } |
434 | tmp = le32_to_cpu(u.get_c->offset); | 473 | tmp = le32_to_cpu(u.get_c->offset); |
435 | if (unlikely((tmp + 8) > (1024 - ETH_ALEN) | 474 | if (unlikely((tmp + 8) > (CONTROL_BUFFER_SIZE - ETH_ALEN) |
436 | || u.get_c->len != ccpu2(ETH_ALEN))) { | 475 | || u.get_c->len != ccpu2(ETH_ALEN))) { |
437 | dev_err(&intf->dev, "rndis ethaddr off %d len %d ?\n", | 476 | dev_err(&intf->dev, "rndis ethaddr off %d len %d ?\n", |
438 | tmp, le32_to_cpu(u.get_c->len)); | 477 | tmp, le32_to_cpu(u.get_c->len)); |
@@ -598,6 +637,10 @@ static const struct usb_device_id products [] = { | |||
598 | /* RNDIS is MSFT's un-official variant of CDC ACM */ | 637 | /* RNDIS is MSFT's un-official variant of CDC ACM */ |
599 | USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff), | 638 | USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff), |
600 | .driver_info = (unsigned long) &rndis_info, | 639 | .driver_info = (unsigned long) &rndis_info, |
640 | }, { | ||
641 | /* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */ | ||
642 | USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1), | ||
643 | .driver_info = (unsigned long) &rndis_info, | ||
601 | }, | 644 | }, |
602 | { }, // END | 645 | { }, // END |
603 | }; | 646 | }; |