diff options
Diffstat (limited to 'drivers/net/usb')
-rw-r--r-- | drivers/net/usb/Kconfig | 21 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 2 | ||||
-rw-r--r-- | drivers/net/usb/asix.c | 53 | ||||
-rw-r--r-- | drivers/net/usb/catc.c | 6 | ||||
-rw-r--r-- | drivers/net/usb/cdc_ether.c | 114 | ||||
-rw-r--r-- | drivers/net/usb/dm9601.c | 11 | ||||
-rw-r--r-- | drivers/net/usb/hso.c | 4 | ||||
-rw-r--r-- | drivers/net/usb/ipheth.c | 565 | ||||
-rw-r--r-- | drivers/net/usb/kaweth.c | 14 | ||||
-rw-r--r-- | drivers/net/usb/mcs7830.c | 10 | ||||
-rw-r--r-- | drivers/net/usb/pegasus.c | 9 | ||||
-rw-r--r-- | drivers/net/usb/pegasus.h | 2 | ||||
-rw-r--r-- | drivers/net/usb/rndis_host.c | 18 | ||||
-rw-r--r-- | drivers/net/usb/sierra_net.c | 1004 | ||||
-rw-r--r-- | drivers/net/usb/smsc75xx.c | 6 | ||||
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 6 | ||||
-rw-r--r-- | drivers/net/usb/usbnet.c | 15 |
17 files changed, 1722 insertions, 138 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index ba56ce4382d9..d7b7018a1de1 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -385,4 +385,25 @@ config USB_CDC_PHONET cellular modem, as found on most Nokia handsets with the "PC suite" USB profile. +config USB_IPHETH + tristate "Apple iPhone USB Ethernet driver" + default n + ---help--- + Module used to share Internet connection (tethering) from your + iPhone (Original, 3G and 3GS) to your system. + Note that you need userspace libraries and programs that are needed + to pair your device with your system and that understand the iPhone + protocol. + + For more information: http://giagio.com/wiki/moin.cgi/iPhoneEthernetDriver + +config USB_SIERRA_NET + tristate "USB-to-WWAN Driver for Sierra Wireless modems" + depends on USB_USBNET + help + Choose this option if you have a Sierra Wireless USB-to-WWAN device. + + To compile this driver as a module, choose M here: the + module will be called sierra_net. + endmenu diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index 82ea62955b56..b13a279663ba 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -23,4 +23,6 @@ obj-$(CONFIG_USB_NET_MCS7830) += mcs7830.o obj-$(CONFIG_USB_USBNET) += usbnet.o obj-$(CONFIG_USB_NET_INT51X1) += int51x1.o obj-$(CONFIG_USB_CDC_PHONET) += cdc-phonet.o +obj-$(CONFIG_USB_IPHETH) += ipheth.o +obj-$(CONFIG_USB_SIERRA_NET) += sierra_net.o diff --git a/drivers/net/usb/asix.c b/drivers/net/usb/asix.c index 35f56fc82803..31b73310ec77 100644 --- a/drivers/net/usb/asix.c +++ b/drivers/net/usb/asix.c @@ -224,10 +224,9 @@ static int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, cmd, value, index, size); if (data) { - buf = kmalloc(size, GFP_KERNEL); + buf = kmemdup(data, size, GFP_KERNEL); if (!buf) goto out; - memcpy(buf, data, size); } err = usb_control_msg( @@ -322,8 +321,29 @@ static int asix_rx_fixup(struct usbnet *dev, struct sk_buff *skb) /* get the packet length */ size = (u16) (header & 0x0000ffff); - if ((skb->len) - ((size + 1) & 0xfffe) == 0) + if ((skb->len) - ((size + 1) & 0xfffe) == 0) { + u8 alignment = (u32)skb->data & 0x3; + if (alignment != 0x2) { + /* + * not 16bit aligned so use the room provided by + * the 32 bit header to align the data + * + * note we want 16bit alignment as MAC header is + * 14bytes thus ip header will be aligned on + * 32bit boundary so accessing ipheader elements + * using a cast to struct ip header wont cause + * an unaligned accesses. + */ + u8 realignment = (alignment + 2) & 0x3; + memmove(skb->data - realignment, + skb->data, + size); + skb->data -= realignment; + skb_set_tail_pointer(skb, size); + } return 2; + } + if (size > ETH_FRAME_LEN) { netdev_err(dev->net, "asix_rx_fixup() Bad RX Length %d\n", size); @@ -331,7 +351,18 @@ static int asix_rx_fixup(struct usbnet *dev, struct sk_buff *skb) } ax_skb = skb_clone(skb, GFP_ATOMIC); if (ax_skb) { + u8 alignment = (u32)packet & 0x3; ax_skb->len = size; + + if (alignment != 0x2) { + /* + * not 16bit aligned use the room provided by + * the 32 bit header to align the data + */ + u8 realignment = (alignment + 2) & 0x3; + memmove(packet - realignment, packet, size); + packet -= realignment; + } ax_skb->data = packet; skb_set_tail_pointer(ax_skb, size); usbnet_skb_return(dev, ax_skb); @@ -558,16 +589,14 @@ static void asix_set_multicast(struct net_device *net) * for our 8 byte filter buffer * to avoid allocating memory that * is tricky to free later */ - struct dev_mc_list *mc_list; + struct netdev_hw_addr *ha; u32 crc_bits; memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); /* Build the multicast hash filter. */ - netdev_for_each_mc_addr(mc_list, net) { - crc_bits = - ether_crc(ETH_ALEN, - mc_list->dmi_addr) >> 26; + netdev_for_each_mc_addr(ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7); } @@ -794,16 +823,14 @@ static void ax88172_set_multicast(struct net_device *net) * for our 8 byte filter buffer * to avoid allocating memory that * is tricky to free later */ - struct dev_mc_list *mc_list; + struct netdev_hw_addr *ha; u32 crc_bits; memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); /* Build the multicast hash filter. */ - netdev_for_each_mc_addr(mc_list, net) { - crc_bits = - ether_crc(ETH_ALEN, - mc_list->dmi_addr) >> 26; + netdev_for_each_mc_addr(ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7); } diff --git a/drivers/net/usb/catc.c b/drivers/net/usb/catc.c index 602e123b2741..97687d335903 100644 --- a/drivers/net/usb/catc.c +++ b/drivers/net/usb/catc.c @@ -629,7 +629,7 @@ static void catc_multicast(unsigned char *addr, u8 *multicast) static void catc_set_multicast_list(struct net_device *netdev) { struct catc *catc = netdev_priv(netdev); - struct dev_mc_list *mc; + struct netdev_hw_addr *ha; u8 broadcast[6]; u8 rx = RxEnable | RxPolarity | RxMultiCast; @@ -647,8 +647,8 @@ static void catc_set_multicast_list(struct net_device *netdev) if (netdev->flags & IFF_ALLMULTI) { memset(catc->multicast, 0xff, 64); } else { - netdev_for_each_mc_addr(mc, netdev) { - u32 crc = ether_crc_le(6, mc->dmi_addr); + netdev_for_each_mc_addr(ha, netdev) { + u32 crc = ether_crc_le(6, ha->addr); if (!catc->is_f5u011) { catc->multicast[(crc >> 3) & 0x3f] |= 1 << (crc & 7); } else { diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c index c8cdb7f30adc..b3fe0de40469 100644 --- a/drivers/net/usb/cdc_ether.c +++ b/drivers/net/usb/cdc_ether.c @@ -64,6 +64,11 @@ static int is_wireless_rndis(struct usb_interface_descriptor *desc) #endif +static const u8 mbm_guid[16] = { + 0xa3, 0x17, 0xa8, 0x8b, 0x04, 0x5e, 0x4f, 0x01, + 0xa6, 0x07, 0xc0, 0xff, 0xcb, 0x7e, 0x39, 0x2a, +}; + /* * probes control interface, claims data interface, collects the bulk * endpoints, activates data interface (if needed), maybe sets MTU. @@ -79,6 +84,8 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf) int status; int rndis; struct usb_driver *driver = driver_of(intf); + struct usb_cdc_mdlm_desc *desc = NULL; + struct usb_cdc_mdlm_detail_desc *detail = NULL; if (sizeof dev->data < sizeof *info) return -EDOM; @@ -229,6 +236,34 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf) * side link address we were given. */ break; + case USB_CDC_MDLM_TYPE: + if (desc) { + dev_dbg(&intf->dev, "extra MDLM descriptor\n"); + goto bad_desc; + } + + desc = (void *)buf; + + if (desc->bLength != sizeof(*desc)) + goto bad_desc; + + if (memcmp(&desc->bGUID, mbm_guid, 16)) + goto bad_desc; + break; + case USB_CDC_MDLM_DETAIL_TYPE: + if (detail) { + dev_dbg(&intf->dev, "extra MDLM detail descriptor\n"); + goto bad_desc; + } + + detail = (void *)buf; + + if (detail->bGuidDescriptorType == 0) { + if (detail->bLength < (sizeof(*detail) + 1)) + goto bad_desc; + } else + goto bad_desc; + break; } next_desc: len -= buf [0]; /* bLength */ @@ -431,6 +466,7 @@ static const struct driver_info mbm_info = { .bind = cdc_bind, .unbind = usbnet_cdc_unbind, .status = cdc_status, + .manage_power = cdc_manage_power, }; /*-------------------------------------------------------------------------*/ @@ -542,80 +578,10 @@ static const struct usb_device_id products [] = { USB_CDC_PROTO_NONE), .driver_info = (unsigned long) &cdc_info, }, { - /* Ericsson F3507g */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1900, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson F3507g ver. 2 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1902, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson F3607gw */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1904, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson F3607gw ver 2 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1905, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson F3607gw ver 3 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1906, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson F3307 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x190a, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson F3307 ver 2 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1909, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson C3607w */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x1049, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Ericsson C3607w ver 2 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0bdb, 0x190b, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Toshiba F3507g */ - USB_DEVICE_AND_INTERFACE_INFO(0x0930, 0x130b, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Toshiba F3607gw */ - USB_DEVICE_AND_INTERFACE_INFO(0x0930, 0x130c, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Toshiba F3607gw ver 2 */ - USB_DEVICE_AND_INTERFACE_INFO(0x0930, 0x1311, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Dell F3507g */ - USB_DEVICE_AND_INTERFACE_INFO(0x413c, 0x8147, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Dell F3607gw */ - USB_DEVICE_AND_INTERFACE_INFO(0x413c, 0x8183, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, -}, { - /* Dell F3607gw ver 2 */ - USB_DEVICE_AND_INTERFACE_INFO(0x413c, 0x8184, USB_CLASS_COMM, - USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), - .driver_info = (unsigned long) &mbm_info, + USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MDLM, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&mbm_info, + }, { }, // END }; diff --git a/drivers/net/usb/dm9601.c b/drivers/net/usb/dm9601.c index 04b281002a76..02b622e3b9fb 100644 --- a/drivers/net/usb/dm9601.c +++ b/drivers/net/usb/dm9601.c @@ -93,10 +93,9 @@ static int dm_write(struct usbnet *dev, u8 reg, u16 length, void *data) netdev_dbg(dev->net, "dm_write() reg=0x%02x, length=%d\n", reg, length); if (data) { - buf = kmalloc(length, GFP_KERNEL); + buf = kmemdup(data, length, GFP_KERNEL); if (!buf) goto out; - memcpy(buf, data, length); } err = usb_control_msg(dev->udev, @@ -240,7 +239,7 @@ static int dm_write_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 valu goto out; dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg); - dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0x1c : 0x14); + dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0x1a : 0x12); for (i = 0; i < DM_TIMEOUT; i++) { u8 tmp; @@ -387,10 +386,10 @@ static void dm9601_set_multicast(struct net_device *net) netdev_mc_count(net) > DM_MAX_MCAST) { rx_ctl |= 0x04; } else if (!netdev_mc_empty(net)) { - struct dev_mc_list *mc_list; + struct netdev_hw_addr *ha; - netdev_for_each_mc_addr(mc_list, net) { - u32 crc = ether_crc(ETH_ALEN, mc_list->dmi_addr) >> 26; + netdev_for_each_mc_addr(ha, net) { + u32 crc = ether_crc(ETH_ALEN, ha->addr) >> 26; hashes[crc >> 3] |= 1 << (crc & 0x7); } } diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index be0cc99e881a..9964df199511 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -834,8 +834,6 @@ static netdev_tx_t hso_net_start_xmit(struct sk_buff *skb, } else { net->stats.tx_packets++; net->stats.tx_bytes += skb->len; - /* And tell the kernel when the last transmit started. */ - net->trans_start = jiffies; } dev_kfree_skb(skb); /* we're done */ @@ -1474,7 +1472,6 @@ static void hso_serial_set_termios(struct tty_struct *tty, struct ktermios *old) spin_unlock_irqrestore(&serial->serial_lock, flags); /* done */ - return; } /* how many characters in the buffer */ @@ -1994,7 +1991,6 @@ static void hso_std_serial_write_bulk_callback(struct urb *urb) hso_kick_transmit(serial); D1(" "); - return; } /* called for writing diag or CS serial port */ diff --git a/drivers/net/usb/ipheth.c b/drivers/net/usb/ipheth.c new file mode 100644 index 000000000000..197c352c47fb --- /dev/null +++ b/drivers/net/usb/ipheth.c @@ -0,0 +1,565 @@ +/* + * ipheth.c - Apple iPhone USB Ethernet driver + * + * Copyright (c) 2009 Diego Giagio <diego@giagio.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of GIAGIO.COM nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * The provided data structures and external interfaces from this code + * are not restricted to be used by modules with a GPL compatible license. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * Attention: iPhone device must be paired, otherwise it won't respond to our + * driver. For more info: http://giagio.com/wiki/moin.cgi/iPhoneEthernetDriver + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +#define USB_VENDOR_APPLE 0x05ac +#define USB_PRODUCT_IPHONE 0x1290 +#define USB_PRODUCT_IPHONE_3G 0x1292 +#define USB_PRODUCT_IPHONE_3GS 0x1294 + +#define IPHETH_USBINTF_CLASS 255 +#define IPHETH_USBINTF_SUBCLASS 253 +#define IPHETH_USBINTF_PROTO 1 + +#define IPHETH_BUF_SIZE 1516 +#define IPHETH_TX_TIMEOUT (5 * HZ) + +#define IPHETH_INTFNUM 2 +#define IPHETH_ALT_INTFNUM 1 + +#define IPHETH_CTRL_ENDP 0x00 +#define IPHETH_CTRL_BUF_SIZE 0x40 +#define IPHETH_CTRL_TIMEOUT (5 * HZ) + +#define IPHETH_CMD_GET_MACADDR 0x00 +#define IPHETH_CMD_CARRIER_CHECK 0x45 + +#define IPHETH_CARRIER_CHECK_TIMEOUT round_jiffies_relative(1 * HZ) +#define IPHETH_CARRIER_ON 0x04 + +static struct usb_device_id ipheth_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO( + USB_VENDOR_APPLE, USB_PRODUCT_IPHONE, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO) }, + { USB_DEVICE_AND_INTERFACE_INFO( + USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_3G, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO) }, + { USB_DEVICE_AND_INTERFACE_INFO( + USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_3GS, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO) }, + { } +}; +MODULE_DEVICE_TABLE(usb, ipheth_table); + +struct ipheth_device { + struct usb_device *udev; + struct usb_interface *intf; + struct net_device *net; + struct sk_buff *tx_skb; + struct urb *tx_urb; + struct urb *rx_urb; + unsigned char *tx_buf; + unsigned char *rx_buf; + unsigned char *ctrl_buf; + u8 bulk_in; + u8 bulk_out; + struct delayed_work carrier_work; +}; + +static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags); + +static int ipheth_alloc_urbs(struct ipheth_device *iphone) +{ + struct urb *tx_urb = NULL; + struct urb *rx_urb = NULL; + u8 *tx_buf = NULL; + u8 *rx_buf = NULL; + + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (tx_urb == NULL) + goto error_nomem; + + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (rx_urb == NULL) + goto free_tx_urb; + + tx_buf = usb_alloc_coherent(iphone->udev, IPHETH_BUF_SIZE, + GFP_KERNEL, &tx_urb->transfer_dma); + if (tx_buf == NULL) + goto free_rx_urb; + + rx_buf = usb_alloc_coherent(iphone->udev, IPHETH_BUF_SIZE, + GFP_KERNEL, &rx_urb->transfer_dma); + if (rx_buf == NULL) + goto free_tx_buf; + + + iphone->tx_urb = tx_urb; + iphone->rx_urb = rx_urb; + iphone->tx_buf = tx_buf; + iphone->rx_buf = rx_buf; + return 0; + +free_tx_buf: + usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, tx_buf, + tx_urb->transfer_dma); +free_rx_urb: + usb_free_urb(rx_urb); +free_tx_urb: + usb_free_urb(tx_urb); +error_nomem: + return -ENOMEM; +} + +static void ipheth_free_urbs(struct ipheth_device *iphone) +{ + usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, iphone->rx_buf, + iphone->rx_urb->transfer_dma); + usb_free_coherent(iphone->udev, IPHETH_BUF_SIZE, iphone->tx_buf, + iphone->tx_urb->transfer_dma); + usb_free_urb(iphone->rx_urb); + usb_free_urb(iphone->tx_urb); +} + +static void ipheth_kill_urbs(struct ipheth_device *dev) +{ + usb_kill_urb(dev->tx_urb); + usb_kill_urb(dev->rx_urb); +} + +static void ipheth_rcvbulk_callback(struct urb *urb) +{ + struct ipheth_device *dev; + struct sk_buff *skb; + int status; + char *buf; + int len; + + dev = urb->context; + if (dev == NULL) + return; + + status = urb->status; + switch (status) { + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + return; + case 0: + break; + default: + err("%s: urb status: %d", __func__, urb->status); + return; + } + + len = urb->actual_length; + buf = urb->transfer_buffer; + + skb = dev_alloc_skb(NET_IP_ALIGN + len); + if (!skb) { + err("%s: dev_alloc_skb: -ENOMEM", __func__); + dev->net->stats.rx_dropped++; + return; + } + + skb_reserve(skb, NET_IP_ALIGN); + memcpy(skb_put(skb, len), buf + NET_IP_ALIGN, len - NET_IP_ALIGN); + skb->dev = dev->net; + skb->protocol = eth_type_trans(skb, dev->net); + + dev->net->stats.rx_packets++; + dev->net->stats.rx_bytes += len; + + netif_rx(skb); + ipheth_rx_submit(dev, GFP_ATOMIC); +} + +static void ipheth_sndbulk_callback(struct urb *urb) +{ + struct ipheth_device *dev; + + dev = urb->context; + if (dev == NULL) + return; + + if (urb->status != 0 && + urb->status != -ENOENT && + urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN) + err("%s: urb status: %d", __func__, urb->status); + + dev_kfree_skb_irq(dev->tx_skb); + netif_wake_queue(dev->net); +} + +static int ipheth_carrier_set(struct ipheth_device *dev) +{ + struct usb_device *udev = dev->udev; + int retval; + + retval = usb_control_msg(udev, + usb_rcvctrlpipe(udev, IPHETH_CTRL_ENDP), + IPHETH_CMD_CARRIER_CHECK, /* request */ + 0xc0, /* request type */ + 0x00, /* value */ + 0x02, /* index */ + dev->ctrl_buf, IPHETH_CTRL_BUF_SIZE, + IPHETH_CTRL_TIMEOUT); + if (retval < 0) { + err("%s: usb_control_msg: %d", __func__, retval); + return retval; + } + + if (dev->ctrl_buf[0] == IPHETH_CARRIER_ON) + netif_carrier_on(dev->net); + else + netif_carrier_off(dev->net); + + return 0; +} + +static void ipheth_carrier_check_work(struct work_struct *work) +{ + struct ipheth_device *dev = container_of(work, struct ipheth_device, + carrier_work.work); + + ipheth_carrier_set(dev); + schedule_delayed_work(&dev->carrier_work, IPHETH_CARRIER_CHECK_TIMEOUT); +} + +static int ipheth_get_macaddr(struct ipheth_device *dev) +{ + struct usb_device *udev = dev->udev; + struct net_device *net = dev->net; + int retval; + + retval = usb_control_msg(udev, + usb_rcvctrlpipe(udev, IPHETH_CTRL_ENDP), + IPHETH_CMD_GET_MACADDR, /* request */ + 0xc0, /* request type */ + 0x00, /* value */ + 0x02, /* index */ + dev->ctrl_buf, + IPHETH_CTRL_BUF_SIZE, + IPHETH_CTRL_TIMEOUT); + if (retval < 0) { + err("%s: usb_control_msg: %d", __func__, retval); + } else if (retval < ETH_ALEN) { + err("%s: usb_control_msg: short packet: %d bytes", + __func__, retval); + retval = -EINVAL; + } else { + memcpy(net->dev_addr, dev->ctrl_buf, ETH_ALEN); + retval = 0; + } + + return retval; +} + +static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags) +{ + struct usb_device *udev = dev->udev; + int retval; + + usb_fill_bulk_urb(dev->rx_urb, udev, + usb_rcvbulkpipe(udev, dev->bulk_in), + dev->rx_buf, IPHETH_BUF_SIZE, + ipheth_rcvbulk_callback, + dev); + dev->rx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + retval = usb_submit_urb(dev->rx_urb, mem_flags); + if (retval) + err("%s: usb_submit_urb: %d", __func__, retval); + return retval; +} + +static int ipheth_open(struct net_device *net) +{ + struct ipheth_device *dev = netdev_priv(net); + struct usb_device *udev = dev->udev; + int retval = 0; + + usb_set_interface(udev, IPHETH_INTFNUM, IPHETH_ALT_INTFNUM); + + retval = ipheth_carrier_set(dev); + if (retval) + return retval; + + retval = ipheth_rx_submit(dev, GFP_KERNEL); + if (retval) + return retval; + + schedule_delayed_work(&dev->carrier_work, IPHETH_CARRIER_CHECK_TIMEOUT); + netif_start_queue(net); + return retval; +} + +static int ipheth_close(struct net_device *net) +{ + struct ipheth_device *dev = netdev_priv(net); + + cancel_delayed_work_sync(&dev->carrier_work); + netif_stop_queue(net); + return 0; +} + +static int ipheth_tx(struct sk_buff *skb, struct net_device *net) +{ + struct ipheth_device *dev = netdev_priv(net); + struct usb_device *udev = dev->udev; + int retval; + + /* Paranoid */ + if (skb->len > IPHETH_BUF_SIZE) { + WARN(1, "%s: skb too large: %d bytes", __func__, skb->len); + dev->net->stats.tx_dropped++; + dev_kfree_skb_irq(skb); + return NETDEV_TX_OK; + } + + memcpy(dev->tx_buf, skb->data, skb->len); + if (skb->len < IPHETH_BUF_SIZE) + memset(dev->tx_buf + skb->len, 0, IPHETH_BUF_SIZE - skb->len); + + usb_fill_bulk_urb(dev->tx_urb, udev, + usb_sndbulkpipe(udev, dev->bulk_out), + dev->tx_buf, IPHETH_BUF_SIZE, + ipheth_sndbulk_callback, + dev); + dev->tx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + retval = usb_submit_urb(dev->tx_urb, GFP_ATOMIC); + if (retval) { + err("%s: usb_submit_urb: %d", __func__, retval); + dev->net->stats.tx_errors++; + dev_kfree_skb_irq(skb); + } else { + dev->tx_skb = skb; + + dev->net->stats.tx_packets++; + dev->net->stats.tx_bytes += skb->len; + netif_stop_queue(net); + } + + return NETDEV_TX_OK; +} + +static void ipheth_tx_timeout(struct net_device *net) +{ + struct ipheth_device *dev = netdev_priv(net); + + err("%s: TX timeout", __func__); + dev->net->stats.tx_errors++; + usb_unlink_urb(dev->tx_urb); +} + +static struct net_device_stats *ipheth_stats(struct net_device *net) +{ + struct ipheth_device *dev = netdev_priv(net); + return &dev->net->stats; +} + +static u32 ipheth_ethtool_op_get_link(struct net_device *net) +{ + struct ipheth_device *dev = netdev_priv(net); + return netif_carrier_ok(dev->net); +} + +static struct ethtool_ops ops = { + .get_link = ipheth_ethtool_op_get_link +}; + +static const struct net_device_ops ipheth_netdev_ops = { + .ndo_open = &ipheth_open, + .ndo_stop = &ipheth_close, + .ndo_start_xmit = &ipheth_tx, + .ndo_tx_timeout = &ipheth_tx_timeout, + .ndo_get_stats = &ipheth_stats, +}; + +static struct device_type ipheth_type = { + .name = "wwan", +}; + +static int ipheth_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *hintf; + struct usb_endpoint_descriptor *endp; + struct ipheth_device *dev; + struct net_device *netdev; + int i; + int retval; + + netdev = alloc_etherdev(sizeof(struct ipheth_device)); + if (!netdev) + return -ENOMEM; + + netdev->netdev_ops = &ipheth_netdev_ops; + netdev->watchdog_timeo = IPHETH_TX_TIMEOUT; + strcpy(netdev->name, "wwan%d"); + + dev = netdev_priv(netdev); + dev->udev = udev; + dev->net = netdev; + dev->intf = intf; + + /* Set up endpoints */ + hintf = usb_altnum_to_altsetting(intf, IPHETH_ALT_INTFNUM); + if (hintf == NULL) { + retval = -ENODEV; + err("Unable to find alternate settings interface"); + goto err_endpoints; + } + + for (i = 0; i < hintf->desc.bNumEndpoints; i++) { + endp = &hintf->endpoint[i].desc; + if (usb_endpoint_is_bulk_in(endp)) + dev->bulk_in = endp->bEndpointAddress; + else if (usb_endpoint_is_bulk_out(endp)) + dev->bulk_out = endp->bEndpointAddress; + } + if (!(dev->bulk_in && dev->bulk_out)) { + retval = -ENODEV; + err("Unable to find endpoints"); + goto err_endpoints; + } + + dev->ctrl_buf = kmalloc(IPHETH_CTRL_BUF_SIZE, GFP_KERNEL); + if (dev->ctrl_buf == NULL) { + retval = -ENOMEM; + goto err_alloc_ctrl_buf; + } + + retval = ipheth_get_macaddr(dev); + if (retval) + goto err_get_macaddr; + + INIT_DELAYED_WORK(&dev->carrier_work, ipheth_carrier_check_work); + + retval = ipheth_alloc_urbs(dev); + if (retval) { + err("error allocating urbs: %d", retval); + goto err_alloc_urbs; + } + + usb_set_intfdata(intf, dev); + + SET_NETDEV_DEV(netdev, &intf->dev); + SET_ETHTOOL_OPS(netdev, &ops); + SET_NETDEV_DEVTYPE(netdev, &ipheth_type); + + retval = register_netdev(netdev); + if (retval) { + err("error registering netdev: %d", retval); + retval = -EIO; + goto err_register_netdev; + } + + dev_info(&intf->dev, "Apple iPhone USB Ethernet device attached\n"); + return 0; + +err_register_netdev: + ipheth_free_urbs(dev); +err_alloc_urbs: +err_get_macaddr: +err_alloc_ctrl_buf: + kfree(dev->ctrl_buf); +err_endpoints: + free_netdev(netdev); + return retval; +} + +static void ipheth_disconnect(struct usb_interface *intf) +{ + struct ipheth_device *dev; + + dev = usb_get_intfdata(intf); + if (dev != NULL) { + unregister_netdev(dev->net); + ipheth_kill_urbs(dev); + ipheth_free_urbs(dev); + kfree(dev->ctrl_buf); + free_netdev(dev->net); + } + usb_set_intfdata(intf, NULL); + dev_info(&intf->dev, "Apple iPhone USB Ethernet now disconnected\n"); +} + +static struct usb_driver ipheth_driver = { + .name = "ipheth", + .probe = ipheth_probe, + .disconnect = ipheth_disconnect, + .id_table = ipheth_table, +}; + +static int __init ipheth_init(void) +{ + int retval; + + retval = usb_register(&ipheth_driver); + if (retval) { + err("usb_register failed: %d", retval); + return retval; + } + return 0; +} + +static void __exit ipheth_exit(void) +{ + usb_deregister(&ipheth_driver); +} + +module_init(ipheth_init); +module_exit(ipheth_exit); + +MODULE_AUTHOR("Diego Giagio <diego@giagio.com>"); +MODULE_DESCRIPTION("Apple iPhone USB Ethernet driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/usb/kaweth.c b/drivers/net/usb/kaweth.c index 52671ea043a7..d6078b8c4273 100644 --- a/drivers/net/usb/kaweth.c +++ b/drivers/net/usb/kaweth.c @@ -145,6 +145,7 @@ static struct usb_device_id usb_klsi_table[] = { { USB_DEVICE(0x0707, 0x0100) }, /* SMC 2202USB */ { USB_DEVICE(0x07aa, 0x0001) }, /* Correga K.K. */ { USB_DEVICE(0x07b8, 0x4000) }, /* D-Link DU-E10 */ + { USB_DEVICE(0x07c9, 0xb010) }, /* Allied Telesyn AT-USB10 USB Ethernet Adapter */ { USB_DEVICE(0x0846, 0x1001) }, /* NetGear EA-101 */ { USB_DEVICE(0x0846, 0x1002) }, /* NetGear EA-101 */ { USB_DEVICE(0x085a, 0x0008) }, /* PortGear Ethernet Adapter */ @@ -855,7 +856,6 @@ skip: { kaweth->stats.tx_packets++; kaweth->stats.tx_bytes += skb->len; - net->trans_start = jiffies; } spin_unlock_irq(&kaweth->device_lock); @@ -1155,13 +1155,13 @@ err_fw: if (!kaweth->irq_urb) goto err_tx_and_rx; - kaweth->intbuffer = usb_buffer_alloc( kaweth->dev, + kaweth->intbuffer = usb_alloc_coherent( kaweth->dev, INTBUFFERSIZE, GFP_KERNEL, &kaweth->intbufferhandle); if (!kaweth->intbuffer) goto err_tx_and_rx_and_irq; - kaweth->rx_buf = usb_buffer_alloc( kaweth->dev, + kaweth->rx_buf = usb_alloc_coherent( kaweth->dev, KAWETH_BUF_SIZE, GFP_KERNEL, &kaweth->rxbufferhandle); @@ -1202,9 +1202,9 @@ err_fw: err_intfdata: usb_set_intfdata(intf, NULL); - usb_buffer_free(kaweth->dev, KAWETH_BUF_SIZE, (void *)kaweth->rx_buf, kaweth->rxbufferhandle); + usb_free_coherent(kaweth->dev, KAWETH_BUF_SIZE, (void *)kaweth->rx_buf, kaweth->rxbufferhandle); err_all_but_rxbuf: - usb_buffer_free(kaweth->dev, INTBUFFERSIZE, (void *)kaweth->intbuffer, kaweth->intbufferhandle); + usb_free_coherent(kaweth->dev, INTBUFFERSIZE, (void *)kaweth->intbuffer, kaweth->intbufferhandle); err_tx_and_rx_and_irq: usb_free_urb(kaweth->irq_urb); err_tx_and_rx: @@ -1241,8 +1241,8 @@ static void kaweth_disconnect(struct usb_interface *intf) usb_free_urb(kaweth->tx_urb); usb_free_urb(kaweth->irq_urb); - usb_buffer_free(kaweth->dev, KAWETH_BUF_SIZE, (void *)kaweth->rx_buf, kaweth->rxbufferhandle); - usb_buffer_free(kaweth->dev, INTBUFFERSIZE, (void *)kaweth->intbuffer, kaweth->intbufferhandle); + usb_free_coherent(kaweth->dev, KAWETH_BUF_SIZE, (void *)kaweth->rx_buf, kaweth->rxbufferhandle); + usb_free_coherent(kaweth->dev, INTBUFFERSIZE, (void *)kaweth->intbuffer, kaweth->intbufferhandle); free_netdev(netdev); } diff --git a/drivers/net/usb/mcs7830.c b/drivers/net/usb/mcs7830.c index 9f24e3f871e1..a6281e3987b5 100644 --- a/drivers/net/usb/mcs7830.c +++ b/drivers/net/usb/mcs7830.c @@ -142,12 +142,10 @@ static int mcs7830_set_reg(struct usbnet *dev, u16 index, u16 size, const void * int ret; void *buffer; - buffer = kmalloc(size, GFP_NOIO); + buffer = kmemdup(data, size, GFP_NOIO); if (buffer == NULL) return -ENOMEM; - memcpy(buffer, data, size); - ret = usb_control_msg(xdev, usb_sndctrlpipe(xdev, 0), MCS7830_WR_BREQ, MCS7830_WR_BMREQ, 0x0000, index, buffer, size, MCS7830_CTRL_TIMEOUT); @@ -453,12 +451,12 @@ static void mcs7830_data_set_multicast(struct net_device *net) * for our 8 byte filter buffer * to avoid allocating memory that * is tricky to free later */ - struct dev_mc_list *mc_list; + struct netdev_hw_addr *ha; u32 crc_bits; /* Build the multicast hash filter. */ - netdev_for_each_mc_addr(mc_list, net) { - crc_bits = ether_crc(ETH_ALEN, mc_list->dmi_addr) >> 26; + netdev_for_each_mc_addr(ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7); } } diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index 41838773b568..974d17f0263e 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -203,13 +203,12 @@ static int set_registers(pegasus_t * pegasus, __u16 indx, __u16 size, char *buffer; DECLARE_WAITQUEUE(wait, current); - buffer = kmalloc(size, GFP_KERNEL); + buffer = kmemdup(data, size, GFP_KERNEL); if (!buffer) { netif_warn(pegasus, drv, pegasus->net, "out of memory in %s\n", __func__); return -ENOMEM; } - memcpy(buffer, data, size); add_wait_queue(&pegasus->ctrl_wait, &wait); set_current_state(TASK_UNINTERRUPTIBLE); @@ -255,13 +254,12 @@ static int set_register(pegasus_t * pegasus, __u16 indx, __u8 data) char *tmp; DECLARE_WAITQUEUE(wait, current); - tmp = kmalloc(1, GFP_KERNEL); + tmp = kmemdup(&data, 1, GFP_KERNEL); if (!tmp) { netif_warn(pegasus, drv, pegasus->net, "out of memory in %s\n", __func__); return -ENOMEM; } - memcpy(tmp, &data, 1); add_wait_queue(&pegasus->ctrl_wait, &wait); set_current_state(TASK_UNINTERRUPTIBLE); while (pegasus->flags & ETH_REGS_CHANGED) @@ -808,7 +806,7 @@ static void write_bulk_callback(struct urb *urb) break; } - net->trans_start = jiffies; + net->trans_start = jiffies; /* prevent tx timeout */ netif_wake_queue(net); } @@ -909,7 +907,6 @@ static netdev_tx_t pegasus_start_xmit(struct sk_buff *skb, } else { pegasus->stats.tx_packets++; pegasus->stats.tx_bytes += skb->len; - net->trans_start = jiffies; } dev_kfree_skb(skb); diff --git a/drivers/net/usb/pegasus.h b/drivers/net/usb/pegasus.h index b90d8766ab74..29f5211e645b 100644 --- a/drivers/net/usb/pegasus.h +++ b/drivers/net/usb/pegasus.h @@ -256,7 +256,7 @@ PEGASUS_DEV( "IO DATA USB ET/TX", VENDOR_IODATA, 0x0904, DEFAULT_GPIO_RESET ) PEGASUS_DEV( "IO DATA USB ET/TX-S", VENDOR_IODATA, 0x0913, DEFAULT_GPIO_RESET | PEGASUS_II ) -PEGASUS_DEV( "IO DATA USB ETX-US2", VENDOR_IODATA, 0x092a, +PEGASUS_DEV( "IO DATA USB ETX-US2", VENDOR_IODATA, 0x093a, DEFAULT_GPIO_RESET | PEGASUS_II ) PEGASUS_DEV( "Kingston KNU101TX Ethernet", VENDOR_KINGSTON, 0x000a, DEFAULT_GPIO_RESET) diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c index dd8a4adf48ca..28d3ee175e7b 100644 --- a/drivers/net/usb/rndis_host.c +++ b/drivers/net/usb/rndis_host.c @@ -104,8 +104,10 @@ static void rndis_msg_indicate(struct usbnet *dev, struct rndis_indicate *msg, int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf, int buflen) { struct cdc_state *info = (void *) &dev->data; + struct usb_cdc_notification notification; int master_ifnum; int retval; + int partial; unsigned count; __le32 rsp; u32 xid = 0, msg_len, request_id; @@ -133,13 +135,17 @@ int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf, int buflen) if (unlikely(retval < 0 || xid == 0)) return retval; - // FIXME Seems like some devices discard responses when - // we time out and cancel our "get response" requests... - // so, this is fragile. Probably need to poll for status. + /* Some devices don't respond on the control channel until + * polled on the status channel, so do that first. */ + retval = usb_interrupt_msg( + dev->udev, + usb_rcvintpipe(dev->udev, dev->status->desc.bEndpointAddress), + ¬ification, sizeof(notification), &partial, + RNDIS_CONTROL_TIMEOUT_MS); + if (unlikely(retval < 0)) + return retval; - /* ignore status endpoint, just poll the control channel; - * the request probably completed immediately - */ + /* Poll the control channel; the request probably completed immediately */ rsp = buf->msg_type | RNDIS_MSG_COMPLETION; for (count = 0; count < 10; count++) { memset(buf, 0, CONTROL_BUFFER_SIZE); diff --git a/drivers/net/usb/sierra_net.c b/drivers/net/usb/sierra_net.c new file mode 100644 index 000000000000..f1942d69a0d5 --- /dev/null +++ b/drivers/net/usb/sierra_net.c @@ -0,0 +1,1004 @@ +/* + * USB-to-WWAN Driver for Sierra Wireless modems + * + * Copyright (C) 2008, 2009, 2010 Paxton Smith, Matthew Safar, Rory Filer + * <linux@sierrawireless.com> + * + * Portions of this based on the cdc_ether driver by David Brownell (2003-2005) + * and Ole Andre Vadla Ravnas (ActiveSync) (2006). + * + * IMPORTANT DISCLAIMER: This driver is not commercially supported by + * Sierra Wireless. Use at your own risk. + * + * 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 + */ + +#define DRIVER_VERSION "v.2.0" +#define DRIVER_AUTHOR "Paxton Smith, Matthew Safar, Rory Filer" +#define DRIVER_DESC "USB-to-WWAN Driver for Sierra Wireless modems" +static const char driver_name[] = "sierra_net"; + +/* if defined debug messages enabled */ +/*#define DEBUG*/ + +#include <linux/module.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/usb.h> +#include <linux/usb/cdc.h> +#include <net/ip.h> +#include <net/udp.h> +#include <asm/unaligned.h> +#include <linux/usb/usbnet.h> + +#define SWI_USB_REQUEST_GET_FW_ATTR 0x06 +#define SWI_GET_FW_ATTR_MASK 0x08 + +/* atomic counter partially included in MAC address to make sure 2 devices + * do not end up with the same MAC - concept breaks in case of > 255 ifaces + */ +static atomic_t iface_counter = ATOMIC_INIT(0); + +/* + * SYNC Timer Delay definition used to set the expiry time + */ +#define SIERRA_NET_SYNCDELAY (2*HZ) + +/* Max. MTU supported. The modem buffers are limited to 1500 */ +#define SIERRA_NET_MAX_SUPPORTED_MTU 1500 + +/* The SIERRA_NET_USBCTL_BUF_LEN defines a buffer size allocated for control + * message reception ... and thus the max. received packet. + * (May be the cause for parse_hip returning -EINVAL) + */ +#define SIERRA_NET_USBCTL_BUF_LEN 1024 + +/* list of interface numbers - used for constructing interface lists */ +struct sierra_net_iface_info { + const u32 infolen; /* number of interface numbers on list */ + const u8 *ifaceinfo; /* pointer to the array holding the numbers */ +}; + +struct sierra_net_info_data { + u16 rx_urb_size; + struct sierra_net_iface_info whitelist; +}; + +/* Private data structure */ +struct sierra_net_data { + + u8 ethr_hdr_tmpl[ETH_HLEN]; /* ethernet header template for rx'd pkts */ + + u16 link_up; /* air link up or down */ + u8 tx_hdr_template[4]; /* part of HIP hdr for tx'd packets */ + + u8 sync_msg[4]; /* SYNC message */ + u8 shdwn_msg[4]; /* Shutdown message */ + + /* Backpointer to the container */ + struct usbnet *usbnet; + + u8 ifnum; /* interface number */ + +/* Bit masks, must be a power of 2 */ +#define SIERRA_NET_EVENT_RESP_AVAIL 0x01 +#define SIERRA_NET_TIMER_EXPIRY 0x02 + unsigned long kevent_flags; + struct work_struct sierra_net_kevent; + struct timer_list sync_timer; /* For retrying SYNC sequence */ +}; + +struct param { + int is_present; + union { + void *ptr; + u32 dword; + u16 word; + u8 byte; + }; +}; + +/* HIP message type */ +#define SIERRA_NET_HIP_EXTENDEDID 0x7F +#define SIERRA_NET_HIP_HSYNC_ID 0x60 /* Modem -> host */ +#define SIERRA_NET_HIP_RESTART_ID 0x62 /* Modem -> host */ +#define SIERRA_NET_HIP_MSYNC_ID 0x20 /* Host -> modem */ +#define SIERRA_NET_HIP_SHUTD_ID 0x26 /* Host -> modem */ + +#define SIERRA_NET_HIP_EXT_IP_IN_ID 0x0202 +#define SIERRA_NET_HIP_EXT_IP_OUT_ID 0x0002 + +/* 3G UMTS Link Sense Indication definitions */ +#define SIERRA_NET_HIP_LSI_UMTSID 0x78 + +/* Reverse Channel Grant Indication HIP message */ +#define SIERRA_NET_HIP_RCGI 0x64 + +/* LSI Protocol types */ +#define SIERRA_NET_PROTOCOL_UMTS 0x01 +/* LSI Coverage */ +#define SIERRA_NET_COVERAGE_NONE 0x00 +#define SIERRA_NET_COVERAGE_NOPACKET 0x01 + +/* LSI Session */ +#define SIERRA_NET_SESSION_IDLE 0x00 +/* LSI Link types */ +#define SIERRA_NET_AS_LINK_TYPE_IPv4 0x00 + +struct lsi_umts { + u8 protocol; + u8 unused1; + __be16 length; + /* eventually use a union for the rest - assume umts for now */ + u8 coverage; + u8 unused2[41]; + u8 session_state; + u8 unused3[33]; + u8 link_type; + u8 pdp_addr_len; /* NW-supplied PDP address len */ + u8 pdp_addr[16]; /* NW-supplied PDP address (bigendian)) */ + u8 unused4[23]; + u8 dns1_addr_len; /* NW-supplied 1st DNS address len (bigendian) */ + u8 dns1_addr[16]; /* NW-supplied 1st DNS address */ + u8 dns2_addr_len; /* NW-supplied 2nd DNS address len */ + u8 dns2_addr[16]; /* NW-supplied 2nd DNS address (bigendian)*/ + u8 wins1_addr_len; /* NW-supplied 1st Wins address len */ + u8 wins1_addr[16]; /* NW-supplied 1st Wins address (bigendian)*/ + u8 wins2_addr_len; /* NW-supplied 2nd Wins address len */ + u8 wins2_addr[16]; /* NW-supplied 2nd Wins address (bigendian) */ + u8 unused5[4]; + u8 gw_addr_len; /* NW-supplied GW address len */ + u8 gw_addr[16]; /* NW-supplied GW address (bigendian) */ + u8 reserved[8]; +} __attribute__ ((packed)); + +#define SIERRA_NET_LSI_COMMON_LEN 4 +#define SIERRA_NET_LSI_UMTS_LEN (sizeof(struct lsi_umts)) +#define SIERRA_NET_LSI_UMTS_STATUS_LEN \ + (SIERRA_NET_LSI_UMTS_LEN - SIERRA_NET_LSI_COMMON_LEN) + +/* Forward definitions */ +static void sierra_sync_timer(unsigned long syncdata); +static int sierra_net_change_mtu(struct net_device *net, int new_mtu); + +/* Our own net device operations structure */ +static const struct net_device_ops sierra_net_device_ops = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_change_mtu = sierra_net_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +/* get private data associated with passed in usbnet device */ +static inline struct sierra_net_data *sierra_net_get_private(struct usbnet *dev) +{ + return (struct sierra_net_data *)dev->data[0]; +} + +/* set private data associated with passed in usbnet device */ +static inline void sierra_net_set_private(struct usbnet *dev, + struct sierra_net_data *priv) +{ + dev->data[0] = (unsigned long)priv; +} + +/* is packet IPv4 */ +static inline int is_ip(struct sk_buff *skb) +{ + return (skb->protocol == cpu_to_be16(ETH_P_IP)); +} + +/* + * check passed in packet and make sure that: + * - it is linear (no scatter/gather) + * - it is ethernet (mac_header properly set) + */ +static int check_ethip_packet(struct sk_buff *skb, struct usbnet *dev) +{ + skb_reset_mac_header(skb); /* ethernet header */ + + if (skb_is_nonlinear(skb)) { + netdev_err(dev->net, "Non linear buffer-dropping\n"); + return 0; + } + + if (!pskb_may_pull(skb, ETH_HLEN)) + return 0; + skb->protocol = eth_hdr(skb)->h_proto; + + return 1; +} + +static const u8 *save16bit(struct param *p, const u8 *datap) +{ + p->is_present = 1; + p->word = get_unaligned_be16(datap); + return datap + sizeof(p->word); +} + +static const u8 *save8bit(struct param *p, const u8 *datap) +{ + p->is_present = 1; + p->byte = *datap; + return datap + sizeof(p->byte); +} + +/*----------------------------------------------------------------------------* + * BEGIN HIP * + *----------------------------------------------------------------------------*/ +/* HIP header */ +#define SIERRA_NET_HIP_HDR_LEN 4 +/* Extended HIP header */ +#define SIERRA_NET_HIP_EXT_HDR_LEN 6 + +struct hip_hdr { + int hdrlen; + struct param payload_len; + struct param msgid; + struct param msgspecific; + struct param extmsgid; +}; + +static int parse_hip(const u8 *buf, const u32 buflen, struct hip_hdr *hh) +{ + const u8 *curp = buf; + int padded; + + if (buflen < SIERRA_NET_HIP_HDR_LEN) + return -EPROTO; + + curp = save16bit(&hh->payload_len, curp); + curp = save8bit(&hh->msgid, curp); + curp = save8bit(&hh->msgspecific, curp); + + padded = hh->msgid.byte & 0x80; + hh->msgid.byte &= 0x7F; /* 7 bits */ + + hh->extmsgid.is_present = (hh->msgid.byte == SIERRA_NET_HIP_EXTENDEDID); + if (hh->extmsgid.is_present) { + if (buflen < SIERRA_NET_HIP_EXT_HDR_LEN) + return -EPROTO; + + hh->payload_len.word &= 0x3FFF; /* 14 bits */ + + curp = save16bit(&hh->extmsgid, curp); + hh->extmsgid.word &= 0x03FF; /* 10 bits */ + + hh->hdrlen = SIERRA_NET_HIP_EXT_HDR_LEN; + } else { + hh->payload_len.word &= 0x07FF; /* 11 bits */ + hh->hdrlen = SIERRA_NET_HIP_HDR_LEN; + } + + if (padded) { + hh->hdrlen++; + hh->payload_len.word--; + } + + /* if real packet shorter than the claimed length */ + if (buflen < (hh->hdrlen + hh->payload_len.word)) + return -EINVAL; + + return 0; +} + +static void build_hip(u8 *buf, const u16 payloadlen, + struct sierra_net_data *priv) +{ + /* the following doesn't have the full functionality. We + * currently build only one kind of header, so it is faster this way + */ + put_unaligned_be16(payloadlen, buf); + memcpy(buf+2, priv->tx_hdr_template, sizeof(priv->tx_hdr_template)); +} +/*----------------------------------------------------------------------------* + * END HIP * + *----------------------------------------------------------------------------*/ + +static int sierra_net_send_cmd(struct usbnet *dev, + u8 *cmd, int cmdlen, const char * cmd_name) +{ + struct sierra_net_data *priv = sierra_net_get_private(dev); + int status; + + status = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + USB_CDC_SEND_ENCAPSULATED_COMMAND, + USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE, 0, + priv->ifnum, cmd, cmdlen, USB_CTRL_SET_TIMEOUT); + + if (status != cmdlen && status != -ENODEV) + netdev_err(dev->net, "Submit %s failed %d\n", cmd_name, status); + + return status; +} + +static int sierra_net_send_sync(struct usbnet *dev) +{ + int status; + struct sierra_net_data *priv = sierra_net_get_private(dev); + + dev_dbg(&dev->udev->dev, "%s", __func__); + + status = sierra_net_send_cmd(dev, priv->sync_msg, + sizeof(priv->sync_msg), "SYNC"); + + return status; +} + +static void sierra_net_set_ctx_index(struct sierra_net_data *priv, u8 ctx_ix) +{ + dev_dbg(&(priv->usbnet->udev->dev), "%s %d", __func__, ctx_ix); + priv->tx_hdr_template[0] = 0x3F; + priv->tx_hdr_template[1] = ctx_ix; + *((u16 *)&priv->tx_hdr_template[2]) = + cpu_to_be16(SIERRA_NET_HIP_EXT_IP_OUT_ID); +} + +static inline int sierra_net_is_valid_addrlen(u8 len) +{ + return (len == sizeof(struct in_addr)); +} + +static int sierra_net_parse_lsi(struct usbnet *dev, char *data, int datalen) +{ + struct lsi_umts *lsi = (struct lsi_umts *)data; + + if (datalen < sizeof(struct lsi_umts)) { + netdev_err(dev->net, "%s: Data length %d, exp %Zu\n", + __func__, datalen, + sizeof(struct lsi_umts)); + return -1; + } + + if (lsi->length != cpu_to_be16(SIERRA_NET_LSI_UMTS_STATUS_LEN)) { + netdev_err(dev->net, "%s: LSI_UMTS_STATUS_LEN %d, exp %u\n", + __func__, be16_to_cpu(lsi->length), + (u32)SIERRA_NET_LSI_UMTS_STATUS_LEN); + return -1; + } + + /* Validate the protocol - only support UMTS for now */ + if (lsi->protocol != SIERRA_NET_PROTOCOL_UMTS) { + netdev_err(dev->net, "Protocol unsupported, 0x%02x\n", + lsi->protocol); + return -1; + } + + /* Validate the link type */ + if (lsi->link_type != SIERRA_NET_AS_LINK_TYPE_IPv4) { + netdev_err(dev->net, "Link type unsupported: 0x%02x\n", + lsi->link_type); + return -1; + } + + /* Validate the coverage */ + if (lsi->coverage == SIERRA_NET_COVERAGE_NONE + || lsi->coverage == SIERRA_NET_COVERAGE_NOPACKET) { + netdev_err(dev->net, "No coverage, 0x%02x\n", lsi->coverage); + return 0; + } + + /* Validate the session state */ + if (lsi->session_state == SIERRA_NET_SESSION_IDLE) { + netdev_err(dev->net, "Session idle, 0x%02x\n", + lsi->session_state); + return 0; + } + + /* Set link_sense true */ + return 1; +} + +static void sierra_net_handle_lsi(struct usbnet *dev, char *data, + struct hip_hdr *hh) +{ + struct sierra_net_data *priv = sierra_net_get_private(dev); + int link_up; + + link_up = sierra_net_parse_lsi(dev, data + hh->hdrlen, + hh->payload_len.word); + if (link_up < 0) { + netdev_err(dev->net, "Invalid LSI\n"); + return; + } + if (link_up) { + sierra_net_set_ctx_index(priv, hh->msgspecific.byte); + priv->link_up = 1; + netif_carrier_on(dev->net); + } else { + priv->link_up = 0; + netif_carrier_off(dev->net); + } +} + +static void sierra_net_dosync(struct usbnet *dev) +{ + int status; + struct sierra_net_data *priv = sierra_net_get_private(dev); + + dev_dbg(&dev->udev->dev, "%s", __func__); + + /* tell modem we are ready */ + status = sierra_net_send_sync(dev); + if (status < 0) + netdev_err(dev->net, + "Send SYNC failed, status %d\n", status); + status = sierra_net_send_sync(dev); + if (status < 0) + netdev_err(dev->net, + "Send SYNC failed, status %d\n", status); + + /* Now, start a timer and make sure we get the Restart Indication */ + priv->sync_timer.function = sierra_sync_timer; + priv->sync_timer.data = (unsigned long) dev; + priv->sync_timer.expires = jiffies + SIERRA_NET_SYNCDELAY; + add_timer(&priv->sync_timer); +} + +static void sierra_net_kevent(struct work_struct *work) +{ + struct sierra_net_data *priv = + container_of(work, struct sierra_net_data, sierra_net_kevent); + struct usbnet *dev = priv->usbnet; + int len; + int err; + u8 *buf; + u8 ifnum; + + if (test_bit(SIERRA_NET_EVENT_RESP_AVAIL, &priv->kevent_flags)) { + clear_bit(SIERRA_NET_EVENT_RESP_AVAIL, &priv->kevent_flags); + + /* Query the modem for the LSI message */ + buf = kzalloc(SIERRA_NET_USBCTL_BUF_LEN, GFP_KERNEL); + if (!buf) { + netdev_err(dev->net, + "failed to allocate buf for LS msg\n"); + return; + } + ifnum = priv->ifnum; + len = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + USB_CDC_GET_ENCAPSULATED_RESPONSE, + USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE, + 0, ifnum, buf, SIERRA_NET_USBCTL_BUF_LEN, + USB_CTRL_SET_TIMEOUT); + + if (len < 0) { + netdev_err(dev->net, + "usb_control_msg failed, status %d\n", len); + } else { + struct hip_hdr hh; + + dev_dbg(&dev->udev->dev, "%s: Received status message," + " %04x bytes", __func__, len); + + err = parse_hip(buf, len, &hh); + if (err) { + netdev_err(dev->net, "%s: Bad packet," + " parse result %d\n", __func__, err); + kfree(buf); + return; + } + + /* Validate packet length */ + if (len != hh.hdrlen + hh.payload_len.word) { + netdev_err(dev->net, "%s: Bad packet, received" + " %d, expected %d\n", __func__, len, + hh.hdrlen + hh.payload_len.word); + kfree(buf); + return; + } + + /* Switch on received message types */ + switch (hh.msgid.byte) { + case SIERRA_NET_HIP_LSI_UMTSID: + dev_dbg(&dev->udev->dev, "LSI for ctx:%d", + hh.msgspecific.byte); + sierra_net_handle_lsi(dev, buf, &hh); + break; + case SIERRA_NET_HIP_RESTART_ID: + dev_dbg(&dev->udev->dev, "Restart reported: %d," + " stopping sync timer", + hh.msgspecific.byte); + /* Got sync resp - stop timer & clear mask */ + del_timer_sync(&priv->sync_timer); + clear_bit(SIERRA_NET_TIMER_EXPIRY, + &priv->kevent_flags); + break; + case SIERRA_NET_HIP_HSYNC_ID: + dev_dbg(&dev->udev->dev, "SYNC received"); + err = sierra_net_send_sync(dev); + if (err < 0) + netdev_err(dev->net, + "Send SYNC failed %d\n", err); + break; + case SIERRA_NET_HIP_EXTENDEDID: + netdev_err(dev->net, "Unrecognized HIP msg, " + "extmsgid 0x%04x\n", hh.extmsgid.word); + break; + case SIERRA_NET_HIP_RCGI: + /* Ignored */ + break; + default: + netdev_err(dev->net, "Unrecognized HIP msg, " + "msgid 0x%02x\n", hh.msgid.byte); + break; + } + } + kfree(buf); + } + /* The sync timer bit might be set */ + if (test_bit(SIERRA_NET_TIMER_EXPIRY, &priv->kevent_flags)) { + clear_bit(SIERRA_NET_TIMER_EXPIRY, &priv->kevent_flags); + dev_dbg(&dev->udev->dev, "Deferred sync timer expiry"); + sierra_net_dosync(priv->usbnet); + } + + if (priv->kevent_flags) + dev_dbg(&dev->udev->dev, "sierra_net_kevent done, " + "kevent_flags = 0x%lx", priv->kevent_flags); +} + +static void sierra_net_defer_kevent(struct usbnet *dev, int work) +{ + struct sierra_net_data *priv = sierra_net_get_private(dev); + + set_bit(work, &priv->kevent_flags); + schedule_work(&priv->sierra_net_kevent); +} + +/* + * Sync Retransmit Timer Handler. On expiry, kick the work queue + */ +void sierra_sync_timer(unsigned long syncdata) +{ + struct usbnet *dev = (struct usbnet *)syncdata; + + dev_dbg(&dev->udev->dev, "%s", __func__); + /* Kick the tasklet */ + sierra_net_defer_kevent(dev, SIERRA_NET_TIMER_EXPIRY); +} + +static void sierra_net_status(struct usbnet *dev, struct urb *urb) +{ + struct usb_cdc_notification *event; + + dev_dbg(&dev->udev->dev, "%s", __func__); + + if (urb->actual_length < sizeof *event) + return; + + /* Add cases to handle other standard notifications. */ + event = urb->transfer_buffer; + switch (event->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + case USB_CDC_NOTIFY_SPEED_CHANGE: + /* USB 305 sends those */ + break; + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: + sierra_net_defer_kevent(dev, SIERRA_NET_EVENT_RESP_AVAIL); + break; + default: + netdev_err(dev->net, ": unexpected notification %02x!\n", + event->bNotificationType); + break; + } +} + +static void sierra_net_get_drvinfo(struct net_device *net, + struct ethtool_drvinfo *info) +{ + /* Inherit standard device info */ + usbnet_get_drvinfo(net, info); + strncpy(info->driver, driver_name, sizeof info->driver); + strncpy(info->version, DRIVER_VERSION, sizeof info->version); +} + +static u32 sierra_net_get_link(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + /* Report link is down whenever the interface is down */ + return sierra_net_get_private(dev)->link_up && netif_running(net); +} + +static struct ethtool_ops sierra_net_ethtool_ops = { + .get_drvinfo = sierra_net_get_drvinfo, + .get_link = sierra_net_get_link, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, + .get_settings = usbnet_get_settings, + .set_settings = usbnet_set_settings, + .nway_reset = usbnet_nway_reset, +}; + +/* MTU can not be more than 1500 bytes, enforce it. */ +static int sierra_net_change_mtu(struct net_device *net, int new_mtu) +{ + if (new_mtu > SIERRA_NET_MAX_SUPPORTED_MTU) + return -EINVAL; + + return usbnet_change_mtu(net, new_mtu); +} + +static int is_whitelisted(const u8 ifnum, + const struct sierra_net_iface_info *whitelist) +{ + if (whitelist) { + const u8 *list = whitelist->ifaceinfo; + int i; + + for (i = 0; i < whitelist->infolen; i++) { + if (list[i] == ifnum) + return 1; + } + } + return 0; +} + +static int sierra_net_get_fw_attr(struct usbnet *dev, u16 *datap) +{ + int result = 0; + u16 *attrdata; + + attrdata = kmalloc(sizeof(*attrdata), GFP_KERNEL); + if (!attrdata) + return -ENOMEM; + + result = usb_control_msg( + dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + /* _u8 vendor specific request */ + SWI_USB_REQUEST_GET_FW_ATTR, + USB_DIR_IN | USB_TYPE_VENDOR, /* __u8 request type */ + 0x0000, /* __u16 value not used */ + 0x0000, /* __u16 index not used */ + attrdata, /* char *data */ + sizeof(*attrdata), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + + if (result < 0) { + kfree(attrdata); + return -EIO; + } + + *datap = *attrdata; + + kfree(attrdata); + return result; +} + +/* + * collects the bulk endpoints, the status endpoint. + */ +static int sierra_net_bind(struct usbnet *dev, struct usb_interface *intf) +{ + u8 ifacenum; + u8 numendpoints; + u16 fwattr = 0; + int status; + struct ethhdr *eth; + struct sierra_net_data *priv; + static const u8 sync_tmplate[sizeof(priv->sync_msg)] = { + 0x00, 0x00, SIERRA_NET_HIP_MSYNC_ID, 0x00}; + static const u8 shdwn_tmplate[sizeof(priv->shdwn_msg)] = { + 0x00, 0x00, SIERRA_NET_HIP_SHUTD_ID, 0x00}; + + struct sierra_net_info_data *data = + (struct sierra_net_info_data *)dev->driver_info->data; + + dev_dbg(&dev->udev->dev, "%s", __func__); + + ifacenum = intf->cur_altsetting->desc.bInterfaceNumber; + /* We only accept certain interfaces */ + if (!is_whitelisted(ifacenum, &data->whitelist)) { + dev_dbg(&dev->udev->dev, "Ignoring interface: %d", ifacenum); + return -ENODEV; + } + numendpoints = intf->cur_altsetting->desc.bNumEndpoints; + /* We have three endpoints, bulk in and out, and a status */ + if (numendpoints != 3) { + dev_err(&dev->udev->dev, "Expected 3 endpoints, found: %d", + numendpoints); + return -ENODEV; + } + /* Status endpoint set in usbnet_get_endpoints() */ + dev->status = NULL; + status = usbnet_get_endpoints(dev, intf); + if (status < 0) { + dev_err(&dev->udev->dev, "Error in usbnet_get_endpoints (%d)", + status); + return -ENODEV; + } + /* Initialize sierra private data */ + priv = kzalloc(sizeof *priv, GFP_KERNEL); + if (!priv) { + dev_err(&dev->udev->dev, "No memory"); + return -ENOMEM; + } + + priv->usbnet = dev; + priv->ifnum = ifacenum; + dev->net->netdev_ops = &sierra_net_device_ops; + + /* change MAC addr to include, ifacenum, and to be unique */ + dev->net->dev_addr[ETH_ALEN-2] = atomic_inc_return(&iface_counter); + dev->net->dev_addr[ETH_ALEN-1] = ifacenum; + + /* we will have to manufacture ethernet headers, prepare template */ + eth = (struct ethhdr *)priv->ethr_hdr_tmpl; + memcpy(ð->h_dest, dev->net->dev_addr, ETH_ALEN); + eth->h_proto = cpu_to_be16(ETH_P_IP); + + /* prepare shutdown message template */ + memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg)); + /* set context index initially to 0 - prepares tx hdr template */ + sierra_net_set_ctx_index(priv, 0); + + /* decrease the rx_urb_size and max_tx_size to 4k on USB 1.1 */ + dev->rx_urb_size = data->rx_urb_size; + if (dev->udev->speed != USB_SPEED_HIGH) + dev->rx_urb_size = min_t(size_t, 4096, data->rx_urb_size); + + dev->net->hard_header_len += SIERRA_NET_HIP_EXT_HDR_LEN; + dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len; + + /* Set up the netdev */ + dev->net->flags |= IFF_NOARP; + dev->net->ethtool_ops = &sierra_net_ethtool_ops; + netif_carrier_off(dev->net); + + sierra_net_set_private(dev, priv); + + priv->kevent_flags = 0; + + /* Use the shared workqueue */ + INIT_WORK(&priv->sierra_net_kevent, sierra_net_kevent); + + /* Only need to do this once */ + init_timer(&priv->sync_timer); + + /* verify fw attributes */ + status = sierra_net_get_fw_attr(dev, &fwattr); + dev_dbg(&dev->udev->dev, "Fw attr: %x\n", fwattr); + + /* test whether firmware supports DHCP */ + if (!(status == sizeof(fwattr) && (fwattr & SWI_GET_FW_ATTR_MASK))) { + /* found incompatible firmware version */ + dev_err(&dev->udev->dev, "Incompatible driver and firmware" + " versions\n"); + kfree(priv); + return -ENODEV; + } + /* prepare sync message from template */ + memcpy(priv->sync_msg, sync_tmplate, sizeof(priv->sync_msg)); + + /* initiate the sync sequence */ + sierra_net_dosync(dev); + + return 0; +} + +static void sierra_net_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + int status; + struct sierra_net_data *priv = sierra_net_get_private(dev); + + dev_dbg(&dev->udev->dev, "%s", __func__); + + /* Kill the timer then flush the work queue */ + del_timer_sync(&priv->sync_timer); + + flush_scheduled_work(); + + /* tell modem we are going away */ + status = sierra_net_send_cmd(dev, priv->shdwn_msg, + sizeof(priv->shdwn_msg), "Shutdown"); + if (status < 0) + netdev_err(dev->net, + "usb_control_msg failed, status %d\n", status); + + sierra_net_set_private(dev, NULL); + + kfree(priv); +} + +static struct sk_buff *sierra_net_skb_clone(struct usbnet *dev, + struct sk_buff *skb, int len) +{ + struct sk_buff *new_skb; + + /* clone skb */ + new_skb = skb_clone(skb, GFP_ATOMIC); + + /* remove len bytes from original */ + skb_pull(skb, len); + + /* trim next packet to it's length */ + if (new_skb) { + skb_trim(new_skb, len); + } else { + if (netif_msg_rx_err(dev)) + netdev_err(dev->net, "failed to get skb\n"); + dev->net->stats.rx_dropped++; + } + + return new_skb; +} + +/* ---------------------------- Receive data path ----------------------*/ +static int sierra_net_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + int err; + struct hip_hdr hh; + struct sk_buff *new_skb; + + dev_dbg(&dev->udev->dev, "%s", __func__); + + /* could contain multiple packets */ + while (likely(skb->len)) { + err = parse_hip(skb->data, skb->len, &hh); + if (err) { + if (netif_msg_rx_err(dev)) + netdev_err(dev->net, "Invalid HIP header %d\n", + err); + /* dev->net->stats.rx_errors incremented by caller */ + dev->net->stats.rx_length_errors++; + return 0; + } + + /* Validate Extended HIP header */ + if (!hh.extmsgid.is_present + || hh.extmsgid.word != SIERRA_NET_HIP_EXT_IP_IN_ID) { + if (netif_msg_rx_err(dev)) + netdev_err(dev->net, "HIP/ETH: Invalid pkt\n"); + + dev->net->stats.rx_frame_errors++; + /* dev->net->stats.rx_errors incremented by caller */; + return 0; + } + + skb_pull(skb, hh.hdrlen); + + /* We are going to accept this packet, prepare it */ + memcpy(skb->data, sierra_net_get_private(dev)->ethr_hdr_tmpl, + ETH_HLEN); + + /* Last packet in batch handled by usbnet */ + if (hh.payload_len.word == skb->len) + return 1; + + new_skb = sierra_net_skb_clone(dev, skb, hh.payload_len.word); + if (new_skb) + usbnet_skb_return(dev, new_skb); + + } /* while */ + + return 0; +} + +/* ---------------------------- Transmit data path ----------------------*/ +struct sk_buff *sierra_net_tx_fixup(struct usbnet *dev, struct sk_buff *skb, + gfp_t flags) +{ + struct sierra_net_data *priv = sierra_net_get_private(dev); + u16 len; + bool need_tail; + + dev_dbg(&dev->udev->dev, "%s", __func__); + if (priv->link_up && check_ethip_packet(skb, dev) && is_ip(skb)) { + /* enough head room as is? */ + if (SIERRA_NET_HIP_EXT_HDR_LEN <= skb_headroom(skb)) { + /* Save the Eth/IP length and set up HIP hdr */ + len = skb->len; + skb_push(skb, SIERRA_NET_HIP_EXT_HDR_LEN); + /* Handle ZLP issue */ + need_tail = ((len + SIERRA_NET_HIP_EXT_HDR_LEN) + % dev->maxpacket == 0); + if (need_tail) { + if (unlikely(skb_tailroom(skb) == 0)) { + netdev_err(dev->net, "tx_fixup:" + "no room for packet\n"); + dev_kfree_skb_any(skb); + return NULL; + } else { + skb->data[skb->len] = 0; + __skb_put(skb, 1); + len = len + 1; + } + } + build_hip(skb->data, len, priv); + return skb; + } else { + /* + * compensate in the future if necessary + */ + netdev_err(dev->net, "tx_fixup: no room for HIP\n"); + } /* headroom */ + } + + if (!priv->link_up) + dev->net->stats.tx_carrier_errors++; + + /* tx_dropped incremented by usbnet */ + + /* filter the packet out, release it */ + dev_kfree_skb_any(skb); + return NULL; +} + +static const u8 sierra_net_ifnum_list[] = { 7, 10, 11 }; +static const struct sierra_net_info_data sierra_net_info_data_68A3 = { + .rx_urb_size = 8 * 1024, + .whitelist = { + .infolen = ARRAY_SIZE(sierra_net_ifnum_list), + .ifaceinfo = sierra_net_ifnum_list + } +}; + +static const struct driver_info sierra_net_info_68A3 = { + .description = "Sierra Wireless USB-to-WWAN Modem", + .flags = FLAG_WWAN | FLAG_SEND_ZLP, + .bind = sierra_net_bind, + .unbind = sierra_net_unbind, + .status = sierra_net_status, + .rx_fixup = sierra_net_rx_fixup, + .tx_fixup = sierra_net_tx_fixup, + .data = (unsigned long)&sierra_net_info_data_68A3, +}; + +static const struct usb_device_id products[] = { + {USB_DEVICE(0x1199, 0x68A3), /* Sierra Wireless USB-to-WWAN modem */ + .driver_info = (unsigned long) &sierra_net_info_68A3}, + + {}, /* last item */ +}; +MODULE_DEVICE_TABLE(usb, products); + +/* We are based on usbnet, so let it handle the USB driver specifics */ +static struct usb_driver sierra_net_driver = { + .name = "sierra_net", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, + .no_dynamic_id = 1, +}; + +static int __init sierra_net_init(void) +{ + BUILD_BUG_ON(FIELD_SIZEOF(struct usbnet, data) + < sizeof(struct cdc_state)); + + return usb_register(&sierra_net_driver); +} + +static void __exit sierra_net_exit(void) +{ + usb_deregister(&sierra_net_driver); +} + +module_exit(sierra_net_exit); +module_init(sierra_net_init); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/smsc75xx.c b/drivers/net/usb/smsc75xx.c index 35b98b1b79e4..753ee6eb7edd 100644 --- a/drivers/net/usb/smsc75xx.c +++ b/drivers/net/usb/smsc75xx.c @@ -445,14 +445,14 @@ static void smsc75xx_set_multicast(struct net_device *netdev) netif_dbg(dev, drv, dev->net, "receive all multicast enabled"); pdata->rfe_ctl |= RFE_CTL_AM | RFE_CTL_DPF; } else if (!netdev_mc_empty(dev->net)) { - struct dev_mc_list *mc_list; + struct netdev_hw_addr *ha; netif_dbg(dev, drv, dev->net, "receive multicast hash filter"); pdata->rfe_ctl |= RFE_CTL_MHF | RFE_CTL_DPF; - netdev_for_each_mc_addr(mc_list, netdev) { - u32 bitnum = smsc75xx_hash(mc_list->dmi_addr); + netdev_for_each_mc_addr(ha, netdev) { + u32 bitnum = smsc75xx_hash(ha->addr); pdata->multicast_hash_table[bitnum / 32] |= (1 << (bitnum % 32)); } diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 3135af63d378..12a3c88c5282 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c @@ -385,13 +385,13 @@ static void smsc95xx_set_multicast(struct net_device *netdev) pdata->mac_cr |= MAC_CR_MCPAS_; pdata->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_HPFILT_); } else if (!netdev_mc_empty(dev->net)) { - struct dev_mc_list *mc_list; + struct netdev_hw_addr *ha; pdata->mac_cr |= MAC_CR_HPFILT_; pdata->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_); - netdev_for_each_mc_addr(mc_list, netdev) { - u32 bitnum = smsc95xx_hash(mc_list->dmi_addr); + netdev_for_each_mc_addr(ha, netdev) { + u32 bitnum = smsc95xx_hash(ha->addr); u32 mask = 0x01 << (bitnum & 0x1F); if (bitnum & 0x20) hash_hi |= mask; diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 7177abc78dc6..a95c73de5824 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -1069,12 +1069,15 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb, * NOTE: strictly conforming cdc-ether devices should expect * the ZLP here, but ignore the one-byte packet. */ - if (!(info->flags & FLAG_SEND_ZLP) && (length % dev->maxpacket) == 0) { - urb->transfer_buffer_length++; - if (skb_tailroom(skb)) { - skb->data[skb->len] = 0; - __skb_put(skb, 1); - } + if (length % dev->maxpacket == 0) { + if (!(info->flags & FLAG_SEND_ZLP)) { + urb->transfer_buffer_length++; + if (skb_tailroom(skb)) { + skb->data[skb->len] = 0; + __skb_put(skb, 1); + } + } else + urb->transfer_flags |= URB_ZERO_PACKET; } spin_lock_irqsave(&dev->txq.lock, flags); |