diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/eth/Makefile | 48 | ||||
-rw-r--r-- | drivers/usb/eth/asix.c | 635 | ||||
-rw-r--r-- | drivers/usb/eth/usb_ether.c | 150 | ||||
-rw-r--r-- | drivers/usb/gadget/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/gadget/ether.c | 854 | ||||
-rw-r--r-- | drivers/usb/gadget/ndis.h | 217 | ||||
-rw-r--r-- | drivers/usb/gadget/rndis.c | 1317 | ||||
-rw-r--r-- | drivers/usb/gadget/rndis.h | 260 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 10 | ||||
-rw-r--r-- | drivers/usb/host/ohci-hcd.c | 7 |
10 files changed, 3357 insertions, 142 deletions
diff --git a/drivers/usb/eth/Makefile b/drivers/usb/eth/Makefile new file mode 100644 index 0000000..6a5f25a --- /dev/null +++ b/drivers/usb/eth/Makefile @@ -0,0 +1,48 @@ +# +# Copyright (c) 2011 The Chromium OS Authors. +# See file CREDITS for list of people who contributed to this +# project. +# +# 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 the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# + +include $(TOPDIR)/config.mk + +LIB := $(obj)libusb_eth.a + +# new USB host ethernet layer dependencies +COBJS-$(CONFIG_USB_HOST_ETHER) += usb_ether.o +ifdef CONFIG_USB_ETHER_ASIX +COBJS-y += asix.o +endif + +COBJS := $(COBJS-y) +SRCS := $(COBJS:.o=.c) +OBJS := $(addprefix $(obj),$(COBJS)) + +all: $(LIB) + +$(LIB): $(obj).depend $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + +######################################################################### + +# defines $(obj).depend target +include $(SRCTREE)/rules.mk + +sinclude $(obj).depend + +######################################################################### diff --git a/drivers/usb/eth/asix.c b/drivers/usb/eth/asix.c new file mode 100644 index 0000000..9b012e4 --- /dev/null +++ b/drivers/usb/eth/asix.c @@ -0,0 +1,635 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <usb.h> +#include <linux/mii.h> +#include "usb_ether.h" + + +/* ASIX AX8817X based USB 2.0 Ethernet Devices */ + +#define AX_CMD_SET_SW_MII 0x06 +#define AX_CMD_READ_MII_REG 0x07 +#define AX_CMD_WRITE_MII_REG 0x08 +#define AX_CMD_SET_HW_MII 0x0a +#define AX_CMD_READ_RX_CTL 0x0f +#define AX_CMD_WRITE_RX_CTL 0x10 +#define AX_CMD_WRITE_IPG0 0x12 +#define AX_CMD_READ_NODE_ID 0x13 +#define AX_CMD_READ_PHY_ID 0x19 +#define AX_CMD_WRITE_MEDIUM_MODE 0x1b +#define AX_CMD_WRITE_GPIOS 0x1f +#define AX_CMD_SW_RESET 0x20 +#define AX_CMD_SW_PHY_SELECT 0x22 + +#define AX_SWRESET_CLEAR 0x00 +#define AX_SWRESET_PRTE 0x04 +#define AX_SWRESET_PRL 0x08 +#define AX_SWRESET_IPRL 0x20 +#define AX_SWRESET_IPPD 0x40 + +#define AX88772_IPG0_DEFAULT 0x15 +#define AX88772_IPG1_DEFAULT 0x0c +#define AX88772_IPG2_DEFAULT 0x12 + +/* AX88772 & AX88178 Medium Mode Register */ +#define AX_MEDIUM_PF 0x0080 +#define AX_MEDIUM_JFE 0x0040 +#define AX_MEDIUM_TFC 0x0020 +#define AX_MEDIUM_RFC 0x0010 +#define AX_MEDIUM_ENCK 0x0008 +#define AX_MEDIUM_AC 0x0004 +#define AX_MEDIUM_FD 0x0002 +#define AX_MEDIUM_GM 0x0001 +#define AX_MEDIUM_SM 0x1000 +#define AX_MEDIUM_SBP 0x0800 +#define AX_MEDIUM_PS 0x0200 +#define AX_MEDIUM_RE 0x0100 + +#define AX88178_MEDIUM_DEFAULT \ + (AX_MEDIUM_PS | AX_MEDIUM_FD | AX_MEDIUM_AC | \ + AX_MEDIUM_RFC | AX_MEDIUM_TFC | AX_MEDIUM_JFE | \ + AX_MEDIUM_RE) + +#define AX88772_MEDIUM_DEFAULT \ + (AX_MEDIUM_FD | AX_MEDIUM_RFC | \ + AX_MEDIUM_TFC | AX_MEDIUM_PS | \ + AX_MEDIUM_AC | AX_MEDIUM_RE) + +/* AX88772 & AX88178 RX_CTL values */ +#define AX_RX_CTL_SO 0x0080 +#define AX_RX_CTL_AB 0x0008 + +#define AX_DEFAULT_RX_CTL \ + (AX_RX_CTL_SO | AX_RX_CTL_AB) + +/* GPIO 2 toggles */ +#define AX_GPIO_GPO2EN 0x10 /* GPIO2 Output enable */ +#define AX_GPIO_GPO_2 0x20 /* GPIO2 Output value */ +#define AX_GPIO_RSE 0x80 /* Reload serial EEPROM */ + +/* local defines */ +#define ASIX_BASE_NAME "asx" +#define USB_CTRL_SET_TIMEOUT 5000 +#define USB_CTRL_GET_TIMEOUT 5000 +#define USB_BULK_SEND_TIMEOUT 5000 +#define USB_BULK_RECV_TIMEOUT 5000 + +#define AX_RX_URB_SIZE 2048 +#define PHY_CONNECT_TIMEOUT 5000 + +/* local vars */ +static int curr_eth_dev; /* index for name of next device detected */ + +/* + * Asix infrastructure commands + */ +static int asix_write_cmd(struct ueth_data *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int len; + + debug("asix_write_cmd() cmd=0x%02x value=0x%04x index=0x%04x " + "size=%d\n", cmd, value, index, size); + + len = usb_control_msg( + dev->pusb_dev, + usb_sndctrlpipe(dev->pusb_dev, 0), + cmd, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + data, + size, + USB_CTRL_SET_TIMEOUT); + + return len == size ? 0 : -1; +} + +static int asix_read_cmd(struct ueth_data *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int len; + + debug("asix_read_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n", + cmd, value, index, size); + + len = usb_control_msg( + dev->pusb_dev, + usb_rcvctrlpipe(dev->pusb_dev, 0), + cmd, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + data, + size, + USB_CTRL_GET_TIMEOUT); + return len == size ? 0 : -1; +} + +static inline int asix_set_sw_mii(struct ueth_data *dev) +{ + int ret; + + ret = asix_write_cmd(dev, AX_CMD_SET_SW_MII, 0x0000, 0, 0, NULL); + if (ret < 0) + debug("Failed to enable software MII access\n"); + return ret; +} + +static inline int asix_set_hw_mii(struct ueth_data *dev) +{ + int ret; + + ret = asix_write_cmd(dev, AX_CMD_SET_HW_MII, 0x0000, 0, 0, NULL); + if (ret < 0) + debug("Failed to enable hardware MII access\n"); + return ret; +} + +static int asix_mdio_read(struct ueth_data *dev, int phy_id, int loc) +{ + __le16 res; + + asix_set_sw_mii(dev); + asix_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, (__u16)loc, 2, &res); + asix_set_hw_mii(dev); + + debug("asix_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n", + phy_id, loc, le16_to_cpu(res)); + + return le16_to_cpu(res); +} + +static void +asix_mdio_write(struct ueth_data *dev, int phy_id, int loc, int val) +{ + __le16 res = cpu_to_le16(val); + + debug("asix_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n", + phy_id, loc, val); + asix_set_sw_mii(dev); + asix_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, &res); + asix_set_hw_mii(dev); +} + +/* + * Asix "high level" commands + */ +static int asix_sw_reset(struct ueth_data *dev, u8 flags) +{ + int ret; + + ret = asix_write_cmd(dev, AX_CMD_SW_RESET, flags, 0, 0, NULL); + if (ret < 0) + debug("Failed to send software reset: %02x\n", ret); + else + udelay(150 * 1000); + + return ret; +} + +static inline int asix_get_phy_addr(struct ueth_data *dev) +{ + u8 buf[2]; + int ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf); + + debug("asix_get_phy_addr()\n"); + + if (ret < 0) { + debug("Error reading PHYID register: %02x\n", ret); + goto out; + } + debug("asix_get_phy_addr() returning 0x%04x\n", *((__le16 *)buf)); + ret = buf[1]; + +out: + return ret; +} + +static int asix_write_medium_mode(struct ueth_data *dev, u16 mode) +{ + int ret; + + debug("asix_write_medium_mode() - mode = 0x%04x\n", mode); + ret = asix_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, mode, + 0, 0, NULL); + if (ret < 0) { + debug("Failed to write Medium Mode mode to 0x%04x: %02x\n", + mode, ret); + } + return ret; +} + +static u16 asix_read_rx_ctl(struct ueth_data *dev) +{ + __le16 v; + int ret = asix_read_cmd(dev, AX_CMD_READ_RX_CTL, 0, 0, 2, &v); + + if (ret < 0) + debug("Error reading RX_CTL register: %02x\n", ret); + else + ret = le16_to_cpu(v); + return ret; +} + +static int asix_write_rx_ctl(struct ueth_data *dev, u16 mode) +{ + int ret; + + debug("asix_write_rx_ctl() - mode = 0x%04x\n", mode); + ret = asix_write_cmd(dev, AX_CMD_WRITE_RX_CTL, mode, 0, 0, NULL); + if (ret < 0) { + debug("Failed to write RX_CTL mode to 0x%04x: %02x\n", + mode, ret); + } + return ret; +} + +static int asix_write_gpio(struct ueth_data *dev, u16 value, int sleep) +{ + int ret; + + debug("asix_write_gpio() - value = 0x%04x\n", value); + ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, value, 0, 0, NULL); + if (ret < 0) { + debug("Failed to write GPIO value 0x%04x: %02x\n", + value, ret); + } + if (sleep) + udelay(sleep * 1000); + + return ret; +} + +/* + * mii commands + */ + +/* + * mii_nway_restart - restart NWay (autonegotiation) for this interface + * + * Returns 0 on success, negative on error. + */ +static int mii_nway_restart(struct ueth_data *dev) +{ + int bmcr; + int r = -1; + + /* if autoneg is off, it's an error */ + bmcr = asix_mdio_read(dev, dev->phy_id, MII_BMCR); + + if (bmcr & BMCR_ANENABLE) { + bmcr |= BMCR_ANRESTART; + asix_mdio_write(dev, dev->phy_id, MII_BMCR, bmcr); + r = 0; + } + + return r; +} + +/* + * Asix callbacks + */ +static int asix_init(struct eth_device *eth, bd_t *bd) +{ + int embd_phy; + unsigned char buf[ETH_ALEN]; + u16 rx_ctl; + struct ueth_data *dev = (struct ueth_data *)eth->priv; + int timeout = 0; +#define TIMEOUT_RESOLUTION 50 /* ms */ + int link_detected; + + debug("** %s()\n", __func__); + + if (asix_write_gpio(dev, + AX_GPIO_RSE | AX_GPIO_GPO_2 | AX_GPIO_GPO2EN, 5) < 0) + goto out_err; + + /* 0x10 is the phy id of the embedded 10/100 ethernet phy */ + embd_phy = ((asix_get_phy_addr(dev) & 0x1f) == 0x10 ? 1 : 0); + if (asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT, + embd_phy, 0, 0, NULL) < 0) { + debug("Select PHY #1 failed\n"); + goto out_err; + } + + if (asix_sw_reset(dev, AX_SWRESET_IPPD | AX_SWRESET_PRL) < 0) + goto out_err; + + if (asix_sw_reset(dev, AX_SWRESET_CLEAR) < 0) + goto out_err; + + if (embd_phy) { + if (asix_sw_reset(dev, AX_SWRESET_IPRL) < 0) + goto out_err; + } else { + if (asix_sw_reset(dev, AX_SWRESET_PRTE) < 0) + goto out_err; + } + + rx_ctl = asix_read_rx_ctl(dev); + debug("RX_CTL is 0x%04x after software reset\n", rx_ctl); + if (asix_write_rx_ctl(dev, 0x0000) < 0) + goto out_err; + + rx_ctl = asix_read_rx_ctl(dev); + debug("RX_CTL is 0x%04x setting to 0x0000\n", rx_ctl); + + /* Get the MAC address */ + if (asix_read_cmd(dev, AX_CMD_READ_NODE_ID, + 0, 0, ETH_ALEN, buf) < 0) { + debug("Failed to read MAC address.\n"); + goto out_err; + } + memcpy(eth->enetaddr, buf, ETH_ALEN); + debug("MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + eth->enetaddr[0], eth->enetaddr[1], + eth->enetaddr[2], eth->enetaddr[3], + eth->enetaddr[4], eth->enetaddr[5]); + + dev->phy_id = asix_get_phy_addr(dev); + if (dev->phy_id < 0) + debug("Failed to read phy id\n"); + + if (asix_sw_reset(dev, AX_SWRESET_PRL) < 0) + goto out_err; + + if (asix_sw_reset(dev, AX_SWRESET_IPRL | AX_SWRESET_PRL) < 0) + goto out_err; + + asix_mdio_write(dev, dev->phy_id, MII_BMCR, BMCR_RESET); + asix_mdio_write(dev, dev->phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA); + mii_nway_restart(dev); + + if (asix_write_medium_mode(dev, AX88772_MEDIUM_DEFAULT) < 0) + goto out_err; + + if (asix_write_cmd(dev, AX_CMD_WRITE_IPG0, + AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT, + AX88772_IPG2_DEFAULT, 0, NULL) < 0) { + debug("Write IPG,IPG1,IPG2 failed\n"); + goto out_err; + } + + if (asix_write_rx_ctl(dev, AX_DEFAULT_RX_CTL) < 0) + goto out_err; + + do { + link_detected = asix_mdio_read(dev, dev->phy_id, MII_BMSR) & + BMSR_LSTATUS; + if (!link_detected) { + if (timeout == 0) + printf("Waiting for Ethernet connection... "); + udelay(TIMEOUT_RESOLUTION * 1000); + timeout += TIMEOUT_RESOLUTION; + } + } while (!link_detected && timeout < PHY_CONNECT_TIMEOUT); + if (link_detected) { + if (timeout != 0) + printf("done.\n"); + } else { + printf("unable to connect.\n"); + goto out_err; + } + + return 0; +out_err: + return -1; +} + +static int asix_send(struct eth_device *eth, volatile void *packet, int length) +{ + struct ueth_data *dev = (struct ueth_data *)eth->priv; + int err; + u32 packet_len; + int actual_len; + unsigned char msg[PKTSIZE + sizeof(packet_len)]; + + debug("** %s(), len %d\n", __func__, length); + + packet_len = (((length) ^ 0x0000ffff) << 16) + (length); + cpu_to_le32s(&packet_len); + + memcpy(msg, &packet_len, sizeof(packet_len)); + memcpy(msg + sizeof(packet_len), (void *)packet, length); + if (length & 1) + length++; + + err = usb_bulk_msg(dev->pusb_dev, + usb_sndbulkpipe(dev->pusb_dev, dev->ep_out), + (void *)msg, + length + sizeof(packet_len), + &actual_len, + USB_BULK_SEND_TIMEOUT); + debug("Tx: len = %u, actual = %u, err = %d\n", + length + sizeof(packet_len), actual_len, err); + + return err; +} + +static int asix_recv(struct eth_device *eth) +{ + struct ueth_data *dev = (struct ueth_data *)eth->priv; + static unsigned char recv_buf[AX_RX_URB_SIZE]; + unsigned char *buf_ptr; + int err; + int actual_len; + u32 packet_len; + + debug("** %s()\n", __func__); + + err = usb_bulk_msg(dev->pusb_dev, + usb_rcvbulkpipe(dev->pusb_dev, dev->ep_in), + (void *)recv_buf, + AX_RX_URB_SIZE, + &actual_len, + USB_BULK_RECV_TIMEOUT); + debug("Rx: len = %u, actual = %u, err = %d\n", AX_RX_URB_SIZE, + actual_len, err); + if (err != 0) { + debug("Rx: failed to receive\n"); + return -1; + } + if (actual_len > AX_RX_URB_SIZE) { + debug("Rx: received too many bytes %d\n", actual_len); + return -1; + } + + buf_ptr = recv_buf; + while (actual_len > 0) { + /* + * 1st 4 bytes contain the length of the actual data as two + * complementary 16-bit words. Extract the length of the data. + */ + if (actual_len < sizeof(packet_len)) { + debug("Rx: incomplete packet length\n"); + return -1; + } + memcpy(&packet_len, buf_ptr, sizeof(packet_len)); + le32_to_cpus(&packet_len); + if (((packet_len >> 16) ^ 0xffff) != (packet_len & 0xffff)) { + debug("Rx: malformed packet length: %#x (%#x:%#x)\n", + packet_len, (packet_len >> 16) ^ 0xffff, + packet_len & 0xffff); + return -1; + } + packet_len = packet_len & 0xffff; + if (packet_len > actual_len - sizeof(packet_len)) { + debug("Rx: too large packet: %d\n", packet_len); + return -1; + } + + /* Notify net stack */ + NetReceive(buf_ptr + sizeof(packet_len), packet_len); + + /* Adjust for next iteration. Packets are padded to 16-bits */ + if (packet_len & 1) + packet_len++; + actual_len -= sizeof(packet_len) + packet_len; + buf_ptr += sizeof(packet_len) + packet_len; + } + + return err; +} + +static void asix_halt(struct eth_device *eth) +{ + debug("** %s()\n", __func__); +} + +/* + * Asix probing functions + */ +void asix_eth_before_probe(void) +{ + curr_eth_dev = 0; +} + +struct asix_dongle { + unsigned short vendor; + unsigned short product; +}; + +static struct asix_dongle asix_dongles[] = { + { 0x05ac, 0x1402 }, /* Apple USB Ethernet Adapter */ + { 0x07d1, 0x3c05 }, /* D-Link DUB-E100 H/W Ver B1 */ + { 0x0b95, 0x772a }, /* Cables-to-Go USB Ethernet Adapter */ + { 0x0b95, 0x7720 }, /* Trendnet TU2-ET100 V3.0R */ + { 0x0b95, 0x1720 }, /* SMC */ + { 0x0db0, 0xa877 }, /* MSI - ASIX 88772a */ + { 0x13b1, 0x0018 }, /* Linksys 200M v2.1 */ + { 0x1557, 0x7720 }, /* 0Q0 cable ethernet */ + { 0x2001, 0x3c05 }, /* DLink DUB-E100 H/W Ver B1 Alternate */ + { 0x0000, 0x0000 } /* END - Do not remove */ +}; + +/* Probe to see if a new device is actually an asix device */ +int asix_eth_probe(struct usb_device *dev, unsigned int ifnum, + struct ueth_data *ss) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *iface_desc; + int i; + + /* let's examine the device now */ + iface = &dev->config.if_desc[ifnum]; + iface_desc = &dev->config.if_desc[ifnum].desc; + + for (i = 0; asix_dongles[i].vendor != 0; i++) { + if (dev->descriptor.idVendor == asix_dongles[i].vendor && + dev->descriptor.idProduct == asix_dongles[i].product) + /* Found a supported dongle */ + break; + } + + if (asix_dongles[i].vendor == 0) + return 0; + + memset(ss, 0, sizeof(struct ueth_data)); + + /* At this point, we know we've got a live one */ + debug("\n\nUSB Ethernet device detected: %#04x:%#04x\n", + dev->descriptor.idVendor, dev->descriptor.idProduct); + + /* Initialize the ueth_data structure with some useful info */ + ss->ifnum = ifnum; + ss->pusb_dev = dev; + ss->subclass = iface_desc->bInterfaceSubClass; + ss->protocol = iface_desc->bInterfaceProtocol; + + /* + * We are expecting a minimum of 3 endpoints - in, out (bulk), and + * int. We will ignore any others. + */ + for (i = 0; i < iface_desc->bNumEndpoints; i++) { + /* is it an BULK endpoint? */ + if ((iface->ep_desc[i].bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) { + if (iface->ep_desc[i].bEndpointAddress & USB_DIR_IN) + ss->ep_in = iface->ep_desc[i].bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + else + ss->ep_out = + iface->ep_desc[i].bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + } + + /* is it an interrupt endpoint? */ + if ((iface->ep_desc[i].bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) { + ss->ep_int = iface->ep_desc[i].bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + ss->irqinterval = iface->ep_desc[i].bInterval; + } + } + debug("Endpoints In %d Out %d Int %d\n", + ss->ep_in, ss->ep_out, ss->ep_int); + + /* Do some basic sanity checks, and bail if we find a problem */ + if (usb_set_interface(dev, iface_desc->bInterfaceNumber, 0) || + !ss->ep_in || !ss->ep_out || !ss->ep_int) { + debug("Problems with device\n"); + return 0; + } + dev->privptr = (void *)ss; + return 1; +} + +int asix_eth_get_info(struct usb_device *dev, struct ueth_data *ss, + struct eth_device *eth) +{ + if (!eth) { + debug("%s: missing parameter.\n", __func__); + return 0; + } + sprintf(eth->name, "%s%d", ASIX_BASE_NAME, curr_eth_dev++); + eth->init = asix_init; + eth->send = asix_send; + eth->recv = asix_recv; + eth->halt = asix_halt; + eth->priv = ss; + + return 1; +} diff --git a/drivers/usb/eth/usb_ether.c b/drivers/usb/eth/usb_ether.c new file mode 100644 index 0000000..9e3d006 --- /dev/null +++ b/drivers/usb/eth/usb_ether.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <usb.h> + +#include "usb_ether.h" + +typedef void (*usb_eth_before_probe)(void); +typedef int (*usb_eth_probe)(struct usb_device *dev, unsigned int ifnum, + struct ueth_data *ss); +typedef int (*usb_eth_get_info)(struct usb_device *dev, struct ueth_data *ss, + struct eth_device *dev_desc); + +struct usb_eth_prob_dev { + usb_eth_before_probe before_probe; /* optional */ + usb_eth_probe probe; + usb_eth_get_info get_info; +}; + +/* driver functions go here, each bracketed by #ifdef CONFIG_USB_ETHER_xxx */ +static const struct usb_eth_prob_dev prob_dev[] = { +#ifdef CONFIG_USB_ETHER_ASIX + { + .before_probe = asix_eth_before_probe, + .probe = asix_eth_probe, + .get_info = asix_eth_get_info, + }, +#endif + { }, /* END */ +}; + +static int usb_max_eth_dev; /* number of highest available usb eth device */ +static struct ueth_data usb_eth[USB_MAX_ETH_DEV]; + +/******************************************************************************* + * tell if current ethernet device is a usb dongle + */ +int is_eth_dev_on_usb_host(void) +{ + int i; + struct eth_device *dev = eth_get_dev(); + + if (dev) { + for (i = 0; i < usb_max_eth_dev; i++) + if (&usb_eth[i].eth_dev == dev) + return 1; + } + return 0; +} + +/* + * Given a USB device, ask each driver if it can support it, and attach it + * to the first driver that says 'yes' + */ +static void probe_valid_drivers(struct usb_device *dev) +{ + int j; + + for (j = 0; prob_dev[j].probe && prob_dev[j].get_info; j++) { + if (!prob_dev[j].probe(dev, 0, &usb_eth[usb_max_eth_dev])) + continue; + /* + * ok, it is a supported eth device. Get info and fill it in + */ + if (prob_dev[j].get_info(dev, + &usb_eth[usb_max_eth_dev], + &usb_eth[usb_max_eth_dev].eth_dev)) { + /* found proper driver */ + /* register with networking stack */ + usb_max_eth_dev++; + + /* + * usb_max_eth_dev must be incremented prior to this + * call since eth_current_changed (internally called) + * relies on it + */ + eth_register(&usb_eth[usb_max_eth_dev - 1].eth_dev); + break; + } + } + } + +/******************************************************************************* + * scan the usb and reports device info + * to the user if mode = 1 + * returns current device or -1 if no + */ +int usb_host_eth_scan(int mode) +{ + int i, old_async; + struct usb_device *dev; + + + if (mode == 1) + printf(" scanning bus for ethernet devices... "); + + old_async = usb_disable_asynch(1); /* asynch transfer not allowed */ + + for (i = 0; i < USB_MAX_ETH_DEV; i++) + memset(&usb_eth[i], 0, sizeof(usb_eth[i])); + + for (i = 0; prob_dev[i].probe; i++) { + if (prob_dev[i].before_probe) + prob_dev[i].before_probe(); + } + + usb_max_eth_dev = 0; + for (i = 0; i < USB_MAX_DEVICE; i++) { + dev = usb_get_dev_index(i); /* get device */ + debug("i=%d\n", i); + if (dev == NULL) + break; /* no more devices avaiable */ + + /* find valid usb_ether driver for this device, if any */ + probe_valid_drivers(dev); + + /* check limit */ + if (usb_max_eth_dev == USB_MAX_ETH_DEV) { + printf("max USB Ethernet Device reached: %d stopping\n", + usb_max_eth_dev); + break; + } + } /* for */ + + usb_disable_asynch(old_async); /* restore asynch value */ + printf("%d Ethernet Device(s) found\n", usb_max_eth_dev); + if (usb_max_eth_dev > 0) + return 0; + return -1; +} + diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index f137817..7d5b504 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libusb_gadget.o # new USB gadget layer dependencies ifdef CONFIG_USB_ETHER COBJS-y += ether.o epautoconf.o config.o usbstring.o +COBJS-$(CONFIG_USB_ETH_RNDIS) += rndis.o else # Devices not related to the new gadget layer depend on CONFIG_USB_DEVICE ifdef CONFIG_USB_DEVICE diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 5a18e03..9fb0e80 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -22,15 +22,18 @@ #include <common.h> #include <asm/errno.h> +#include <linux/netdevice.h> #include <linux/usb/ch9.h> #include <linux/usb/cdc.h> #include <linux/usb/gadget.h> #include <net.h> +#include <malloc.h> #include <linux/ctype.h> #include "gadget_chips.h" +#include "rndis.h" -#define USB_NET_NAME "usb0" +#define USB_NET_NAME "usb_ether" #define atomic_read extern struct platform_data brd; @@ -94,7 +97,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 \ @@ -103,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; static struct usb_gadget_driver eth_driver; @@ -121,8 +168,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 +219,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 +245,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 +263,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 +296,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 +309,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 +371,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 +421,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 +444,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 +455,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 +528,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 +540,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 +595,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 +695,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 +795,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, @@ -671,6 +849,10 @@ static struct usb_string strings[] = { #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 +913,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 +941,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 +950,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 +976,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 +1010,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 +1029,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 +1053,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 +1081,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 +1120,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 +1136,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 +1227,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 +1363,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 +1385,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 +1405,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 +1445,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 +1529,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 +1566,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 +1632,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 +1774,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) { @@ -1456,12 +1803,14 @@ static void eth_unbind(struct usb_gadget *gadget) /* unregister_netdev (dev->net);*/ /* free_netdev(dev->net);*/ + dev->gadget = NULL; set_gadget_data(gadget, NULL); } 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) @@ -1476,6 +1825,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) { @@ -1537,8 +2007,9 @@ 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]; @@ -1546,6 +2017,9 @@ static int eth_bind(struct usb_gadget *gadget) #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 * standard protocol is _strongly_ preferred for interop purposes. @@ -1560,6 +2034,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; @@ -1585,22 +2060,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) @@ -1628,18 +2126,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 @@ -1660,8 +2163,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 ... */ @@ -1672,7 +2181,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; @@ -1683,10 +2192,15 @@ 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; - strcpy(dev->net->name, USB_NET_NAME); dev->cdc = cdc; dev->zlp = zlp; @@ -1697,7 +2211,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 +2228,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 +2250,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 +2274,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; @@ -1789,6 +2340,34 @@ static int usb_eth_init(struct eth_device *netdev, bd_t *bd) goto fail; } + /* Configure default mac-addresses for the USB ethernet device */ +#ifdef CONFIG_USBNET_DEV_ADDR + strlcpy(dev_addr, CONFIG_USBNET_DEV_ADDR, sizeof(dev_addr)); +#endif +#ifdef CONFIG_USBNET_HOST_ADDR + strlcpy(host_addr, CONFIG_USBNET_HOST_ADDR, sizeof(host_addr)); +#endif + /* Check if the user overruled the MAC addresses */ + if (getenv("usbnet_devaddr")) + strlcpy(dev_addr, getenv("usbnet_devaddr"), + sizeof(dev_addr)); + + if (getenv("usbnet_hostaddr")) + strlcpy(host_addr, getenv("usbnet_hostaddr"), + sizeof(host_addr)); + + if (!is_eth_addr_valid(dev_addr)) { + error("Need valid 'usbnet_devaddr' to be set"); + goto fail; + } + if (!is_eth_addr_valid(host_addr)) { + error("Need valid 'usbnet_hostaddr' to be set"); + goto fail; + } + + if (usb_gadget_register_driver(ð_driver) < 0) + goto fail; + dev->network_started = 0; packet_received = 0; @@ -1821,6 +2400,7 @@ static int usb_eth_send(struct eth_device *netdev, volatile 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; @@ -1828,6 +2408,20 @@ static int usb_eth_send(struct eth_device *netdev, debug("%s:...\n", __func__); + /* 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), + (void *)packet, length); + packet = rndis_pkt; + length += sizeof(struct rndis_packet_msg_type); + } req->buf = (void *)packet; req->context = NULL; req->complete = tx_complete; @@ -1863,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) @@ -1895,7 +2494,32 @@ void usb_eth_halt(struct eth_device *netdev) return; } + /* If the gadget not registered, simple return */ + 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); } static struct usb_gadget_driver eth_driver = { @@ -1913,10 +2537,9 @@ static struct usb_gadget_driver eth_driver = { int usb_eth_initialize(bd_t *bi) { - int status = 0; struct eth_device *netdev = &l_netdev; - sprintf(netdev->name, "usb_ether"); + strlcpy(netdev->name, USB_NET_NAME, sizeof(netdev->name)); netdev->init = usb_eth_init; netdev->send = usb_eth_send; @@ -1926,45 +2549,6 @@ int usb_eth_initialize(bd_t *bi) #ifdef CONFIG_MCAST_TFTP #error not supported #endif - /* Configure default mac-addresses for the USB ethernet device */ -#ifdef CONFIG_USBNET_DEV_ADDR - strncpy(dev_addr, CONFIG_USBNET_DEV_ADDR, sizeof(dev_addr)); -#endif -#ifdef CONFIG_USBNET_HOST_ADDR - strncpy(host_addr, CONFIG_USBNET_HOST_ADDR, sizeof(host_addr)); -#endif - /* Check if the user overruled the MAC addresses */ - if (getenv("usbnet_devaddr")) - strncpy(dev_addr, getenv("usbnet_devaddr"), - sizeof(dev_addr)); - - if (getenv("usbnet_hostaddr")) - strncpy(host_addr, getenv("usbnet_hostaddr"), - sizeof(host_addr)); - - /* Make sure both strings are terminated */ - dev_addr[sizeof(dev_addr)-1] = '\0'; - host_addr[sizeof(host_addr)-1] = '\0'; - - if (!is_eth_addr_valid(dev_addr)) { - error("Need valid 'usbnet_devaddr' to be set"); - status = -1; - } - if (!is_eth_addr_valid(host_addr)) { - error("Need valid 'usbnet_hostaddr' to be set"); - status = -1; - } - if (status) - goto fail; - - status = usb_gadget_register_driver(ð_driver); - if (status < 0) - goto fail; - eth_register(netdev); return 0; - -fail: - error("%s failed. error = %d", __func__, status); - return status; } diff --git a/drivers/usb/gadget/ndis.h b/drivers/usb/gadget/ndis.h new file mode 100644 index 0000000..753838f --- /dev/null +++ b/drivers/usb/gadget/ndis.h @@ -0,0 +1,217 @@ +/* + * ndis.h + * + * ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de> + * + * Thanks to the cygwin development team, + * espacially to Casper S. Hornstrup <chorns@users.sourceforge.net> + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#ifndef _USBGADGET_NDIS_H +#define _USBGADGET_NDIS_H + + +#define NDIS_STATUS_MULTICAST_FULL 0xC0010009 +#define NDIS_STATUS_MULTICAST_EXISTS 0xC001000A +#define NDIS_STATUS_MULTICAST_NOT_FOUND 0xC001000B + +enum NDIS_DEVICE_POWER_STATE { + NdisDeviceStateUnspecified = 0, + NdisDeviceStateD0, + NdisDeviceStateD1, + NdisDeviceStateD2, + NdisDeviceStateD3, + NdisDeviceStateMaximum +}; + +struct NDIS_PM_WAKE_UP_CAPABILITIES { + enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp; + enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp; + enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp; +}; + +/* NDIS_PNP_CAPABILITIES.Flags constants */ +#define NDIS_DEVICE_WAKE_UP_ENABLE 0x00000001 +#define NDIS_DEVICE_WAKE_ON_PATTERN_MATCH_ENABLE 0x00000002 +#define NDIS_DEVICE_WAKE_ON_MAGIC_PACKET_ENABLE 0x00000004 + +struct NDIS_PNP_CAPABILITIES { + __le32 Flags; + struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities; +}; + +struct NDIS_PM_PACKET_PATTERN { + __le32 Priority; + __le32 Reserved; + __le32 MaskSize; + __le32 PatternOffset; + __le32 PatternSize; + __le32 PatternFlags; +}; + + +/* Required Object IDs (OIDs) */ +#define OID_GEN_SUPPORTED_LIST 0x00010101 +#define OID_GEN_HARDWARE_STATUS 0x00010102 +#define OID_GEN_MEDIA_SUPPORTED 0x00010103 +#define OID_GEN_MEDIA_IN_USE 0x00010104 +#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105 +#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106 +#define OID_GEN_LINK_SPEED 0x00010107 +#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108 +#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109 +#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A +#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B +#define OID_GEN_VENDOR_ID 0x0001010C +#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D +#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E +#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F +#define OID_GEN_DRIVER_VERSION 0x00010110 +#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111 +#define OID_GEN_PROTOCOL_OPTIONS 0x00010112 +#define OID_GEN_MAC_OPTIONS 0x00010113 +#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114 +#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115 +#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116 +#define OID_GEN_SUPPORTED_GUIDS 0x00010117 +#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118 +#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119 +#define OID_GEN_MACHINE_NAME 0x0001021A +#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B +#define OID_GEN_VLAN_ID 0x0001021C + +/* Optional OIDs */ +#define OID_GEN_MEDIA_CAPABILITIES 0x00010201 +#define OID_GEN_PHYSICAL_MEDIUM 0x00010202 + +/* Required statistics OIDs */ +#define OID_GEN_XMIT_OK 0x00020101 +#define OID_GEN_RCV_OK 0x00020102 +#define OID_GEN_XMIT_ERROR 0x00020103 +#define OID_GEN_RCV_ERROR 0x00020104 +#define OID_GEN_RCV_NO_BUFFER 0x00020105 + +/* Optional statistics OIDs */ +#define OID_GEN_DIRECTED_BYTES_XMIT 0x00020201 +#define OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202 +#define OID_GEN_MULTICAST_BYTES_XMIT 0x00020203 +#define OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204 +#define OID_GEN_BROADCAST_BYTES_XMIT 0x00020205 +#define OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206 +#define OID_GEN_DIRECTED_BYTES_RCV 0x00020207 +#define OID_GEN_DIRECTED_FRAMES_RCV 0x00020208 +#define OID_GEN_MULTICAST_BYTES_RCV 0x00020209 +#define OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A +#define OID_GEN_BROADCAST_BYTES_RCV 0x0002020B +#define OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C +#define OID_GEN_RCV_CRC_ERROR 0x0002020D +#define OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E +#define OID_GEN_GET_TIME_CAPS 0x0002020F +#define OID_GEN_GET_NETCARD_TIME 0x00020210 +#define OID_GEN_NETCARD_LOAD 0x00020211 +#define OID_GEN_DEVICE_PROFILE 0x00020212 +#define OID_GEN_INIT_TIME_MS 0x00020213 +#define OID_GEN_RESET_COUNTS 0x00020214 +#define OID_GEN_MEDIA_SENSE_COUNTS 0x00020215 +#define OID_GEN_FRIENDLY_NAME 0x00020216 +#define OID_GEN_MINIPORT_INFO 0x00020217 +#define OID_GEN_RESET_VERIFY_PARAMETERS 0x00020218 + +/* IEEE 802.3 (Ethernet) OIDs */ +#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001 + +#define OID_802_3_PERMANENT_ADDRESS 0x01010101 +#define OID_802_3_CURRENT_ADDRESS 0x01010102 +#define OID_802_3_MULTICAST_LIST 0x01010103 +#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104 +#define OID_802_3_MAC_OPTIONS 0x01010105 +#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101 +#define OID_802_3_XMIT_ONE_COLLISION 0x01020102 +#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103 +#define OID_802_3_XMIT_DEFERRED 0x01020201 +#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202 +#define OID_802_3_RCV_OVERRUN 0x01020203 +#define OID_802_3_XMIT_UNDERRUN 0x01020204 +#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205 +#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206 +#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207 + +/* OID_GEN_MINIPORT_INFO constants */ +#define NDIS_MINIPORT_BUS_MASTER 0x00000001 +#define NDIS_MINIPORT_WDM_DRIVER 0x00000002 +#define NDIS_MINIPORT_SG_LIST 0x00000004 +#define NDIS_MINIPORT_SUPPORTS_MEDIA_QUERY 0x00000008 +#define NDIS_MINIPORT_INDICATES_PACKETS 0x00000010 +#define NDIS_MINIPORT_IGNORE_PACKET_QUEUE 0x00000020 +#define NDIS_MINIPORT_IGNORE_REQUEST_QUEUE 0x00000040 +#define NDIS_MINIPORT_IGNORE_TOKEN_RING_ERRORS 0x00000080 +#define NDIS_MINIPORT_INTERMEDIATE_DRIVER 0x00000100 +#define NDIS_MINIPORT_IS_NDIS_5 0x00000200 +#define NDIS_MINIPORT_IS_CO 0x00000400 +#define NDIS_MINIPORT_DESERIALIZE 0x00000800 +#define NDIS_MINIPORT_REQUIRES_MEDIA_POLLING 0x00001000 +#define NDIS_MINIPORT_SUPPORTS_MEDIA_SENSE 0x00002000 +#define NDIS_MINIPORT_NETBOOT_CARD 0x00004000 +#define NDIS_MINIPORT_PM_SUPPORTED 0x00008000 +#define NDIS_MINIPORT_SUPPORTS_MAC_ADDRESS_OVERWRITE 0x00010000 +#define NDIS_MINIPORT_USES_SAFE_BUFFER_APIS 0x00020000 +#define NDIS_MINIPORT_HIDDEN 0x00040000 +#define NDIS_MINIPORT_SWENUM 0x00080000 +#define NDIS_MINIPORT_SURPRISE_REMOVE_OK 0x00100000 +#define NDIS_MINIPORT_NO_HALT_ON_SUSPEND 0x00200000 +#define NDIS_MINIPORT_HARDWARE_DEVICE 0x00400000 +#define NDIS_MINIPORT_SUPPORTS_CANCEL_SEND_PACKETS 0x00800000 +#define NDIS_MINIPORT_64BITS_DMA 0x01000000 + +#define NDIS_MEDIUM_802_3 0x00000000 +#define NDIS_MEDIUM_802_5 0x00000001 +#define NDIS_MEDIUM_FDDI 0x00000002 +#define NDIS_MEDIUM_WAN 0x00000003 +#define NDIS_MEDIUM_LOCAL_TALK 0x00000004 +#define NDIS_MEDIUM_DIX 0x00000005 +#define NDIS_MEDIUM_ARCENT_RAW 0x00000006 +#define NDIS_MEDIUM_ARCENT_878_2 0x00000007 +#define NDIS_MEDIUM_ATM 0x00000008 +#define NDIS_MEDIUM_WIRELESS_LAN 0x00000009 +#define NDIS_MEDIUM_IRDA 0x0000000A +#define NDIS_MEDIUM_BPC 0x0000000B +#define NDIS_MEDIUM_CO_WAN 0x0000000C +#define NDIS_MEDIUM_1394 0x0000000D + +#define NDIS_PACKET_TYPE_DIRECTED 0x00000001 +#define NDIS_PACKET_TYPE_MULTICAST 0x00000002 +#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004 +#define NDIS_PACKET_TYPE_BROADCAST 0x00000008 +#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010 +#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020 +#define NDIS_PACKET_TYPE_SMT 0x00000040 +#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080 +#define NDIS_PACKET_TYPE_GROUP 0x00000100 +#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200 +#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400 +#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800 + +#define NDIS_MEDIA_STATE_CONNECTED 0x00000000 +#define NDIS_MEDIA_STATE_DISCONNECTED 0x00000001 + +#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA 0x00000001 +#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED 0x00000002 +#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND 0x00000004 +#define NDIS_MAC_OPTION_NO_LOOPBACK 0x00000008 +#define NDIS_MAC_OPTION_FULL_DUPLEX 0x00000010 +#define NDIS_MAC_OPTION_EOTX_INDICATION 0x00000020 +#define NDIS_MAC_OPTION_8021P_PRIORITY 0x00000040 +#define NDIS_MAC_OPTION_RESERVED 0x80000000 + +#endif /* _USBGADGET_NDIS_H */ diff --git a/drivers/usb/gadget/rndis.c b/drivers/usb/gadget/rndis.c new file mode 100644 index 0000000..886c093 --- /dev/null +++ b/drivers/usb/gadget/rndis.c @@ -0,0 +1,1317 @@ +/* + * RNDIS MSG parser + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + * + * 03/12/2004 Kai-Uwe Bloem <linux-development@auerswald.de> + * Fixed message length bug in init_response + * + * 03/25/2004 Kai-Uwe Bloem <linux-development@auerswald.de> + * Fixed rndis_rm_hdr length bug. + * + * Copyright (C) 2004 by David Brownell + * updates to merge with Linux 2.6, better match RNDIS spec + */ + +#include <common.h> +#include <net.h> +#include <malloc.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/netdevice.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <asm/errno.h> + +#undef RNDIS_PM +#undef RNDIS_WAKEUP +#undef VERBOSE + +#include "rndis.h" + +#define ETH_ALEN 6 /* Octets in one ethernet addr */ +#define ETH_HLEN 14 /* Total octets in header. */ +#define ETH_ZLEN 60 /* Min. octets in frame sans FCS */ +#define ETH_DATA_LEN 1500 /* Max. octets in payload */ +#define ETH_FRAME_LEN PKTSIZE_ALIGN /* Max. octets in frame sans FCS */ +#define ETH_FCS_LEN 4 /* Octets in the FCS */ +#define ENOTSUPP 524 /* Operation is not supported */ + + +/* + * The driver for your USB chip needs to support ep0 OUT to work with + * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional). + * + * Windows hosts need an INF file like Documentation/usb/linux.inf + * and will be happier if you provide the host_addr module parameter. + */ + +#define RNDIS_MAX_CONFIGS 1 + +static rndis_params rndis_per_dev_params[RNDIS_MAX_CONFIGS]; + +/* Driver Version */ +static const __le32 rndis_driver_version = __constant_cpu_to_le32(1); + +/* Function Prototypes */ +static rndis_resp_t *rndis_add_response(int configNr, u32 length); + + +/* supported OIDs */ +static const u32 oid_supported_list[] = { + /* the general stuff */ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_PHYSICAL_MEDIUM, +#if 0 + OID_GEN_RNDIS_CONFIG_PARAMETER, +#endif + + /* the statistical stuff */ + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, +#ifdef RNDIS_OPTIONAL_STATS + OID_GEN_DIRECTED_BYTES_XMIT, + OID_GEN_DIRECTED_FRAMES_XMIT, + OID_GEN_MULTICAST_BYTES_XMIT, + OID_GEN_MULTICAST_FRAMES_XMIT, + OID_GEN_BROADCAST_BYTES_XMIT, + OID_GEN_BROADCAST_FRAMES_XMIT, + OID_GEN_DIRECTED_BYTES_RCV, + OID_GEN_DIRECTED_FRAMES_RCV, + OID_GEN_MULTICAST_BYTES_RCV, + OID_GEN_MULTICAST_FRAMES_RCV, + OID_GEN_BROADCAST_BYTES_RCV, + OID_GEN_BROADCAST_FRAMES_RCV, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, +#endif /* RNDIS_OPTIONAL_STATS */ + + /* mandatory 802.3 */ + /* the general stuff */ + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAC_OPTIONS, + OID_802_3_MAXIMUM_LIST_SIZE, + + /* the statistical stuff */ + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, +#ifdef RNDIS_OPTIONAL_STATS + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, +#endif /* RNDIS_OPTIONAL_STATS */ + +#ifdef RNDIS_PM + /* PM and wakeup are mandatory for USB: */ + + /* power management */ + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_PNP_SET_POWER, + +#ifdef RNDIS_WAKEUP + /* wake up host */ + OID_PNP_ENABLE_WAKE_UP, + OID_PNP_ADD_WAKE_UP_PATTERN, + OID_PNP_REMOVE_WAKE_UP_PATTERN, +#endif /* RNDIS_WAKEUP */ +#endif /* RNDIS_PM */ +}; + + +/* NDIS Functions */ +static int gen_ndis_query_resp(int configNr, u32 OID, u8 *buf, + unsigned buf_len, rndis_resp_t *r) +{ + int retval = -ENOTSUPP; + u32 length = 4; /* usually */ + __le32 *outbuf; + int i, count; + rndis_query_cmplt_type *resp; + rndis_params *params; + + if (!r) + return -ENOMEM; + resp = (rndis_query_cmplt_type *) r->buf; + + if (!resp) + return -ENOMEM; + +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + if (buf_len) { + debug("query OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } +#endif + + /* response goes here, right after the header */ + outbuf = (__le32 *) &resp[1]; + resp->InformationBufferOffset = __constant_cpu_to_le32(16); + + params = &rndis_per_dev_params[configNr]; + switch (OID) { + + /* general oids (table 4-1) */ + + /* mandatory */ + case OID_GEN_SUPPORTED_LIST: + debug("%s: OID_GEN_SUPPORTED_LIST\n", __func__); + length = sizeof(oid_supported_list); + count = length / sizeof(u32); + for (i = 0; i < count; i++) + outbuf[i] = cpu_to_le32(oid_supported_list[i]); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_HARDWARE_STATUS: + debug("%s: OID_GEN_HARDWARE_STATUS\n", __func__); + /* + * Bogus question! + * Hardware must be ready to receive high level protocols. + * BTW: + * reddite ergo quae sunt Caesaris Caesari + * et quae sunt Dei Deo! + */ + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_SUPPORTED: + debug("%s: OID_GEN_MEDIA_SUPPORTED\n", __func__); + *outbuf = cpu_to_le32(params->medium); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_IN_USE: + debug("%s: OID_GEN_MEDIA_IN_USE\n", __func__); + /* one medium, one transport... (maybe you do it better) */ + *outbuf = cpu_to_le32(params->medium); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MAXIMUM_FRAME_SIZE: + debug("%s: OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_LINK_SPEED: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_LINK_SPEED\n", __func__); +#endif + if (params->media_state == NDIS_MEDIA_STATE_DISCONNECTED) + *outbuf = __constant_cpu_to_le32(0); + else + *outbuf = cpu_to_le32(params->speed); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_TRANSMIT_BLOCK_SIZE: + debug("%s: OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RECEIVE_BLOCK_SIZE: + debug("%s: OID_GEN_RECEIVE_BLOCK_SIZE\n", __func__); + if (params->dev) { + *outbuf = cpu_to_le32(params->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_VENDOR_ID: + debug("%s: OID_GEN_VENDOR_ID\n", __func__); + *outbuf = cpu_to_le32(params->vendorID); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_VENDOR_DESCRIPTION: + debug("%s: OID_GEN_VENDOR_DESCRIPTION\n", __func__); + length = strlen(params->vendorDescr); + memcpy(outbuf, params->vendorDescr, length); + retval = 0; + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + debug("%s: OID_GEN_VENDOR_DRIVER_VERSION\n", __func__); + /* Created as LE */ + *outbuf = rndis_driver_version; + retval = 0; + break; + + /* mandatory */ + case OID_GEN_CURRENT_PACKET_FILTER: + debug("%s: OID_GEN_CURRENT_PACKET_FILTER\n", __func__); + *outbuf = cpu_to_le32(*params->filter); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MAXIMUM_TOTAL_SIZE: + debug("%s: OID_GEN_MAXIMUM_TOTAL_SIZE\n", __func__); + *outbuf = __constant_cpu_to_le32(RNDIS_MAX_TOTAL_SIZE); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_CONNECT_STATUS: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_MEDIA_CONNECT_STATUS\n", __func__); +#endif + *outbuf = cpu_to_le32(params->media_state); + retval = 0; + break; + + case OID_GEN_PHYSICAL_MEDIUM: + debug("%s: OID_GEN_PHYSICAL_MEDIUM\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + /* + * The RNDIS specification is incomplete/wrong. Some versions + * of MS-Windows expect OIDs that aren't specified there. Other + * versions emit undefined RNDIS messages. DOCUMENT ALL THESE! + */ + case OID_GEN_MAC_OPTIONS: /* from WinME */ + debug("%s: OID_GEN_MAC_OPTIONS\n", __func__); + *outbuf = __constant_cpu_to_le32( + NDIS_MAC_OPTION_RECEIVE_SERIALIZED + | NDIS_MAC_OPTION_FULL_DUPLEX); + retval = 0; + break; + + /* statistics OIDs (table 4-2) */ + + /* mandatory */ + case OID_GEN_XMIT_OK: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_XMIT_OK\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32( + params->stats->tx_packets - + params->stats->tx_errors - + params->stats->tx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_OK: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_RCV_OK\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32( + params->stats->rx_packets - + params->stats->rx_errors - + params->stats->rx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_XMIT_ERROR: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_XMIT_ERROR\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->tx_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_ERROR: +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: OID_GEN_RCV_ERROR\n", __func__); +#endif + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_NO_BUFFER: + debug("%s: OID_GEN_RCV_NO_BUFFER\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_dropped); + retval = 0; + } + break; + +#ifdef RNDIS_OPTIONAL_STATS + case OID_GEN_DIRECTED_BYTES_XMIT: + debug("%s: OID_GEN_DIRECTED_BYTES_XMIT\n", __func__); + /* + * Aunt Tilly's size of shoes + * minus antarctica count of penguins + * divided by weight of Alpha Centauri + */ + if (params->stats) { + *outbuf = cpu_to_le32( + (params->stats->tx_packets - + params->stats->tx_errors - + params->stats->tx_dropped) + * 123); + retval = 0; + } + break; + + case OID_GEN_DIRECTED_FRAMES_XMIT: + debug("%s: OID_GEN_DIRECTED_FRAMES_XMIT\n", __func__); + /* dito */ + if (params->stats) { + *outbuf = cpu_to_le32( + (params->stats->tx_packets - + params->stats->tx_errors - + params->stats->tx_dropped) + / 123); + retval = 0; + } + break; + + case OID_GEN_MULTICAST_BYTES_XMIT: + debug("%s: OID_GEN_MULTICAST_BYTES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast * 1234); + retval = 0; + } + break; + + case OID_GEN_MULTICAST_FRAMES_XMIT: + debug("%s: OID_GEN_MULTICAST_FRAMES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_BYTES_XMIT: + debug("%s: OID_GEN_BROADCAST_BYTES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->tx_packets/42*255); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_FRAMES_XMIT: + debug("%s: OID_GEN_BROADCAST_FRAMES_XMIT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->tx_packets / 42); + retval = 0; + } + break; + + case OID_GEN_DIRECTED_BYTES_RCV: + debug("%s: OID_GEN_DIRECTED_BYTES_RCV\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + case OID_GEN_DIRECTED_FRAMES_RCV: + debug("%s: OID_GEN_DIRECTED_FRAMES_RCV\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + case OID_GEN_MULTICAST_BYTES_RCV: + debug("%s: OID_GEN_MULTICAST_BYTES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast * 1111); + retval = 0; + } + break; + + case OID_GEN_MULTICAST_FRAMES_RCV: + debug("%s: OID_GEN_MULTICAST_FRAMES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->multicast); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_BYTES_RCV: + debug("%s: OID_GEN_BROADCAST_BYTES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_packets/42*255); + retval = 0; + } + break; + + case OID_GEN_BROADCAST_FRAMES_RCV: + debug("%s: OID_GEN_BROADCAST_FRAMES_RCV\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_packets / 42); + retval = 0; + } + break; + + case OID_GEN_RCV_CRC_ERROR: + debug("%s: OID_GEN_RCV_CRC_ERROR\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_crc_errors); + retval = 0; + } + break; + + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + debug("%s: OID_GEN_TRANSMIT_QUEUE_LENGTH\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; +#endif /* RNDIS_OPTIONAL_STATS */ + + /* ieee802.3 OIDs (table 4-3) */ + + /* mandatory */ + case OID_802_3_PERMANENT_ADDRESS: + debug("%s: OID_802_3_PERMANENT_ADDRESS\n", __func__); + if (params->dev) { + length = ETH_ALEN; + memcpy(outbuf, params->host_mac, length); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_CURRENT_ADDRESS: + debug("%s: OID_802_3_CURRENT_ADDRESS\n", __func__); + if (params->dev) { + length = ETH_ALEN; + memcpy(outbuf, params->host_mac, length); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_MULTICAST_LIST: + debug("%s: OID_802_3_MULTICAST_LIST\n", __func__); + /* Multicast base address only */ + *outbuf = __constant_cpu_to_le32(0xE0000000); + retval = 0; + break; + + /* mandatory */ + case OID_802_3_MAXIMUM_LIST_SIZE: + debug("%s: OID_802_3_MAXIMUM_LIST_SIZE\n", __func__); + /* Multicast base address only */ + *outbuf = __constant_cpu_to_le32(1); + retval = 0; + break; + + case OID_802_3_MAC_OPTIONS: + debug("%s: OID_802_3_MAC_OPTIONS\n", __func__); + break; + + /* ieee802.3 statistics OIDs (table 4-4) */ + + /* mandatory */ + case OID_802_3_RCV_ERROR_ALIGNMENT: + debug("%s: OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__); + if (params->stats) { + *outbuf = cpu_to_le32(params->stats->rx_frame_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_XMIT_ONE_COLLISION: + debug("%s: OID_802_3_XMIT_ONE_COLLISION\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case OID_802_3_XMIT_MORE_COLLISIONS: + debug("%s: OID_802_3_XMIT_MORE_COLLISIONS\n", __func__); + *outbuf = __constant_cpu_to_le32(0); + retval = 0; + break; + +#ifdef RNDIS_OPTIONAL_STATS + case OID_802_3_XMIT_DEFERRED: + debug("%s: OID_802_3_XMIT_DEFERRED\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_MAX_COLLISIONS: + debug("%s: OID_802_3_XMIT_MAX_COLLISIONS\n", __func__); + /* TODO */ + break; + + case OID_802_3_RCV_OVERRUN: + debug("%s: OID_802_3_RCV_OVERRUN\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_UNDERRUN: + debug("%s: OID_802_3_XMIT_UNDERRUN\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + debug("%s: OID_802_3_XMIT_HEARTBEAT_FAILURE\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_TIMES_CRS_LOST: + debug("%s: OID_802_3_XMIT_TIMES_CRS_LOST\n", __func__); + /* TODO */ + break; + + case OID_802_3_XMIT_LATE_COLLISIONS: + debug("%s: OID_802_3_XMIT_LATE_COLLISIONS\n", __func__); + /* TODO */ + break; +#endif /* RNDIS_OPTIONAL_STATS */ + +#ifdef RNDIS_PM + /* power management OIDs (table 4-5) */ + case OID_PNP_CAPABILITIES: + debug("%s: OID_PNP_CAPABILITIES\n", __func__); + + /* for now, no wakeup capabilities */ + length = sizeof(struct NDIS_PNP_CAPABILITIES); + memset(outbuf, 0, length); + retval = 0; + break; + case OID_PNP_QUERY_POWER: + debug("%s: OID_PNP_QUERY_POWER D%d\n", __func__, + get_unaligned_le32(buf) - 1); + /* + * only suspend is a real power state, and + * it can't be entered by OID_PNP_SET_POWER... + */ + length = 0; + retval = 0; + break; +#endif + + default: + debug("%s: query unknown OID 0x%08X\n", __func__, OID); + } + if (retval < 0) + length = 0; + + resp->InformationBufferLength = cpu_to_le32(length); + r->length = length + sizeof *resp; + resp->MessageLength = cpu_to_le32(r->length); + return retval; +} + +static int gen_ndis_set_resp(u8 configNr, u32 OID, u8 *buf, u32 buf_len, + rndis_resp_t *r) +{ + rndis_set_cmplt_type *resp; + int retval = -ENOTSUPP; + struct rndis_params *params; +#if (defined(DEBUG) && defined(DEBUG_VERBOSE)) || defined(RNDIS_PM) + int i; +#endif + + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *) r->buf; + if (!resp) + return -ENOMEM; + +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + if (buf_len) { + debug("set OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } +#endif + + params = &rndis_per_dev_params[configNr]; + switch (OID) { + case OID_GEN_CURRENT_PACKET_FILTER: + + /* + * these NDIS_PACKET_TYPE_* bitflags are shared with + * cdc_filter; it's not RNDIS-specific + * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in: + * PROMISCUOUS, DIRECTED, + * MULTICAST, ALL_MULTICAST, BROADCAST + */ + *params->filter = (u16) get_unaligned_le32(buf); + debug("%s: OID_GEN_CURRENT_PACKET_FILTER %08x\n", + __func__, *params->filter); + + /* + * this call has a significant side effect: it's + * what makes the packet flow start and stop, like + * activating the CDC Ethernet altsetting. + */ +#ifdef RNDIS_PM +update_linkstate: +#endif + retval = 0; + if (*params->filter) + params->state = RNDIS_DATA_INITIALIZED; + else + params->state = RNDIS_INITIALIZED; + break; + + case OID_802_3_MULTICAST_LIST: + /* I think we can ignore this */ + debug("%s: OID_802_3_MULTICAST_LIST\n", __func__); + retval = 0; + break; +#if 0 + case OID_GEN_RNDIS_CONFIG_PARAMETER: + { + struct rndis_config_parameter *param; + param = (struct rndis_config_parameter *) buf; + debug("%s: OID_GEN_RNDIS_CONFIG_PARAMETER '%*s'\n", + __func__, + min(cpu_to_le32(param->ParameterNameLength), 80), + buf + param->ParameterNameOffset); + retval = 0; + } + break; +#endif + +#ifdef RNDIS_PM + case OID_PNP_SET_POWER: + /* + * The only real power state is USB suspend, and RNDIS requests + * can't enter it; this one isn't really about power. After + * resuming, Windows forces a reset, and then SET_POWER D0. + * FIXME ... then things go batty; Windows wedges itself. + */ + i = get_unaligned_le32(buf); + debug("%s: OID_PNP_SET_POWER D%d\n", __func__, i - 1); + switch (i) { + case NdisDeviceStateD0: + *params->filter = params->saved_filter; + goto update_linkstate; + case NdisDeviceStateD3: + case NdisDeviceStateD2: + case NdisDeviceStateD1: + params->saved_filter = *params->filter; + retval = 0; + break; + } + break; + +#ifdef RNDIS_WAKEUP + /* + * no wakeup support advertised, so wakeup OIDs always fail: + * - OID_PNP_ENABLE_WAKE_UP + * - OID_PNP_{ADD,REMOVE}_WAKE_UP_PATTERN + */ +#endif + +#endif /* RNDIS_PM */ + + default: + debug("%s: set unknown OID 0x%08X, size %d\n", + __func__, OID, buf_len); + } + + return retval; +} + +/* + * Response Functions + */ + +static int rndis_init_response(int configNr, rndis_init_msg_type *buf) +{ + rndis_init_cmplt_type *resp; + rndis_resp_t *r; + + if (!rndis_per_dev_params[configNr].dev) + return -ENOTSUPP; + + r = rndis_add_response(configNr, sizeof(rndis_init_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_init_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32( + REMOTE_NDIS_INITIALIZE_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(52); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->MajorVersion = __constant_cpu_to_le32(RNDIS_MAJOR_VERSION); + resp->MinorVersion = __constant_cpu_to_le32(RNDIS_MINOR_VERSION); + resp->DeviceFlags = __constant_cpu_to_le32(RNDIS_DF_CONNECTIONLESS); + resp->Medium = __constant_cpu_to_le32(RNDIS_MEDIUM_802_3); + resp->MaxPacketsPerTransfer = __constant_cpu_to_le32(1); + resp->MaxTransferSize = cpu_to_le32( + rndis_per_dev_params[configNr].mtu + + ETHER_HDR_SIZE + + sizeof(struct rndis_packet_msg_type) + + 22); + resp->PacketAlignmentFactor = __constant_cpu_to_le32(0); + resp->AFListOffset = __constant_cpu_to_le32(0); + resp->AFListSize = __constant_cpu_to_le32(0); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + +static int rndis_query_response(int configNr, rndis_query_msg_type *buf) +{ + rndis_query_cmplt_type *resp; + rndis_resp_t *r; + + debug("%s: OID = %08X\n", __func__, get_unaligned_le32(&buf->OID)); + if (!rndis_per_dev_params[configNr].dev) + return -ENOTSUPP; + + /* + * we need more memory: + * gen_ndis_query_resp expects enough space for + * rndis_query_cmplt_type followed by data. + * oid_supported_list is the largest data reply + */ + r = rndis_add_response(configNr, + sizeof(oid_supported_list) + sizeof(rndis_query_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_query_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_QUERY_CMPLT); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + + if (gen_ndis_query_resp(configNr, get_unaligned_le32(&buf->OID), + get_unaligned_le32(&buf->InformationBufferOffset) + + 8 + (u8 *) buf, + get_unaligned_le32(&buf->InformationBufferLength), + r)) { + /* OID not supported */ + resp->Status = __constant_cpu_to_le32( + RNDIS_STATUS_NOT_SUPPORTED); + resp->MessageLength = __constant_cpu_to_le32(sizeof *resp); + resp->InformationBufferLength = __constant_cpu_to_le32(0); + resp->InformationBufferOffset = __constant_cpu_to_le32(0); + } else + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + return 0; +} + +static int rndis_set_response(int configNr, rndis_set_msg_type *buf) +{ + u32 BufLength, BufOffset; + rndis_set_cmplt_type *resp; + rndis_resp_t *r; + + r = rndis_add_response(configNr, sizeof(rndis_set_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *) r->buf; + + BufLength = get_unaligned_le32(&buf->InformationBufferLength); + BufOffset = get_unaligned_le32(&buf->InformationBufferOffset); + +#ifdef VERBOSE + debug("%s: Length: %d\n", __func__, BufLength); + debug("%s: Offset: %d\n", __func__, BufOffset); + debug("%s: InfoBuffer: ", __func__); + + for (i = 0; i < BufLength; i++) + debug("%02x ", *(((u8 *) buf) + i + 8 + BufOffset)); + + debug("\n"); +#endif + + resp->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_SET_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(16); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + if (gen_ndis_set_resp(configNr, get_unaligned_le32(&buf->OID), + ((u8 *) buf) + 8 + BufOffset, BufLength, r)) + resp->Status = __constant_cpu_to_le32( + RNDIS_STATUS_NOT_SUPPORTED); + else + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + +static int rndis_reset_response(int configNr, rndis_reset_msg_type *buf) +{ + rndis_reset_cmplt_type *resp; + rndis_resp_t *r; + + r = rndis_add_response(configNr, sizeof(rndis_reset_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_reset_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_RESET_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(16); + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + /* resent information */ + resp->AddressingReset = __constant_cpu_to_le32(1); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + +static int rndis_keepalive_response(int configNr, + rndis_keepalive_msg_type *buf) +{ + rndis_keepalive_cmplt_type *resp; + rndis_resp_t *r; + + /* host "should" check only in RNDIS_DATA_INITIALIZED state */ + + r = rndis_add_response(configNr, sizeof(rndis_keepalive_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_keepalive_cmplt_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32( + REMOTE_NDIS_KEEPALIVE_CMPLT); + resp->MessageLength = __constant_cpu_to_le32(16); + resp->RequestID = get_unaligned(&buf->RequestID); /* Still LE in msg buffer */ + resp->Status = __constant_cpu_to_le32(RNDIS_STATUS_SUCCESS); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + + return 0; +} + + +/* + * Device to Host Comunication + */ +static int rndis_indicate_status_msg(int configNr, u32 status) +{ + rndis_indicate_status_msg_type *resp; + rndis_resp_t *r; + + if (rndis_per_dev_params[configNr].state == RNDIS_UNINITIALIZED) + return -ENOTSUPP; + + r = rndis_add_response(configNr, + sizeof(rndis_indicate_status_msg_type)); + if (!r) + return -ENOMEM; + resp = (rndis_indicate_status_msg_type *) r->buf; + + resp->MessageType = __constant_cpu_to_le32( + REMOTE_NDIS_INDICATE_STATUS_MSG); + resp->MessageLength = __constant_cpu_to_le32(20); + resp->Status = cpu_to_le32(status); + resp->StatusBufferLength = __constant_cpu_to_le32(0); + resp->StatusBufferOffset = __constant_cpu_to_le32(0); + + if (rndis_per_dev_params[configNr].ack) + rndis_per_dev_params[configNr].ack( + rndis_per_dev_params[configNr].dev); + return 0; +} + +int rndis_signal_connect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = NDIS_MEDIA_STATE_CONNECTED; + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_CONNECT); +} + +int rndis_signal_disconnect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = NDIS_MEDIA_STATE_DISCONNECTED; + +#ifdef RNDIS_COMPLETE_SIGNAL_DISCONNECT + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_DISCONNECT); +#else + return 0; +#endif +} + +void rndis_uninit(int configNr) +{ + u8 *buf; + u32 length; + + if (configNr >= RNDIS_MAX_CONFIGS) + return; + rndis_per_dev_params[configNr].used = 0; + rndis_per_dev_params[configNr].state = RNDIS_UNINITIALIZED; + + /* drain the response queue */ + while ((buf = rndis_get_next_response(configNr, &length))) + rndis_free_response(configNr, buf); +} + +void rndis_set_host_mac(int configNr, const u8 *addr) +{ + rndis_per_dev_params[configNr].host_mac = addr; +} + +enum rndis_state rndis_get_state(int configNr) +{ + if (configNr >= RNDIS_MAX_CONFIGS || configNr < 0) + return -ENOTSUPP; + return rndis_per_dev_params[configNr].state; +} + +/* + * Message Parser + */ +int rndis_msg_parser(u8 configNr, u8 *buf) +{ + u32 MsgType, MsgLength; + __le32 *tmp; + struct rndis_params *params; + + debug("%s: configNr = %d, %p\n", __func__, configNr, buf); + + if (!buf) + return -ENOMEM; + + tmp = (__le32 *) buf; + MsgType = get_unaligned_le32(tmp++); + MsgLength = get_unaligned_le32(tmp++); + + if (configNr >= RNDIS_MAX_CONFIGS) + return -ENOTSUPP; + params = &rndis_per_dev_params[configNr]; + + /* + * NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for + * rx/tx statistics and link status, in addition to KEEPALIVE traffic + * and normal HC level polling to see if there's any IN traffic. + */ + + /* For USB: responses may take up to 10 seconds */ + switch (MsgType) { + case REMOTE_NDIS_INITIALIZE_MSG: + debug("%s: REMOTE_NDIS_INITIALIZE_MSG\n", __func__); + params->state = RNDIS_INITIALIZED; + return rndis_init_response(configNr, + (rndis_init_msg_type *) buf); + + case REMOTE_NDIS_HALT_MSG: + debug("%s: REMOTE_NDIS_HALT_MSG\n", __func__); + params->state = RNDIS_UNINITIALIZED; + return 0; + + case REMOTE_NDIS_QUERY_MSG: + return rndis_query_response(configNr, + (rndis_query_msg_type *) buf); + + case REMOTE_NDIS_SET_MSG: + return rndis_set_response(configNr, + (rndis_set_msg_type *) buf); + + case REMOTE_NDIS_RESET_MSG: + debug("%s: REMOTE_NDIS_RESET_MSG\n", __func__); + return rndis_reset_response(configNr, + (rndis_reset_msg_type *) buf); + + case REMOTE_NDIS_KEEPALIVE_MSG: + /* For USB: host does this every 5 seconds */ +#if defined(DEBUG) && defined(DEBUG_VERBOSE) + debug("%s: REMOTE_NDIS_KEEPALIVE_MSG\n", __func__); +#endif + return rndis_keepalive_response(configNr, + (rndis_keepalive_msg_type *) buf); + + default: + /* + * At least Windows XP emits some undefined RNDIS messages. + * In one case those messages seemed to relate to the host + * suspending itself. + */ + debug("%s: unknown RNDIS message 0x%08X len %d\n", + __func__ , MsgType, MsgLength); + { + unsigned i; + for (i = 0; i < MsgLength; i += 16) { + debug("%03d: " + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + "\n", + i, + buf[i], buf[i+1], + buf[i+2], buf[i+3], + buf[i+4], buf[i+5], + buf[i+6], buf[i+7], + buf[i+8], buf[i+9], + buf[i+10], buf[i+11], + buf[i+12], buf[i+13], + buf[i+14], buf[i+15]); + } + } + break; + } + + return -ENOTSUPP; +} + +int rndis_register(int (*rndis_control_ack)(struct eth_device *)) +{ + u8 i; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + if (!rndis_per_dev_params[i].used) { + rndis_per_dev_params[i].used = 1; + rndis_per_dev_params[i].ack = rndis_control_ack; + debug("%s: configNr = %d\n", __func__, i); + return i; + } + } + debug("%s failed\n", __func__); + + return -1; +} + +void rndis_deregister(int configNr) +{ + debug("%s: configNr = %d\n", __func__, configNr); + + if (configNr >= RNDIS_MAX_CONFIGS) + return; + rndis_per_dev_params[configNr].used = 0; + + return; +} + +int rndis_set_param_dev(u8 configNr, struct eth_device *dev, int mtu, + struct net_device_stats *stats, u16 *cdc_filter) +{ + debug("%s: configNr = %d\n", __func__, configNr); + if (!dev || !stats) + return -1; + if (configNr >= RNDIS_MAX_CONFIGS) + return -1; + + rndis_per_dev_params[configNr].dev = dev; + rndis_per_dev_params[configNr].stats = stats; + rndis_per_dev_params[configNr].mtu = mtu; + rndis_per_dev_params[configNr].filter = cdc_filter; + + return 0; +} + +int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr) +{ + debug("%s: configNr = %d\n", __func__, configNr); + if (!vendorDescr) + return -1; + if (configNr >= RNDIS_MAX_CONFIGS) + return -1; + + rndis_per_dev_params[configNr].vendorID = vendorID; + rndis_per_dev_params[configNr].vendorDescr = vendorDescr; + + return 0; +} + +int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed) +{ + debug("%s: configNr = %d, %u %u\n", __func__, configNr, medium, speed); + if (configNr >= RNDIS_MAX_CONFIGS) + return -1; + + rndis_per_dev_params[configNr].medium = medium; + rndis_per_dev_params[configNr].speed = speed; + + return 0; +} + +void rndis_add_hdr(void *buf, int length) +{ + struct rndis_packet_msg_type *header; + + header = buf; + memset(header, 0, sizeof *header); + header->MessageType = __constant_cpu_to_le32(REMOTE_NDIS_PACKET_MSG); + header->MessageLength = cpu_to_le32(length + sizeof *header); + header->DataOffset = __constant_cpu_to_le32(36); + header->DataLength = cpu_to_le32(length); +} + +void rndis_free_response(int configNr, u8 *buf) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (r && r->buf == buf) { + list_del(&r->list); + free(r); + } + } +} + +u8 *rndis_get_next_response(int configNr, u32 *length) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + if (!length) + return NULL; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (!r->send) { + r->send = 1; + *length = r->length; + return r->buf; + } + } + + return NULL; +} + +static rndis_resp_t *rndis_add_response(int configNr, u32 length) +{ + rndis_resp_t *r; + + /* NOTE: this gets copied into ether.c USB_BUFSIZ bytes ... */ + r = malloc(sizeof(rndis_resp_t) + length); + if (!r) + return NULL; + + r->buf = (u8 *) (r + 1); + r->length = length; + r->send = 0; + + list_add_tail(&r->list, + &(rndis_per_dev_params[configNr].resp_queue)); + return r; +} + +int rndis_rm_hdr(void *buf, int length) +{ + /* tmp points to a struct rndis_packet_msg_type */ + __le32 *tmp = buf; + int offs, len; + + /* MessageType, MessageLength */ + if (__constant_cpu_to_le32(REMOTE_NDIS_PACKET_MSG) + != get_unaligned(tmp++)) + return -EINVAL; + tmp++; + + /* DataOffset, DataLength */ + offs = get_unaligned_le32(tmp++) + 8 /* offset of DataOffset */; + if (offs != sizeof(struct rndis_packet_msg_type)) + debug("%s: unexpected DataOffset: %d\n", __func__, offs); + if (offs >= length) + return -EOVERFLOW; + + len = get_unaligned_le32(tmp++); + if (len + sizeof(struct rndis_packet_msg_type) != length) + debug("%s: unexpected DataLength: %d, packet length=%d\n", + __func__, len, length); + + memmove(buf, buf + offs, len); + + return offs; +} + +int rndis_init(void) +{ + u8 i; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + rndis_per_dev_params[i].confignr = i; + rndis_per_dev_params[i].used = 0; + rndis_per_dev_params[i].state = RNDIS_UNINITIALIZED; + rndis_per_dev_params[i].media_state + = NDIS_MEDIA_STATE_DISCONNECTED; + INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue)); + } + + return 0; +} + +void rndis_exit(void) +{ + /* Nothing to do */ +} + diff --git a/drivers/usb/gadget/rndis.h b/drivers/usb/gadget/rndis.h new file mode 100644 index 0000000..d9e3a75 --- /dev/null +++ b/drivers/usb/gadget/rndis.h @@ -0,0 +1,260 @@ +/* + * RNDIS Definitions for Remote NDIS + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + */ + +#ifndef _USBGADGET_RNDIS_H +#define _USBGADGET_RNDIS_H + +#include "ndis.h" + +/* + * By default rndis_signal_disconnect does not send status message about + * RNDIS disconnection to USB host (indicated as cable disconnected). + * Define RNDIS_COMPLETE_SIGNAL_DISCONNECT to send it. + * However, this will cause 1 sec delay on Ethernet device halt. + * Usually you do not need to define it. Mostly usable for debugging. + */ + +#define RNDIS_MAXIMUM_FRAME_SIZE 1518 +#define RNDIS_MAX_TOTAL_SIZE 1558 + +/* Remote NDIS Versions */ +#define RNDIS_MAJOR_VERSION 1 +#define RNDIS_MINOR_VERSION 0 + +/* Status Values */ +#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */ +#define RNDIS_STATUS_FAILURE 0xC0000001U /* Unspecified error */ +#define RNDIS_STATUS_INVALID_DATA 0xC0010015U /* Invalid data */ +#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBU /* Unsupported request */ +#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BU /* Device connected */ +#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CU /* Device disconnected */ +/* + * For all not specified status messages: + * RNDIS_STATUS_Xxx -> NDIS_STATUS_Xxx + */ + +/* Message Set for Connectionless (802.3) Devices */ +#define REMOTE_NDIS_PACKET_MSG 0x00000001U +#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002U /* Initialize device */ +#define REMOTE_NDIS_HALT_MSG 0x00000003U +#define REMOTE_NDIS_QUERY_MSG 0x00000004U +#define REMOTE_NDIS_SET_MSG 0x00000005U +#define REMOTE_NDIS_RESET_MSG 0x00000006U +#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007U +#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008U + +/* Message completion */ +#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002U +#define REMOTE_NDIS_QUERY_CMPLT 0x80000004U +#define REMOTE_NDIS_SET_CMPLT 0x80000005U +#define REMOTE_NDIS_RESET_CMPLT 0x80000006U +#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008U + +/* Device Flags */ +#define RNDIS_DF_CONNECTIONLESS 0x00000001U +#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002U + +#define RNDIS_MEDIUM_802_3 0x00000000U + +/* from drivers/net/sk98lin/h/skgepnmi.h */ +#define OID_PNP_CAPABILITIES 0xFD010100 +#define OID_PNP_SET_POWER 0xFD010101 +#define OID_PNP_QUERY_POWER 0xFD010102 +#define OID_PNP_ADD_WAKE_UP_PATTERN 0xFD010103 +#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xFD010104 +#define OID_PNP_ENABLE_WAKE_UP 0xFD010106 + + +typedef struct rndis_init_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 MaxTransferSize; +} rndis_init_msg_type; + +typedef struct rndis_init_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 DeviceFlags; + __le32 Medium; + __le32 MaxPacketsPerTransfer; + __le32 MaxTransferSize; + __le32 PacketAlignmentFactor; + __le32 AFListOffset; + __le32 AFListSize; +} rndis_init_cmplt_type; + +typedef struct rndis_halt_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_halt_msg_type; + +typedef struct rndis_query_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_query_msg_type; + +typedef struct rndis_query_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; +} rndis_query_cmplt_type; + +typedef struct rndis_set_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_set_msg_type; + +typedef struct rndis_set_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_set_cmplt_type; + +typedef struct rndis_reset_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 Reserved; +} rndis_reset_msg_type; + +typedef struct rndis_reset_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 AddressingReset; +} rndis_reset_cmplt_type; + +typedef struct rndis_indicate_status_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 StatusBufferLength; + __le32 StatusBufferOffset; +} rndis_indicate_status_msg_type; + +typedef struct rndis_keepalive_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_keepalive_msg_type; + +typedef struct rndis_keepalive_cmplt_type { + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_keepalive_cmplt_type; + +struct rndis_packet_msg_type { + __le32 MessageType; + __le32 MessageLength; + __le32 DataOffset; + __le32 DataLength; + __le32 OOBDataOffset; + __le32 OOBDataLength; + __le32 NumOOBDataElements; + __le32 PerPacketInfoOffset; + __le32 PerPacketInfoLength; + __le32 VcHandle; + __le32 Reserved; +} __attribute__ ((packed)); + +struct rndis_config_parameter { + __le32 ParameterNameOffset; + __le32 ParameterNameLength; + __le32 ParameterType; + __le32 ParameterValueOffset; + __le32 ParameterValueLength; +}; + +/* implementation specific */ +enum rndis_state { + RNDIS_UNINITIALIZED, + RNDIS_INITIALIZED, + RNDIS_DATA_INITIALIZED, +}; + +typedef struct rndis_resp_t { + struct list_head list; + u8 *buf; + u32 length; + int send; +} rndis_resp_t; + +typedef struct rndis_params { + u8 confignr; + u8 used; + u16 saved_filter; + enum rndis_state state; + u32 medium; + u32 speed; + u32 media_state; + + const u8 *host_mac; + u16 *filter; + struct eth_device *dev; + struct net_device_stats *stats; + int mtu; + + u32 vendorID; + const char *vendorDescr; + int (*ack)(struct eth_device *); + struct list_head resp_queue; +} rndis_params; + +/* RNDIS Message parser and other useless functions */ +int rndis_msg_parser(u8 configNr, u8 *buf); +enum rndis_state rndis_get_state(int configNr); +int rndis_register(int (*rndis_control_ack)(struct eth_device *)); +void rndis_deregister(int configNr); +int rndis_set_param_dev(u8 configNr, struct eth_device *dev, int mtu, + struct net_device_stats *stats, u16 *cdc_filter); +int rndis_set_param_vendor(u8 configNr, u32 vendorID, + const char *vendorDescr); +int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed); +void rndis_add_hdr(void *bug, int length); +int rndis_rm_hdr(void *bug, int length); +u8 *rndis_get_next_response(int configNr, u32 *length); +void rndis_free_response(int configNr, u8 *buf); + +void rndis_uninit(int configNr); +int rndis_signal_connect(int configNr); +int rndis_signal_disconnect(int configNr); +extern void rndis_set_host_mac(int configNr, const u8 *addr); + +int rndis_init(void); +void rndis_exit(void); + +#endif /* _USBGADGET_RNDIS_H */ diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 6eb38a4..70c02c9 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -319,6 +319,7 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer, uint32_t endpt, token, usbsts; uint32_t c, toggle; uint32_t cmd; + int timeout; int ret = 0; debug("dev=%p, pipe=%lx, buffer=%p, length=%d, req=%p\n", dev, pipe, @@ -447,6 +448,7 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer, /* Wait for TDs to be processed. */ ts = get_timer(0); vtd = td; + timeout = USB_TIMEOUT_MS(pipe); do { /* Invalidate dcache */ ehci_invalidate_dcache(&qh_list); @@ -454,7 +456,13 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer, if (!(token & 0x80)) break; WATCHDOG_RESET(); - } while (get_timer(ts) < CONFIG_SYS_HZ); + } while (get_timer(ts) < timeout); + + /* Check that the TD processing happened */ + if (token & 0x80) { + printf("EHCI timed out on TD - token=%#x\n", token); + goto fail; + } /* Disable async schedule. */ cmd = ehci_readl(&hcor->or_usbcmd); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index d246978..bc8bb20 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1524,12 +1524,7 @@ int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, /* ohci_dump_status(&gohci); */ #endif - /* allow more time for a BULK device to react - some are slow */ -#define BULK_TO 5000 /* timeout in milliseconds */ - if (usb_pipebulk(pipe)) - timeout = BULK_TO; - else - timeout = 1000; + timeout = USB_TIMEOUT_MS(pipe); /* wait for it to complete */ for (;;) { |