X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=drivers%2Fusb%2Fgadget%2Fether.c;h=d975fb680e3ce11d62b807ee845c1427887d9751;hb=495dbd72dd1172de866ba323263a5b62cf454972;hp=638486998d177f68ea5b128db64c351979a0e079;hpb=5aebe3b07254d9ec8f7ae23a4525a6c535a7f858;p=u-boot diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 638486998d..d975fb680e 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -22,13 +22,17 @@ #include #include +#include #include +#include #include #include #include +#include #include #include "gadget_chips.h" +#include "rndis.h" #define USB_NET_NAME "usb_ether" @@ -94,7 +98,13 @@ static const char driver_desc[] = DRIVER_DESC; #define RX_EXTRA 20 /* guard against rx overflows */ -/* CDC support the same host-chosen outgoing packet filters. */ +#ifndef CONFIG_USB_ETH_RNDIS +#define rndis_uninit(x) do {} while (0) +#define rndis_deregister(c) do {} while (0) +#define rndis_exit() do {} while (0) +#endif + +/* CDC and RNDIS support the same host-chosen outgoing packet filters. */ #define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ |USB_CDC_PACKET_TYPE_ALL_MULTICAST \ |USB_CDC_PACKET_TYPE_PROMISCUOUS \ @@ -102,6 +112,44 @@ static const char driver_desc[] = DRIVER_DESC; #define USB_CONNECT_TIMEOUT (3 * CONFIG_SYS_HZ) +/*-------------------------------------------------------------------------*/ + +struct eth_dev { + struct usb_gadget *gadget; + struct usb_request *req; /* for control responses */ + struct usb_request *stat_req; /* for cdc & rndis status */ + + u8 config; + struct usb_ep *in_ep, *out_ep, *status_ep; + const struct usb_endpoint_descriptor + *in, *out, *status; + + struct usb_request *tx_req, *rx_req; + + struct eth_device *net; + struct net_device_stats stats; + unsigned int tx_qlen; + + unsigned zlp:1; + unsigned cdc:1; + unsigned rndis:1; + unsigned suspended:1; + unsigned network_started:1; + u16 cdc_filter; + unsigned long todo; + int mtu; +#define WORK_RX_MEMORY 0 + int rndis_config; + u8 host_mac[ETH_ALEN]; +}; + +/* + * This version autoconfigures as much as possible at run-time. + * + * It also ASSUMES a self-powered device, without remote wakeup, + * although remote wakeup support would make sense. + */ + /*-------------------------------------------------------------------------*/ static struct eth_dev l_ethdev; static struct eth_device l_netdev; @@ -121,8 +169,18 @@ static inline int is_cdc(struct eth_dev *dev) #endif } -#define subset_active(dev) (!is_cdc(dev)) -#define cdc_active(dev) (is_cdc(dev)) +/* "secondary" RNDIS config may sometimes be activated */ +static inline int rndis_active(struct eth_dev *dev) +{ +#ifdef CONFIG_USB_ETH_RNDIS + return dev->rndis; +#else + return 0; +#endif +} + +#define subset_active(dev) (!is_cdc(dev) && !rndis_active(dev)) +#define cdc_active(dev) (is_cdc(dev) && !rndis_active(dev)) #define DEFAULT_QLEN 2 /* double buffering by default */ @@ -162,39 +220,6 @@ static inline int BITRATE(struct usb_gadget *g) } #endif -struct eth_dev { - struct usb_gadget *gadget; - struct usb_request *req; /* for control responses */ - struct usb_request *stat_req; /* for cdc status */ - - u8 config; - struct usb_ep *in_ep, *out_ep, *status_ep; - const struct usb_endpoint_descriptor - *in, *out, *status; - - struct usb_request *tx_req, *rx_req; - - struct eth_device *net; - unsigned int tx_qlen; - - unsigned zlp:1; - unsigned cdc:1; - unsigned suspended:1; - unsigned network_started:1; - u16 cdc_filter; - unsigned long todo; - int mtu; -#define WORK_RX_MEMORY 0 - u8 host_mac[ETH_ALEN]; -}; - -/* - * This version autoconfigures as much as possible at run-time. - * - * It also ASSUMES a self-powered device, without remote wakeup, - * although remote wakeup support would make sense. - */ - /*-------------------------------------------------------------------------*/ /* @@ -221,8 +246,17 @@ struct eth_dev { * RNDIS (like SA-1100, with no interrupt endpoint, or anything that * doesn't handle control-OUT). */ -#define SIMPLE_VENDOR_NUM 0x049f -#define SIMPLE_PRODUCT_NUM 0x505a +#define SIMPLE_VENDOR_NUM 0x049f /* Compaq Computer Corp. */ +#define SIMPLE_PRODUCT_NUM 0x505a /* Linux-USB "CDC Subset" Device */ + +/* + * For hardware that can talk RNDIS and either of the above protocols, + * use this ID ... the windows INF files will know it. Unless it's + * used with CDC Ethernet, Linux 2.4 hosts will need updates to choose + * the non-RNDIS configuration. + */ +#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */ +#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */ /* * Some systems will want different product identifers published in the @@ -230,17 +264,28 @@ struct eth_dev { * parameters are in UTF-8 (superset of ASCII's 7 bit characters). */ -static ushort bcdDevice; +/* + * Emulating them in eth_bind: + * static ushort idVendor; + * static ushort idProduct; + */ + #if defined(CONFIG_USBNET_MANUFACTURER) static char *iManufacturer = CONFIG_USBNET_MANUFACTURER; #else static char *iManufacturer = "U-boot"; #endif + +/* These probably need to be configurable. */ +static ushort bcdDevice; static char *iProduct; static char *iSerialNumber; + static char dev_addr[18]; + static char host_addr[18]; + /*-------------------------------------------------------------------------*/ /* @@ -252,7 +297,12 @@ static char host_addr[18]; /* * DESCRIPTORS ... most are static, but strings and (full) configuration * descriptors are built on demand. For now we do either full CDC, or - * our simple subset. + * our simple subset, with RNDIS as an optional second configuration. + * + * RNDIS includes some CDC ACM descriptors ... like CDC Ethernet. But + * the class descriptors match a modem (they're ignored; it's really just + * Ethernet functionality), they don't need the NOP altsetting, and the + * status transfer endpoint isn't optional. */ #define STRING_MANUFACTURER 1 @@ -260,22 +310,28 @@ static char host_addr[18]; #define STRING_ETHADDR 3 #define STRING_DATA 4 #define STRING_CONTROL 5 +#define STRING_RNDIS_CONTROL 6 #define STRING_CDC 7 #define STRING_SUBSET 8 +#define STRING_RNDIS 9 #define STRING_SERIALNUMBER 10 -/* holds our biggest descriptor */ +/* holds our biggest descriptor (or RNDIS response) */ #define USB_BUFSIZ 256 /* - * This device advertises one configuration, eth_config, - * on hardware supporting at least two configs. + * This device advertises one configuration, eth_config, unless RNDIS + * is enabled (rndis_config) on hardware supporting at least two configs. + * + * NOTE: Controllers like superh_udc should probably be able to use + * an RNDIS-only configuration. * * FIXME define some higher-powered configurations to make it easier * to recharge batteries ... */ #define DEV_CONFIG_VALUE 1 /* cdc or subset */ +#define DEV_RNDIS_CONFIG_VALUE 2 /* rndis; optional */ static struct usb_device_descriptor device_desc = { @@ -316,11 +372,38 @@ eth_config = { .bMaxPower = 1, }; +#ifdef CONFIG_USB_ETH_RNDIS +static struct usb_config_descriptor +rndis_config = { + .bLength = sizeof rndis_config, + .bDescriptorType = USB_DT_CONFIG, + + /* compute wTotalLength on the fly */ + .bNumInterfaces = 2, + .bConfigurationValue = DEV_RNDIS_CONFIG_VALUE, + .iConfiguration = STRING_RNDIS, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 1, +}; +#endif + /* * Compared to the simple CDC subset, the full CDC Ethernet model adds * three class descriptors, two interface descriptors, optional status * endpoint. Both have a "data" interface and two bulk endpoints. * There are also differences in how control requests are handled. + * + * RNDIS shares a lot with CDC-Ethernet, since it's a variant of the + * CDC-ACM (modem) spec. Unfortunately MSFT's RNDIS driver is buggy; it + * may hang or oops. Since bugfixes (or accurate specs, letting Linux + * work around those bugs) are unlikely to ever come from MSFT, you may + * wish to avoid using RNDIS. + * + * MCCI offers an alternative to RNDIS if you need to connect to Windows + * but have hardware that can't support CDC Ethernet. We add descriptors + * to present the CDC Subset as a (nonconformant) CDC MDLM variant called + * "SAFE". That borrows from both CDC Ethernet and CDC MDLM. You can + * get those drivers from MCCI, or bundled with various products. */ #ifdef DEV_CONFIG_CDC @@ -339,6 +422,21 @@ control_intf = { }; #endif +#ifdef CONFIG_USB_ETH_RNDIS +static const struct usb_interface_descriptor +rndis_control_intf = { + .bLength = sizeof rndis_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, + .iInterface = STRING_RNDIS_CONTROL, +}; +#endif + static const struct usb_cdc_header_desc header_desc = { .bLength = sizeof header_desc, .bDescriptorType = USB_DT_CS_INTERFACE, @@ -347,7 +445,7 @@ static const struct usb_cdc_header_desc header_desc = { .bcdCDC = __constant_cpu_to_le16(0x0110), }; -#if defined(DEV_CONFIG_CDC) +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) static const struct usb_cdc_union_desc union_desc = { .bLength = sizeof union_desc, @@ -358,7 +456,28 @@ static const struct usb_cdc_union_desc union_desc = { .bSlaveInterface0 = 1, /* index of DATA interface */ }; -#endif /* CDC */ +#endif /* CDC || RNDIS */ + +#ifdef CONFIG_USB_ETH_RNDIS + +static const struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = { + .bLength = sizeof call_mgmt_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + + .bmCapabilities = 0x00, + .bDataInterface = 0x01, +}; + +static const struct usb_cdc_acm_descriptor acm_descriptor = { + .bLength = sizeof acm_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + + .bmCapabilities = 0x00, +}; + +#endif #ifndef DEV_CONFIG_CDC @@ -410,7 +529,7 @@ static const struct usb_cdc_ether_desc ether_desc = { .bNumberPowerFilters = 0, }; -#if defined(DEV_CONFIG_CDC) +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) /* * include the status endpoint if we can, even where it's optional. @@ -422,6 +541,9 @@ static const struct usb_cdc_ether_desc ether_desc = { * if they ignore the connect/disconnect notifications that real aether * can provide. more advanced cdc configurations might want to support * encapsulated commands (vendor-specific, using control-OUT). + * + * RNDIS requires the status endpoint, since it uses that encapsulation + * mechanism for its funky RPC scheme. */ #define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ @@ -474,6 +596,26 @@ data_intf = { #endif +#ifdef CONFIG_USB_ETH_RNDIS + +/* RNDIS doesn't activate by changing to the "real" altsetting */ + +static const struct usb_interface_descriptor +rndis_data_intf = { + .bLength = sizeof rndis_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = STRING_DATA, +}; + +#endif + #ifdef DEV_CONFIG_SUBSET /* @@ -554,12 +696,30 @@ static inline void fs_subset_descriptors(void) #endif } +#ifdef CONFIG_USB_ETH_RNDIS +static const struct usb_descriptor_header *fs_rndis_function[] = { + (struct usb_descriptor_header *) &otg_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &union_desc, + (struct usb_descriptor_header *) &fs_status_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &fs_source_desc, + (struct usb_descriptor_header *) &fs_sink_desc, + NULL, +}; +#endif + /* * usb 2.0 devices need to expose both high speed and full speed * descriptors, unless they only run at full speed. */ -#if defined(DEV_CONFIG_CDC) +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) static struct usb_endpoint_descriptor hs_status_desc = { .bLength = USB_DT_ENDPOINT_SIZE, @@ -636,6 +796,25 @@ static inline void hs_subset_descriptors(void) #endif } +#ifdef CONFIG_USB_ETH_RNDIS +static const struct usb_descriptor_header *hs_rndis_function[] = { + (struct usb_descriptor_header *) &otg_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &union_desc, + (struct usb_descriptor_header *) &hs_status_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &hs_source_desc, + (struct usb_descriptor_header *) &hs_sink_desc, + NULL, +}; +#endif + + /* maxpacket and other transfer characteristics vary by speed. */ static inline struct usb_endpoint_descriptor * ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *hs, @@ -670,6 +849,10 @@ static struct usb_string strings[] = { #endif #ifdef DEV_CONFIG_SUBSET { STRING_SUBSET, "CDC Ethernet Subset", }, +#endif +#ifdef CONFIG_USB_ETH_RNDIS + { STRING_RNDIS, "RNDIS", }, + { STRING_RNDIS_CONTROL, "RNDIS Communications Control", }, #endif { } /* end of list */ }; @@ -731,8 +914,20 @@ config_buf(struct usb_gadget *g, u8 *buf, u8 type, unsigned index, int is_otg) if (index >= device_desc.bNumConfigurations) return -EINVAL; - config = ð_config; - function = which_fn(eth); +#ifdef CONFIG_USB_ETH_RNDIS + /* + * list the RNDIS config first, to make Microsoft's drivers + * happy. DOCSIS 1.0 needs this too. + */ + if (device_desc.bNumConfigurations == 2 && index == 0) { + config = &rndis_config; + function = which_fn(rndis); + } else +#endif + { + config = ð_config; + function = which_fn(eth); + } /* for now, don't advertise srp-only devices */ if (!is_otg) @@ -747,6 +942,7 @@ config_buf(struct usb_gadget *g, u8 *buf, u8 type, unsigned index, int is_otg) /*-------------------------------------------------------------------------*/ +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags); static int alloc_requests(struct eth_dev *dev, unsigned n, gfp_t gfp_flags); static int @@ -755,8 +951,8 @@ set_ether_config(struct eth_dev *dev, gfp_t gfp_flags) int result = 0; struct usb_gadget *gadget = dev->gadget; -#if defined(DEV_CONFIG_CDC) - /* status endpoint used for (optionally) CDC */ +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) + /* status endpoint used for RNDIS and (optionally) CDC */ if (!subset_active(dev) && dev->status_ep) { dev->status = ep_desc(gadget, &hs_status_desc, &fs_status_desc); @@ -781,6 +977,10 @@ set_ether_config(struct eth_dev *dev, gfp_t gfp_flags) * With CDC, the host isn't allowed to use these two data * endpoints in the default altsetting for the interface. * so we don't activate them yet. Reset from SET_INTERFACE. + * + * Strictly speaking RNDIS should work the same: activation is + * a side effect of setting a packet filter. Deactivation is + * from REMOTE_NDIS_HALT_MSG, reset from REMOTE_NDIS_RESET_MSG. */ if (!cdc_active(dev)) { result = usb_ep_enable(dev->in_ep, dev->in); @@ -811,6 +1011,12 @@ done: (void) usb_ep_disable(dev->out_ep); dev->in = NULL; dev->out = NULL; + } else if (!cdc_active(dev)) { + /* + * activate non-CDC configs right away + * this isn't strictly according to the RNDIS spec + */ + eth_start(dev, GFP_ATOMIC); } /* caller is responsible for cleanup on error */ @@ -824,6 +1030,8 @@ static void eth_reset_config(struct eth_dev *dev) debug("%s\n", __func__); + rndis_uninit(dev->rndis_config); + /* * disable endpoints, forcing (synchronous) completion of * pending i/o. then free the requests. @@ -846,6 +1054,7 @@ static void eth_reset_config(struct eth_dev *dev) if (dev->status) usb_ep_disable(dev->status_ep); + dev->rndis = 0; dev->cdc_filter = 0; dev->config = 0; } @@ -873,6 +1082,12 @@ static int eth_set_config(struct eth_dev *dev, unsigned number, case DEV_CONFIG_VALUE: result = set_ether_config(dev, gfp_flags); break; +#ifdef CONFIG_USB_ETH_RNDIS + case DEV_RNDIS_CONFIG_VALUE: + dev->rndis = 1; + result = set_ether_config(dev, gfp_flags); + break; +#endif default: result = -EINVAL; /* FALL THROUGH */ @@ -906,7 +1121,10 @@ static int eth_set_config(struct eth_dev *dev, unsigned number, dev->config = number; printf("%s speed config #%d: %d mA, %s, using %s\n", speed, number, power, driver_desc, - (cdc_active(dev) ? "CDC Ethernet" + rndis_active(dev) + ? "RNDIS" + : (cdc_active(dev) + ? "CDC Ethernet" : "CDC Ethernet Subset")); } return result; @@ -919,7 +1137,7 @@ static int eth_set_config(struct eth_dev *dev, unsigned number, /* * The interrupt endpoint is used in CDC networking models (Ethernet, ATM) * only to notify the host about link status changes (which we support) or - * report completion of some encapsulated command. Since + * report completion of some encapsulated command (as used in RNDIS). Since * we want this CDC Ethernet code to be vendor-neutral, we don't use that * command mechanism; and only one status request is ever queued. */ @@ -1010,6 +1228,30 @@ static void eth_setup_complete(struct usb_ep *ep, struct usb_request *req) req->status, req->actual, req->length); } +#ifdef CONFIG_USB_ETH_RNDIS + +static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + debug("rndis response complete --> %d, %d/%d\n", + req->status, req->actual, req->length); + + /* done sending after USB_CDC_GET_ENCAPSULATED_RESPONSE */ +} + +static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct eth_dev *dev = ep->driver_data; + int status; + + /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ + status = rndis_msg_parser(dev->rndis_config, (u8 *) req->buf); + if (status < 0) + error("%s: rndis parse error %d", __func__, status); +} + +#endif /* RNDIS */ + /* * The setup() callback implements all the ep0 functionality that's not * handled lower down. CDC has a number of less-common features: @@ -1122,6 +1364,7 @@ eth_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) usb_ep_disable(dev->status_ep); usb_ep_enable(dev->status_ep, dev->status); } + value = 0; break; case 1: /* data intf */ @@ -1143,8 +1386,8 @@ eth_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) dev->cdc_filter = DEFAULT_FILTER; if (dev->status) issue_start_status(dev); + eth_start(dev, GFP_ATOMIC); } - value = 0; break; } @@ -1163,11 +1406,11 @@ done_set_intf: || !dev->config || wIndex > 1) break; - if (!(cdc_active(dev)) && wIndex != 0) + if (!(cdc_active(dev) || rndis_active(dev)) && wIndex != 0) break; /* for CDC, iff carrier is on, data interface is active. */ - if (wIndex != 1) + if (rndis_active(dev) || wIndex != 1) *(u8 *)req->buf = 0; else { /* *(u8 *)req->buf = netif_carrier_ok (dev->net) ? 1 : 0; */ @@ -1203,6 +1446,49 @@ done_set_intf: #endif /* DEV_CONFIG_CDC */ +#ifdef CONFIG_USB_ETH_RNDIS + /* + * RNDIS uses the CDC command encapsulation mechanism to implement + * an RPC scheme, with much getting/setting of attributes by OID. + */ + case USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (ctrl->bRequestType != (USB_TYPE_CLASS|USB_RECIP_INTERFACE) + || !rndis_active(dev) + || wLength > USB_BUFSIZ + || wValue + || rndis_control_intf.bInterfaceNumber + != wIndex) + break; + /* read the request, then process it */ + value = wLength; + req->complete = rndis_command_complete; + /* later, rndis_control_ack () sends a notification */ + break; + + case USB_CDC_GET_ENCAPSULATED_RESPONSE: + if ((USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE) + == ctrl->bRequestType + && rndis_active(dev) + /* && wLength >= 0x0400 */ + && !wValue + && rndis_control_intf.bInterfaceNumber + == wIndex) { + u8 *buf; + u32 n; + + /* return the result */ + buf = rndis_get_next_response(dev->rndis_config, &n); + if (buf) { + memcpy(req->buf, buf, n); + req->complete = rndis_response_complete; + rndis_free_response(dev->rndis_config, buf); + value = n; + } + /* else stalls ... spec says to avoid that */ + } + break; +#endif /* RNDIS */ + default: debug("unknown control req%02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, @@ -1244,17 +1530,24 @@ static int rx_submit(struct eth_dev *dev, struct usb_request *req, * already allocated. Some hardware doesn't deal well with short * reads (e.g. DMA must be N*maxpacket), so for now don't trim a * byte off the end (to force hardware errors on overflow). + * + * RNDIS uses internal framing, and explicitly allows senders to + * pad to end-of-packet. That's potentially nice for speed, + * but means receivers can't recover synch on their own. */ debug("%s\n", __func__); size = (ETHER_HDR_SIZE + dev->mtu + RX_EXTRA); size += dev->out_ep->maxpacket - 1; + if (rndis_active(dev)) + size += sizeof(struct rndis_packet_msg_type); size -= size % dev->out_ep->maxpacket; /* * Some platforms perform better when IP packets are aligned, - * but on at least one, checksumming fails otherwise. + * but on at least one, checksumming fails otherwise. Note: + * RNDIS headers involve variable numbers of LE32 values. */ req->buf = (u8 *) NetRxPackets[0]; @@ -1274,6 +1567,44 @@ static void rx_complete(struct usb_ep *ep, struct usb_request *req) struct eth_dev *dev = ep->driver_data; debug("%s: status %d\n", __func__, req->status); + switch (req->status) { + /* normal completion */ + case 0: + if (rndis_active(dev)) { + /* we know MaxPacketsPerTransfer == 1 here */ + int length = rndis_rm_hdr(req->buf, req->actual); + if (length < 0) + goto length_err; + req->length -= length; + req->actual -= length; + } + if (req->actual < ETH_HLEN || ETH_FRAME_LEN < req->actual) { +length_err: + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + debug("rx length %d\n", req->length); + break; + } + + dev->stats.rx_packets++; + dev->stats.rx_bytes += req->length; + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + break; + + /* data overrun */ + case -EOVERFLOW: + dev->stats.rx_over_errors++; + /* FALLTHROUGH */ + default: + dev->stats.rx_errors++; + break; + } packet_received = 1; } @@ -1302,7 +1633,22 @@ fail1: static void tx_complete(struct usb_ep *ep, struct usb_request *req) { + struct eth_dev *dev = ep->driver_data; + debug("%s: status %s\n", __func__, (req->status) ? "failed" : "ok"); + switch (req->status) { + default: + dev->stats.tx_errors++; + debug("tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + dev->stats.tx_bytes += req->length; + } + dev->stats.tx_packets++; + packet_sent = 1; } @@ -1429,9 +1775,11 @@ drop: static void eth_unbind(struct usb_gadget *gadget) { - struct eth_dev *dev = get_gadget_data(gadget); + struct eth_dev *dev = get_gadget_data(gadget); debug("%s...\n", __func__); + rndis_deregister(dev->rndis_config); + rndis_exit(); /* we've already been disconnected ... no i/o is active */ if (dev->req) { @@ -1463,6 +1811,7 @@ static void eth_unbind(struct usb_gadget *gadget) static void eth_disconnect(struct usb_gadget *gadget) { eth_reset_config(get_gadget_data(gadget)); + /* FIXME RNDIS should enter RNDIS_UNINITIALIZED */ } static void eth_suspend(struct usb_gadget *gadget) @@ -1477,6 +1826,127 @@ static void eth_resume(struct usb_gadget *gadget) /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_ETH_RNDIS + +/* + * The interrupt endpoint is used in RNDIS to notify the host when messages + * other than data packets are available ... notably the REMOTE_NDIS_*_CMPLT + * messages, but also REMOTE_NDIS_INDICATE_STATUS_MSG and potentially even + * REMOTE_NDIS_KEEPALIVE_MSG. + * + * The RNDIS control queue is processed by GET_ENCAPSULATED_RESPONSE, and + * normally just one notification will be queued. + */ + +static void rndis_control_ack_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct eth_dev *dev = ep->driver_data; + + debug("%s...\n", __func__); + if (req->status || req->actual != req->length) + debug("rndis control ack complete --> %d, %d/%d\n", + req->status, req->actual, req->length); + + if (!l_ethdev.network_started) { + if (rndis_get_state(dev->rndis_config) + == RNDIS_DATA_INITIALIZED) { + l_ethdev.network_started = 1; + printf("USB RNDIS network up!\n"); + } + } + + req->context = NULL; + + if (req != dev->stat_req) + usb_ep_free_request(ep, req); +} + +static char rndis_resp_buf[8] __attribute__((aligned(sizeof(__le32)))); + +static int rndis_control_ack(struct eth_device *net) +{ + struct eth_dev *dev = &l_ethdev; + int length; + struct usb_request *resp = dev->stat_req; + + /* in case RNDIS calls this after disconnect */ + if (!dev->status) { + debug("status ENODEV\n"); + return -ENODEV; + } + + /* in case queue length > 1 */ + if (resp->context) { + resp = usb_ep_alloc_request(dev->status_ep, GFP_ATOMIC); + if (!resp) + return -ENOMEM; + resp->buf = rndis_resp_buf; + } + + /* + * Send RNDIS RESPONSE_AVAILABLE notification; + * USB_CDC_NOTIFY_RESPONSE_AVAILABLE should work too + */ + resp->length = 8; + resp->complete = rndis_control_ack_complete; + resp->context = dev; + + *((__le32 *) resp->buf) = __constant_cpu_to_le32(1); + *((__le32 *) (resp->buf + 4)) = __constant_cpu_to_le32(0); + + length = usb_ep_queue(dev->status_ep, resp, GFP_ATOMIC); + if (length < 0) { + resp->status = 0; + rndis_control_ack_complete(dev->status_ep, resp); + } + + return 0; +} + +#else + +#define rndis_control_ack NULL + +#endif /* RNDIS */ + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) +{ + if (rndis_active(dev)) { + rndis_set_param_medium(dev->rndis_config, + NDIS_MEDIUM_802_3, + BITRATE(dev->gadget)/100); + rndis_signal_connect(dev->rndis_config); + } +} + +static int eth_stop(struct eth_dev *dev) +{ +#ifdef RNDIS_COMPLETE_SIGNAL_DISCONNECT + unsigned long ts; + unsigned long timeout = CONFIG_SYS_HZ; /* 1 sec to stop RNDIS */ +#endif + + if (rndis_active(dev)) { + rndis_set_param_medium(dev->rndis_config, NDIS_MEDIUM_802_3, 0); + rndis_signal_disconnect(dev->rndis_config); + +#ifdef RNDIS_COMPLETE_SIGNAL_DISCONNECT + /* Wait until host receives OID_GEN_MEDIA_CONNECT_STATUS */ + ts = get_timer(0); + while (get_timer(ts) < timeout) + usb_gadget_handle_interrupts(); +#endif + + rndis_uninit(dev->rndis_config); + dev->rndis = 0; + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + static int is_eth_addr_valid(char *str) { if (strlen(str) == 17) { @@ -1538,14 +2008,18 @@ static int get_ether_addr(const char *str, u8 *dev_addr) static int eth_bind(struct usb_gadget *gadget) { struct eth_dev *dev = &l_ethdev; - u8 cdc = 1, zlp = 1; + u8 cdc = 1, zlp = 1, rndis = 1; struct usb_ep *in_ep, *out_ep, *status_ep = NULL; + int status = -ENOMEM; int gcnum; u8 tmp[7]; /* these flags are only ever cleared; compiler take note */ #ifndef DEV_CONFIG_CDC cdc = 0; +#endif +#ifndef CONFIG_USB_ETH_RNDIS + rndis = 0; #endif /* * Because most host side USB stacks handle CDC Ethernet, that @@ -1561,6 +2035,7 @@ static int eth_bind(struct usb_gadget *gadget) } else if (gadget_is_sh(gadget)) { /* sh doesn't support multiple interfaces or configs */ cdc = 0; + rndis = 0; } else if (gadget_is_sa1100(gadget)) { /* hardware can't write zlps */ zlp = 0; @@ -1586,22 +2061,45 @@ static int eth_bind(struct usb_gadget *gadget) } /* - * CDC subset ... recognized by Linux since 2.4.10, but Windows - * drivers aren't widely available. (That may be improved by - * supporting one submode of the "SAFE" variant of MDLM.) + * If there's an RNDIS configuration, that's what Windows wants to + * be using ... so use these product IDs here and in the "linux.inf" + * needed to install MSFT drivers. Current Linux kernels will use + * the second configuration if it's CDC Ethernet, and need some help + * to choose the right configuration otherwise. */ - if (!cdc) { + if (rndis) { +#if defined(CONFIG_USB_RNDIS_VENDOR_ID) && defined(CONFIG_USB_RNDIS_PRODUCT_ID) device_desc.idVendor = - __constant_cpu_to_le16(SIMPLE_VENDOR_NUM); + __constant_cpu_to_le16(CONFIG_USB_RNDIS_VENDOR_ID); device_desc.idProduct = - __constant_cpu_to_le16(SIMPLE_PRODUCT_NUM); - } + __constant_cpu_to_le16(CONFIG_USB_RNDIS_PRODUCT_ID); +#else + device_desc.idVendor = + __constant_cpu_to_le16(RNDIS_VENDOR_NUM); + device_desc.idProduct = + __constant_cpu_to_le16(RNDIS_PRODUCT_NUM); +#endif + sprintf(product_desc, "RNDIS/%s", driver_desc); - /* support optional vendor/distro customization */ + /* + * CDC subset ... recognized by Linux since 2.4.10, but Windows + * drivers aren't widely available. (That may be improved by + * supporting one submode of the "SAFE" variant of MDLM.) + */ + } else { #if defined(CONFIG_USB_CDC_VENDOR_ID) && defined(CONFIG_USB_CDC_PRODUCT_ID) - device_desc.idVendor = cpu_to_le16(CONFIG_USB_CDC_VENDOR_ID); - device_desc.idProduct = cpu_to_le16(CONFIG_USB_CDC_PRODUCT_ID); + device_desc.idVendor = cpu_to_le16(CONFIG_USB_CDC_VENDOR_ID); + device_desc.idProduct = cpu_to_le16(CONFIG_USB_CDC_PRODUCT_ID); +#else + if (!cdc) { + device_desc.idVendor = + __constant_cpu_to_le16(SIMPLE_VENDOR_NUM); + device_desc.idProduct = + __constant_cpu_to_le16(SIMPLE_PRODUCT_NUM); + } #endif + } + /* support optional vendor/distro customization */ if (bcdDevice) device_desc.bcdDevice = cpu_to_le16(bcdDevice); if (iManufacturer) @@ -1629,18 +2127,23 @@ autoconf_fail: goto autoconf_fail; out_ep->driver_data = out_ep; /* claim */ -#if defined(DEV_CONFIG_CDC) +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) /* * CDC Ethernet control interface doesn't require a status endpoint. * Since some hosts expect one, try to allocate one anyway. */ - if (cdc) { + if (cdc || rndis) { status_ep = usb_ep_autoconfig(gadget, &fs_status_desc); if (status_ep) { status_ep->driver_data = status_ep; /* claim */ + } else if (rndis) { + error("can't run RNDIS on %s", gadget->name); + return -ENODEV; +#ifdef DEV_CONFIG_CDC } else if (cdc) { control_intf.bNumEndpoints = 0; /* FIXME remove endpoint from descriptor list */ +#endif } } #endif @@ -1661,8 +2164,14 @@ autoconf_fail: device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket; usb_gadget_set_selfpowered(gadget); + /* For now RNDIS is always a second config */ + if (rndis) + device_desc.bNumConfigurations = 2; + if (gadget_is_dualspeed(gadget)) { - if (!cdc) + if (rndis) + dev_qualifier.bNumConfigurations = 2; + else if (!cdc) dev_qualifier.bDeviceClass = USB_CLASS_VENDOR_SPEC; /* assumes ep0 uses the same value for both speeds ... */ @@ -1673,7 +2182,7 @@ autoconf_fail: fs_source_desc.bEndpointAddress; hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; -#if defined(DEV_CONFIG_CDC) +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) if (status_ep) hs_status_desc.bEndpointAddress = fs_status_desc.bEndpointAddress; @@ -1684,8 +2193,14 @@ autoconf_fail: otg_descriptor.bmAttributes |= USB_OTG_HNP, eth_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; eth_config.bMaxPower = 4; +#ifdef CONFIG_USB_ETH_RNDIS + rndis_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + rndis_config.bMaxPower = 4; +#endif } + + /* network device setup */ dev->net = &l_netdev; dev->cdc = cdc; @@ -1697,7 +2212,7 @@ autoconf_fail: /* * Module params for these addresses should come from ID proms. - * The host side address is used with CDC, and commonly + * The host side address is used with CDC and RNDIS, and commonly * ends up in a persistent config database. It's not clear if * host side code for the SAFE thing cares -- its original BLAN * thing didn't, Sharp never assigned those addresses on Zaurii. @@ -1714,21 +2229,12 @@ autoconf_fail: dev->host_mac[2], dev->host_mac[3], dev->host_mac[4], dev->host_mac[5]); - printf("using %s, OUT %s IN %s%s%s\n", gadget->name, - out_ep->name, in_ep->name, - status_ep ? " STATUS " : "", - status_ep ? status_ep->name : "" - ); - printf("MAC %02x:%02x:%02x:%02x:%02x:%02x\n", - dev->net->enetaddr[0], dev->net->enetaddr[1], - dev->net->enetaddr[2], dev->net->enetaddr[3], - dev->net->enetaddr[4], dev->net->enetaddr[5]); - - if (cdc) { - printf("HOST MAC %02x:%02x:%02x:%02x:%02x:%02x\n", - dev->host_mac[0], dev->host_mac[1], - dev->host_mac[2], dev->host_mac[3], - dev->host_mac[4], dev->host_mac[5]); + if (rndis) { + status = rndis_init(); + if (status < 0) { + error("can't init RNDIS, %d", status); + goto fail; + } } /* @@ -1745,7 +2251,7 @@ autoconf_fail: dev->req->complete = eth_setup_complete; /* ... and maybe likewise for status transfer */ -#if defined(DEV_CONFIG_CDC) +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) if (dev->status_ep) { dev->stat_req = usb_ep_alloc_request(dev->status_ep, GFP_KERNEL); @@ -1769,14 +2275,60 @@ autoconf_fail: * - iff DATA transfer is active, carrier is "on" * - tx queueing enabled if open *and* carrier is "on" */ + + printf("using %s, OUT %s IN %s%s%s\n", gadget->name, + out_ep->name, in_ep->name, + status_ep ? " STATUS " : "", + status_ep ? status_ep->name : "" + ); + printf("MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->net->enetaddr[0], dev->net->enetaddr[1], + dev->net->enetaddr[2], dev->net->enetaddr[3], + dev->net->enetaddr[4], dev->net->enetaddr[5]); + + if (cdc || rndis) + printf("HOST MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->host_mac[0], dev->host_mac[1], + dev->host_mac[2], dev->host_mac[3], + dev->host_mac[4], dev->host_mac[5]); + + if (rndis) { + u32 vendorID = 0; + + /* FIXME RNDIS vendor id == "vendor NIC code" == ? */ + + dev->rndis_config = rndis_register(rndis_control_ack); + if (dev->rndis_config < 0) { +fail0: + eth_unbind(gadget); + debug("RNDIS setup failed\n"); + status = -ENODEV; + goto fail; + } + + /* these set up a lot of the OIDs that RNDIS needs */ + rndis_set_host_mac(dev->rndis_config, dev->host_mac); + if (rndis_set_param_dev(dev->rndis_config, dev->net, dev->mtu, + &dev->stats, &dev->cdc_filter)) + goto fail0; + if (rndis_set_param_vendor(dev->rndis_config, vendorID, + manufacturer)) + goto fail0; + if (rndis_set_param_medium(dev->rndis_config, + NDIS_MEDIUM_802_3, 0)) + goto fail0; + printf("RNDIS ready\n"); + } return 0; fail: - error("%s failed", __func__); + error("%s failed, status = %d", __func__, status); eth_unbind(gadget); - return -ENOMEM; + return status; } +/*-------------------------------------------------------------------------*/ + static int usb_eth_init(struct eth_device *netdev, bd_t *bd) { struct eth_dev *dev = &l_ethdev; @@ -1845,10 +2397,10 @@ fail: return -1; } -static int usb_eth_send(struct eth_device *netdev, - volatile void *packet, int length) +static int usb_eth_send(struct eth_device *netdev, void *packet, int length) { int retval; + void *rndis_pkt = NULL; struct eth_dev *dev = &l_ethdev; struct usb_request *req = dev->tx_req; unsigned long ts; @@ -1856,7 +2408,21 @@ static int usb_eth_send(struct eth_device *netdev, debug("%s:...\n", __func__); - req->buf = (void *)packet; + /* new buffer is needed to include RNDIS header */ + if (rndis_active(dev)) { + rndis_pkt = malloc(length + + sizeof(struct rndis_packet_msg_type)); + if (!rndis_pkt) { + error("No memory to alloc RNDIS packet"); + goto drop; + } + rndis_add_hdr(rndis_pkt, length); + memcpy(rndis_pkt + sizeof(struct rndis_packet_msg_type), + packet, length); + packet = rndis_pkt; + length += sizeof(struct rndis_packet_msg_type); + } + req->buf = packet; req->context = NULL; req->complete = tx_complete; @@ -1891,8 +2457,13 @@ static int usb_eth_send(struct eth_device *netdev, } usb_gadget_handle_interrupts(); } + if (rndis_pkt) + free(rndis_pkt); return 0; +drop: + dev->stats.tx_dropped++; + return -ENOMEM; } static int usb_eth_recv(struct eth_device *netdev) @@ -1927,7 +2498,27 @@ void usb_eth_halt(struct eth_device *netdev) if (!dev->gadget) return; + /* + * Some USB controllers may need additional deinitialization here + * before dropping pull-up (also due to hardware issues). + * For example: unhandled interrupt with status stage started may + * bring the controller to fully broken state (until board reset). + * There are some variants to debug and fix such cases: + * 1) In the case of RNDIS connection eth_stop can perform additional + * interrupt handling. See RNDIS_COMPLETE_SIGNAL_DISCONNECT definition. + * 2) 'pullup' callback in your UDC driver can be improved to perform + * this deinitialization. + */ + eth_stop(dev); + usb_gadget_disconnect(dev->gadget); + + /* Clear pending interrupt */ + if (dev->network_started) { + usb_gadget_handle_interrupts(); + dev->network_started = 0; + } + usb_gadget_unregister_driver(ð_driver); }