zte's code,first commit

Change-Id: I9a04da59e459a9bc0d67f101f700d9d7dc8d681b
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/Kconfig b/ap/os/linux/linux-3.4.x/drivers/net/usb/Kconfig
new file mode 100644
index 0000000..833e32f
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/Kconfig
@@ -0,0 +1,487 @@
+#
+# USB Network devices configuration
+#
+comment "Networking support is needed for USB Network Adapter support"
+	depends on USB && !NET
+
+menu "USB Network Adapters"
+	depends on USB && NET
+
+config USB_CATC
+	tristate "USB CATC NetMate-based Ethernet device support (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	select CRC32
+	---help---
+	  Say Y if you want to use one of the following 10Mbps USB Ethernet
+	  device based on the EL1210A chip. Supported devices are:
+	  Belkin F5U011
+	  Belkin F5U111
+	  CATC NetMate
+	  CATC NetMate II
+	  smartBridges smartNIC
+
+	  This driver makes the adapter appear as a normal Ethernet interface,
+	  typically on eth0, if it is the only ethernet device, or perhaps on
+	  eth1, if you have a PCI or ISA ethernet card installed.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called catc.
+
+config USB_KAWETH
+	tristate "USB KLSI KL5USB101-based ethernet device support"
+	---help---
+	  Say Y here if you want to use one of the following 10Mbps only
+	  USB Ethernet adapters based on the KLSI KL5KUSB101B chipset:
+	  3Com 3C19250
+	  ADS USB-10BT
+	  ATEN USB Ethernet
+	  ASANTE USB To Ethernet Adapter
+	  AOX Endpoints USB Ethernet
+	  Correga K.K.
+	  D-Link DSB-650C and DU-E10
+	  Entrega / Portgear E45
+	  I-O DATA USB-ET/T
+	  Jaton USB Ethernet Device Adapter
+	  Kingston Technology USB Ethernet Adapter
+	  Linksys USB10T
+	  Mobility USB-Ethernet Adapter
+	  NetGear EA-101
+	  Peracom Enet and Enet2
+	  Portsmith Express Ethernet Adapter
+	  Shark Pocket Adapter
+	  SMC 2202USB
+	  Sony Vaio port extender
+
+	  This driver is likely to work with most 10Mbps only USB Ethernet
+	  adapters, including some "no brand" devices. It does NOT work on
+	  SmartBridges smartNIC or on Belkin F5U111 devices - you should use
+	  the CATC NetMate driver for those. If you are not sure which one
+	  you need, select both, and the correct one should be selected for
+	  you.
+
+	  This driver makes the adapter appear as a normal Ethernet interface,
+	  typically on eth0, if it is the only ethernet device, or perhaps on
+	  eth1, if you have a PCI or ISA ethernet card installed.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called kaweth.
+
+config USB_PEGASUS
+	tristate "USB Pegasus/Pegasus-II based ethernet device support"
+	select NET_CORE
+	select MII
+	---help---
+	  Say Y here if you know you have Pegasus or Pegasus-II based adapter.
+	  If in doubt then look at <file:drivers/net/usb/pegasus.h> for the
+	  complete list of supported devices.
+
+	  If your particular adapter is not in the list and you are _sure_ it
+	  is Pegasus or Pegasus II based then send me
+	  <petkan@users.sourceforge.net> vendor and device IDs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pegasus.
+
+config USB_RTL8150
+	tristate "USB RTL8150 based ethernet device support (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	select NET_CORE
+	select MII
+	help
+	  Say Y here if you have RTL8150 based usb-ethernet adapter.
+	  Send me <petkan@users.sourceforge.net> any comments you may have.
+	  You can also check for updates at <http://pegasus2.sourceforge.net/>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rtl8150.
+
+config USB_USBNET
+	tristate "Multi-purpose USB Networking Framework"
+	select NET_CORE
+	select MII
+	---help---
+	  This driver supports several kinds of network links over USB,
+	  with "minidrivers" built around a common network driver core
+	  that supports deep queues for efficient transfers.  (This gives
+	  better performance with small packets and at high speeds).
+
+	  The USB host runs "usbnet", and the other end of the link might be:
+
+	  - Another USB host, when using USB "network" or "data transfer"
+	    cables.  These are often used to network laptops to PCs, like
+	    "Laplink" parallel cables or some motherboards.  These rely
+	    on specialized chips from many suppliers.
+
+	  - An intelligent USB gadget, perhaps embedding a Linux system.
+	    These include PDAs running Linux (iPaq, Yopy, Zaurus, and
+	    others), and devices that interoperate using the standard
+	    CDC-Ethernet specification (including many cable modems).
+
+	  - Network adapter hardware (like those for 10/100 Ethernet) which
+	    uses this driver framework.
+
+	  The link will appear with a name like "usb0", when the link is
+	  a two-node link, or "eth0" for most CDC-Ethernet devices.  Those
+	  two-node links are most easily managed with Ethernet Bridging
+	  (CONFIG_BRIDGE) instead of routing.
+
+	  For more information see <http://www.linux-usb.org/usbnet/>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbnet.
+
+config USB_NET_AX8817X
+	tristate "ASIX AX88xxx Based USB 2.0 Ethernet Adapters"
+	depends on USB_USBNET
+	select CRC32
+	default y
+	help
+	  This option adds support for ASIX AX88xxx based USB 2.0
+	  10/100 Ethernet adapters.
+
+ 	  This driver should work with at least the following devices:
+	    * Aten UC210T
+	    * ASIX AX88172
+	    * Billionton Systems, USB2AR
+	    * Buffalo LUA-U2-KTX
+	    * Corega FEther USB2-TX
+	    * D-Link DUB-E100
+	    * Hawking UF200
+	    * Linksys USB200M
+	    * Netgear FA120
+	    * Sitecom LN-029
+	    * Intellinet USB 2.0 Ethernet
+	    * ST Lab USB 2.0 Ethernet
+	    * TrendNet TU2-ET100
+
+	  This driver creates an interface named "ethX", where X depends on
+	  what other networking devices you have in use.
+
+config USB_NET_CDCETHER
+	tristate "CDC Ethernet support (smart devices such as cable modems)"
+	depends on USB_USBNET
+	default y
+	help
+	  This option supports devices conforming to the Communication Device
+	  Class (CDC) Ethernet Control Model, a specification that's easy to
+	  implement in device firmware.  The CDC specifications are available
+	  from <http://www.usb.org/>.
+
+	  CDC Ethernet is an implementation option for DOCSIS cable modems
+	  that support USB connectivity, used for non-Microsoft USB hosts.
+	  The Linux-USB CDC Ethernet Gadget driver is an open implementation.
+ 	  This driver should work with at least the following devices:
+
+	    * Dell Wireless 5530 HSPA
+ 	    * Ericsson PipeRider (all variants)
+	    * Ericsson Mobile Broadband Module (all variants)
+ 	    * Motorola (DM100 and SB4100)
+ 	    * Broadcom Cable Modem (reference design)
+	    * Toshiba (PCX1100U and F3507g/F3607gw)
+	    * ...
+
+	  This driver creates an interface named "ethX", where X depends on
+	  what other networking devices you have in use.  However, if the
+	  IEEE 802 "local assignment" bit is set in the address, a "usbX"
+	  name is used instead.
+
+config USB_NET_CDC_EEM
+	tristate "CDC EEM support"
+	depends on USB_USBNET && EXPERIMENTAL
+	help
+	  This option supports devices conforming to the Communication Device
+	  Class (CDC) Ethernet Emulation Model, a specification that's easy to
+	  implement in device firmware.  The CDC EEM specifications are available
+	  from <http://www.usb.org/>.
+
+	  This driver creates an interface named "ethX", where X depends on
+	  what other networking devices you have in use.  However, if the
+	  IEEE 802 "local assignment" bit is set in the address, a "usbX"
+	  name is used instead.
+
+config USB_NET_CDC_NCM
+	tristate "CDC NCM support"
+	depends on USB_USBNET
+	default y
+	help
+	  This driver provides support for CDC NCM (Network Control Model
+	  Device USB Class Specification). The CDC NCM specification is
+	  available from <http://www.usb.org/>.
+
+	  Say "y" to link the driver statically, or "m" to build a
+	  dynamically linked module.
+
+	  This driver should work with at least the following devices:
+	    * ST-Ericsson M700 LTE FDD/TDD Mobile Broadband Modem (ref. design)
+	    * ST-Ericsson M5730 HSPA+ Mobile Broadband Modem (reference design)
+	    * ST-Ericsson M570 HSPA+ Mobile Broadband Modem (reference design)
+	    * ST-Ericsson M343 HSPA Mobile Broadband Modem (reference design)
+	    * Ericsson F5521gw Mobile Broadband Module
+
+config USB_NET_DM9601
+	tristate "Davicom DM9601 based USB 1.1 10/100 ethernet devices"
+	depends on USB_USBNET
+	select CRC32
+	help
+	  This option adds support for Davicom DM9601 based USB 1.1
+	  10/100 Ethernet adapters.
+
+config USB_NET_SMSC75XX
+	tristate "SMSC LAN75XX based USB 2.0 gigabit ethernet devices"
+	depends on USB_USBNET
+	select CRC32
+	help
+	  This option adds support for SMSC LAN95XX based USB 2.0
+	  Gigabit Ethernet adapters.
+
+config USB_NET_SMSC95XX
+	tristate "SMSC LAN95XX based USB 2.0 10/100 ethernet devices"
+	depends on USB_USBNET
+	select CRC32
+	help
+	  This option adds support for SMSC LAN95XX based USB 2.0
+	  10/100 Ethernet adapters.
+
+config USB_NET_GL620A
+	tristate "GeneSys GL620USB-A based cables"
+	depends on USB_USBNET
+	help
+	  Choose this option if you're using a host-to-host cable,
+	  or PC2PC motherboard, with this chip.
+
+	  Note that the half-duplex "GL620USB" is not supported.
+
+config USB_NET_NET1080
+	tristate "NetChip 1080 based cables (Laplink, ...)"
+	default y
+	depends on USB_USBNET
+	help
+	  Choose this option if you're using a host-to-host cable based
+	  on this design:  one NetChip 1080 chip and supporting logic,
+	  optionally with LEDs that indicate traffic
+
+config USB_NET_PLUSB
+	tristate "Prolific PL-2301/2302/25A1 based cables"
+	# if the handshake/init/reset problems, from original 'plusb',
+	# are ever resolved ... then remove "experimental"
+	depends on USB_USBNET && EXPERIMENTAL
+	help
+	  Choose this option if you're using a host-to-host cable
+	  with one of these chips.
+
+config USB_NET_MCS7830
+	tristate "MosChip MCS7830 based Ethernet adapters"
+	depends on USB_USBNET
+	help
+	  Choose this option if you're using a 10/100 Ethernet USB2
+	  adapter based on the MosChip 7830 controller. This includes
+	  adapters marketed under the DeLOCK brand.
+
+config USB_NET_RNDIS_HOST
+	tristate "Host for RNDIS and ActiveSync devices (EXPERIMENTAL)"
+	depends on USB_USBNET && EXPERIMENTAL
+	select USB_NET_CDCETHER
+	help
+	  This option enables hosting "Remote NDIS" USB networking links,
+	  as encouraged by Microsoft (instead of CDC Ethernet!) for use in
+	  various devices that may only support this protocol.  A variant
+	  of this protocol (with even less public documentation) seems to
+	  be at the root of Microsoft's "ActiveSync" too.
+
+	  Avoid using this protocol unless you have no better options.
+	  The protocol specification is incomplete, and is controlled by
+	  (and for) Microsoft; it isn't an "Open" ecosystem or market.
+
+config USB_NET_CDC_SUBSET
+	tristate "Simple USB Network Links (CDC Ethernet subset)"
+	depends on USB_USBNET
+	default y
+	help
+	  This driver module supports USB network devices that can work
+	  without any device-specific information.  Select it if you have
+	  one of these drivers.
+
+	  Note that while many USB host-to-host cables can work in this mode,
+	  that may mean not being able to talk to Win32 systems or more
+	  commonly not being able to handle certain events (like replugging
+	  the host on the other end) very well.  Also, these devices will
+	  not generally have permanently assigned Ethernet addresses.
+
+config USB_ALI_M5632
+	boolean "ALi M5632 based 'USB 2.0 Data Link' cables"
+	depends on USB_NET_CDC_SUBSET
+	help
+	  Choose this option if you're using a host-to-host cable
+	  based on this design, which supports USB 2.0 high speed.
+
+config USB_AN2720
+	boolean "AnchorChips 2720 based cables (Xircom PGUNET, ...)"
+	depends on USB_NET_CDC_SUBSET
+	help
+	  Choose this option if you're using a host-to-host cable
+	  based on this design.  Note that AnchorChips is now a
+	  Cypress brand.
+
+config USB_BELKIN
+	boolean "eTEK based host-to-host cables (Advance, Belkin, ...)"
+	depends on USB_NET_CDC_SUBSET
+	default y
+	help
+	  Choose this option if you're using a host-to-host cable
+	  based on this design:  two NetChip 2890 chips and an Atmel
+	  microcontroller, with LEDs that indicate traffic.
+
+config USB_ARMLINUX
+	boolean "Embedded ARM Linux links (iPaq, ...)"
+	depends on USB_NET_CDC_SUBSET
+	default y
+	help
+	  Choose this option to support the "usb-eth" networking driver
+	  used by most of the ARM Linux community with device controllers
+	  such as the SA-11x0 and PXA-25x UDCs, or the tftp capabilities
+	  in some PXA versions of the "blob" boot loader.
+
+	  Linux-based "Gumstix" PXA-25x based systems use this protocol
+	  to talk with other Linux systems.
+
+	  Although the ROMs shipped with Sharp Zaurus products use a
+	  different link level framing protocol, you can have them use
+	  this simpler protocol by installing a different kernel.
+
+config USB_EPSON2888
+	boolean "Epson 2888 based firmware (DEVELOPMENT)"
+	depends on USB_NET_CDC_SUBSET
+	help
+	  Choose this option to support the usb networking links used
+	  by some sample firmware from Epson.
+
+config USB_KC2190
+	boolean "KT Technology KC2190 based cables (InstaNet)"
+	depends on USB_NET_CDC_SUBSET && EXPERIMENTAL
+	help
+	  Choose this option if you're using a host-to-host cable
+	  with one of these chips.
+
+config USB_NET_ZAURUS
+	tristate "Sharp Zaurus (stock ROMs) and compatible"
+	depends on USB_USBNET
+	select USB_NET_CDCETHER
+	select CRC32
+	default y
+	help
+	  Choose this option to support the usb networking links used by
+	  Zaurus models like the SL-5000D, SL-5500, SL-5600, A-300, B-500.
+	  This also supports some related device firmware, as used in some
+	  PDAs from Olympus and some cell phones from Motorola.
+
+	  If you install an alternate image, such as the Linux 2.6 based
+	  versions of OpenZaurus, you should no longer need to support this
+	  protocol.  Only the "eth-fd" or "net_fd" drivers in these devices
+	  really need this non-conformant variant of CDC Ethernet (or in
+	  some cases CDC MDLM) protocol, not "g_ether".
+
+config USB_NET_CX82310_ETH
+	tristate "Conexant CX82310 USB ethernet port"
+	depends on USB_USBNET
+	help
+	  Choose this option if you're using a Conexant CX82310-based ADSL
+	  router with USB ethernet port. This driver is for routers only,
+	  it will not work with ADSL modems (use cxacru driver instead).
+
+config USB_NET_KALMIA
+	tristate "Samsung Kalmia based LTE USB modem"
+	depends on USB_USBNET
+	help
+	  Choose this option if you have a Samsung Kalmia based USB modem
+	  as Samsung GT-B3730.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called kalmia.
+
+config USB_NET_QMI_WWAN
+	tristate "QMI WWAN driver for Qualcomm MSM based 3G and LTE modems"
+	depends on USB_USBNET
+	select USB_WDM
+	help
+	  Support WWAN LTE/3G devices based on Qualcomm Mobile Data Modem
+	  (MDM) chipsets.  Examples of such devices are
+	    * Huawei E392/E398
+
+	  This driver will only drive the ethernet part of the chips.
+	  The devices require additional configuration to be usable.
+	  Multiple management interfaces with linux drivers are
+	  available:
+
+	    * option: AT commands on /dev/ttyUSBx
+	    * cdc-wdm: Qualcomm MSM Interface (QMI) protocol on /dev/cdc-wdmx
+
+	  A modem manager with support for QMI is recommended.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called qmi_wwan.
+
+config USB_HSO
+	tristate "Option USB High Speed Mobile Devices"
+	depends on USB && RFKILL
+	default n
+	help
+	  Choose this option if you have an Option HSDPA/HSUPA card.
+	  These cards support downlink speeds of 7.2Mbps or greater.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hso.
+
+config USB_NET_INT51X1
+	tristate "Intellon PLC based usb adapter"
+	depends on USB_USBNET
+	help
+	  Choose this option if you're using a 14Mb USB-based PLC
+	  (Powerline Communications) solution with an Intellon
+	  INT51x1/INT5200 chip, like the "devolo dLan duo".
+
+config USB_CDC_PHONET
+	tristate "CDC Phonet support"
+	depends on PHONET
+	help
+	  Choose this option to support the Phonet interface to a Nokia
+	  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.
+
+config USB_VL600
+	tristate "LG VL600 modem dongle"
+	depends on USB_NET_CDCETHER
+	select USB_ACM
+	help
+	  Select this if you want to use an LG Electronics 4G/LTE usb modem
+	  called VL600.  This driver only handles the ethernet
+	  interface exposed by the modem firmware.  To establish a connection
+	  you will first need a userspace program that sends the right
+	  command to the modem through its CDC ACM port, and most
+	  likely also a DHCP client.  See this thread about using the
+	  4G modem from Verizon:
+
+	  http://ubuntuforums.org/showpost.php?p=10589647&postcount=17
+
+
+endmenu
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/Makefile b/ap/os/linux/linux-3.4.x/drivers/net/usb/Makefile
new file mode 100644
index 0000000..a2e2d72
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for USB Network drivers
+#
+
+obj-$(CONFIG_USB_CATC)		+= catc.o
+obj-$(CONFIG_USB_KAWETH)	+= kaweth.o
+obj-$(CONFIG_USB_PEGASUS)	+= pegasus.o
+obj-$(CONFIG_USB_RTL8150)	+= rtl8150.o
+obj-$(CONFIG_USB_HSO)		+= hso.o
+obj-$(CONFIG_USB_NET_AX8817X)	+= asix.o
+obj-$(CONFIG_USB_NET_CDCETHER)	+= cdc_ether.o
+obj-$(CONFIG_USB_NET_CDC_EEM)	+= cdc_eem.o
+obj-$(CONFIG_USB_NET_DM9601)	+= dm9601.o
+obj-$(CONFIG_USB_NET_SMSC75XX)	+= smsc75xx.o
+obj-$(CONFIG_USB_NET_SMSC95XX)	+= smsc95xx.o
+obj-$(CONFIG_USB_NET_GL620A)	+= gl620a.o
+obj-$(CONFIG_USB_NET_NET1080)	+= net1080.o
+obj-$(CONFIG_USB_NET_PLUSB)	+= plusb.o
+obj-$(CONFIG_USB_NET_RNDIS_HOST)	+= rndis_host.o
+obj-$(CONFIG_USB_NET_CDC_SUBSET)	+= cdc_subset.o
+obj-$(CONFIG_USB_NET_ZAURUS)	+= zaurus.o
+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_NET_KALMIA)	+= kalmia.o
+obj-$(CONFIG_USB_IPHETH)	+= ipheth.o
+obj-$(CONFIG_USB_SIERRA_NET)	+= sierra_net.o
+obj-$(CONFIG_USB_NET_CX82310_ETH)	+= cx82310_eth.o
+obj-$(CONFIG_USB_NET_CDC_NCM)	+= cdc_ncm.o
+obj-$(CONFIG_USB_VL600)		+= lg-vl600.o
+obj-$(CONFIG_USB_NET_QMI_WWAN)	+= qmi_wwan.o
+
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/asix.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/asix.c
new file mode 100644
index 0000000..e3e0ca1
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/asix.c
@@ -0,0 +1,1733 @@
+/*
+ * ASIX AX8817X based USB 2.0 Ethernet Devices
+ * Copyright (C) 2003-2006 David Hollis <dhollis@davehollis.com>
+ * Copyright (C) 2005 Phil Chang <pchang23@sbcglobal.net>
+ * Copyright (C) 2006 James Painter <jamie.painter@iname.com>
+ * Copyright (c) 2002-2003 TiVo Inc.
+ *
+ * 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	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+#include <linux/if_vlan.h>
+
+#define DRIVER_VERSION "22-Dec-2011"
+#define DRIVER_NAME "asix"
+
+/* 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_EEPROM		0x0b
+#define AX_CMD_WRITE_EEPROM		0x0c
+#define AX_CMD_WRITE_ENABLE		0x0d
+#define AX_CMD_WRITE_DISABLE		0x0e
+#define AX_CMD_READ_RX_CTL		0x0f
+#define AX_CMD_WRITE_RX_CTL		0x10
+#define AX_CMD_READ_IPG012		0x11
+#define AX_CMD_WRITE_IPG0		0x12
+#define AX_CMD_WRITE_IPG1		0x13
+#define AX_CMD_READ_NODE_ID		0x13
+#define AX_CMD_WRITE_NODE_ID		0x14
+#define AX_CMD_WRITE_IPG2		0x14
+#define AX_CMD_WRITE_MULTI_FILTER	0x16
+#define AX88172_CMD_READ_NODE_ID	0x17
+#define AX_CMD_READ_PHY_ID		0x19
+#define AX_CMD_READ_MEDIUM_STATUS	0x1a
+#define AX_CMD_WRITE_MEDIUM_MODE	0x1b
+#define AX_CMD_READ_MONITOR_MODE	0x1c
+#define AX_CMD_WRITE_MONITOR_MODE	0x1d
+#define AX_CMD_READ_GPIOS		0x1e
+#define AX_CMD_WRITE_GPIOS		0x1f
+#define AX_CMD_SW_RESET			0x20
+#define AX_CMD_SW_PHY_STATUS		0x21
+#define AX_CMD_SW_PHY_SELECT		0x22
+
+#define AX_MONITOR_MODE			0x01
+#define AX_MONITOR_LINK			0x02
+#define AX_MONITOR_MAGIC		0x04
+#define AX_MONITOR_HSFS			0x10
+
+/* AX88172 Medium Status Register values */
+#define AX88172_MEDIUM_FD		0x02
+#define AX88172_MEDIUM_TX		0x04
+#define AX88172_MEDIUM_FC		0x10
+#define AX88172_MEDIUM_DEFAULT \
+		( AX88172_MEDIUM_FD | AX88172_MEDIUM_TX | AX88172_MEDIUM_FC )
+
+#define AX_MCAST_FILTER_SIZE		8
+#define AX_MAX_MCAST			64
+
+#define AX_SWRESET_CLEAR		0x00
+#define AX_SWRESET_RR			0x01
+#define AX_SWRESET_RT			0x02
+#define AX_SWRESET_PRTE			0x04
+#define AX_SWRESET_PRL			0x08
+#define AX_SWRESET_BZ			0x10
+#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_AP		0x0020
+#define AX_RX_CTL_AM		0x0010
+#define AX_RX_CTL_AB		0x0008
+#define AX_RX_CTL_SEP		0x0004
+#define AX_RX_CTL_AMALL		0x0002
+#define AX_RX_CTL_PRO		0x0001
+#define AX_RX_CTL_MFB_2048	0x0000
+#define AX_RX_CTL_MFB_4096	0x0100
+#define AX_RX_CTL_MFB_8192	0x0200
+#define AX_RX_CTL_MFB_16384	0x0300
+
+#define AX_DEFAULT_RX_CTL	(AX_RX_CTL_SO | AX_RX_CTL_AB)
+
+/* GPIO 0 .. 2 toggles */
+#define AX_GPIO_GPO0EN		0x01	/* GPIO0 Output enable */
+#define AX_GPIO_GPO_0		0x02	/* GPIO0 Output value */
+#define AX_GPIO_GPO1EN		0x04	/* GPIO1 Output enable */
+#define AX_GPIO_GPO_1		0x08	/* GPIO1 Output value */
+#define AX_GPIO_GPO2EN		0x10	/* GPIO2 Output enable */
+#define AX_GPIO_GPO_2		0x20	/* GPIO2 Output value */
+#define AX_GPIO_RESERVED	0x40	/* Reserved */
+#define AX_GPIO_RSE		0x80	/* Reload serial EEPROM */
+
+#define AX_EEPROM_MAGIC		0xdeadbeef
+#define AX88172_EEPROM_LEN	0x40
+#define AX88772_EEPROM_LEN	0xff
+
+#define PHY_MODE_MARVELL	0x0000
+#define MII_MARVELL_LED_CTRL	0x0018
+#define MII_MARVELL_STATUS	0x001b
+#define MII_MARVELL_CTRL	0x0014
+
+#define MARVELL_LED_MANUAL	0x0019
+
+#define MARVELL_STATUS_HWCFG	0x0004
+
+#define MARVELL_CTRL_TXDELAY	0x0002
+#define MARVELL_CTRL_RXDELAY	0x0080
+
+#define	PHY_MODE_RTL8211CL	0x000C
+
+/* This structure cannot exceed sizeof(unsigned long [5]) AKA 20 bytes */
+struct asix_data {
+	u8 multi_filter[AX_MCAST_FILTER_SIZE];
+	u8 mac_addr[ETH_ALEN];
+	u8 phymode;
+	u8 ledmode;
+	u8 eeprom_len;
+};
+
+struct ax88172_int_data {
+	__le16 res1;
+	u8 link;
+	__le16 res2;
+	u8 status;
+	__le16 res3;
+} __packed;
+
+struct asix_rx_fixup_info {
+	struct sk_buff *ax_skb;
+	u32 header;
+	u16 size;
+	bool split_head;
+};
+
+struct asix_common_private {
+	struct asix_rx_fixup_info rx_fixup_info;
+};
+
+static int asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+			    u16 size, void *data)
+{
+	void *buf;
+	int err = -ENOMEM;
+
+	netdev_dbg(dev->net, "asix_read_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n",
+		   cmd, value, index, size);
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (!buf)
+		goto out;
+
+	err = usb_control_msg(
+		dev->udev,
+		usb_rcvctrlpipe(dev->udev, 0),
+		cmd,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		value,
+		index,
+		buf,
+		size,
+		USB_CTRL_GET_TIMEOUT);
+	if (err == size)
+		memcpy(data, buf, size);
+	else if (err >= 0)
+		err = -EINVAL;
+	kfree(buf);
+
+out:
+	return err;
+}
+
+static int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+			     u16 size, void *data)
+{
+	void *buf = NULL;
+	int err = -ENOMEM;
+
+	netdev_dbg(dev->net, "asix_write_cmd() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n",
+		   cmd, value, index, size);
+
+	if (data) {
+		buf = kmemdup(data, size, GFP_KERNEL);
+		if (!buf)
+			goto out;
+	}
+
+	err = usb_control_msg(
+		dev->udev,
+		usb_sndctrlpipe(dev->udev, 0),
+		cmd,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		value,
+		index,
+		buf,
+		size,
+		USB_CTRL_SET_TIMEOUT);
+	kfree(buf);
+
+out:
+	return err;
+}
+
+static void asix_async_cmd_callback(struct urb *urb)
+{
+	struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
+	int status = urb->status;
+
+	if (status < 0)
+		printk(KERN_DEBUG "asix_async_cmd_callback() failed with %d",
+			status);
+
+	kfree(req);
+	usb_free_urb(urb);
+}
+
+static void
+asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+				    u16 size, void *data)
+{
+	struct usb_ctrlrequest *req;
+	int status;
+	struct urb *urb;
+
+	netdev_dbg(dev->net, "asix_write_cmd_async() cmd=0x%02x value=0x%04x index=0x%04x size=%d\n",
+		   cmd, value, index, size);
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb) {
+		netdev_err(dev->net, "Error allocating URB in write_cmd_async!\n");
+		return;
+	}
+
+	req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC);
+	if (!req) {
+		netdev_err(dev->net, "Failed to allocate memory for control request\n");
+		usb_free_urb(urb);
+		return;
+	}
+
+	req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+	req->bRequest = cmd;
+	req->wValue = cpu_to_le16(value);
+	req->wIndex = cpu_to_le16(index);
+	req->wLength = cpu_to_le16(size);
+
+	usb_fill_control_urb(urb, dev->udev,
+			     usb_sndctrlpipe(dev->udev, 0),
+			     (void *)req, data, size,
+			     asix_async_cmd_callback, req);
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0) {
+		netdev_err(dev->net, "Error submitting the control message: status=%d\n",
+			   status);
+		kfree(req);
+		usb_free_urb(urb);
+	}
+}
+
+static int asix_rx_fixup_internal(struct usbnet *dev, struct sk_buff *skb,
+				  struct asix_rx_fixup_info *rx)
+{
+	int offset = 0;
+
+	while (offset + sizeof(u16) <= skb->len) {
+		u16 remaining = 0;
+		unsigned char *data;
+
+		if (!rx->size) {
+			if ((skb->len - offset == sizeof(u16)) ||
+			    rx->split_head) {
+				if (!rx->split_head) {
+					rx->header = get_unaligned_le16(
+							skb->data + offset);
+					rx->split_head = true;
+					offset += sizeof(u16);
+					break;
+				} else {
+					rx->header |= (get_unaligned_le16(
+							skb->data + offset)
+							<< 16);
+					rx->split_head = false;
+					offset += sizeof(u16);
+				}
+			} else {
+				rx->header = get_unaligned_le32(skb->data +
+								offset);
+				offset += sizeof(u32);
+			}
+
+			/* get the packet length */
+			rx->size = (u16) (rx->header & 0x7ff);
+			if (rx->size != ((~rx->header >> 16) & 0x7ff)) {
+				netdev_err(dev->net, "asix_rx_fixup() Bad Header Length 0x%x, offset %d\n",
+					   rx->header, offset);
+				rx->size = 0;
+				return 0;
+			}
+			rx->ax_skb = netdev_alloc_skb_ip_align(dev->net,
+							       rx->size);
+			if (!rx->ax_skb)
+				return 0;
+		}
+
+		if (rx->size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) {
+			netdev_err(dev->net, "asix_rx_fixup() Bad RX Length %d\n",
+				   rx->size);
+			kfree_skb(rx->ax_skb);
+			return 0;
+		}
+
+		if (rx->size > skb->len - offset) {
+			remaining = rx->size - (skb->len - offset);
+			rx->size = skb->len - offset;
+		}
+
+		data = skb_put(rx->ax_skb, rx->size);
+		memcpy(data, skb->data + offset, rx->size);
+		if (!remaining)
+			usbnet_skb_return(dev, rx->ax_skb);
+
+		offset += (rx->size + 1) & 0xfffe;
+		rx->size = remaining;
+	}
+
+	if (skb->len != offset) {
+		netdev_err(dev->net, "asix_rx_fixup() Bad SKB Length %d, %d\n",
+			   skb->len, offset);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int asix_rx_fixup_common(struct usbnet *dev, struct sk_buff *skb)
+{
+	struct asix_common_private *dp = dev->driver_priv;
+	struct asix_rx_fixup_info *rx = &dp->rx_fixup_info;
+
+	return asix_rx_fixup_internal(dev, skb, rx);
+}
+
+static struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+					gfp_t flags)
+{
+	int padlen;
+	int headroom = skb_headroom(skb);
+	int tailroom = skb_tailroom(skb);
+	u32 packet_len;
+	u32 padbytes = 0xffff0000;
+
+	padlen = ((skb->len + 4) & (dev->maxpacket - 1)) ? 0 : 4;
+
+	if ((!skb_cloned(skb)) &&
+	    ((headroom + tailroom) >= (4 + padlen))) {
+		if ((headroom < 4) || (tailroom < padlen)) {
+			skb->data = memmove(skb->head + 4, skb->data, skb->len);
+			skb_set_tail_pointer(skb, skb->len);
+		}
+	} else {
+		struct sk_buff *skb2;
+		skb2 = skb_copy_expand(skb, 4, padlen, flags);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+		if (!skb)
+			return NULL;
+	}
+
+	skb_push(skb, 4);
+	packet_len = (((skb->len - 4) ^ 0x0000ffff) << 16) + (skb->len - 4);
+	cpu_to_le32s(&packet_len);
+	skb_copy_to_linear_data(skb, &packet_len, sizeof(packet_len));
+
+	if (padlen) {
+		cpu_to_le32s(&padbytes);
+		memcpy(skb_tail_pointer(skb), &padbytes, sizeof(padbytes));
+		skb_put(skb, sizeof(padbytes));
+	}
+	return skb;
+}
+
+static void asix_status(struct usbnet *dev, struct urb *urb)
+{
+	struct ax88172_int_data *event;
+	int link;
+
+	if (urb->actual_length < 8)
+		return;
+
+	event = urb->transfer_buffer;
+	link = event->link & 0x01;
+	if (netif_carrier_ok(dev->net) != link) {
+		if (link) {
+			netif_carrier_on(dev->net);
+			usbnet_defer_kevent (dev, EVENT_LINK_RESET );
+		} else
+			netif_carrier_off(dev->net);
+		netdev_dbg(dev->net, "Link Status is: %d\n", link);
+	}
+}
+
+static inline int asix_set_sw_mii(struct usbnet *dev)
+{
+	int ret;
+	ret = asix_write_cmd(dev, AX_CMD_SET_SW_MII, 0x0000, 0, 0, NULL);
+	if (ret < 0)
+		netdev_err(dev->net, "Failed to enable software MII access\n");
+	return ret;
+}
+
+static inline int asix_set_hw_mii(struct usbnet *dev)
+{
+	int ret;
+	ret = asix_write_cmd(dev, AX_CMD_SET_HW_MII, 0x0000, 0, 0, NULL);
+	if (ret < 0)
+		netdev_err(dev->net, "Failed to enable hardware MII access\n");
+	return ret;
+}
+
+static inline int asix_get_phy_addr(struct usbnet *dev)
+{
+	u8 buf[2];
+	int ret = asix_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf);
+
+	netdev_dbg(dev->net, "asix_get_phy_addr()\n");
+
+	if (ret < 0) {
+		netdev_err(dev->net, "Error reading PHYID register: %02x\n", ret);
+		goto out;
+	}
+	netdev_dbg(dev->net, "asix_get_phy_addr() returning 0x%04x\n",
+		   *((__le16 *)buf));
+	ret = buf[1];
+
+out:
+	return ret;
+}
+
+static int asix_sw_reset(struct usbnet *dev, u8 flags)
+{
+	int ret;
+
+        ret = asix_write_cmd(dev, AX_CMD_SW_RESET, flags, 0, 0, NULL);
+	if (ret < 0)
+		netdev_err(dev->net, "Failed to send software reset: %02x\n", ret);
+
+	return ret;
+}
+
+static u16 asix_read_rx_ctl(struct usbnet *dev)
+{
+	__le16 v;
+	int ret = asix_read_cmd(dev, AX_CMD_READ_RX_CTL, 0, 0, 2, &v);
+
+	if (ret < 0) {
+		netdev_err(dev->net, "Error reading RX_CTL register: %02x\n", ret);
+		goto out;
+	}
+	ret = le16_to_cpu(v);
+out:
+	return ret;
+}
+
+static int asix_write_rx_ctl(struct usbnet *dev, u16 mode)
+{
+	int ret;
+
+	netdev_dbg(dev->net, "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)
+		netdev_err(dev->net, "Failed to write RX_CTL mode to 0x%04x: %02x\n",
+			   mode, ret);
+
+	return ret;
+}
+
+static u16 asix_read_medium_status(struct usbnet *dev)
+{
+	__le16 v;
+	int ret = asix_read_cmd(dev, AX_CMD_READ_MEDIUM_STATUS, 0, 0, 2, &v);
+
+	if (ret < 0) {
+		netdev_err(dev->net, "Error reading Medium Status register: %02x\n",
+			   ret);
+		return ret;	/* TODO: callers not checking for error ret */
+	}
+
+	return le16_to_cpu(v);
+
+}
+
+static int asix_write_medium_mode(struct usbnet *dev, u16 mode)
+{
+	int ret;
+
+	netdev_dbg(dev->net, "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)
+		netdev_err(dev->net, "Failed to write Medium Mode mode to 0x%04x: %02x\n",
+			   mode, ret);
+
+	return ret;
+}
+
+static int asix_write_gpio(struct usbnet *dev, u16 value, int sleep)
+{
+	int ret;
+
+	netdev_dbg(dev->net, "asix_write_gpio() - value = 0x%04x\n", value);
+	ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS, value, 0, 0, NULL);
+	if (ret < 0)
+		netdev_err(dev->net, "Failed to write GPIO value 0x%04x: %02x\n",
+			   value, ret);
+
+	if (sleep)
+		msleep(sleep);
+
+	return ret;
+}
+
+/*
+ * AX88772 & AX88178 have a 16-bit RX_CTL value
+ */
+static void asix_set_multicast(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	u16 rx_ctl = AX_DEFAULT_RX_CTL;
+
+	if (net->flags & IFF_PROMISC) {
+		rx_ctl |= AX_RX_CTL_PRO;
+	} else if (net->flags & IFF_ALLMULTI ||
+		   netdev_mc_count(net) > AX_MAX_MCAST) {
+		rx_ctl |= AX_RX_CTL_AMALL;
+	} else if (netdev_mc_empty(net)) {
+		/* just broadcast and directed */
+	} else {
+		/* We use the 20 byte dev->data
+		 * for our 8 byte filter buffer
+		 * to avoid allocating memory that
+		 * is tricky to free later */
+		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(ha, net) {
+			crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26;
+			data->multi_filter[crc_bits >> 3] |=
+			    1 << (crc_bits & 7);
+		}
+
+		asix_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0,
+				   AX_MCAST_FILTER_SIZE, data->multi_filter);
+
+		rx_ctl |= AX_RX_CTL_AM;
+	}
+
+	asix_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL);
+}
+
+static int asix_mdio_read(struct net_device *netdev, int phy_id, int loc)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	__le16 res;
+
+	mutex_lock(&dev->phy_mutex);
+	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);
+	mutex_unlock(&dev->phy_mutex);
+
+	netdev_dbg(dev->net, "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 net_device *netdev, int phy_id, int loc, int val)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	__le16 res = cpu_to_le16(val);
+
+	netdev_dbg(dev->net, "asix_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n",
+		   phy_id, loc, val);
+	mutex_lock(&dev->phy_mutex);
+	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);
+	mutex_unlock(&dev->phy_mutex);
+}
+
+/* Get the PHY Identifier from the PHYSID1 & PHYSID2 MII registers */
+static u32 asix_get_phyid(struct usbnet *dev)
+{
+	int phy_reg;
+	u32 phy_id;
+	int i;
+
+	/* Poll for the rare case the FW or phy isn't ready yet.  */
+	for (i = 0; i < 100; i++) {
+		phy_reg = asix_mdio_read(dev->net, dev->mii.phy_id, MII_PHYSID1);
+		if (phy_reg != 0 && phy_reg != 0xFFFF)
+			break;
+		mdelay(1);
+	}
+
+	if (phy_reg <= 0 || phy_reg == 0xFFFF)
+		return 0;
+
+	phy_id = (phy_reg & 0xffff) << 16;
+
+	phy_reg = asix_mdio_read(dev->net, dev->mii.phy_id, MII_PHYSID2);
+	if (phy_reg < 0)
+		return 0;
+
+	phy_id |= (phy_reg & 0xffff);
+
+	return phy_id;
+}
+
+static void
+asix_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
+{
+	struct usbnet *dev = netdev_priv(net);
+	u8 opt;
+
+	if (asix_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, 0, 0, 1, &opt) < 0) {
+		wolinfo->supported = 0;
+		wolinfo->wolopts = 0;
+		return;
+	}
+	wolinfo->supported = WAKE_PHY | WAKE_MAGIC;
+	wolinfo->wolopts = 0;
+	if (opt & AX_MONITOR_LINK)
+		wolinfo->wolopts |= WAKE_PHY;
+	if (opt & AX_MONITOR_MAGIC)
+		wolinfo->wolopts |= WAKE_MAGIC;
+}
+
+static int
+asix_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
+{
+	struct usbnet *dev = netdev_priv(net);
+	u8 opt = 0;
+
+	if (wolinfo->wolopts & WAKE_PHY)
+		opt |= AX_MONITOR_LINK;
+	if (wolinfo->wolopts & WAKE_MAGIC)
+		opt |= AX_MONITOR_MAGIC;
+
+	if (asix_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE,
+			      opt, 0, 0, NULL) < 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int asix_get_eeprom_len(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct asix_data *data = (struct asix_data *)&dev->data;
+
+	return data->eeprom_len;
+}
+
+static int asix_get_eeprom(struct net_device *net,
+			      struct ethtool_eeprom *eeprom, u8 *data)
+{
+	struct usbnet *dev = netdev_priv(net);
+	__le16 *ebuf = (__le16 *)data;
+	int i;
+
+	/* Crude hack to ensure that we don't overwrite memory
+	 * if an odd length is supplied
+	 */
+	if (eeprom->len % 2)
+		return -EINVAL;
+
+	eeprom->magic = AX_EEPROM_MAGIC;
+
+	/* ax8817x returns 2 bytes from eeprom on read */
+	for (i=0; i < eeprom->len / 2; i++) {
+		if (asix_read_cmd(dev, AX_CMD_READ_EEPROM,
+			eeprom->offset + i, 0, 2, &ebuf[i]) < 0)
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static void asix_get_drvinfo (struct net_device *net,
+				 struct ethtool_drvinfo *info)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct asix_data *data = (struct asix_data *)&dev->data;
+
+	/* 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);
+	info->eedump_len = data->eeprom_len;
+}
+
+static u32 asix_get_link(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	return mii_link_ok(&dev->mii);
+}
+
+static int asix_ioctl (struct net_device *net, struct ifreq *rq, int cmd)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
+}
+
+static int asix_set_mac_address(struct net_device *net, void *p)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	struct sockaddr *addr = p;
+
+	if (netif_running(net))
+		return -EBUSY;
+	if (!is_valid_ether_addr(addr->sa_data))
+		return -EADDRNOTAVAIL;
+
+	memcpy(net->dev_addr, addr->sa_data, ETH_ALEN);
+
+	/* We use the 20 byte dev->data
+	 * for our 6 byte mac buffer
+	 * to avoid allocating memory that
+	 * is tricky to free later */
+	memcpy(data->mac_addr, addr->sa_data, ETH_ALEN);
+	asix_write_cmd_async(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN,
+							data->mac_addr);
+
+	return 0;
+}
+
+/* We need to override some ethtool_ops so we require our
+   own structure so we don't interfere with other usbnet
+   devices that may be connected at the same time. */
+static const struct ethtool_ops ax88172_ethtool_ops = {
+	.get_drvinfo		= asix_get_drvinfo,
+	.get_link		= asix_get_link,
+	.get_msglevel		= usbnet_get_msglevel,
+	.set_msglevel		= usbnet_set_msglevel,
+	.get_wol		= asix_get_wol,
+	.set_wol		= asix_set_wol,
+	.get_eeprom_len		= asix_get_eeprom_len,
+	.get_eeprom		= asix_get_eeprom,
+	.get_settings		= usbnet_get_settings,
+	.set_settings		= usbnet_set_settings,
+	.nway_reset		= usbnet_nway_reset,
+};
+
+static void ax88172_set_multicast(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	u8 rx_ctl = 0x8c;
+
+	if (net->flags & IFF_PROMISC) {
+		rx_ctl |= 0x01;
+	} else if (net->flags & IFF_ALLMULTI ||
+		   netdev_mc_count(net) > AX_MAX_MCAST) {
+		rx_ctl |= 0x02;
+	} else if (netdev_mc_empty(net)) {
+		/* just broadcast and directed */
+	} else {
+		/* We use the 20 byte dev->data
+		 * for our 8 byte filter buffer
+		 * to avoid allocating memory that
+		 * is tricky to free later */
+		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(ha, net) {
+			crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26;
+			data->multi_filter[crc_bits >> 3] |=
+			    1 << (crc_bits & 7);
+		}
+
+		asix_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0,
+				   AX_MCAST_FILTER_SIZE, data->multi_filter);
+
+		rx_ctl |= 0x10;
+	}
+
+	asix_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL);
+}
+
+static int ax88172_link_reset(struct usbnet *dev)
+{
+	u8 mode;
+	struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+
+	mii_check_media(&dev->mii, 1, 1);
+	mii_ethtool_gset(&dev->mii, &ecmd);
+	mode = AX88172_MEDIUM_DEFAULT;
+
+	if (ecmd.duplex != DUPLEX_FULL)
+		mode |= ~AX88172_MEDIUM_FD;
+
+	netdev_dbg(dev->net, "ax88172_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n",
+		   ethtool_cmd_speed(&ecmd), ecmd.duplex, mode);
+
+	asix_write_medium_mode(dev, mode);
+
+	return 0;
+}
+
+static const struct net_device_ops ax88172_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= asix_ioctl,
+	.ndo_set_rx_mode	= ax88172_set_multicast,
+};
+
+static int ax88172_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int ret = 0;
+	u8 buf[ETH_ALEN];
+	int i;
+	unsigned long gpio_bits = dev->driver_info->data;
+	struct asix_data *data = (struct asix_data *)&dev->data;
+
+	data->eeprom_len = AX88172_EEPROM_LEN;
+
+	usbnet_get_endpoints(dev,intf);
+
+	/* Toggle the GPIOs in a manufacturer/model specific way */
+	for (i = 2; i >= 0; i--) {
+		ret = asix_write_cmd(dev, AX_CMD_WRITE_GPIOS,
+				(gpio_bits >> (i * 8)) & 0xff, 0, 0, NULL);
+		if (ret < 0)
+			goto out;
+		msleep(5);
+	}
+
+	ret = asix_write_rx_ctl(dev, 0x80);
+	if (ret < 0)
+		goto out;
+
+	/* Get the MAC address */
+	ret = asix_read_cmd(dev, AX88172_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf);
+	if (ret < 0) {
+		dbg("read AX_CMD_READ_NODE_ID failed: %d", ret);
+		goto out;
+	}
+	memcpy(dev->net->dev_addr, buf, ETH_ALEN);
+
+	/* Initialize MII structure */
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = asix_mdio_read;
+	dev->mii.mdio_write = asix_mdio_write;
+	dev->mii.phy_id_mask = 0x3f;
+	dev->mii.reg_num_mask = 0x1f;
+	dev->mii.phy_id = asix_get_phy_addr(dev);
+
+	dev->net->netdev_ops = &ax88172_netdev_ops;
+	dev->net->ethtool_ops = &ax88172_ethtool_ops;
+
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+		ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP);
+	mii_nway_restart(&dev->mii);
+
+	return 0;
+
+out:
+	return ret;
+}
+
+static const struct ethtool_ops ax88772_ethtool_ops = {
+	.get_drvinfo		= asix_get_drvinfo,
+	.get_link		= asix_get_link,
+	.get_msglevel		= usbnet_get_msglevel,
+	.set_msglevel		= usbnet_set_msglevel,
+	.get_wol		= asix_get_wol,
+	.set_wol		= asix_set_wol,
+	.get_eeprom_len		= asix_get_eeprom_len,
+	.get_eeprom		= asix_get_eeprom,
+	.get_settings		= usbnet_get_settings,
+	.set_settings		= usbnet_set_settings,
+	.nway_reset		= usbnet_nway_reset,
+};
+
+static int ax88772_link_reset(struct usbnet *dev)
+{
+	u16 mode;
+	struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+
+	mii_check_media(&dev->mii, 1, 1);
+	mii_ethtool_gset(&dev->mii, &ecmd);
+	mode = AX88772_MEDIUM_DEFAULT;
+
+	if (ethtool_cmd_speed(&ecmd) != SPEED_100)
+		mode &= ~AX_MEDIUM_PS;
+
+	if (ecmd.duplex != DUPLEX_FULL)
+		mode &= ~AX_MEDIUM_FD;
+
+	netdev_dbg(dev->net, "ax88772_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n",
+		   ethtool_cmd_speed(&ecmd), ecmd.duplex, mode);
+
+	asix_write_medium_mode(dev, mode);
+
+	return 0;
+}
+
+static int ax88772_reset(struct usbnet *dev)
+{
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	int ret, embd_phy;
+	u16 rx_ctl;
+
+	ret = asix_write_gpio(dev,
+			AX_GPIO_RSE | AX_GPIO_GPO_2 | AX_GPIO_GPO2EN, 5);
+	if (ret < 0)
+		goto out;
+
+	embd_phy = ((asix_get_phy_addr(dev) & 0x1f) == 0x10 ? 1 : 0);
+
+	ret = asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT, embd_phy, 0, 0, NULL);
+	if (ret < 0) {
+		dbg("Select PHY #1 failed: %d", ret);
+		goto out;
+	}
+
+	ret = asix_sw_reset(dev, AX_SWRESET_IPPD | AX_SWRESET_PRL);
+	if (ret < 0)
+		goto out;
+
+	msleep(150);
+
+	ret = asix_sw_reset(dev, AX_SWRESET_CLEAR);
+	if (ret < 0)
+		goto out;
+
+	msleep(150);
+
+	if (embd_phy) {
+		ret = asix_sw_reset(dev, AX_SWRESET_IPRL);
+		if (ret < 0)
+			goto out;
+	} else {
+		ret = asix_sw_reset(dev, AX_SWRESET_PRTE);
+		if (ret < 0)
+			goto out;
+	}
+
+	msleep(150);
+	rx_ctl = asix_read_rx_ctl(dev);
+	dbg("RX_CTL is 0x%04x after software reset", rx_ctl);
+	ret = asix_write_rx_ctl(dev, 0x0000);
+	if (ret < 0)
+		goto out;
+
+	rx_ctl = asix_read_rx_ctl(dev);
+	dbg("RX_CTL is 0x%04x setting to 0x0000", rx_ctl);
+
+	ret = asix_sw_reset(dev, AX_SWRESET_PRL);
+	if (ret < 0)
+		goto out;
+
+	msleep(150);
+
+	ret = asix_sw_reset(dev, AX_SWRESET_IPRL | AX_SWRESET_PRL);
+	if (ret < 0)
+		goto out;
+
+	msleep(150);
+
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+			ADVERTISE_ALL | ADVERTISE_CSMA);
+	mii_nway_restart(&dev->mii);
+
+	ret = asix_write_medium_mode(dev, AX88772_MEDIUM_DEFAULT);
+	if (ret < 0)
+		goto out;
+
+	ret = asix_write_cmd(dev, AX_CMD_WRITE_IPG0,
+				AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT,
+				AX88772_IPG2_DEFAULT, 0, NULL);
+	if (ret < 0) {
+		dbg("Write IPG,IPG1,IPG2 failed: %d", ret);
+		goto out;
+	}
+
+	/* Rewrite MAC address */
+	memcpy(data->mac_addr, dev->net->dev_addr, ETH_ALEN);
+	ret = asix_write_cmd(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN,
+							data->mac_addr);
+	if (ret < 0)
+		goto out;
+
+	/* Set RX_CTL to default values with 2k buffer, and enable cactus */
+	ret = asix_write_rx_ctl(dev, AX_DEFAULT_RX_CTL);
+	if (ret < 0)
+		goto out;
+
+	rx_ctl = asix_read_rx_ctl(dev);
+	dbg("RX_CTL is 0x%04x after all initializations", rx_ctl);
+
+	rx_ctl = asix_read_medium_status(dev);
+	dbg("Medium Status is 0x%04x after all initializations", rx_ctl);
+
+	return 0;
+
+out:
+	return ret;
+
+}
+
+static const struct net_device_ops ax88772_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_set_mac_address 	= asix_set_mac_address,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl		= asix_ioctl,
+	.ndo_set_rx_mode        = asix_set_multicast,
+};
+
+static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int ret, embd_phy;
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	u8 buf[ETH_ALEN];
+	u32 phyid;
+
+	data->eeprom_len = AX88772_EEPROM_LEN;
+
+	usbnet_get_endpoints(dev,intf);
+
+	/* Get the MAC address */
+	ret = asix_read_cmd(dev, AX_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf);
+	if (ret < 0) {
+		dbg("Failed to read MAC address: %d", ret);
+		return ret;
+	}
+	memcpy(dev->net->dev_addr, buf, ETH_ALEN);
+
+	/* Initialize MII structure */
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = asix_mdio_read;
+	dev->mii.mdio_write = asix_mdio_write;
+	dev->mii.phy_id_mask = 0x1f;
+	dev->mii.reg_num_mask = 0x1f;
+	dev->mii.phy_id = asix_get_phy_addr(dev);
+
+	dev->net->netdev_ops = &ax88772_netdev_ops;
+	dev->net->ethtool_ops = &ax88772_ethtool_ops;
+
+	embd_phy = ((dev->mii.phy_id & 0x1f) == 0x10 ? 1 : 0);
+
+	/* Reset the PHY to normal operation mode */
+	ret = asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT, embd_phy, 0, 0, NULL);
+	if (ret < 0) {
+		dbg("Select PHY #1 failed: %d", ret);
+		return ret;
+	}
+
+	ret = asix_sw_reset(dev, AX_SWRESET_IPPD | AX_SWRESET_PRL);
+	if (ret < 0)
+		return ret;
+
+	msleep(150);
+
+	ret = asix_sw_reset(dev, AX_SWRESET_CLEAR);
+	if (ret < 0)
+		return ret;
+
+	msleep(150);
+
+	ret = asix_sw_reset(dev, embd_phy ? AX_SWRESET_IPRL : AX_SWRESET_PRTE);
+
+	/* Read PHYID register *AFTER* the PHY was reset properly */
+	phyid = asix_get_phyid(dev);
+	dbg("PHYID=0x%08x", phyid);
+
+	/* Asix framing packs multiple eth frames into a 2K usb bulk transfer */
+	if (dev->driver_info->flags & FLAG_FRAMING_AX) {
+		/* hard_mtu  is still the default - the device does not support
+		   jumbo eth frames */
+		dev->rx_urb_size = 2048;
+	}
+
+	dev->driver_priv = kzalloc(sizeof(struct asix_common_private),
+				   GFP_KERNEL);
+	if (!dev->driver_priv)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void ax88772_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	kfree(dev->driver_priv);
+}
+
+static const struct ethtool_ops ax88178_ethtool_ops = {
+	.get_drvinfo		= asix_get_drvinfo,
+	.get_link		= asix_get_link,
+	.get_msglevel		= usbnet_get_msglevel,
+	.set_msglevel		= usbnet_set_msglevel,
+	.get_wol		= asix_get_wol,
+	.set_wol		= asix_set_wol,
+	.get_eeprom_len		= asix_get_eeprom_len,
+	.get_eeprom		= asix_get_eeprom,
+	.get_settings		= usbnet_get_settings,
+	.set_settings		= usbnet_set_settings,
+	.nway_reset		= usbnet_nway_reset,
+};
+
+static int marvell_phy_init(struct usbnet *dev)
+{
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	u16 reg;
+
+	netdev_dbg(dev->net, "marvell_phy_init()\n");
+
+	reg = asix_mdio_read(dev->net, dev->mii.phy_id, MII_MARVELL_STATUS);
+	netdev_dbg(dev->net, "MII_MARVELL_STATUS = 0x%04x\n", reg);
+
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_MARVELL_CTRL,
+			MARVELL_CTRL_RXDELAY | MARVELL_CTRL_TXDELAY);
+
+	if (data->ledmode) {
+		reg = asix_mdio_read(dev->net, dev->mii.phy_id,
+			MII_MARVELL_LED_CTRL);
+		netdev_dbg(dev->net, "MII_MARVELL_LED_CTRL (1) = 0x%04x\n", reg);
+
+		reg &= 0xf8ff;
+		reg |= (1 + 0x0100);
+		asix_mdio_write(dev->net, dev->mii.phy_id,
+			MII_MARVELL_LED_CTRL, reg);
+
+		reg = asix_mdio_read(dev->net, dev->mii.phy_id,
+			MII_MARVELL_LED_CTRL);
+		netdev_dbg(dev->net, "MII_MARVELL_LED_CTRL (2) = 0x%04x\n", reg);
+		reg &= 0xfc0f;
+	}
+
+	return 0;
+}
+
+static int rtl8211cl_phy_init(struct usbnet *dev)
+{
+	struct asix_data *data = (struct asix_data *)&dev->data;
+
+	netdev_dbg(dev->net, "rtl8211cl_phy_init()\n");
+
+	asix_mdio_write (dev->net, dev->mii.phy_id, 0x1f, 0x0005);
+	asix_mdio_write (dev->net, dev->mii.phy_id, 0x0c, 0);
+	asix_mdio_write (dev->net, dev->mii.phy_id, 0x01,
+		asix_mdio_read (dev->net, dev->mii.phy_id, 0x01) | 0x0080);
+	asix_mdio_write (dev->net, dev->mii.phy_id, 0x1f, 0);
+
+	if (data->ledmode == 12) {
+		asix_mdio_write (dev->net, dev->mii.phy_id, 0x1f, 0x0002);
+		asix_mdio_write (dev->net, dev->mii.phy_id, 0x1a, 0x00cb);
+		asix_mdio_write (dev->net, dev->mii.phy_id, 0x1f, 0);
+	}
+
+	return 0;
+}
+
+static int marvell_led_status(struct usbnet *dev, u16 speed)
+{
+	u16 reg = asix_mdio_read(dev->net, dev->mii.phy_id, MARVELL_LED_MANUAL);
+
+	netdev_dbg(dev->net, "marvell_led_status() read 0x%04x\n", reg);
+
+	/* Clear out the center LED bits - 0x03F0 */
+	reg &= 0xfc0f;
+
+	switch (speed) {
+		case SPEED_1000:
+			reg |= 0x03e0;
+			break;
+		case SPEED_100:
+			reg |= 0x03b0;
+			break;
+		default:
+			reg |= 0x02f0;
+	}
+
+	netdev_dbg(dev->net, "marvell_led_status() writing 0x%04x\n", reg);
+	asix_mdio_write(dev->net, dev->mii.phy_id, MARVELL_LED_MANUAL, reg);
+
+	return 0;
+}
+
+static int ax88178_reset(struct usbnet *dev)
+{
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	int ret;
+	__le16 eeprom;
+	u8 status;
+	int gpio0 = 0;
+	u32 phyid;
+
+	asix_read_cmd(dev, AX_CMD_READ_GPIOS, 0, 0, 1, &status);
+	dbg("GPIO Status: 0x%04x", status);
+
+	asix_write_cmd(dev, AX_CMD_WRITE_ENABLE, 0, 0, 0, NULL);
+	asix_read_cmd(dev, AX_CMD_READ_EEPROM, 0x0017, 0, 2, &eeprom);
+	asix_write_cmd(dev, AX_CMD_WRITE_DISABLE, 0, 0, 0, NULL);
+
+	dbg("EEPROM index 0x17 is 0x%04x", eeprom);
+
+	if (eeprom == cpu_to_le16(0xffff)) {
+		data->phymode = PHY_MODE_MARVELL;
+		data->ledmode = 0;
+		gpio0 = 1;
+	} else {
+		data->phymode = le16_to_cpu(eeprom) & 0x7F;
+		data->ledmode = le16_to_cpu(eeprom) >> 8;
+		gpio0 = (le16_to_cpu(eeprom) & 0x80) ? 0 : 1;
+	}
+	dbg("GPIO0: %d, PhyMode: %d", gpio0, data->phymode);
+
+	/* Power up external GigaPHY through AX88178 GPIO pin */
+	asix_write_gpio(dev, AX_GPIO_RSE | AX_GPIO_GPO_1 | AX_GPIO_GPO1EN, 40);
+	if ((le16_to_cpu(eeprom) >> 8) != 1) {
+		asix_write_gpio(dev, 0x003c, 30);
+		asix_write_gpio(dev, 0x001c, 300);
+		asix_write_gpio(dev, 0x003c, 30);
+	} else {
+		dbg("gpio phymode == 1 path");
+		asix_write_gpio(dev, AX_GPIO_GPO1EN, 30);
+		asix_write_gpio(dev, AX_GPIO_GPO1EN | AX_GPIO_GPO_1, 30);
+	}
+
+	/* Read PHYID register *AFTER* powering up PHY */
+	phyid = asix_get_phyid(dev);
+	dbg("PHYID=0x%08x", phyid);
+
+	/* Set AX88178 to enable MII/GMII/RGMII interface for external PHY */
+	asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT, 0, 0, 0, NULL);
+
+	asix_sw_reset(dev, 0);
+	msleep(150);
+
+	asix_sw_reset(dev, AX_SWRESET_PRL | AX_SWRESET_IPPD);
+	msleep(150);
+
+	asix_write_rx_ctl(dev, 0);
+
+	if (data->phymode == PHY_MODE_MARVELL) {
+		marvell_phy_init(dev);
+		msleep(60);
+	} else if (data->phymode == PHY_MODE_RTL8211CL)
+		rtl8211cl_phy_init(dev);
+
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR,
+			BMCR_RESET | BMCR_ANENABLE);
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+			ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP);
+	asix_mdio_write(dev->net, dev->mii.phy_id, MII_CTRL1000,
+			ADVERTISE_1000FULL);
+
+	mii_nway_restart(&dev->mii);
+
+	ret = asix_write_medium_mode(dev, AX88178_MEDIUM_DEFAULT);
+	if (ret < 0)
+		return ret;
+
+	/* Rewrite MAC address */
+	memcpy(data->mac_addr, dev->net->dev_addr, ETH_ALEN);
+	ret = asix_write_cmd(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN,
+							data->mac_addr);
+	if (ret < 0)
+		return ret;
+
+	ret = asix_write_rx_ctl(dev, AX_DEFAULT_RX_CTL);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ax88178_link_reset(struct usbnet *dev)
+{
+	u16 mode;
+	struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+	struct asix_data *data = (struct asix_data *)&dev->data;
+	u32 speed;
+
+	netdev_dbg(dev->net, "ax88178_link_reset()\n");
+
+	mii_check_media(&dev->mii, 1, 1);
+	mii_ethtool_gset(&dev->mii, &ecmd);
+	mode = AX88178_MEDIUM_DEFAULT;
+	speed = ethtool_cmd_speed(&ecmd);
+
+	if (speed == SPEED_1000)
+		mode |= AX_MEDIUM_GM;
+	else if (speed == SPEED_100)
+		mode |= AX_MEDIUM_PS;
+	else
+		mode &= ~(AX_MEDIUM_PS | AX_MEDIUM_GM);
+
+	mode |= AX_MEDIUM_ENCK;
+
+	if (ecmd.duplex == DUPLEX_FULL)
+		mode |= AX_MEDIUM_FD;
+	else
+		mode &= ~AX_MEDIUM_FD;
+
+	netdev_dbg(dev->net, "ax88178_link_reset() speed: %u duplex: %d setting mode to 0x%04x\n",
+		   speed, ecmd.duplex, mode);
+
+	asix_write_medium_mode(dev, mode);
+
+	if (data->phymode == PHY_MODE_MARVELL && data->ledmode)
+		marvell_led_status(dev, speed);
+
+	return 0;
+}
+
+static void ax88178_set_mfb(struct usbnet *dev)
+{
+	u16 mfb = AX_RX_CTL_MFB_16384;
+	u16 rxctl;
+	u16 medium;
+	int old_rx_urb_size = dev->rx_urb_size;
+
+	if (dev->hard_mtu < 2048) {
+		dev->rx_urb_size = 2048;
+		mfb = AX_RX_CTL_MFB_2048;
+	} else if (dev->hard_mtu < 4096) {
+		dev->rx_urb_size = 4096;
+		mfb = AX_RX_CTL_MFB_4096;
+	} else if (dev->hard_mtu < 8192) {
+		dev->rx_urb_size = 8192;
+		mfb = AX_RX_CTL_MFB_8192;
+	} else if (dev->hard_mtu < 16384) {
+		dev->rx_urb_size = 16384;
+		mfb = AX_RX_CTL_MFB_16384;
+	}
+
+	rxctl = asix_read_rx_ctl(dev);
+	asix_write_rx_ctl(dev, (rxctl & ~AX_RX_CTL_MFB_16384) | mfb);
+
+	medium = asix_read_medium_status(dev);
+	if (dev->net->mtu > 1500)
+		medium |= AX_MEDIUM_JFE;
+	else
+		medium &= ~AX_MEDIUM_JFE;
+	asix_write_medium_mode(dev, medium);
+
+	if (dev->rx_urb_size > old_rx_urb_size)
+		usbnet_unlink_rx_urbs(dev);
+}
+
+static int ax88178_change_mtu(struct net_device *net, int new_mtu)
+{
+	struct usbnet *dev = netdev_priv(net);
+	int ll_mtu = new_mtu + net->hard_header_len + 4;
+
+	netdev_dbg(dev->net, "ax88178_change_mtu() new_mtu=%d\n", new_mtu);
+
+	if (new_mtu <= 0 || ll_mtu > 16384)
+		return -EINVAL;
+
+	if ((ll_mtu % dev->maxpacket) == 0)
+		return -EDOM;
+
+	net->mtu = new_mtu;
+	dev->hard_mtu = net->mtu + net->hard_header_len;
+	ax88178_set_mfb(dev);
+
+	return 0;
+}
+
+static const struct net_device_ops ax88178_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_set_mac_address 	= asix_set_mac_address,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_set_rx_mode	= asix_set_multicast,
+	.ndo_do_ioctl 		= asix_ioctl,
+	.ndo_change_mtu 	= ax88178_change_mtu,
+};
+
+static int ax88178_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int ret;
+	u8 buf[ETH_ALEN];
+	struct asix_data *data = (struct asix_data *)&dev->data;
+
+	data->eeprom_len = AX88772_EEPROM_LEN;
+
+	usbnet_get_endpoints(dev,intf);
+
+	/* Get the MAC address */
+	ret = asix_read_cmd(dev, AX_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf);
+	if (ret < 0) {
+		dbg("Failed to read MAC address: %d", ret);
+		return ret;
+	}
+	memcpy(dev->net->dev_addr, buf, ETH_ALEN);
+
+	/* Initialize MII structure */
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = asix_mdio_read;
+	dev->mii.mdio_write = asix_mdio_write;
+	dev->mii.phy_id_mask = 0x1f;
+	dev->mii.reg_num_mask = 0xff;
+	dev->mii.supports_gmii = 1;
+	dev->mii.phy_id = asix_get_phy_addr(dev);
+
+	dev->net->netdev_ops = &ax88178_netdev_ops;
+	dev->net->ethtool_ops = &ax88178_ethtool_ops;
+
+	/* Blink LEDS so users know driver saw dongle */
+	asix_sw_reset(dev, 0);
+	msleep(150);
+
+	asix_sw_reset(dev, AX_SWRESET_PRL | AX_SWRESET_IPPD);
+	msleep(150);
+
+	/* Asix framing packs multiple eth frames into a 2K usb bulk transfer */
+	if (dev->driver_info->flags & FLAG_FRAMING_AX) {
+		/* hard_mtu  is still the default - the device does not support
+		   jumbo eth frames */
+		dev->rx_urb_size = 2048;
+	}
+
+	dev->driver_priv = kzalloc(sizeof(struct asix_common_private),
+				   GFP_KERNEL);
+	if (!dev->driver_priv)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static const struct driver_info ax8817x_info = {
+	.description = "ASIX AX8817x USB 2.0 Ethernet",
+	.bind = ax88172_bind,
+	.status = asix_status,
+	.link_reset = ax88172_link_reset,
+	.reset = ax88172_link_reset,
+	.flags =  FLAG_ETHER | FLAG_LINK_INTR,
+	.data = 0x00130103,
+};
+
+static const struct driver_info dlink_dub_e100_info = {
+	.description = "DLink DUB-E100 USB Ethernet",
+	.bind = ax88172_bind,
+	.status = asix_status,
+	.link_reset = ax88172_link_reset,
+	.reset = ax88172_link_reset,
+	.flags =  FLAG_ETHER | FLAG_LINK_INTR,
+	.data = 0x009f9d9f,
+};
+
+static const struct driver_info netgear_fa120_info = {
+	.description = "Netgear FA-120 USB Ethernet",
+	.bind = ax88172_bind,
+	.status = asix_status,
+	.link_reset = ax88172_link_reset,
+	.reset = ax88172_link_reset,
+	.flags =  FLAG_ETHER | FLAG_LINK_INTR,
+	.data = 0x00130103,
+};
+
+static const struct driver_info hawking_uf200_info = {
+	.description = "Hawking UF200 USB Ethernet",
+	.bind = ax88172_bind,
+	.status = asix_status,
+	.link_reset = ax88172_link_reset,
+	.reset = ax88172_link_reset,
+	.flags =  FLAG_ETHER | FLAG_LINK_INTR,
+	.data = 0x001f1d1f,
+};
+
+static const struct driver_info ax88772_info = {
+	.description = "ASIX AX88772 USB 2.0 Ethernet",
+	.bind = ax88772_bind,
+	.unbind = ax88772_unbind,
+	.status = asix_status,
+	.link_reset = ax88772_link_reset,
+	.reset = ax88772_reset,
+	.flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR |
+		 FLAG_MULTI_PACKET,
+	.rx_fixup = asix_rx_fixup_common,
+	.tx_fixup = asix_tx_fixup,
+};
+
+static const struct driver_info ax88178_info = {
+	.description = "ASIX AX88178 USB 2.0 Ethernet",
+	.bind = ax88178_bind,
+	.unbind = ax88772_unbind,
+	.status = asix_status,
+	.link_reset = ax88178_link_reset,
+	.reset = ax88178_reset,
+	.flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR |
+		 FLAG_MULTI_PACKET,
+	.rx_fixup = asix_rx_fixup_common,
+	.tx_fixup = asix_tx_fixup,
+};
+
+static const struct usb_device_id	products [] = {
+{
+	// Linksys USB200M
+	USB_DEVICE (0x077b, 0x2226),
+	.driver_info =	(unsigned long) &ax8817x_info,
+}, {
+	// Netgear FA120
+	USB_DEVICE (0x0846, 0x1040),
+	.driver_info =  (unsigned long) &netgear_fa120_info,
+}, {
+	// DLink DUB-E100
+	USB_DEVICE (0x2001, 0x1a00),
+	.driver_info =  (unsigned long) &dlink_dub_e100_info,
+}, {
+	// Intellinet, ST Lab USB Ethernet
+	USB_DEVICE (0x0b95, 0x1720),
+	.driver_info =  (unsigned long) &ax8817x_info,
+}, {
+	// Hawking UF200, TrendNet TU2-ET100
+	USB_DEVICE (0x07b8, 0x420a),
+	.driver_info =  (unsigned long) &hawking_uf200_info,
+}, {
+	// Billionton Systems, USB2AR
+	USB_DEVICE (0x08dd, 0x90ff),
+	.driver_info =  (unsigned long) &ax8817x_info,
+}, {
+	// ATEN UC210T
+	USB_DEVICE (0x0557, 0x2009),
+	.driver_info =  (unsigned long) &ax8817x_info,
+}, {
+	// Buffalo LUA-U2-KTX
+	USB_DEVICE (0x0411, 0x003d),
+	.driver_info =  (unsigned long) &ax8817x_info,
+}, {
+	// Buffalo LUA-U2-GT 10/100/1000
+	USB_DEVICE (0x0411, 0x006e),
+	.driver_info =  (unsigned long) &ax88178_info,
+}, {
+	// Sitecom LN-029 "USB 2.0 10/100 Ethernet adapter"
+	USB_DEVICE (0x6189, 0x182d),
+	.driver_info =  (unsigned long) &ax8817x_info,
+}, {
+	// Sitecom LN-031 "USB 2.0 10/100/1000 Ethernet adapter"
+	USB_DEVICE (0x0df6, 0x0056),
+	.driver_info =  (unsigned long) &ax88178_info,
+}, {
+	// corega FEther USB2-TX
+	USB_DEVICE (0x07aa, 0x0017),
+	.driver_info =  (unsigned long) &ax8817x_info,
+}, {
+	// Surecom EP-1427X-2
+	USB_DEVICE (0x1189, 0x0893),
+	.driver_info = (unsigned long) &ax8817x_info,
+}, {
+	// goodway corp usb gwusb2e
+	USB_DEVICE (0x1631, 0x6200),
+	.driver_info = (unsigned long) &ax8817x_info,
+}, {
+	// JVC MP-PRX1 Port Replicator
+	USB_DEVICE (0x04f1, 0x3008),
+	.driver_info = (unsigned long) &ax8817x_info,
+}, {
+	// ASIX AX88772B 10/100
+	USB_DEVICE (0x0b95, 0x772b),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// ASIX AX88772 10/100
+	USB_DEVICE (0x0b95, 0x7720),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// ASIX AX88178 10/100/1000
+	USB_DEVICE (0x0b95, 0x1780),
+	.driver_info = (unsigned long) &ax88178_info,
+}, {
+	// Logitec LAN-GTJ/U2A
+	USB_DEVICE (0x0789, 0x0160),
+	.driver_info = (unsigned long) &ax88178_info,
+}, {
+	// Linksys USB200M Rev 2
+	USB_DEVICE (0x13b1, 0x0018),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// 0Q0 cable ethernet
+	USB_DEVICE (0x1557, 0x7720),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// DLink DUB-E100 H/W Ver B1
+	USB_DEVICE (0x07d1, 0x3c05),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// DLink DUB-E100 H/W Ver B1 Alternate
+	USB_DEVICE (0x2001, 0x3c05),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+       // DLink DUB-E100 H/W Ver C1
+       USB_DEVICE (0x2001, 0x1a02),
+       .driver_info = (unsigned long) &ax88772_info,
+}, {
+	// Linksys USB1000
+	USB_DEVICE (0x1737, 0x0039),
+	.driver_info = (unsigned long) &ax88178_info,
+}, {
+	// IO-DATA ETG-US2
+	USB_DEVICE (0x04bb, 0x0930),
+	.driver_info = (unsigned long) &ax88178_info,
+}, {
+	// Belkin F5D5055
+	USB_DEVICE(0x050d, 0x5055),
+	.driver_info = (unsigned long) &ax88178_info,
+}, {
+	// Apple USB Ethernet Adapter
+	USB_DEVICE(0x05ac, 0x1402),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// Cables-to-Go USB Ethernet Adapter
+	USB_DEVICE(0x0b95, 0x772a),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// ABOCOM for pci
+	USB_DEVICE(0x14ea, 0xab11),
+	.driver_info = (unsigned long) &ax88178_info,
+}, {
+	// ASIX 88772a
+	USB_DEVICE(0x0db0, 0xa877),
+	.driver_info = (unsigned long) &ax88772_info,
+}, {
+	// Asus USB Ethernet Adapter
+	USB_DEVICE (0x0b95, 0x7e2b),
+	.driver_info = (unsigned long) &ax88772_info,
+},
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver asix_driver = {
+	.name =		DRIVER_NAME,
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+	.disconnect =	usbnet_disconnect,
+	.supports_autosuspend = 1,
+};
+
+module_usb_driver(asix_driver);
+
+MODULE_AUTHOR("David Hollis");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_DESCRIPTION("ASIX AX8817X based USB 2.0 Ethernet Devices");
+MODULE_LICENSE("GPL");
+
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/catc.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/catc.c
new file mode 100644
index 0000000..182cfb4
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/catc.c
@@ -0,0 +1,952 @@
+/*
+ *  Copyright (c) 2001 Vojtech Pavlik
+ *
+ *  CATC EL1210A NetMate USB Ethernet driver
+ *
+ *  Sponsored by SuSE
+ *
+ *  Based on the work of
+ *		Donald Becker
+ * 
+ *  Old chipset support added by Simon Evans <spse@secret.org.uk> 2002
+ *    - adds support for Belkin F5U011
+ */
+
+/*
+ * 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
+ * 
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@suse.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/ethtool.h>
+#include <linux/crc32.h>
+#include <linux/bitops.h>
+#include <linux/gfp.h>
+#include <asm/uaccess.h>
+
+#undef DEBUG
+
+#include <linux/usb.h>
+
+/*
+ * Version information.
+ */
+
+#define DRIVER_VERSION "v2.8"
+#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@suse.cz>"
+#define DRIVER_DESC "CATC EL1210A NetMate USB Ethernet driver"
+#define SHORT_DRIVER_DESC "EL1210A NetMate USB Ethernet"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+static const char driver_name[] = "catc";
+
+/*
+ * Some defines.
+ */ 
+
+#define STATS_UPDATE		(HZ)	/* Time between stats updates */
+#define TX_TIMEOUT		(5*HZ)	/* Max time the queue can be stopped */
+#define PKT_SZ			1536	/* Max Ethernet packet size */
+#define RX_MAX_BURST		15	/* Max packets per rx buffer (> 0, < 16) */
+#define TX_MAX_BURST		15	/* Max full sized packets per tx buffer (> 0) */
+#define CTRL_QUEUE		16	/* Max control requests in flight (power of two) */
+#define RX_PKT_SZ		1600	/* Max size of receive packet for F5U011 */
+
+/*
+ * Control requests.
+ */
+
+enum control_requests {
+	ReadMem =	0xf1,
+	GetMac =	0xf2,
+	Reset =		0xf4,
+	SetMac =	0xf5,
+	SetRxMode =     0xf5,  /* F5U011 only */
+	WriteROM =	0xf8,
+	SetReg =	0xfa,
+	GetReg =	0xfb,
+	WriteMem =	0xfc,
+	ReadROM =	0xfd,
+};
+
+/*
+ * Registers.
+ */
+
+enum register_offsets {
+	TxBufCount =	0x20,
+	RxBufCount =	0x21,
+	OpModes =	0x22,
+	TxQed =		0x23,
+	RxQed =		0x24,
+	MaxBurst =	0x25,
+	RxUnit =	0x60,
+	EthStatus =	0x61,
+	StationAddr0 =	0x67,
+	EthStats =	0x69,
+	LEDCtrl =	0x81,
+};
+
+enum eth_stats {
+	TxSingleColl =	0x00,
+        TxMultiColl =	0x02,
+        TxExcessColl =	0x04,
+        RxFramErr =	0x06,
+};
+
+enum op_mode_bits {
+	Op3MemWaits =	0x03,
+	OpLenInclude =	0x08,
+	OpRxMerge =	0x10,
+	OpTxMerge =	0x20,
+	OpWin95bugfix =	0x40,
+	OpLoopback =	0x80,
+};
+
+enum rx_filter_bits {
+	RxEnable =	0x01,
+	RxPolarity =	0x02,
+	RxForceOK =	0x04,
+	RxMultiCast =	0x08,
+	RxPromisc =	0x10,
+	AltRxPromisc =  0x20, /* F5U011 uses different bit */
+};
+
+enum led_values {
+	LEDFast = 	0x01,
+	LEDSlow =	0x02,
+	LEDFlash =	0x03,
+	LEDPulse =	0x04,
+	LEDLink =	0x08,
+};
+
+enum link_status {
+	LinkNoChange = 0,
+	LinkGood     = 1,
+	LinkBad      = 2
+};
+
+/*
+ * The catc struct.
+ */
+
+#define CTRL_RUNNING	0
+#define RX_RUNNING	1
+#define TX_RUNNING	2
+
+struct catc {
+	struct net_device *netdev;
+	struct usb_device *usbdev;
+
+	unsigned long flags;
+
+	unsigned int tx_ptr, tx_idx;
+	unsigned int ctrl_head, ctrl_tail;
+	spinlock_t tx_lock, ctrl_lock;
+
+	u8 tx_buf[2][TX_MAX_BURST * (PKT_SZ + 2)];
+	u8 rx_buf[RX_MAX_BURST * (PKT_SZ + 2)];
+	u8 irq_buf[2];
+	u8 ctrl_buf[64];
+	struct usb_ctrlrequest ctrl_dr;
+
+	struct timer_list timer;
+	u8 stats_buf[8];
+	u16 stats_vals[4];
+	unsigned long last_stats;
+
+	u8 multicast[64];
+
+	struct ctrl_queue {
+		u8 dir;
+		u8 request;
+		u16 value;
+		u16 index;
+		void *buf;
+		int len;
+		void (*callback)(struct catc *catc, struct ctrl_queue *q);
+	} ctrl_queue[CTRL_QUEUE];
+
+	struct urb *tx_urb, *rx_urb, *irq_urb, *ctrl_urb;
+
+	u8 is_f5u011;	/* Set if device is an F5U011 */
+	u8 rxmode[2];	/* Used for F5U011 */
+	atomic_t recq_sz; /* Used for F5U011 - counter of waiting rx packets */
+};
+
+/*
+ * Useful macros.
+ */
+
+#define catc_get_mac(catc, mac)				catc_ctrl_msg(catc, USB_DIR_IN,  GetMac, 0, 0, mac,  6)
+#define catc_reset(catc)				catc_ctrl_msg(catc, USB_DIR_OUT, Reset, 0, 0, NULL, 0)
+#define catc_set_reg(catc, reg, val)			catc_ctrl_msg(catc, USB_DIR_OUT, SetReg, val, reg, NULL, 0)
+#define catc_get_reg(catc, reg, buf)			catc_ctrl_msg(catc, USB_DIR_IN,  GetReg, 0, reg, buf, 1)
+#define catc_write_mem(catc, addr, buf, size)		catc_ctrl_msg(catc, USB_DIR_OUT, WriteMem, 0, addr, buf, size)
+#define catc_read_mem(catc, addr, buf, size)		catc_ctrl_msg(catc, USB_DIR_IN,  ReadMem, 0, addr, buf, size)
+
+#define f5u011_rxmode(catc, rxmode)			catc_ctrl_msg(catc, USB_DIR_OUT, SetRxMode, 0, 1, rxmode, 2)
+#define f5u011_rxmode_async(catc, rxmode)		catc_ctrl_async(catc, USB_DIR_OUT, SetRxMode, 0, 1, &rxmode, 2, NULL)
+#define f5u011_mchash_async(catc, hash)			catc_ctrl_async(catc, USB_DIR_OUT, SetRxMode, 0, 2, &hash, 8, NULL)
+
+#define catc_set_reg_async(catc, reg, val)		catc_ctrl_async(catc, USB_DIR_OUT, SetReg, val, reg, NULL, 0, NULL)
+#define catc_get_reg_async(catc, reg, cb)		catc_ctrl_async(catc, USB_DIR_IN, GetReg, 0, reg, NULL, 1, cb)
+#define catc_write_mem_async(catc, addr, buf, size)	catc_ctrl_async(catc, USB_DIR_OUT, WriteMem, 0, addr, buf, size, NULL)
+
+/*
+ * Receive routines.
+ */
+
+static void catc_rx_done(struct urb *urb)
+{
+	struct catc *catc = urb->context;
+	u8 *pkt_start = urb->transfer_buffer;
+	struct sk_buff *skb;
+	int pkt_len, pkt_offset = 0;
+	int status = urb->status;
+
+	if (!catc->is_f5u011) {
+		clear_bit(RX_RUNNING, &catc->flags);
+		pkt_offset = 2;
+	}
+
+	if (status) {
+		dbg("rx_done, status %d, length %d", status, urb->actual_length);
+		return;
+	}
+
+	do {
+		if(!catc->is_f5u011) {
+			pkt_len = le16_to_cpup((__le16*)pkt_start);
+			if (pkt_len > urb->actual_length) {
+				catc->netdev->stats.rx_length_errors++;
+				catc->netdev->stats.rx_errors++;
+				break;
+			}
+		} else {
+			pkt_len = urb->actual_length;
+		}
+
+		if (!(skb = dev_alloc_skb(pkt_len)))
+			return;
+
+		skb_copy_to_linear_data(skb, pkt_start + pkt_offset, pkt_len);
+		skb_put(skb, pkt_len);
+
+		skb->protocol = eth_type_trans(skb, catc->netdev);
+		netif_rx(skb);
+
+		catc->netdev->stats.rx_packets++;
+		catc->netdev->stats.rx_bytes += pkt_len;
+
+		/* F5U011 only does one packet per RX */
+		if (catc->is_f5u011)
+			break;
+		pkt_start += (((pkt_len + 1) >> 6) + 1) << 6;
+
+	} while (pkt_start - (u8 *) urb->transfer_buffer < urb->actual_length);
+
+	if (catc->is_f5u011) {
+		if (atomic_read(&catc->recq_sz)) {
+			int state;
+			atomic_dec(&catc->recq_sz);
+			dbg("getting extra packet");
+			urb->dev = catc->usbdev;
+			if ((state = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+				dbg("submit(rx_urb) status %d", state);
+			}
+		} else {
+			clear_bit(RX_RUNNING, &catc->flags);
+		}
+	}
+}
+
+static void catc_irq_done(struct urb *urb)
+{
+	struct catc *catc = urb->context;
+	u8 *data = urb->transfer_buffer;
+	int status = urb->status;
+	unsigned int hasdata = 0, linksts = LinkNoChange;
+	int res;
+
+	if (!catc->is_f5u011) {
+		hasdata = data[1] & 0x80;
+		if (data[1] & 0x40)
+			linksts = LinkGood;
+		else if (data[1] & 0x20)
+			linksts = LinkBad;
+	} else {
+		hasdata = (unsigned int)(be16_to_cpup((__be16*)data) & 0x0fff);
+		if (data[0] == 0x90)
+			linksts = LinkGood;
+		else if (data[0] == 0xA0)
+			linksts = LinkBad;
+	}
+
+	switch (status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		dbg("irq_done, status %d, data %02x %02x.", status, data[0], data[1]);
+		goto resubmit;
+	}
+
+	if (linksts == LinkGood) {
+		netif_carrier_on(catc->netdev);
+		dbg("link ok");
+	}
+
+	if (linksts == LinkBad) {
+		netif_carrier_off(catc->netdev);
+		dbg("link bad");
+	}
+
+	if (hasdata) {
+		if (test_and_set_bit(RX_RUNNING, &catc->flags)) {
+			if (catc->is_f5u011)
+				atomic_inc(&catc->recq_sz);
+		} else {
+			catc->rx_urb->dev = catc->usbdev;
+			if ((res = usb_submit_urb(catc->rx_urb, GFP_ATOMIC)) < 0) {
+				err("submit(rx_urb) status %d", res);
+			}
+		} 
+	}
+resubmit:
+	res = usb_submit_urb (urb, GFP_ATOMIC);
+	if (res)
+		err ("can't resubmit intr, %s-%s, status %d",
+				catc->usbdev->bus->bus_name,
+				catc->usbdev->devpath, res);
+}
+
+/*
+ * Transmit routines.
+ */
+
+static int catc_tx_run(struct catc *catc)
+{
+	int status;
+
+	if (catc->is_f5u011)
+		catc->tx_ptr = (catc->tx_ptr + 63) & ~63;
+
+	catc->tx_urb->transfer_buffer_length = catc->tx_ptr;
+	catc->tx_urb->transfer_buffer = catc->tx_buf[catc->tx_idx];
+	catc->tx_urb->dev = catc->usbdev;
+
+	if ((status = usb_submit_urb(catc->tx_urb, GFP_ATOMIC)) < 0)
+		err("submit(tx_urb), status %d", status);
+
+	catc->tx_idx = !catc->tx_idx;
+	catc->tx_ptr = 0;
+
+	catc->netdev->trans_start = jiffies;
+	return status;
+}
+
+static void catc_tx_done(struct urb *urb)
+{
+	struct catc *catc = urb->context;
+	unsigned long flags;
+	int r, status = urb->status;
+
+	if (status == -ECONNRESET) {
+		dbg("Tx Reset.");
+		urb->status = 0;
+		catc->netdev->trans_start = jiffies;
+		catc->netdev->stats.tx_errors++;
+		clear_bit(TX_RUNNING, &catc->flags);
+		netif_wake_queue(catc->netdev);
+		return;
+	}
+
+	if (status) {
+		dbg("tx_done, status %d, length %d", status, urb->actual_length);
+		return;
+	}
+
+	spin_lock_irqsave(&catc->tx_lock, flags);
+
+	if (catc->tx_ptr) {
+		r = catc_tx_run(catc);
+		if (unlikely(r < 0))
+			clear_bit(TX_RUNNING, &catc->flags);
+	} else {
+		clear_bit(TX_RUNNING, &catc->flags);
+	}
+
+	netif_wake_queue(catc->netdev);
+
+	spin_unlock_irqrestore(&catc->tx_lock, flags);
+}
+
+static netdev_tx_t catc_start_xmit(struct sk_buff *skb,
+					 struct net_device *netdev)
+{
+	struct catc *catc = netdev_priv(netdev);
+	unsigned long flags;
+	int r = 0;
+	char *tx_buf;
+
+	spin_lock_irqsave(&catc->tx_lock, flags);
+
+	catc->tx_ptr = (((catc->tx_ptr - 1) >> 6) + 1) << 6;
+	tx_buf = catc->tx_buf[catc->tx_idx] + catc->tx_ptr;
+	if (catc->is_f5u011)
+		*(__be16 *)tx_buf = cpu_to_be16(skb->len);
+	else
+		*(__le16 *)tx_buf = cpu_to_le16(skb->len);
+	skb_copy_from_linear_data(skb, tx_buf + 2, skb->len);
+	catc->tx_ptr += skb->len + 2;
+
+	if (!test_and_set_bit(TX_RUNNING, &catc->flags)) {
+		r = catc_tx_run(catc);
+		if (r < 0)
+			clear_bit(TX_RUNNING, &catc->flags);
+	}
+
+	if ((catc->is_f5u011 && catc->tx_ptr) ||
+	    (catc->tx_ptr >= ((TX_MAX_BURST - 1) * (PKT_SZ + 2))))
+		netif_stop_queue(netdev);
+
+	spin_unlock_irqrestore(&catc->tx_lock, flags);
+
+	if (r >= 0) {
+		catc->netdev->stats.tx_bytes += skb->len;
+		catc->netdev->stats.tx_packets++;
+	}
+
+	dev_kfree_skb(skb);
+
+	return NETDEV_TX_OK;
+}
+
+static void catc_tx_timeout(struct net_device *netdev)
+{
+	struct catc *catc = netdev_priv(netdev);
+
+	dev_warn(&netdev->dev, "Transmit timed out.\n");
+	usb_unlink_urb(catc->tx_urb);
+}
+
+/*
+ * Control messages.
+ */
+
+static int catc_ctrl_msg(struct catc *catc, u8 dir, u8 request, u16 value, u16 index, void *buf, int len)
+{
+        int retval = usb_control_msg(catc->usbdev,
+		dir ? usb_rcvctrlpipe(catc->usbdev, 0) : usb_sndctrlpipe(catc->usbdev, 0),
+		 request, 0x40 | dir, value, index, buf, len, 1000);
+        return retval < 0 ? retval : 0;
+}
+
+static void catc_ctrl_run(struct catc *catc)
+{
+	struct ctrl_queue *q = catc->ctrl_queue + catc->ctrl_tail;
+	struct usb_device *usbdev = catc->usbdev;
+	struct urb *urb = catc->ctrl_urb;
+	struct usb_ctrlrequest *dr = &catc->ctrl_dr;
+	int status;
+
+	dr->bRequest = q->request;
+	dr->bRequestType = 0x40 | q->dir;
+	dr->wValue = cpu_to_le16(q->value);
+	dr->wIndex = cpu_to_le16(q->index);
+	dr->wLength = cpu_to_le16(q->len);
+
+        urb->pipe = q->dir ? usb_rcvctrlpipe(usbdev, 0) : usb_sndctrlpipe(usbdev, 0);
+	urb->transfer_buffer_length = q->len;
+	urb->transfer_buffer = catc->ctrl_buf;
+	urb->setup_packet = (void *) dr;
+	urb->dev = usbdev;
+
+	if (!q->dir && q->buf && q->len)
+		memcpy(catc->ctrl_buf, q->buf, q->len);
+
+	if ((status = usb_submit_urb(catc->ctrl_urb, GFP_ATOMIC)))
+		err("submit(ctrl_urb) status %d", status);
+}
+
+static void catc_ctrl_done(struct urb *urb)
+{
+	struct catc *catc = urb->context;
+	struct ctrl_queue *q;
+	unsigned long flags;
+	int status = urb->status;
+
+	if (status)
+		dbg("ctrl_done, status %d, len %d.", status, urb->actual_length);
+
+	spin_lock_irqsave(&catc->ctrl_lock, flags);
+
+	q = catc->ctrl_queue + catc->ctrl_tail;
+
+	if (q->dir) {
+		if (q->buf && q->len)
+			memcpy(q->buf, catc->ctrl_buf, q->len);
+		else
+			q->buf = catc->ctrl_buf;
+	}
+
+	if (q->callback)
+		q->callback(catc, q);
+
+	catc->ctrl_tail = (catc->ctrl_tail + 1) & (CTRL_QUEUE - 1);
+
+	if (catc->ctrl_head != catc->ctrl_tail)
+		catc_ctrl_run(catc);
+	else
+		clear_bit(CTRL_RUNNING, &catc->flags);
+
+	spin_unlock_irqrestore(&catc->ctrl_lock, flags);
+}
+
+static int catc_ctrl_async(struct catc *catc, u8 dir, u8 request, u16 value,
+	u16 index, void *buf, int len, void (*callback)(struct catc *catc, struct ctrl_queue *q))
+{
+	struct ctrl_queue *q;
+	int retval = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&catc->ctrl_lock, flags);
+	
+	q = catc->ctrl_queue + catc->ctrl_head;
+
+	q->dir = dir;
+	q->request = request;
+	q->value = value;
+	q->index = index;
+	q->buf = buf;
+	q->len = len;
+	q->callback = callback;
+
+	catc->ctrl_head = (catc->ctrl_head + 1) & (CTRL_QUEUE - 1);
+
+	if (catc->ctrl_head == catc->ctrl_tail) {
+		err("ctrl queue full");
+		catc->ctrl_tail = (catc->ctrl_tail + 1) & (CTRL_QUEUE - 1);
+		retval = -1;
+	}
+
+	if (!test_and_set_bit(CTRL_RUNNING, &catc->flags))
+		catc_ctrl_run(catc);
+
+	spin_unlock_irqrestore(&catc->ctrl_lock, flags);
+
+	return retval;
+}
+
+/*
+ * Statistics.
+ */
+
+static void catc_stats_done(struct catc *catc, struct ctrl_queue *q)
+{
+	int index = q->index - EthStats;
+	u16 data, last;
+
+	catc->stats_buf[index] = *((char *)q->buf);
+
+	if (index & 1)
+		return;
+
+	data = ((u16)catc->stats_buf[index] << 8) | catc->stats_buf[index + 1];
+	last = catc->stats_vals[index >> 1];
+
+	switch (index) {
+		case TxSingleColl:
+		case TxMultiColl:
+			catc->netdev->stats.collisions += data - last;
+			break;
+		case TxExcessColl:
+			catc->netdev->stats.tx_aborted_errors += data - last;
+			catc->netdev->stats.tx_errors += data - last;
+			break;
+		case RxFramErr:
+			catc->netdev->stats.rx_frame_errors += data - last;
+			catc->netdev->stats.rx_errors += data - last;
+			break;
+	}
+
+	catc->stats_vals[index >> 1] = data;
+}
+
+static void catc_stats_timer(unsigned long data)
+{
+	struct catc *catc = (void *) data;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		catc_get_reg_async(catc, EthStats + 7 - i, catc_stats_done);
+
+	mod_timer(&catc->timer, jiffies + STATS_UPDATE);
+}
+
+/*
+ * Receive modes. Broadcast, Multicast, Promisc.
+ */
+
+static void catc_multicast(unsigned char *addr, u8 *multicast)
+{
+	u32 crc;
+
+	crc = ether_crc_le(6, addr);
+	multicast[(crc >> 3) & 0x3f] |= 1 << (crc & 7);
+}
+
+static void catc_set_multicast_list(struct net_device *netdev)
+{
+	struct catc *catc = netdev_priv(netdev);
+	struct netdev_hw_addr *ha;
+	u8 broadcast[6];
+	u8 rx = RxEnable | RxPolarity | RxMultiCast;
+
+	memset(broadcast, 0xff, 6);
+	memset(catc->multicast, 0, 64);
+
+	catc_multicast(broadcast, catc->multicast);
+	catc_multicast(netdev->dev_addr, catc->multicast);
+
+	if (netdev->flags & IFF_PROMISC) {
+		memset(catc->multicast, 0xff, 64);
+		rx |= (!catc->is_f5u011) ? RxPromisc : AltRxPromisc;
+	} 
+
+	if (netdev->flags & IFF_ALLMULTI) {
+		memset(catc->multicast, 0xff, 64);
+	} else {
+		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 {
+				catc->multicast[7-(crc >> 29)] |= 1 << ((crc >> 26) & 7);
+			}
+		}
+	}
+	if (!catc->is_f5u011) {
+		catc_set_reg_async(catc, RxUnit, rx);
+		catc_write_mem_async(catc, 0xfa80, catc->multicast, 64);
+	} else {
+		f5u011_mchash_async(catc, catc->multicast);
+		if (catc->rxmode[0] != rx) {
+			catc->rxmode[0] = rx;
+			dbg("Setting RX mode to %2.2X %2.2X", catc->rxmode[0], catc->rxmode[1]);
+			f5u011_rxmode_async(catc, catc->rxmode);
+		}
+	}
+}
+
+static void catc_get_drvinfo(struct net_device *dev,
+			     struct ethtool_drvinfo *info)
+{
+	struct catc *catc = netdev_priv(dev);
+	strncpy(info->driver, driver_name, ETHTOOL_BUSINFO_LEN);
+	strncpy(info->version, DRIVER_VERSION, ETHTOOL_BUSINFO_LEN);
+	usb_make_path (catc->usbdev, info->bus_info, sizeof info->bus_info);
+}
+
+static int catc_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+	struct catc *catc = netdev_priv(dev);
+	if (!catc->is_f5u011)
+		return -EOPNOTSUPP;
+
+	cmd->supported = SUPPORTED_10baseT_Half | SUPPORTED_TP;
+	cmd->advertising = ADVERTISED_10baseT_Half | ADVERTISED_TP;
+	ethtool_cmd_speed_set(cmd, SPEED_10);
+	cmd->duplex = DUPLEX_HALF;
+	cmd->port = PORT_TP; 
+	cmd->phy_address = 0;
+	cmd->transceiver = XCVR_INTERNAL;
+	cmd->autoneg = AUTONEG_DISABLE;
+	cmd->maxtxpkt = 1;
+	cmd->maxrxpkt = 1;
+	return 0;
+}
+
+static const struct ethtool_ops ops = {
+	.get_drvinfo = catc_get_drvinfo,
+	.get_settings = catc_get_settings,
+	.get_link = ethtool_op_get_link
+};
+
+/*
+ * Open, close.
+ */
+
+static int catc_open(struct net_device *netdev)
+{
+	struct catc *catc = netdev_priv(netdev);
+	int status;
+
+	catc->irq_urb->dev = catc->usbdev;
+	if ((status = usb_submit_urb(catc->irq_urb, GFP_KERNEL)) < 0) {
+		err("submit(irq_urb) status %d", status);
+		return -1;
+	}
+
+	netif_start_queue(netdev);
+
+	if (!catc->is_f5u011)
+		mod_timer(&catc->timer, jiffies + STATS_UPDATE);
+
+	return 0;
+}
+
+static int catc_stop(struct net_device *netdev)
+{
+	struct catc *catc = netdev_priv(netdev);
+
+	netif_stop_queue(netdev);
+
+	if (!catc->is_f5u011)
+		del_timer_sync(&catc->timer);
+
+	usb_kill_urb(catc->rx_urb);
+	usb_kill_urb(catc->tx_urb);
+	usb_kill_urb(catc->irq_urb);
+	usb_kill_urb(catc->ctrl_urb);
+
+	return 0;
+}
+
+static const struct net_device_ops catc_netdev_ops = {
+	.ndo_open		= catc_open,
+	.ndo_stop		= catc_stop,
+	.ndo_start_xmit		= catc_start_xmit,
+
+	.ndo_tx_timeout		= catc_tx_timeout,
+	.ndo_set_rx_mode	= catc_set_multicast_list,
+	.ndo_change_mtu		= eth_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+/*
+ * USB probe, disconnect.
+ */
+
+static int catc_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *usbdev = interface_to_usbdev(intf);
+	struct net_device *netdev;
+	struct catc *catc;
+	u8 broadcast[6];
+	int i, pktsz;
+
+	if (usb_set_interface(usbdev,
+			intf->altsetting->desc.bInterfaceNumber, 1)) {
+                err("Can't set altsetting 1.");
+		return -EIO;
+	}
+
+	netdev = alloc_etherdev(sizeof(struct catc));
+	if (!netdev)
+		return -ENOMEM;
+
+	catc = netdev_priv(netdev);
+
+	netdev->netdev_ops = &catc_netdev_ops;
+	netdev->watchdog_timeo = TX_TIMEOUT;
+	SET_ETHTOOL_OPS(netdev, &ops);
+
+	catc->usbdev = usbdev;
+	catc->netdev = netdev;
+
+	spin_lock_init(&catc->tx_lock);
+	spin_lock_init(&catc->ctrl_lock);
+
+	init_timer(&catc->timer);
+	catc->timer.data = (long) catc;
+	catc->timer.function = catc_stats_timer;
+
+	catc->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL);
+	catc->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	catc->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	catc->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if ((!catc->ctrl_urb) || (!catc->tx_urb) || 
+	    (!catc->rx_urb) || (!catc->irq_urb)) {
+		err("No free urbs available.");
+		usb_free_urb(catc->ctrl_urb);
+		usb_free_urb(catc->tx_urb);
+		usb_free_urb(catc->rx_urb);
+		usb_free_urb(catc->irq_urb);
+		free_netdev(netdev);
+		return -ENOMEM;
+	}
+
+	/* The F5U011 has the same vendor/product as the netmate but a device version of 0x130 */
+	if (le16_to_cpu(usbdev->descriptor.idVendor) == 0x0423 && 
+	    le16_to_cpu(usbdev->descriptor.idProduct) == 0xa &&
+	    le16_to_cpu(catc->usbdev->descriptor.bcdDevice) == 0x0130) {
+		dbg("Testing for f5u011");
+		catc->is_f5u011 = 1;		
+		atomic_set(&catc->recq_sz, 0);
+		pktsz = RX_PKT_SZ;
+	} else {
+		pktsz = RX_MAX_BURST * (PKT_SZ + 2);
+	}
+	
+	usb_fill_control_urb(catc->ctrl_urb, usbdev, usb_sndctrlpipe(usbdev, 0),
+		NULL, NULL, 0, catc_ctrl_done, catc);
+
+	usb_fill_bulk_urb(catc->tx_urb, usbdev, usb_sndbulkpipe(usbdev, 1),
+		NULL, 0, catc_tx_done, catc);
+
+	usb_fill_bulk_urb(catc->rx_urb, usbdev, usb_rcvbulkpipe(usbdev, 1),
+		catc->rx_buf, pktsz, catc_rx_done, catc);
+
+	usb_fill_int_urb(catc->irq_urb, usbdev, usb_rcvintpipe(usbdev, 2),
+                catc->irq_buf, 2, catc_irq_done, catc, 1);
+
+	if (!catc->is_f5u011) {
+		dbg("Checking memory size\n");
+
+		i = 0x12345678;
+		catc_write_mem(catc, 0x7a80, &i, 4);
+		i = 0x87654321;	
+		catc_write_mem(catc, 0xfa80, &i, 4);
+		catc_read_mem(catc, 0x7a80, &i, 4);
+	  
+		switch (i) {
+		case 0x12345678:
+			catc_set_reg(catc, TxBufCount, 8);
+			catc_set_reg(catc, RxBufCount, 32);
+			dbg("64k Memory\n");
+			break;
+		default:
+			dev_warn(&intf->dev,
+				 "Couldn't detect memory size, assuming 32k\n");
+		case 0x87654321:
+			catc_set_reg(catc, TxBufCount, 4);
+			catc_set_reg(catc, RxBufCount, 16);
+			dbg("32k Memory\n");
+			break;
+		}
+	  
+		dbg("Getting MAC from SEEROM.");
+	  
+		catc_get_mac(catc, netdev->dev_addr);
+		
+		dbg("Setting MAC into registers.");
+	  
+		for (i = 0; i < 6; i++)
+			catc_set_reg(catc, StationAddr0 - i, netdev->dev_addr[i]);
+		
+		dbg("Filling the multicast list.");
+	  
+		memset(broadcast, 0xff, 6);
+		catc_multicast(broadcast, catc->multicast);
+		catc_multicast(netdev->dev_addr, catc->multicast);
+		catc_write_mem(catc, 0xfa80, catc->multicast, 64);
+		
+		dbg("Clearing error counters.");
+		
+		for (i = 0; i < 8; i++)
+			catc_set_reg(catc, EthStats + i, 0);
+		catc->last_stats = jiffies;
+		
+		dbg("Enabling.");
+		
+		catc_set_reg(catc, MaxBurst, RX_MAX_BURST);
+		catc_set_reg(catc, OpModes, OpTxMerge | OpRxMerge | OpLenInclude | Op3MemWaits);
+		catc_set_reg(catc, LEDCtrl, LEDLink);
+		catc_set_reg(catc, RxUnit, RxEnable | RxPolarity | RxMultiCast);
+	} else {
+		dbg("Performing reset\n");
+		catc_reset(catc);
+		catc_get_mac(catc, netdev->dev_addr);
+		
+		dbg("Setting RX Mode");
+		catc->rxmode[0] = RxEnable | RxPolarity | RxMultiCast;
+		catc->rxmode[1] = 0;
+		f5u011_rxmode(catc, catc->rxmode);
+	}
+	dbg("Init done.");
+	printk(KERN_INFO "%s: %s USB Ethernet at usb-%s-%s, %pM.\n",
+	       netdev->name, (catc->is_f5u011) ? "Belkin F5U011" : "CATC EL1210A NetMate",
+	       usbdev->bus->bus_name, usbdev->devpath, netdev->dev_addr);
+	usb_set_intfdata(intf, catc);
+
+	SET_NETDEV_DEV(netdev, &intf->dev);
+	if (register_netdev(netdev) != 0) {
+		usb_set_intfdata(intf, NULL);
+		usb_free_urb(catc->ctrl_urb);
+		usb_free_urb(catc->tx_urb);
+		usb_free_urb(catc->rx_urb);
+		usb_free_urb(catc->irq_urb);
+		free_netdev(netdev);
+		return -EIO;
+	}
+	return 0;
+}
+
+static void catc_disconnect(struct usb_interface *intf)
+{
+	struct catc *catc = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (catc) {
+		unregister_netdev(catc->netdev);
+		usb_free_urb(catc->ctrl_urb);
+		usb_free_urb(catc->tx_urb);
+		usb_free_urb(catc->rx_urb);
+		usb_free_urb(catc->irq_urb);
+		free_netdev(catc->netdev);
+	}
+}
+
+/*
+ * Module functions and tables.
+ */
+
+static struct usb_device_id catc_id_table [] = {
+	{ USB_DEVICE(0x0423, 0xa) },	/* CATC Netmate, Belkin F5U011 */
+	{ USB_DEVICE(0x0423, 0xc) },	/* CATC Netmate II, Belkin F5U111 */
+	{ USB_DEVICE(0x08d1, 0x1) },	/* smartBridges smartNIC */
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, catc_id_table);
+
+static struct usb_driver catc_driver = {
+	.name =		driver_name,
+	.probe =	catc_probe,
+	.disconnect =	catc_disconnect,
+	.id_table =	catc_id_table,
+};
+
+module_usb_driver(catc_driver);
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc-phonet.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc-phonet.c
new file mode 100644
index 0000000..3e41b00
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc-phonet.c
@@ -0,0 +1,466 @@
+/*
+ * phonet.c -- USB CDC Phonet host driver
+ *
+ * Copyright (C) 2008-2009 Nokia Corporation. All rights reserved.
+ *
+ * Author: Rémi Denis-Courmont
+ *
+ * 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 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_phonet.h>
+#include <linux/phonet.h>
+
+#define PN_MEDIA_USB	0x1B
+
+static const unsigned rxq_size = 17;
+
+struct usbpn_dev {
+	struct net_device	*dev;
+
+	struct usb_interface	*intf, *data_intf;
+	struct usb_device	*usb;
+	unsigned int		tx_pipe, rx_pipe;
+	u8 active_setting;
+	u8 disconnected;
+
+	unsigned		tx_queue;
+	spinlock_t		tx_lock;
+
+	spinlock_t		rx_lock;
+	struct sk_buff		*rx_skb;
+	struct urb		*urbs[0];
+};
+
+static void tx_complete(struct urb *req);
+static void rx_complete(struct urb *req);
+
+/*
+ * Network device callbacks
+ */
+static netdev_tx_t usbpn_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct usbpn_dev *pnd = netdev_priv(dev);
+	struct urb *req = NULL;
+	unsigned long flags;
+	int err;
+
+	if (skb->protocol != htons(ETH_P_PHONET))
+		goto drop;
+
+	req = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!req)
+		goto drop;
+	usb_fill_bulk_urb(req, pnd->usb, pnd->tx_pipe, skb->data, skb->len,
+				tx_complete, skb);
+	req->transfer_flags = URB_ZERO_PACKET;
+	err = usb_submit_urb(req, GFP_ATOMIC);
+	if (err) {
+		usb_free_urb(req);
+		goto drop;
+	}
+
+	spin_lock_irqsave(&pnd->tx_lock, flags);
+	pnd->tx_queue++;
+	if (pnd->tx_queue >= dev->tx_queue_len)
+		netif_stop_queue(dev);
+	spin_unlock_irqrestore(&pnd->tx_lock, flags);
+	return NETDEV_TX_OK;
+
+drop:
+	dev_kfree_skb(skb);
+	dev->stats.tx_dropped++;
+	return NETDEV_TX_OK;
+}
+
+static void tx_complete(struct urb *req)
+{
+	struct sk_buff *skb = req->context;
+	struct net_device *dev = skb->dev;
+	struct usbpn_dev *pnd = netdev_priv(dev);
+	int status = req->status;
+
+	switch (status) {
+	case 0:
+		dev->stats.tx_bytes += skb->len;
+		break;
+
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		dev->stats.tx_aborted_errors++;
+	default:
+		dev->stats.tx_errors++;
+		dev_dbg(&dev->dev, "TX error (%d)\n", status);
+	}
+	dev->stats.tx_packets++;
+
+	spin_lock(&pnd->tx_lock);
+	pnd->tx_queue--;
+	netif_wake_queue(dev);
+	spin_unlock(&pnd->tx_lock);
+
+	dev_kfree_skb_any(skb);
+	usb_free_urb(req);
+}
+
+static int rx_submit(struct usbpn_dev *pnd, struct urb *req, gfp_t gfp_flags)
+{
+	struct net_device *dev = pnd->dev;
+	struct page *page;
+	int err;
+
+	page = alloc_page(gfp_flags);
+	if (!page)
+		return -ENOMEM;
+
+	usb_fill_bulk_urb(req, pnd->usb, pnd->rx_pipe, page_address(page),
+				PAGE_SIZE, rx_complete, dev);
+	req->transfer_flags = 0;
+	err = usb_submit_urb(req, gfp_flags);
+	if (unlikely(err)) {
+		dev_dbg(&dev->dev, "RX submit error (%d)\n", err);
+		put_page(page);
+	}
+	return err;
+}
+
+static void rx_complete(struct urb *req)
+{
+	struct net_device *dev = req->context;
+	struct usbpn_dev *pnd = netdev_priv(dev);
+	struct page *page = virt_to_page(req->transfer_buffer);
+	struct sk_buff *skb;
+	unsigned long flags;
+	int status = req->status;
+
+	switch (status) {
+	case 0:
+		spin_lock_irqsave(&pnd->rx_lock, flags);
+		skb = pnd->rx_skb;
+		if (!skb) {
+			skb = pnd->rx_skb = netdev_alloc_skb(dev, 12);
+			if (likely(skb)) {
+				/* Can't use pskb_pull() on page in IRQ */
+				memcpy(skb_put(skb, 1), page_address(page), 1);
+				skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+						page, 1, req->actual_length,
+						PAGE_SIZE);
+				page = NULL;
+			}
+		} else {
+			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+					page, 0, req->actual_length,
+					PAGE_SIZE);
+			page = NULL;
+		}
+		if (req->actual_length < PAGE_SIZE)
+			pnd->rx_skb = NULL; /* Last fragment */
+		else
+			skb = NULL;
+		spin_unlock_irqrestore(&pnd->rx_lock, flags);
+		if (skb) {
+			skb->protocol = htons(ETH_P_PHONET);
+			skb_reset_mac_header(skb);
+			__skb_pull(skb, 1);
+			skb->dev = dev;
+			dev->stats.rx_packets++;
+			dev->stats.rx_bytes += skb->len;
+
+			netif_rx(skb);
+		}
+		goto resubmit;
+
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		req = NULL;
+		break;
+
+	case -EOVERFLOW:
+		dev->stats.rx_over_errors++;
+		dev_dbg(&dev->dev, "RX overflow\n");
+		break;
+
+	case -EILSEQ:
+		dev->stats.rx_crc_errors++;
+		break;
+	}
+
+	dev->stats.rx_errors++;
+resubmit:
+	if (page)
+		put_page(page);
+	if (req)
+		rx_submit(pnd, req, GFP_ATOMIC | __GFP_COLD);
+}
+
+static int usbpn_close(struct net_device *dev);
+
+static int usbpn_open(struct net_device *dev)
+{
+	struct usbpn_dev *pnd = netdev_priv(dev);
+	int err;
+	unsigned i;
+	unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
+
+	err = usb_set_interface(pnd->usb, num, pnd->active_setting);
+	if (err)
+		return err;
+
+	for (i = 0; i < rxq_size; i++) {
+		struct urb *req = usb_alloc_urb(0, GFP_KERNEL);
+
+		if (!req || rx_submit(pnd, req, GFP_KERNEL | __GFP_COLD)) {
+			usbpn_close(dev);
+			return -ENOMEM;
+		}
+		pnd->urbs[i] = req;
+	}
+
+	netif_wake_queue(dev);
+	return 0;
+}
+
+static int usbpn_close(struct net_device *dev)
+{
+	struct usbpn_dev *pnd = netdev_priv(dev);
+	unsigned i;
+	unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
+
+	netif_stop_queue(dev);
+
+	for (i = 0; i < rxq_size; i++) {
+		struct urb *req = pnd->urbs[i];
+
+		if (!req)
+			continue;
+		usb_kill_urb(req);
+		usb_free_urb(req);
+		pnd->urbs[i] = NULL;
+	}
+
+	return usb_set_interface(pnd->usb, num, !pnd->active_setting);
+}
+
+static int usbpn_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct if_phonet_req *req = (struct if_phonet_req *)ifr;
+
+	switch (cmd) {
+	case SIOCPNGAUTOCONF:
+		req->ifr_phonet_autoconf.device = PN_DEV_PC;
+		return 0;
+	}
+	return -ENOIOCTLCMD;
+}
+
+static int usbpn_set_mtu(struct net_device *dev, int new_mtu)
+{
+	if ((new_mtu < PHONET_MIN_MTU) || (new_mtu > PHONET_MAX_MTU))
+		return -EINVAL;
+
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+static const struct net_device_ops usbpn_ops = {
+	.ndo_open	= usbpn_open,
+	.ndo_stop	= usbpn_close,
+	.ndo_start_xmit = usbpn_xmit,
+	.ndo_do_ioctl	= usbpn_ioctl,
+	.ndo_change_mtu = usbpn_set_mtu,
+};
+
+static void usbpn_setup(struct net_device *dev)
+{
+	dev->features		= 0;
+	dev->netdev_ops		= &usbpn_ops,
+	dev->header_ops		= &phonet_header_ops;
+	dev->type		= ARPHRD_PHONET;
+	dev->flags		= IFF_POINTOPOINT | IFF_NOARP;
+	dev->mtu		= PHONET_MAX_MTU;
+	dev->hard_header_len	= 1;
+	dev->dev_addr[0]	= PN_MEDIA_USB;
+	dev->addr_len		= 1;
+	dev->tx_queue_len	= 3;
+
+	dev->destructor		= free_netdev;
+}
+
+/*
+ * USB driver callbacks
+ */
+static struct usb_device_id usbpn_ids[] = {
+	{
+		.match_flags = USB_DEVICE_ID_MATCH_VENDOR
+			| USB_DEVICE_ID_MATCH_INT_CLASS
+			| USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+		.idVendor = 0x0421, /* Nokia */
+		.bInterfaceClass = USB_CLASS_COMM,
+		.bInterfaceSubClass = 0xFE,
+	},
+	{ },
+};
+
+MODULE_DEVICE_TABLE(usb, usbpn_ids);
+
+static struct usb_driver usbpn_driver;
+
+int usbpn_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	static const char ifname[] = "usbpn%d";
+	const struct usb_cdc_union_desc *union_header = NULL;
+	const struct usb_host_interface *data_desc;
+	struct usb_interface *data_intf;
+	struct usb_device *usbdev = interface_to_usbdev(intf);
+	struct net_device *dev;
+	struct usbpn_dev *pnd;
+	u8 *data;
+	int phonet = 0;
+	int len, err;
+
+	data = intf->altsetting->extra;
+	len = intf->altsetting->extralen;
+	while (len >= 3) {
+		u8 dlen = data[0];
+		if (dlen < 3)
+			return -EINVAL;
+
+		/* bDescriptorType */
+		if (data[1] == USB_DT_CS_INTERFACE) {
+			/* bDescriptorSubType */
+			switch (data[2]) {
+			case USB_CDC_UNION_TYPE:
+				if (union_header || dlen < 5)
+					break;
+				union_header =
+					(struct usb_cdc_union_desc *)data;
+				break;
+			case 0xAB:
+				phonet = 1;
+				break;
+			}
+		}
+		data += dlen;
+		len -= dlen;
+	}
+
+	if (!union_header || !phonet)
+		return -EINVAL;
+
+	data_intf = usb_ifnum_to_if(usbdev, union_header->bSlaveInterface0);
+	if (data_intf == NULL)
+		return -ENODEV;
+	/* Data interface has one inactive and one active setting */
+	if (data_intf->num_altsetting != 2)
+		return -EINVAL;
+	if (data_intf->altsetting[0].desc.bNumEndpoints == 0 &&
+	    data_intf->altsetting[1].desc.bNumEndpoints == 2)
+		data_desc = data_intf->altsetting + 1;
+	else
+	if (data_intf->altsetting[0].desc.bNumEndpoints == 2 &&
+	    data_intf->altsetting[1].desc.bNumEndpoints == 0)
+		data_desc = data_intf->altsetting;
+	else
+		return -EINVAL;
+
+	dev = alloc_netdev(sizeof(*pnd) + sizeof(pnd->urbs[0]) * rxq_size,
+				ifname, usbpn_setup);
+	if (!dev)
+		return -ENOMEM;
+
+	pnd = netdev_priv(dev);
+	SET_NETDEV_DEV(dev, &intf->dev);
+
+	pnd->dev = dev;
+	pnd->usb = usb_get_dev(usbdev);
+	pnd->intf = intf;
+	pnd->data_intf = data_intf;
+	spin_lock_init(&pnd->tx_lock);
+	spin_lock_init(&pnd->rx_lock);
+	/* Endpoints */
+	if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) {
+		pnd->rx_pipe = usb_rcvbulkpipe(usbdev,
+			data_desc->endpoint[0].desc.bEndpointAddress);
+		pnd->tx_pipe = usb_sndbulkpipe(usbdev,
+			data_desc->endpoint[1].desc.bEndpointAddress);
+	} else {
+		pnd->rx_pipe = usb_rcvbulkpipe(usbdev,
+			data_desc->endpoint[1].desc.bEndpointAddress);
+		pnd->tx_pipe = usb_sndbulkpipe(usbdev,
+			data_desc->endpoint[0].desc.bEndpointAddress);
+	}
+	pnd->active_setting = data_desc - data_intf->altsetting;
+
+	err = usb_driver_claim_interface(&usbpn_driver, data_intf, pnd);
+	if (err)
+		goto out;
+
+	/* Force inactive mode until the network device is brought UP */
+	usb_set_interface(usbdev, union_header->bSlaveInterface0,
+				!pnd->active_setting);
+	usb_set_intfdata(intf, pnd);
+
+	err = register_netdev(dev);
+	if (err) {
+		usb_driver_release_interface(&usbpn_driver, data_intf);
+		goto out;
+	}
+
+	dev_dbg(&dev->dev, "USB CDC Phonet device found\n");
+	return 0;
+
+out:
+	usb_set_intfdata(intf, NULL);
+	free_netdev(dev);
+	return err;
+}
+
+static void usbpn_disconnect(struct usb_interface *intf)
+{
+	struct usbpn_dev *pnd = usb_get_intfdata(intf);
+	struct usb_device *usb = pnd->usb;
+
+	if (pnd->disconnected)
+		return;
+
+	pnd->disconnected = 1;
+	usb_driver_release_interface(&usbpn_driver,
+			(pnd->intf == intf) ? pnd->data_intf : pnd->intf);
+	unregister_netdev(pnd->dev);
+	usb_put_dev(usb);
+}
+
+static struct usb_driver usbpn_driver = {
+	.name =		"cdc_phonet",
+	.probe =	usbpn_probe,
+	.disconnect =	usbpn_disconnect,
+	.id_table =	usbpn_ids,
+};
+
+module_usb_driver(usbpn_driver);
+
+MODULE_AUTHOR("Remi Denis-Courmont");
+MODULE_DESCRIPTION("USB CDC Phonet host interface");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_eem.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_eem.c
new file mode 100644
index 0000000..685a4e2
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_eem.c
@@ -0,0 +1,377 @@
+/*
+ * USB CDC EEM network interface driver
+ * Copyright (C) 2009 Oberthur Technologies
+ * by Omar Laazimani, Olivier Condemine
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ctype.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/gfp.h>
+
+
+/*
+ * This driver is an implementation of the CDC "Ethernet Emulation
+ * Model" (EEM) specification, which encapsulates Ethernet frames
+ * for transport over USB using a simpler USB device model than the
+ * previous CDC "Ethernet Control Model" (ECM, or "CDC Ethernet").
+ *
+ * For details, see www.usb.org/developers/devclass_docs/CDC_EEM10.pdf
+ *
+ * This version has been tested with GIGAntIC WuaoW SIM Smart Card on 2.6.24,
+ * 2.6.27 and 2.6.30rc2 kernel.
+ * It has also been validated on Openmoko Om 2008.12 (based on 2.6.24 kernel).
+ * build on 23-April-2009
+ */
+
+#define EEM_HEAD	2		/* 2 byte header */
+
+/*-------------------------------------------------------------------------*/
+
+static void eem_linkcmd_complete(struct urb *urb)
+{
+	dev_kfree_skb(urb->context);
+	usb_free_urb(urb);
+}
+
+static void eem_linkcmd(struct usbnet *dev, struct sk_buff *skb)
+{
+	struct urb		*urb;
+	int			status;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb)
+		goto fail;
+
+	usb_fill_bulk_urb(urb, dev->udev, dev->out,
+			skb->data, skb->len, eem_linkcmd_complete, skb);
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status) {
+		usb_free_urb(urb);
+fail:
+		dev_kfree_skb(skb);
+		netdev_warn(dev->net, "link cmd failure\n");
+		return;
+	}
+}
+
+static int eem_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int status = 0;
+
+	status = usbnet_get_endpoints(dev, intf);
+	if (status < 0) {
+		usb_set_intfdata(intf, NULL);
+		usb_driver_release_interface(driver_of(intf), intf);
+		return status;
+	}
+
+	/* no jumbogram (16K) support for now */
+
+	dev->net->hard_header_len += EEM_HEAD + ETH_FCS_LEN;
+	dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
+
+	return 0;
+}
+
+/*
+ * EEM permits packing multiple Ethernet frames into USB transfers
+ * (a "bundle"), but for TX we don't try to do that.
+ */
+static struct sk_buff *eem_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+				       gfp_t flags)
+{
+	struct sk_buff	*skb2 = NULL;
+	u16		len = skb->len;
+	u32		crc = 0;
+	int		padlen = 0;
+
+	/* When ((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket) is
+	 * zero, stick two bytes of zero length EEM packet on the end.
+	 * Else the framework would add invalid single byte padding,
+	 * since it can't know whether ZLPs will be handled right by
+	 * all the relevant hardware and software.
+	 */
+	if (!((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket))
+		padlen += 2;
+
+	if (!skb_cloned(skb)) {
+		int	headroom = skb_headroom(skb);
+		int	tailroom = skb_tailroom(skb);
+
+		if ((tailroom >= ETH_FCS_LEN + padlen) &&
+		    (headroom >= EEM_HEAD))
+			goto done;
+
+		if ((headroom + tailroom)
+				> (EEM_HEAD + ETH_FCS_LEN + padlen)) {
+			skb->data = memmove(skb->head +
+					EEM_HEAD,
+					skb->data,
+					skb->len);
+			skb_set_tail_pointer(skb, len);
+			goto done;
+		}
+	}
+
+	skb2 = skb_copy_expand(skb, EEM_HEAD, ETH_FCS_LEN + padlen, flags);
+	if (!skb2)
+		return NULL;
+
+	dev_kfree_skb_any(skb);
+	skb = skb2;
+
+done:
+	/* we don't use the "no Ethernet CRC" option */
+	crc = crc32_le(~0, skb->data, skb->len);
+	crc = ~crc;
+
+	put_unaligned_le32(crc, skb_put(skb, 4));
+
+	/* EEM packet header format:
+	 * b0..13:	length of ethernet frame
+	 * b14:		bmCRC (1 == valid Ethernet CRC)
+	 * b15:		bmType (0 == data)
+	 */
+	len = skb->len;
+	put_unaligned_le16(BIT(14) | len, skb_push(skb, 2));
+
+	/* Bundle a zero length EEM packet if needed */
+	if (padlen)
+		put_unaligned_le16(0, skb_put(skb, 2));
+
+	return skb;
+}
+
+static int eem_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	/*
+	 * Our task here is to strip off framing, leaving skb with one
+	 * data frame for the usbnet framework code to process.  But we
+	 * may have received multiple EEM payloads, or command payloads.
+	 * So we must process _everything_ as if it's a header, except
+	 * maybe the last data payload
+	 *
+	 * REVISIT the framework needs updating so that when we consume
+	 * all payloads (the last or only message was a command, or a
+	 * zero length EEM packet) that is not accounted as an rx_error.
+	 */
+	do {
+		struct sk_buff	*skb2 = NULL;
+		u16		header;
+		u16		len = 0;
+
+		/* incomplete EEM header? */
+		if (skb->len < EEM_HEAD)
+			return 0;
+
+		/*
+		 * EEM packet header format:
+		 * b0..14:	EEM type dependent (Data or Command)
+		 * b15:		bmType
+		 */
+		header = get_unaligned_le16(skb->data);
+		skb_pull(skb, EEM_HEAD);
+
+		/*
+		 * The bmType bit helps to denote when EEM
+		 * packet is data or command :
+		 *	bmType = 0	: EEM data payload
+		 *	bmType = 1	: EEM (link) command
+		 */
+		if (header & BIT(15)) {
+			u16	bmEEMCmd;
+
+			/*
+			 * EEM (link) command packet:
+			 * b0..10:	bmEEMCmdParam
+			 * b11..13:	bmEEMCmd
+			 * b14:		bmReserved (must be 0)
+			 * b15:		1 (EEM command)
+			 */
+			if (header & BIT(14)) {
+				netdev_dbg(dev->net, "reserved command %04x\n",
+					   header);
+				continue;
+			}
+
+			bmEEMCmd = (header >> 11) & 0x7;
+			switch (bmEEMCmd) {
+
+			/* Responding to echo requests is mandatory. */
+			case 0:		/* Echo command */
+				len = header & 0x7FF;
+
+				/* bogus command? */
+				if (skb->len < len)
+					return 0;
+
+				skb2 = skb_clone(skb, GFP_ATOMIC);
+				if (unlikely(!skb2))
+					goto next;
+				skb_trim(skb2, len);
+				put_unaligned_le16(BIT(15) | (1 << 11) | len,
+						skb_push(skb2, 2));
+				eem_linkcmd(dev, skb2);
+				break;
+
+			/*
+			 * Host may choose to ignore hints.
+			 *  - suspend: peripheral ready to suspend
+			 *  - response: suggest N millisec polling
+			 *  - response complete: suggest N sec polling
+			 */
+			case 2:		/* Suspend hint */
+			case 3:		/* Response hint */
+			case 4:		/* Response complete hint */
+				continue;
+
+			/*
+			 * Hosts should never receive host-to-peripheral
+			 * or reserved command codes; or responses to an
+			 * echo command we didn't send.
+			 */
+			case 1:		/* Echo response */
+			case 5:		/* Tickle */
+			default:	/* reserved */
+				netdev_warn(dev->net,
+					    "unexpected link command %d\n",
+					    bmEEMCmd);
+				continue;
+			}
+
+		} else {
+			u32	crc, crc2;
+			int	is_last;
+
+			/* zero length EEM packet? */
+			if (header == 0)
+				continue;
+
+			/*
+			 * EEM data packet header :
+			 * b0..13:	length of ethernet frame
+			 * b14:		bmCRC
+			 * b15:		0 (EEM data)
+			 */
+			len = header & 0x3FFF;
+
+			/* bogus EEM payload? */
+			if (skb->len < len)
+				return 0;
+
+			/* bogus ethernet frame? */
+			if (len < (ETH_HLEN + ETH_FCS_LEN))
+				goto next;
+
+			/*
+			 * Treat the last payload differently: framework
+			 * code expects our "fixup" to have stripped off
+			 * headers, so "skb" is a data packet (or error).
+			 * Else if it's not the last payload, keep "skb"
+			 * for further processing.
+			 */
+			is_last = (len == skb->len);
+			if (is_last)
+				skb2 = skb;
+			else {
+				skb2 = skb_clone(skb, GFP_ATOMIC);
+				if (unlikely(!skb2))
+					return 0;
+			}
+
+			/*
+			 * The bmCRC helps to denote when the CRC field in
+			 * the Ethernet frame contains a calculated CRC:
+			 *	bmCRC = 1	: CRC is calculated
+			 *	bmCRC = 0	: CRC = 0xDEADBEEF
+			 */
+			if (header & BIT(14)) {
+				crc = get_unaligned_le32(skb2->data
+						+ len - ETH_FCS_LEN);
+				crc2 = ~crc32_le(~0, skb2->data, skb2->len
+						- ETH_FCS_LEN);
+			} else {
+				crc = get_unaligned_be32(skb2->data
+						+ len - ETH_FCS_LEN);
+				crc2 = 0xdeadbeef;
+			}
+			skb_trim(skb2, len - ETH_FCS_LEN);
+
+			if (is_last)
+				return crc == crc2;
+
+			if (unlikely(crc != crc2)) {
+				dev->net->stats.rx_errors++;
+				dev_kfree_skb_any(skb2);
+			} else
+				usbnet_skb_return(dev, skb2);
+		}
+
+next:
+		skb_pull(skb, len);
+	} while (skb->len);
+
+	return 1;
+}
+
+static const struct driver_info eem_info = {
+	.description =	"CDC EEM Device",
+	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT,
+	.bind =		eem_bind,
+	.rx_fixup =	eem_rx_fixup,
+	.tx_fixup =	eem_tx_fixup,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static const struct usb_device_id products[] = {
+{
+	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_EEM,
+			USB_CDC_PROTO_EEM),
+	.driver_info = (unsigned long) &eem_info,
+},
+{
+	/* EMPTY == end of list */
+},
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver eem_driver = {
+	.name =		"cdc_eem",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+module_usb_driver(eem_driver);
+
+MODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>");
+MODULE_DESCRIPTION("USB CDC EEM");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_ether.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_ether.c
new file mode 100644
index 0000000..60d8777
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_ether.c
@@ -0,0 +1,667 @@
+/*
+ * CDC Ethernet based networking peripherals
+ * Copyright (C) 2003-2005 by David Brownell
+ * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync)
+ *
+ * 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	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+
+
+#if defined(CONFIG_USB_NET_RNDIS_HOST) || defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)
+
+static int is_rndis(struct usb_interface_descriptor *desc)
+{
+	return (desc->bInterfaceClass == USB_CLASS_COMM &&
+		desc->bInterfaceSubClass == 2 &&
+		desc->bInterfaceProtocol == 0xff);
+}
+
+static int is_activesync(struct usb_interface_descriptor *desc)
+{
+	return (desc->bInterfaceClass == USB_CLASS_MISC &&
+		desc->bInterfaceSubClass == 1 &&
+		desc->bInterfaceProtocol == 1);
+}
+
+static int is_wireless_rndis(struct usb_interface_descriptor *desc)
+{
+	return (desc->bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER &&
+		desc->bInterfaceSubClass == 1 &&
+		desc->bInterfaceProtocol == 3);
+}
+
+#else
+
+#define is_rndis(desc)		0
+#define is_activesync(desc)	0
+#define is_wireless_rndis(desc)	0
+
+#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.
+ * all pure cdc, except for certain firmware workarounds, and knowing
+ * that rndis uses one different rule.
+ */
+int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	u8				*buf = intf->cur_altsetting->extra;
+	int				len = intf->cur_altsetting->extralen;
+	struct usb_interface_descriptor	*d;
+	struct cdc_state		*info = (void *) &dev->data;
+	int				status;
+	int				rndis;
+	bool				android_rndis_quirk = false;
+	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;
+
+	/* expect strict spec conformance for the descriptors, but
+	 * cope with firmware which stores them in the wrong place
+	 */
+	if (len == 0 && dev->udev->actconfig->extralen) {
+		/* Motorola SB4100 (and others: Brad Hards says it's
+		 * from a Broadcom design) put CDC descriptors here
+		 */
+		buf = dev->udev->actconfig->extra;
+		len = dev->udev->actconfig->extralen;
+		dev_dbg(&intf->dev, "CDC descriptors on config\n");
+	}
+
+	/* Maybe CDC descriptors are after the endpoint?  This bug has
+	 * been seen on some 2Wire Inc RNDIS-ish products.
+	 */
+	if (len == 0) {
+		struct usb_host_endpoint	*hep;
+
+		hep = intf->cur_altsetting->endpoint;
+		if (hep) {
+			buf = hep->extra;
+			len = hep->extralen;
+		}
+		if (len)
+			dev_dbg(&intf->dev,
+				"CDC descriptors on endpoint\n");
+	}
+
+	/* this assumes that if there's a non-RNDIS vendor variant
+	 * of cdc-acm, it'll fail RNDIS requests cleanly.
+	 */
+	rndis = (is_rndis(&intf->cur_altsetting->desc) ||
+		 is_activesync(&intf->cur_altsetting->desc) ||
+		 is_wireless_rndis(&intf->cur_altsetting->desc));
+
+	memset(info, 0, sizeof *info);
+	info->control = intf;
+	while (len > 3) {
+		if (buf [1] != USB_DT_CS_INTERFACE)
+			goto next_desc;
+
+		/* use bDescriptorSubType to identify the CDC descriptors.
+		 * We expect devices with CDC header and union descriptors.
+		 * For CDC Ethernet we need the ethernet descriptor.
+		 * For RNDIS, ignore two (pointless) CDC modem descriptors
+		 * in favor of a complicated OID-based RPC scheme doing what
+		 * CDC Ethernet achieves with a simple descriptor.
+		 */
+		switch (buf [2]) {
+		case USB_CDC_HEADER_TYPE:
+			if (info->header) {
+				dev_dbg(&intf->dev, "extra CDC header\n");
+				goto bad_desc;
+			}
+			info->header = (void *) buf;
+			if (info->header->bLength != sizeof *info->header) {
+				dev_dbg(&intf->dev, "CDC header len %u\n",
+					info->header->bLength);
+				goto bad_desc;
+			}
+			break;
+		case USB_CDC_ACM_TYPE:
+			/* paranoia:  disambiguate a "real" vendor-specific
+			 * modem interface from an RNDIS non-modem.
+			 */
+			if (rndis) {
+				struct usb_cdc_acm_descriptor *acm;
+
+				acm = (void *) buf;
+				if (acm->bmCapabilities) {
+					dev_dbg(&intf->dev,
+						"ACM capabilities %02x, "
+						"not really RNDIS?\n",
+						acm->bmCapabilities);
+					goto bad_desc;
+				}
+			}
+			break;
+		case USB_CDC_UNION_TYPE:
+			if (info->u) {
+				dev_dbg(&intf->dev, "extra CDC union\n");
+				goto bad_desc;
+			}
+			info->u = (void *) buf;
+			if (info->u->bLength != sizeof *info->u) {
+				dev_dbg(&intf->dev, "CDC union len %u\n",
+					info->u->bLength);
+				goto bad_desc;
+			}
+
+			/* we need a master/control interface (what we're
+			 * probed with) and a slave/data interface; union
+			 * descriptors sort this all out.
+			 */
+			info->control = usb_ifnum_to_if(dev->udev,
+						info->u->bMasterInterface0);
+			info->data = usb_ifnum_to_if(dev->udev,
+						info->u->bSlaveInterface0);
+			if (!info->control || !info->data) {
+				dev_dbg(&intf->dev,
+					"master #%u/%p slave #%u/%p\n",
+					info->u->bMasterInterface0,
+					info->control,
+					info->u->bSlaveInterface0,
+					info->data);
+				/* fall back to hard-wiring for RNDIS */
+				if (rndis) {
+					android_rndis_quirk = true;
+					goto next_desc;
+				}
+				goto bad_desc;
+			}
+			if (info->control != intf) {
+				dev_dbg(&intf->dev, "bogus CDC Union\n");
+				/* Ambit USB Cable Modem (and maybe others)
+				 * interchanges master and slave interface.
+				 */
+				if (info->data == intf) {
+					info->data = info->control;
+					info->control = intf;
+				} else
+					goto bad_desc;
+			}
+
+			/* a data interface altsetting does the real i/o */
+			d = &info->data->cur_altsetting->desc;
+			if (d->bInterfaceClass != USB_CLASS_CDC_DATA) {
+				dev_dbg(&intf->dev, "slave class %u\n",
+					d->bInterfaceClass);
+				goto bad_desc;
+			}
+			break;
+		case USB_CDC_ETHERNET_TYPE:
+			if (info->ether) {
+				dev_dbg(&intf->dev, "extra CDC ether\n");
+				goto bad_desc;
+			}
+			info->ether = (void *) buf;
+			if (info->ether) {
+				if (info->ether->bLength != sizeof *info->ether) {
+					dev_dbg(&intf->dev, "CDC ether len %u\n",
+						info->ether->bLength);
+					goto bad_desc;
+				}
+				if (info->ether->wMaxSegmentSize){
+					dev->hard_mtu = le16_to_cpu(
+								info->ether->wMaxSegmentSize);
+					/* because of Zaurus, we may be ignoring the host
+					 * 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 */
+		buf += buf [0];
+	}
+
+	/* Microsoft ActiveSync based and some regular RNDIS devices lack the
+	 * CDC descriptors, so we'll hard-wire the interfaces and not check
+	 * for descriptors.
+	 *
+	 * Some Android RNDIS devices have a CDC Union descriptor pointing
+	 * to non-existing interfaces.  Ignore that and attempt the same
+	 * hard-wired 0 and 1 interfaces.
+	 */
+	if (rndis && (!info->u || android_rndis_quirk)) {
+		info->control = usb_ifnum_to_if(dev->udev, 0);
+		info->data = usb_ifnum_to_if(dev->udev, 1);
+		if (!info->control || !info->data || info->control != intf) {
+			dev_dbg(&intf->dev,
+				"rndis: master #0/%p slave #1/%p\n",
+				info->control,
+				info->data);
+			goto bad_desc;
+		}
+
+	} else if (!info->header || !info->u || (!rndis && !info->ether)) {
+		dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n",
+			info->header ? "" : "header ",
+			info->u ? "" : "union ",
+			info->ether ? "" : "ether ");
+		goto bad_desc;
+	}
+
+	/* claim data interface and set it up ... with side effects.
+	 * network traffic can't flow until an altsetting is enabled.
+	 */
+	status = usb_driver_claim_interface(driver, info->data, dev);
+	if (status < 0)
+		return status;
+	status = usbnet_get_endpoints(dev, info->data);
+	if (status < 0) {
+		/* ensure immediate exit from usbnet_disconnect */
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver, info->data);
+		return status;
+	}
+
+	/* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */
+	dev->status = NULL;
+	if (info->control->cur_altsetting->desc.bNumEndpoints == 1) {
+		struct usb_endpoint_descriptor	*desc;
+
+		dev->status = &info->control->cur_altsetting->endpoint [0];
+		desc = &dev->status->desc;
+		if (!usb_endpoint_is_int_in(desc) ||
+		    (le16_to_cpu(desc->wMaxPacketSize)
+		     < sizeof(struct usb_cdc_notification)) ||
+		    !desc->bInterval) {
+			dev_dbg(&intf->dev, "bad notification endpoint\n");
+			dev->status = NULL;
+		}
+	}
+	if (rndis && !dev->status) {
+		dev_dbg(&intf->dev, "missing RNDIS status endpoint\n");
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver, info->data);
+		return -ENODEV;
+	}
+	return 0;
+
+bad_desc:
+	dev_info(&dev->udev->dev, "bad CDC descriptors\n");
+	return -ENODEV;
+}
+EXPORT_SYMBOL_GPL(usbnet_generic_cdc_bind);
+
+void usbnet_cdc_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct cdc_state		*info = (void *) &dev->data;
+	struct usb_driver		*driver = driver_of(intf);
+
+	/* disconnect master --> disconnect slave */
+	if (intf == info->control && info->data) {
+		/* ensure immediate exit from usbnet_disconnect */
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver, info->data);
+		info->data = NULL;
+	}
+
+	/* and vice versa (just in case) */
+	else if (intf == info->data && info->control) {
+		/* ensure immediate exit from usbnet_disconnect */
+		usb_set_intfdata(info->control, NULL);
+		usb_driver_release_interface(driver, info->control);
+		info->control = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(usbnet_cdc_unbind);
+
+/*-------------------------------------------------------------------------
+ *
+ * Communications Device Class, Ethernet Control model
+ *
+ * Takes two interfaces.  The DATA interface is inactive till an altsetting
+ * is selected.  Configuration data includes class descriptors.  There's
+ * an optional status endpoint on the control interface.
+ *
+ * This should interop with whatever the 2.4 "CDCEther.c" driver
+ * (by Brad Hards) talked with, with more functionality.
+ *
+ *-------------------------------------------------------------------------*/
+
+static void dumpspeed(struct usbnet *dev, __le32 *speeds)
+{
+	netif_info(dev, timer, dev->net,
+		   "link speeds: %u kbps up, %u kbps down\n",
+		   __le32_to_cpu(speeds[0]) / 1000,
+		   __le32_to_cpu(speeds[1]) / 1000);
+}
+
+void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)
+{
+	struct usb_cdc_notification	*event;
+
+	if (urb->actual_length < sizeof *event)
+		return;
+
+	/* SPEED_CHANGE can get split into two 8-byte packets */
+	if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) {
+		dumpspeed(dev, (__le32 *) urb->transfer_buffer);
+		return;
+	}
+
+	event = urb->transfer_buffer;
+	switch (event->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		netif_dbg(dev, timer, dev->net, "CDC: carrier %s\n",
+			  event->wValue ? "on" : "off");
+		if (event->wValue)
+			netif_carrier_on(dev->net);
+		else
+			netif_carrier_off(dev->net);
+		break;
+	case USB_CDC_NOTIFY_SPEED_CHANGE:	/* tx/rx rates */
+		netif_dbg(dev, timer, dev->net, "CDC: speed change (len %d)\n",
+			  urb->actual_length);
+		if (urb->actual_length != (sizeof *event + 8))
+			set_bit(EVENT_STS_SPLIT, &dev->flags);
+		else
+			dumpspeed(dev, (__le32 *) &event[1]);
+		break;
+	/* USB_CDC_NOTIFY_RESPONSE_AVAILABLE can happen too (e.g. RNDIS),
+	 * but there are no standard formats for the response data.
+	 */
+	default:
+		netdev_err(dev->net, "CDC: unexpected notification %02x!\n",
+			   event->bNotificationType);
+		break;
+	}
+}
+EXPORT_SYMBOL_GPL(usbnet_cdc_status);
+
+int usbnet_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int				status;
+	struct cdc_state		*info = (void *) &dev->data;
+
+	BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data)
+			< sizeof(struct cdc_state)));
+
+	status = usbnet_generic_cdc_bind(dev, intf);
+	if (status < 0)
+		return status;
+
+	status = usbnet_get_ethernet_addr(dev, info->ether->iMACAddress);
+	if (status < 0) {
+		usb_set_intfdata(info->data, NULL);
+		usb_driver_release_interface(driver_of(intf), info->data);
+		return status;
+	}
+
+	/* FIXME cdc-ether has some multicast code too, though it complains
+	 * in routine cases.  info->ether describes the multicast support.
+	 * Implement that here, manipulating the cdc filter as needed.
+	 */
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_cdc_bind);
+
+static int cdc_manage_power(struct usbnet *dev, int on)
+{
+	dev->intf->needs_remote_wakeup = on;
+	return 0;
+}
+
+static const struct driver_info	cdc_info = {
+	.description =	"CDC Ethernet Device",
+	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT,
+	// .check_connect = cdc_check_connect,
+	.bind =		usbnet_cdc_bind,
+	.unbind =	usbnet_cdc_unbind,
+	.status =	usbnet_cdc_status,
+	.manage_power =	cdc_manage_power,
+};
+
+static const struct driver_info wwan_info = {
+	.description =	"Mobile Broadband Network Device",
+	.flags =	FLAG_WWAN,
+	.bind =		usbnet_cdc_bind,
+	.unbind =	usbnet_cdc_unbind,
+	.status =	usbnet_cdc_status,
+	.manage_power =	cdc_manage_power,
+};
+
+/*-------------------------------------------------------------------------*/
+
+#define HUAWEI_VENDOR_ID	0x12D1
+#define NOVATEL_VENDOR_ID	0x1410
+
+static const struct usb_device_id	products [] = {
+/*
+ * BLACKLIST !!
+ *
+ * First blacklist any products that are egregiously nonconformant
+ * with the CDC Ethernet specs.  Minor braindamage we cope with; when
+ * they're not even trying, needing a separate driver is only the first
+ * of the differences to show up.
+ */
+
+#define	ZAURUS_MASTER_INTERFACE \
+	.bInterfaceClass	= USB_CLASS_COMM, \
+	.bInterfaceSubClass	= USB_CDC_SUBCLASS_ETHERNET, \
+	.bInterfaceProtocol	= USB_CDC_PROTO_NONE
+
+/* SA-1100 based Sharp Zaurus ("collie"), or compatible;
+ * wire-incompatible with true CDC Ethernet implementations.
+ * (And, it seems, needlessly so...)
+ */
+{
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8004,
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+},
+
+/* PXA-25x based Sharp Zaurii.  Note that it seems some of these
+ * (later models especially) may have shipped only with firmware
+ * advertising false "CDC MDLM" compatibility ... but we're not
+ * clear which models did that, so for now let's assume the worst.
+ */
+{
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8005,	/* A-300 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8006,	/* B-500/SL-5600 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+	          | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8007,	/* C-700 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	.idProduct              = 0x9031,	/* C-750 C-760 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	.idProduct              = 0x9032,	/* SL-6000 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	/* reported with some C860 units */
+	.idProduct              = 0x9050,	/* C-860 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+},
+
+/* Olympus has some models with a Zaurus-compatible option.
+ * R-1000 uses a FreeScale i.MXL cpu (ARMv4T)
+ */
+{
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x07B4,
+	.idProduct              = 0x0F02,	/* R-1000 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info		= 0,
+},
+
+/* LG Electronics VL600 wants additional headers on every frame */
+{
+	USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+	.driver_info = 0,
+},
+
+/* Logitech Harmony 900 - uses the pseudo-MDLM (BLAN) driver */
+{
+	USB_DEVICE_AND_INTERFACE_INFO(0x046d, 0xc11f, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE),
+	.driver_info		= 0,
+},
+
+/*
+ * WHITELIST!!!
+ *
+ * CDC Ether uses two interfaces, not necessarily consecutive.
+ * We match the main interface, ignoring the optional device
+ * class so we could handle devices that aren't exclusively
+ * CDC ether.
+ *
+ * NOTE:  this match must come AFTER entries blacklisting devices
+ * because of bugs/quirks in a given product (like Zaurus, above).
+ */
+{
+	/* Novatel USB551L */
+	/* This match must come *before* the generic CDC-ETHER match so that
+	 * we get FLAG_WWAN set on the device, since it's descriptors are
+	 * generic CDC-ETHER.
+	 */
+	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
+		 | USB_DEVICE_ID_MATCH_PRODUCT
+		 | USB_DEVICE_ID_MATCH_INT_INFO,
+	.idVendor               = NOVATEL_VENDOR_ID,
+	.idProduct		= 0xB001,
+	.bInterfaceClass	= USB_CLASS_COMM,
+	.bInterfaceSubClass	= USB_CDC_SUBCLASS_ETHERNET,
+	.bInterfaceProtocol	= USB_CDC_PROTO_NONE,
+	.driver_info = (unsigned long)&wwan_info,
+}, {
+	/* Telit modules */
+	USB_VENDOR_AND_INTERFACE_INFO(0x1bc7, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+	.driver_info = (kernel_ulong_t) &wwan_info,
+}, {
+	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET,
+			USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &cdc_info,
+}, {
+	USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MDLM,
+			USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long)&wwan_info,
+
+}, {
+	/* Various Huawei modems with a network port like the UMG1831 */
+	.match_flags    =   USB_DEVICE_ID_MATCH_VENDOR
+		 | USB_DEVICE_ID_MATCH_INT_INFO,
+	.idVendor               = HUAWEI_VENDOR_ID,
+	.bInterfaceClass	= USB_CLASS_COMM,
+	.bInterfaceSubClass	= USB_CDC_SUBCLASS_ETHERNET,
+	.bInterfaceProtocol	= 255,
+	.driver_info = (unsigned long)&wwan_info,
+},
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver cdc_driver = {
+	.name =		"cdc_ether",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+	.reset_resume =	usbnet_resume,
+	.supports_autosuspend = 1,
+};
+
+module_usb_driver(cdc_driver);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("USB CDC Ethernet devices");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_ncm.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_ncm.c
new file mode 100644
index 0000000..7adc9f6
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_ncm.c
@@ -0,0 +1,1231 @@
+/*
+ * cdc_ncm.c
+ *
+ * Copyright (C) ST-Ericsson 2010-2012
+ * Contact: Alexey Orishko <alexey.orishko@stericsson.com>
+ * Original author: Hans Petter Selasky <hans.petter.selasky@stericsson.com>
+ *
+ * USB Host Driver for Network Control Model (NCM)
+ * http://www.usb.org/developers/devclass_docs/NCM10.zip
+ *
+ * The NCM encoding, decoding and initialization logic
+ * derives from FreeBSD 8.x. if_cdce.c and if_cdcereg.h
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose this file to be licensed under the terms
+ * of the GNU General Public License (GPL) Version 2 or the 2-clause
+ * BSD license listed below:
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/ctype.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/crc32.h>
+#include <linux/usb.h>
+#include <linux/hrtimer.h>
+#include <linux/atomic.h>
+#include <linux/usb/usbnet.h>
+#include <linux/usb/cdc.h>
+
+#define	DRIVER_VERSION				"14-Mar-2012"
+
+/* CDC NCM subclass 3.2.1 */
+#define USB_CDC_NCM_NDP16_LENGTH_MIN		0x10
+
+/* Maximum NTB length */
+#define	CDC_NCM_NTB_MAX_SIZE_TX			32768	/* bytes */
+#define	CDC_NCM_NTB_MAX_SIZE_RX			32768	/* bytes */
+
+/* Minimum value for MaxDatagramSize, ch. 6.2.9 */
+#define	CDC_NCM_MIN_DATAGRAM_SIZE		1514	/* bytes */
+
+#define	CDC_NCM_MIN_TX_PKT			512	/* bytes */
+
+/* Default value for MaxDatagramSize */
+#define	CDC_NCM_MAX_DATAGRAM_SIZE		8192	/* bytes */
+
+/*
+ * Maximum amount of datagrams in NCM Datagram Pointer Table, not counting
+ * the last NULL entry.
+ */
+#define	CDC_NCM_DPT_DATAGRAMS_MAX		40
+
+/* Restart the timer, if amount of datagrams is less than given value */
+#define	CDC_NCM_RESTART_TIMER_DATAGRAM_CNT	3
+#define	CDC_NCM_TIMER_PENDING_CNT		2
+#define CDC_NCM_TIMER_INTERVAL			(400UL * NSEC_PER_USEC)
+
+/* The following macro defines the minimum header space */
+#define	CDC_NCM_MIN_HDR_SIZE \
+	(sizeof(struct usb_cdc_ncm_nth16) + sizeof(struct usb_cdc_ncm_ndp16) + \
+	(CDC_NCM_DPT_DATAGRAMS_MAX + 1) * sizeof(struct usb_cdc_ncm_dpe16))
+
+struct cdc_ncm_data {
+	struct usb_cdc_ncm_nth16 nth16;
+	struct usb_cdc_ncm_ndp16 ndp16;
+	struct usb_cdc_ncm_dpe16 dpe16[CDC_NCM_DPT_DATAGRAMS_MAX + 1];
+};
+
+struct cdc_ncm_ctx {
+	struct cdc_ncm_data tx_ncm;
+	struct usb_cdc_ncm_ntb_parameters ncm_parm;
+	struct hrtimer tx_timer;
+	struct tasklet_struct bh;
+
+	const struct usb_cdc_ncm_desc *func_desc;
+	const struct usb_cdc_header_desc *header_desc;
+	const struct usb_cdc_union_desc *union_desc;
+	const struct usb_cdc_ether_desc *ether_desc;
+
+	struct net_device *netdev;
+	struct usb_device *udev;
+	struct usb_host_endpoint *in_ep;
+	struct usb_host_endpoint *out_ep;
+	struct usb_host_endpoint *status_ep;
+	struct usb_interface *intf;
+	struct usb_interface *control;
+	struct usb_interface *data;
+
+	struct sk_buff *tx_curr_skb;
+	struct sk_buff *tx_rem_skb;
+
+	spinlock_t mtx;
+	atomic_t stop;
+
+	u32 tx_timer_pending;
+	u32 tx_curr_offset;
+	u32 tx_curr_last_offset;
+	u32 tx_curr_frame_num;
+	u32 rx_speed;
+	u32 tx_speed;
+	u32 rx_max;
+	u32 tx_max;
+	u32 max_datagram_size;
+	u16 tx_max_datagrams;
+	u16 tx_remainder;
+	u16 tx_modulus;
+	u16 tx_ndp_modulus;
+	u16 tx_seq;
+	u16 rx_seq;
+	u16 connected;
+};
+
+static void cdc_ncm_txpath_bh(unsigned long param);
+static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx);
+static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *hr_timer);
+static const struct driver_info cdc_ncm_info;
+static struct usb_driver cdc_ncm_driver;
+static const struct ethtool_ops cdc_ncm_ethtool_ops;
+
+static const struct usb_device_id cdc_devs[] = {
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM,
+		USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
+		.driver_info = (unsigned long)&cdc_ncm_info,
+	},
+	{
+	},
+};
+
+MODULE_DEVICE_TABLE(usb, cdc_devs);
+
+static void
+cdc_ncm_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	strncpy(info->driver, dev->driver_name, sizeof(info->driver));
+	strncpy(info->version, DRIVER_VERSION, sizeof(info->version));
+	strncpy(info->fw_version, dev->driver_info->description,
+		sizeof(info->fw_version));
+	usb_make_path(dev->udev, info->bus_info, sizeof(info->bus_info));
+}
+
+static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx)
+{
+	u32 val;
+	u8 flags;
+	u8 iface_no;
+	int err;
+	u16 ntb_fmt_supported;
+
+	iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;
+
+	err = usb_control_msg(ctx->udev,
+				usb_rcvctrlpipe(ctx->udev, 0),
+				USB_CDC_GET_NTB_PARAMETERS,
+				USB_TYPE_CLASS | USB_DIR_IN
+				 | USB_RECIP_INTERFACE,
+				0, iface_no, &ctx->ncm_parm,
+				sizeof(ctx->ncm_parm), 10000);
+	if (err < 0) {
+		pr_debug("failed GET_NTB_PARAMETERS\n");
+		return 1;
+	}
+
+	/* read correct set of parameters according to device mode */
+	ctx->rx_max = le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize);
+	ctx->tx_max = le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize);
+	ctx->tx_remainder = le16_to_cpu(ctx->ncm_parm.wNdpOutPayloadRemainder);
+	ctx->tx_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutDivisor);
+	ctx->tx_ndp_modulus = le16_to_cpu(ctx->ncm_parm.wNdpOutAlignment);
+	/* devices prior to NCM Errata shall set this field to zero */
+	ctx->tx_max_datagrams = le16_to_cpu(ctx->ncm_parm.wNtbOutMaxDatagrams);
+	ntb_fmt_supported = le16_to_cpu(ctx->ncm_parm.bmNtbFormatsSupported);
+
+	if (ctx->func_desc != NULL)
+		flags = ctx->func_desc->bmNetworkCapabilities;
+	else
+		flags = 0;
+
+	pr_debug("dwNtbInMaxSize=%u dwNtbOutMaxSize=%u "
+		 "wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u "
+		 "wNdpOutAlignment=%u wNtbOutMaxDatagrams=%u flags=0x%x\n",
+		 ctx->rx_max, ctx->tx_max, ctx->tx_remainder, ctx->tx_modulus,
+		 ctx->tx_ndp_modulus, ctx->tx_max_datagrams, flags);
+
+	/* max count of tx datagrams */
+	if ((ctx->tx_max_datagrams == 0) ||
+			(ctx->tx_max_datagrams > CDC_NCM_DPT_DATAGRAMS_MAX))
+		ctx->tx_max_datagrams = CDC_NCM_DPT_DATAGRAMS_MAX;
+
+	/* verify maximum size of received NTB in bytes */
+	if (ctx->rx_max < USB_CDC_NCM_NTB_MIN_IN_SIZE) {
+		pr_debug("Using min receive length=%d\n",
+						USB_CDC_NCM_NTB_MIN_IN_SIZE);
+		ctx->rx_max = USB_CDC_NCM_NTB_MIN_IN_SIZE;
+	}
+
+	if (ctx->rx_max > CDC_NCM_NTB_MAX_SIZE_RX) {
+		pr_debug("Using default maximum receive length=%d\n",
+						CDC_NCM_NTB_MAX_SIZE_RX);
+		ctx->rx_max = CDC_NCM_NTB_MAX_SIZE_RX;
+	}
+
+	/* inform device about NTB input size changes */
+	if (ctx->rx_max != le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)) {
+
+		if (flags & USB_CDC_NCM_NCAP_NTB_INPUT_SIZE) {
+			struct usb_cdc_ncm_ndp_input_size *ndp_in_sz;
+
+			ndp_in_sz = kzalloc(sizeof(*ndp_in_sz), GFP_KERNEL);
+			if (!ndp_in_sz) {
+				err = -ENOMEM;
+				goto size_err;
+			}
+
+			err = usb_control_msg(ctx->udev,
+					usb_sndctrlpipe(ctx->udev, 0),
+					USB_CDC_SET_NTB_INPUT_SIZE,
+					USB_TYPE_CLASS | USB_DIR_OUT
+					 | USB_RECIP_INTERFACE,
+					0, iface_no, ndp_in_sz, 8, 1000);
+			kfree(ndp_in_sz);
+		} else {
+			__le32 *dwNtbInMaxSize;
+			dwNtbInMaxSize = kzalloc(sizeof(*dwNtbInMaxSize),
+					GFP_KERNEL);
+			if (!dwNtbInMaxSize) {
+				err = -ENOMEM;
+				goto size_err;
+			}
+			*dwNtbInMaxSize = cpu_to_le32(ctx->rx_max);
+
+			err = usb_control_msg(ctx->udev,
+					usb_sndctrlpipe(ctx->udev, 0),
+					USB_CDC_SET_NTB_INPUT_SIZE,
+					USB_TYPE_CLASS | USB_DIR_OUT
+					 | USB_RECIP_INTERFACE,
+					0, iface_no, dwNtbInMaxSize, 4, 1000);
+			kfree(dwNtbInMaxSize);
+		}
+size_err:
+		if (err < 0)
+			pr_debug("Setting NTB Input Size failed\n");
+	}
+
+	/* verify maximum size of transmitted NTB in bytes */
+	if ((ctx->tx_max <
+	    (CDC_NCM_MIN_HDR_SIZE + CDC_NCM_MIN_DATAGRAM_SIZE)) ||
+	    (ctx->tx_max > CDC_NCM_NTB_MAX_SIZE_TX)) {
+		pr_debug("Using default maximum transmit length=%d\n",
+						CDC_NCM_NTB_MAX_SIZE_TX);
+		ctx->tx_max = CDC_NCM_NTB_MAX_SIZE_TX;
+	}
+
+	/*
+	 * verify that the structure alignment is:
+	 * - power of two
+	 * - not greater than the maximum transmit length
+	 * - not less than four bytes
+	 */
+	val = ctx->tx_ndp_modulus;
+
+	if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||
+	    (val != ((-val) & val)) || (val >= ctx->tx_max)) {
+		pr_debug("Using default alignment: 4 bytes\n");
+		ctx->tx_ndp_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;
+	}
+
+	/*
+	 * verify that the payload alignment is:
+	 * - power of two
+	 * - not greater than the maximum transmit length
+	 * - not less than four bytes
+	 */
+	val = ctx->tx_modulus;
+
+	if ((val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) ||
+	    (val != ((-val) & val)) || (val >= ctx->tx_max)) {
+		pr_debug("Using default transmit modulus: 4 bytes\n");
+		ctx->tx_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE;
+	}
+
+	/* verify the payload remainder */
+	if (ctx->tx_remainder >= ctx->tx_modulus) {
+		pr_debug("Using default transmit remainder: 0 bytes\n");
+		ctx->tx_remainder = 0;
+	}
+
+	/* adjust TX-remainder according to NCM specification. */
+	ctx->tx_remainder = ((ctx->tx_remainder - ETH_HLEN) &
+						(ctx->tx_modulus - 1));
+
+	/* additional configuration */
+
+	/* set CRC Mode */
+	if (flags & USB_CDC_NCM_NCAP_CRC_MODE) {
+		err = usb_control_msg(ctx->udev, usb_sndctrlpipe(ctx->udev, 0),
+				USB_CDC_SET_CRC_MODE,
+				USB_TYPE_CLASS | USB_DIR_OUT
+				 | USB_RECIP_INTERFACE,
+				USB_CDC_NCM_CRC_NOT_APPENDED,
+				iface_no, NULL, 0, 1000);
+		if (err < 0)
+			pr_debug("Setting CRC mode off failed\n");
+	}
+
+	/* set NTB format, if both formats are supported */
+	if (ntb_fmt_supported & USB_CDC_NCM_NTH32_SIGN) {
+		err = usb_control_msg(ctx->udev, usb_sndctrlpipe(ctx->udev, 0),
+				USB_CDC_SET_NTB_FORMAT, USB_TYPE_CLASS
+				 | USB_DIR_OUT | USB_RECIP_INTERFACE,
+				USB_CDC_NCM_NTB16_FORMAT,
+				iface_no, NULL, 0, 1000);
+		if (err < 0)
+			pr_debug("Setting NTB format to 16-bit failed\n");
+	}
+
+	ctx->max_datagram_size = CDC_NCM_MIN_DATAGRAM_SIZE;
+
+	/* set Max Datagram Size (MTU) */
+	if (flags & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE) {
+		__le16 *max_datagram_size;
+		u16 eth_max_sz = le16_to_cpu(ctx->ether_desc->wMaxSegmentSize);
+
+		max_datagram_size = kzalloc(sizeof(*max_datagram_size),
+				GFP_KERNEL);
+		if (!max_datagram_size) {
+			err = -ENOMEM;
+			goto max_dgram_err;
+		}
+
+		err = usb_control_msg(ctx->udev, usb_rcvctrlpipe(ctx->udev, 0),
+				USB_CDC_GET_MAX_DATAGRAM_SIZE,
+				USB_TYPE_CLASS | USB_DIR_IN
+				 | USB_RECIP_INTERFACE,
+				0, iface_no, max_datagram_size,
+				2, 1000);
+		if (err < 0) {
+			pr_debug("GET_MAX_DATAGRAM_SIZE failed, use size=%u\n",
+						CDC_NCM_MIN_DATAGRAM_SIZE);
+		} else {
+			ctx->max_datagram_size =
+				le16_to_cpu(*max_datagram_size);
+			/* Check Eth descriptor value */
+			if (ctx->max_datagram_size > eth_max_sz)
+					ctx->max_datagram_size = eth_max_sz;
+
+			if (ctx->max_datagram_size > CDC_NCM_MAX_DATAGRAM_SIZE)
+				ctx->max_datagram_size =
+						CDC_NCM_MAX_DATAGRAM_SIZE;
+
+			if (ctx->max_datagram_size < CDC_NCM_MIN_DATAGRAM_SIZE)
+				ctx->max_datagram_size =
+					CDC_NCM_MIN_DATAGRAM_SIZE;
+
+			/* if value changed, update device */
+			if (ctx->max_datagram_size !=
+					le16_to_cpu(*max_datagram_size)) {
+				err = usb_control_msg(ctx->udev,
+						usb_sndctrlpipe(ctx->udev, 0),
+						USB_CDC_SET_MAX_DATAGRAM_SIZE,
+						USB_TYPE_CLASS | USB_DIR_OUT
+						 | USB_RECIP_INTERFACE,
+						0,
+						iface_no, max_datagram_size,
+						2, 1000);
+				if (err < 0)
+					pr_debug("SET_MAX_DGRAM_SIZE failed\n");
+			}
+		}
+		kfree(max_datagram_size);
+	}
+
+max_dgram_err:
+	if (ctx->netdev->mtu != (ctx->max_datagram_size - ETH_HLEN))
+		ctx->netdev->mtu = ctx->max_datagram_size - ETH_HLEN;
+
+	return 0;
+}
+
+static void
+cdc_ncm_find_endpoints(struct cdc_ncm_ctx *ctx, struct usb_interface *intf)
+{
+	struct usb_host_endpoint *e;
+	u8 ep;
+
+	for (ep = 0; ep < intf->cur_altsetting->desc.bNumEndpoints; ep++) {
+
+		e = intf->cur_altsetting->endpoint + ep;
+		switch (e->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
+		case USB_ENDPOINT_XFER_INT:
+			if (usb_endpoint_dir_in(&e->desc)) {
+				if (ctx->status_ep == NULL)
+					ctx->status_ep = e;
+			}
+			break;
+
+		case USB_ENDPOINT_XFER_BULK:
+			if (usb_endpoint_dir_in(&e->desc)) {
+				if (ctx->in_ep == NULL)
+					ctx->in_ep = e;
+			} else {
+				if (ctx->out_ep == NULL)
+					ctx->out_ep = e;
+			}
+			break;
+
+		default:
+			break;
+		}
+	}
+}
+
+static void cdc_ncm_free(struct cdc_ncm_ctx *ctx)
+{
+	if (ctx == NULL)
+		return;
+
+	if (ctx->tx_rem_skb != NULL) {
+		dev_kfree_skb_any(ctx->tx_rem_skb);
+		ctx->tx_rem_skb = NULL;
+	}
+
+	if (ctx->tx_curr_skb != NULL) {
+		dev_kfree_skb_any(ctx->tx_curr_skb);
+		ctx->tx_curr_skb = NULL;
+	}
+
+	kfree(ctx);
+}
+
+static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct cdc_ncm_ctx *ctx;
+	struct usb_driver *driver;
+	u8 *buf;
+	int len;
+	int temp;
+	u8 iface_no;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (ctx == NULL)
+		return -ENODEV;
+
+	hrtimer_init(&ctx->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	ctx->tx_timer.function = &cdc_ncm_tx_timer_cb;
+	ctx->bh.data = (unsigned long)ctx;
+	ctx->bh.func = cdc_ncm_txpath_bh;
+	atomic_set(&ctx->stop, 0);
+	spin_lock_init(&ctx->mtx);
+	ctx->netdev = dev->net;
+
+	/* store ctx pointer in device data field */
+	dev->data[0] = (unsigned long)ctx;
+
+	/* get some pointers */
+	driver = driver_of(intf);
+	buf = intf->cur_altsetting->extra;
+	len = intf->cur_altsetting->extralen;
+
+	ctx->udev = dev->udev;
+	ctx->intf = intf;
+
+	/* parse through descriptors associated with control interface */
+	while ((len > 0) && (buf[0] > 2) && (buf[0] <= len)) {
+
+		if (buf[1] != USB_DT_CS_INTERFACE)
+			goto advance;
+
+		switch (buf[2]) {
+		case USB_CDC_UNION_TYPE:
+			if (buf[0] < sizeof(*(ctx->union_desc)))
+				break;
+
+			ctx->union_desc =
+					(const struct usb_cdc_union_desc *)buf;
+
+			ctx->control = usb_ifnum_to_if(dev->udev,
+					ctx->union_desc->bMasterInterface0);
+			ctx->data = usb_ifnum_to_if(dev->udev,
+					ctx->union_desc->bSlaveInterface0);
+			break;
+
+		case USB_CDC_ETHERNET_TYPE:
+			if (buf[0] < sizeof(*(ctx->ether_desc)))
+				break;
+
+			ctx->ether_desc =
+					(const struct usb_cdc_ether_desc *)buf;
+			dev->hard_mtu =
+				le16_to_cpu(ctx->ether_desc->wMaxSegmentSize);
+
+			if (dev->hard_mtu < CDC_NCM_MIN_DATAGRAM_SIZE)
+				dev->hard_mtu =	CDC_NCM_MIN_DATAGRAM_SIZE;
+			else if (dev->hard_mtu > CDC_NCM_MAX_DATAGRAM_SIZE)
+				dev->hard_mtu =	CDC_NCM_MAX_DATAGRAM_SIZE;
+			break;
+
+		case USB_CDC_NCM_TYPE:
+			if (buf[0] < sizeof(*(ctx->func_desc)))
+				break;
+
+			ctx->func_desc = (const struct usb_cdc_ncm_desc *)buf;
+			break;
+
+		default:
+			break;
+		}
+advance:
+		/* advance to next descriptor */
+		temp = buf[0];
+		buf += temp;
+		len -= temp;
+	}
+
+	/* check if we got everything */
+	if ((ctx->control == NULL) || (ctx->data == NULL) ||
+	    (ctx->ether_desc == NULL) || (ctx->control != intf))
+		goto error;
+
+	/* claim interfaces, if any */
+	temp = usb_driver_claim_interface(driver, ctx->data, dev);
+	if (temp)
+		goto error;
+
+	iface_no = ctx->data->cur_altsetting->desc.bInterfaceNumber;
+
+	/* reset data interface */
+	temp = usb_set_interface(dev->udev, iface_no, 0);
+	if (temp)
+		goto error2;
+
+	/* initialize data interface */
+	if (cdc_ncm_setup(ctx))
+		goto error2;
+
+	/* configure data interface */
+	temp = usb_set_interface(dev->udev, iface_no, 1);
+	if (temp)
+		goto error2;
+
+	cdc_ncm_find_endpoints(ctx, ctx->data);
+	cdc_ncm_find_endpoints(ctx, ctx->control);
+
+	if ((ctx->in_ep == NULL) || (ctx->out_ep == NULL) ||
+	    (ctx->status_ep == NULL))
+		goto error2;
+
+	dev->net->ethtool_ops = &cdc_ncm_ethtool_ops;
+
+	usb_set_intfdata(ctx->data, dev);
+	usb_set_intfdata(ctx->control, dev);
+	usb_set_intfdata(ctx->intf, dev);
+
+	temp = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress);
+	if (temp)
+		goto error2;
+
+	dev_info(&dev->udev->dev, "MAC-Address: %pM\n", dev->net->dev_addr);
+
+	dev->in = usb_rcvbulkpipe(dev->udev,
+		ctx->in_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	dev->out = usb_sndbulkpipe(dev->udev,
+		ctx->out_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	dev->status = ctx->status_ep;
+	dev->rx_urb_size = ctx->rx_max;
+
+	/*
+	 * We should get an event when network connection is "connected" or
+	 * "disconnected". Set network connection in "disconnected" state
+	 * (carrier is OFF) during attach, so the IP network stack does not
+	 * start IPv6 negotiation and more.
+	 */
+	netif_carrier_off(dev->net);
+	ctx->tx_speed = ctx->rx_speed = 0;
+	return 0;
+
+error2:
+	usb_set_intfdata(ctx->control, NULL);
+	usb_set_intfdata(ctx->data, NULL);
+	usb_driver_release_interface(driver, ctx->data);
+error:
+	cdc_ncm_free((struct cdc_ncm_ctx *)dev->data[0]);
+	dev->data[0] = 0;
+	dev_info(&dev->udev->dev, "bind() failure\n");
+	return -ENODEV;
+}
+
+static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
+	struct usb_driver *driver = driver_of(intf);
+
+	if (ctx == NULL)
+		return;		/* no setup */
+
+	atomic_set(&ctx->stop, 1);
+
+	if (hrtimer_active(&ctx->tx_timer))
+		hrtimer_cancel(&ctx->tx_timer);
+
+	tasklet_kill(&ctx->bh);
+
+	/* disconnect master --> disconnect slave */
+	if (intf == ctx->control && ctx->data) {
+		usb_set_intfdata(ctx->data, NULL);
+		usb_driver_release_interface(driver, ctx->data);
+		ctx->data = NULL;
+
+	} else if (intf == ctx->data && ctx->control) {
+		usb_set_intfdata(ctx->control, NULL);
+		usb_driver_release_interface(driver, ctx->control);
+		ctx->control = NULL;
+	}
+
+	usb_set_intfdata(ctx->intf, NULL);
+	cdc_ncm_free(ctx);
+}
+
+static void cdc_ncm_zero_fill(u8 *ptr, u32 first, u32 end, u32 max)
+{
+	if (first >= max)
+		return;
+	if (first >= end)
+		return;
+	if (end > max)
+		end = max;
+	memset(ptr + first, 0, end - first);
+}
+
+static struct sk_buff *
+cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
+{
+	struct sk_buff *skb_out;
+	u32 rem;
+	u32 offset;
+	u32 last_offset;
+	u16 n = 0, index;
+	u8 ready2send = 0;
+
+	/* if there is a remaining skb, it gets priority */
+	if (skb != NULL)
+		swap(skb, ctx->tx_rem_skb);
+	else
+		ready2send = 1;
+
+	/*
+	 * +----------------+
+	 * | skb_out        |
+	 * +----------------+
+	 *           ^ offset
+	 *        ^ last_offset
+	 */
+
+	/* check if we are resuming an OUT skb */
+	if (ctx->tx_curr_skb != NULL) {
+		/* pop variables */
+		skb_out = ctx->tx_curr_skb;
+		offset = ctx->tx_curr_offset;
+		last_offset = ctx->tx_curr_last_offset;
+		n = ctx->tx_curr_frame_num;
+
+	} else {
+		/* reset variables */
+		skb_out = alloc_skb((ctx->tx_max + 1), GFP_ATOMIC);
+		if (skb_out == NULL) {
+			if (skb != NULL) {
+				dev_kfree_skb_any(skb);
+				ctx->netdev->stats.tx_dropped++;
+			}
+			goto exit_no_skb;
+		}
+
+		/* make room for NTH and NDP */
+		offset = ALIGN(sizeof(struct usb_cdc_ncm_nth16),
+					ctx->tx_ndp_modulus) +
+					sizeof(struct usb_cdc_ncm_ndp16) +
+					(ctx->tx_max_datagrams + 1) *
+					sizeof(struct usb_cdc_ncm_dpe16);
+
+		/* store last valid offset before alignment */
+		last_offset = offset;
+		/* align first Datagram offset correctly */
+		offset = ALIGN(offset, ctx->tx_modulus) + ctx->tx_remainder;
+		/* zero buffer till the first IP datagram */
+		cdc_ncm_zero_fill(skb_out->data, 0, offset, offset);
+		n = 0;
+		ctx->tx_curr_frame_num = 0;
+	}
+
+	for (; n < ctx->tx_max_datagrams; n++) {
+		/* check if end of transmit buffer is reached */
+		if (offset >= ctx->tx_max) {
+			ready2send = 1;
+			break;
+		}
+		/* compute maximum buffer size */
+		rem = ctx->tx_max - offset;
+
+		if (skb == NULL) {
+			skb = ctx->tx_rem_skb;
+			ctx->tx_rem_skb = NULL;
+
+			/* check for end of skb */
+			if (skb == NULL)
+				break;
+		}
+
+		if (skb->len > rem) {
+			if (n == 0) {
+				/* won't fit, MTU problem? */
+				dev_kfree_skb_any(skb);
+				skb = NULL;
+				ctx->netdev->stats.tx_dropped++;
+			} else {
+				/* no room for skb - store for later */
+				if (ctx->tx_rem_skb != NULL) {
+					dev_kfree_skb_any(ctx->tx_rem_skb);
+					ctx->netdev->stats.tx_dropped++;
+				}
+				ctx->tx_rem_skb = skb;
+				skb = NULL;
+				ready2send = 1;
+			}
+			break;
+		}
+
+		memcpy(((u8 *)skb_out->data) + offset, skb->data, skb->len);
+
+		ctx->tx_ncm.dpe16[n].wDatagramLength = cpu_to_le16(skb->len);
+		ctx->tx_ncm.dpe16[n].wDatagramIndex = cpu_to_le16(offset);
+
+		/* update offset */
+		offset += skb->len;
+
+		/* store last valid offset before alignment */
+		last_offset = offset;
+
+		/* align offset correctly */
+		offset = ALIGN(offset, ctx->tx_modulus) + ctx->tx_remainder;
+
+		/* zero padding */
+		cdc_ncm_zero_fill(skb_out->data, last_offset, offset,
+								ctx->tx_max);
+		dev_kfree_skb_any(skb);
+		skb = NULL;
+	}
+
+	/* free up any dangling skb */
+	if (skb != NULL) {
+		dev_kfree_skb_any(skb);
+		skb = NULL;
+		ctx->netdev->stats.tx_dropped++;
+	}
+
+	ctx->tx_curr_frame_num = n;
+
+	if (n == 0) {
+		/* wait for more frames */
+		/* push variables */
+		ctx->tx_curr_skb = skb_out;
+		ctx->tx_curr_offset = offset;
+		ctx->tx_curr_last_offset = last_offset;
+		goto exit_no_skb;
+
+	} else if ((n < ctx->tx_max_datagrams) && (ready2send == 0)) {
+		/* wait for more frames */
+		/* push variables */
+		ctx->tx_curr_skb = skb_out;
+		ctx->tx_curr_offset = offset;
+		ctx->tx_curr_last_offset = last_offset;
+		/* set the pending count */
+		if (n < CDC_NCM_RESTART_TIMER_DATAGRAM_CNT)
+			ctx->tx_timer_pending = CDC_NCM_TIMER_PENDING_CNT;
+		goto exit_no_skb;
+
+	} else {
+		/* frame goes out */
+		/* variables will be reset at next call */
+	}
+
+	/* check for overflow */
+	if (last_offset > ctx->tx_max)
+		last_offset = ctx->tx_max;
+
+	/* revert offset */
+	offset = last_offset;
+
+	/*
+	 * If collected data size is less or equal CDC_NCM_MIN_TX_PKT bytes,
+	 * we send buffers as it is. If we get more data, it would be more
+	 * efficient for USB HS mobile device with DMA engine to receive a full
+	 * size NTB, than canceling DMA transfer and receiving a short packet.
+	 */
+	if (offset > CDC_NCM_MIN_TX_PKT)
+		offset = ctx->tx_max;
+
+	/* final zero padding */
+	cdc_ncm_zero_fill(skb_out->data, last_offset, offset, ctx->tx_max);
+
+	/* store last offset */
+	last_offset = offset;
+
+	if (((last_offset < ctx->tx_max) && ((last_offset %
+			le16_to_cpu(ctx->out_ep->desc.wMaxPacketSize)) == 0)) ||
+	    (((last_offset == ctx->tx_max) && ((ctx->tx_max %
+		le16_to_cpu(ctx->out_ep->desc.wMaxPacketSize)) == 0)) &&
+		(ctx->tx_max < le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)))) {
+		/* force short packet */
+		*(((u8 *)skb_out->data) + last_offset) = 0;
+		last_offset++;
+	}
+
+	/* zero the rest of the DPEs plus the last NULL entry */
+	for (; n <= CDC_NCM_DPT_DATAGRAMS_MAX; n++) {
+		ctx->tx_ncm.dpe16[n].wDatagramLength = 0;
+		ctx->tx_ncm.dpe16[n].wDatagramIndex = 0;
+	}
+
+	/* fill out 16-bit NTB header */
+	ctx->tx_ncm.nth16.dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN);
+	ctx->tx_ncm.nth16.wHeaderLength =
+					cpu_to_le16(sizeof(ctx->tx_ncm.nth16));
+	ctx->tx_ncm.nth16.wSequence = cpu_to_le16(ctx->tx_seq);
+	ctx->tx_ncm.nth16.wBlockLength = cpu_to_le16(last_offset);
+	index = ALIGN(sizeof(struct usb_cdc_ncm_nth16), ctx->tx_ndp_modulus);
+	ctx->tx_ncm.nth16.wNdpIndex = cpu_to_le16(index);
+
+	memcpy(skb_out->data, &(ctx->tx_ncm.nth16), sizeof(ctx->tx_ncm.nth16));
+	ctx->tx_seq++;
+
+	/* fill out 16-bit NDP table */
+	ctx->tx_ncm.ndp16.dwSignature =
+				cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN);
+	rem = sizeof(ctx->tx_ncm.ndp16) + ((ctx->tx_curr_frame_num + 1) *
+					sizeof(struct usb_cdc_ncm_dpe16));
+	ctx->tx_ncm.ndp16.wLength = cpu_to_le16(rem);
+	ctx->tx_ncm.ndp16.wNextNdpIndex = 0; /* reserved */
+
+	memcpy(((u8 *)skb_out->data) + index,
+						&(ctx->tx_ncm.ndp16),
+						sizeof(ctx->tx_ncm.ndp16));
+
+	memcpy(((u8 *)skb_out->data) + index + sizeof(ctx->tx_ncm.ndp16),
+					&(ctx->tx_ncm.dpe16),
+					(ctx->tx_curr_frame_num + 1) *
+					sizeof(struct usb_cdc_ncm_dpe16));
+
+	/* set frame length */
+	skb_put(skb_out, last_offset);
+
+	/* return skb */
+	ctx->tx_curr_skb = NULL;
+	ctx->netdev->stats.tx_packets += ctx->tx_curr_frame_num;
+	return skb_out;
+
+exit_no_skb:
+	/* Start timer, if there is a remaining skb */
+	if (ctx->tx_curr_skb != NULL)
+		cdc_ncm_tx_timeout_start(ctx);
+	return NULL;
+}
+
+static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx)
+{
+	/* start timer, if not already started */
+	if (!(hrtimer_active(&ctx->tx_timer) || atomic_read(&ctx->stop)))
+		hrtimer_start(&ctx->tx_timer,
+				ktime_set(0, CDC_NCM_TIMER_INTERVAL),
+				HRTIMER_MODE_REL);
+}
+
+static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *timer)
+{
+	struct cdc_ncm_ctx *ctx =
+			container_of(timer, struct cdc_ncm_ctx, tx_timer);
+
+	if (!atomic_read(&ctx->stop))
+		tasklet_schedule(&ctx->bh);
+	return HRTIMER_NORESTART;
+}
+
+static void cdc_ncm_txpath_bh(unsigned long param)
+{
+	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)param;
+
+	spin_lock_bh(&ctx->mtx);
+	if (ctx->tx_timer_pending != 0) {
+		ctx->tx_timer_pending--;
+		cdc_ncm_tx_timeout_start(ctx);
+		spin_unlock_bh(&ctx->mtx);
+	} else if (ctx->netdev != NULL) {
+		spin_unlock_bh(&ctx->mtx);
+		netif_tx_lock_bh(ctx->netdev);
+		usbnet_start_xmit(NULL, ctx->netdev);
+		netif_tx_unlock_bh(ctx->netdev);
+	}
+}
+
+static struct sk_buff *
+cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	struct sk_buff *skb_out;
+	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
+
+	/*
+	 * The Ethernet API we are using does not support transmitting
+	 * multiple Ethernet frames in a single call. This driver will
+	 * accumulate multiple Ethernet frames and send out a larger
+	 * USB frame when the USB buffer is full or when a single jiffies
+	 * timeout happens.
+	 */
+	if (ctx == NULL)
+		goto error;
+
+	spin_lock_bh(&ctx->mtx);
+	skb_out = cdc_ncm_fill_tx_frame(ctx, skb);
+	spin_unlock_bh(&ctx->mtx);
+	return skb_out;
+
+error:
+	if (skb != NULL)
+		dev_kfree_skb_any(skb);
+
+	return NULL;
+}
+
+static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
+{
+	struct sk_buff *skb;
+	struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
+	int len;
+	int nframes;
+	int x;
+	int offset;
+	struct usb_cdc_ncm_nth16 *nth16;
+	struct usb_cdc_ncm_ndp16 *ndp16;
+	struct usb_cdc_ncm_dpe16 *dpe16;
+
+	if (ctx == NULL)
+		goto error;
+
+	if (skb_in->len < (sizeof(struct usb_cdc_ncm_nth16) +
+					sizeof(struct usb_cdc_ncm_ndp16))) {
+		pr_debug("frame too short\n");
+		goto error;
+	}
+
+	nth16 = (struct usb_cdc_ncm_nth16 *)skb_in->data;
+
+	if (le32_to_cpu(nth16->dwSignature) != USB_CDC_NCM_NTH16_SIGN) {
+		pr_debug("invalid NTH16 signature <%u>\n",
+					le32_to_cpu(nth16->dwSignature));
+		goto error;
+	}
+
+	len = le16_to_cpu(nth16->wBlockLength);
+	if (len > ctx->rx_max) {
+		pr_debug("unsupported NTB block length %u/%u\n", len,
+								ctx->rx_max);
+		goto error;
+	}
+
+	if ((ctx->rx_seq + 1) != le16_to_cpu(nth16->wSequence) &&
+		(ctx->rx_seq || le16_to_cpu(nth16->wSequence)) &&
+		!((ctx->rx_seq == 0xffff) && !le16_to_cpu(nth16->wSequence))) {
+		pr_debug("sequence number glitch prev=%d curr=%d\n",
+				ctx->rx_seq, le16_to_cpu(nth16->wSequence));
+	}
+	ctx->rx_seq = le16_to_cpu(nth16->wSequence);
+
+	len = le16_to_cpu(nth16->wNdpIndex);
+	if ((len + sizeof(struct usb_cdc_ncm_ndp16)) > skb_in->len) {
+		pr_debug("invalid DPT16 index <%u>\n",
+					le16_to_cpu(nth16->wNdpIndex));
+		goto error;
+	}
+
+	ndp16 = (struct usb_cdc_ncm_ndp16 *)(((u8 *)skb_in->data) + len);
+
+	if (le32_to_cpu(ndp16->dwSignature) != USB_CDC_NCM_NDP16_NOCRC_SIGN) {
+		pr_debug("invalid DPT16 signature <%u>\n",
+					le32_to_cpu(ndp16->dwSignature));
+		goto error;
+	}
+
+	if (le16_to_cpu(ndp16->wLength) < USB_CDC_NCM_NDP16_LENGTH_MIN) {
+		pr_debug("invalid DPT16 length <%u>\n",
+					le32_to_cpu(ndp16->dwSignature));
+		goto error;
+	}
+
+	nframes = ((le16_to_cpu(ndp16->wLength) -
+					sizeof(struct usb_cdc_ncm_ndp16)) /
+					sizeof(struct usb_cdc_ncm_dpe16));
+	nframes--; /* we process NDP entries except for the last one */
+
+	len += sizeof(struct usb_cdc_ncm_ndp16);
+
+	if ((len + nframes * (sizeof(struct usb_cdc_ncm_dpe16))) >
+								skb_in->len) {
+		pr_debug("Invalid nframes = %d\n", nframes);
+		goto error;
+	}
+
+	dpe16 = (struct usb_cdc_ncm_dpe16 *)(((u8 *)skb_in->data) + len);
+
+	for (x = 0; x < nframes; x++, dpe16++) {
+		offset = le16_to_cpu(dpe16->wDatagramIndex);
+		len = le16_to_cpu(dpe16->wDatagramLength);
+
+		/*
+		 * CDC NCM ch. 3.7
+		 * All entries after first NULL entry are to be ignored
+		 */
+		if ((offset == 0) || (len == 0)) {
+			if (!x)
+				goto error; /* empty NTB */
+			break;
+		}
+
+		/* sanity checking */
+		if (((offset + len) > skb_in->len) ||
+				(len > ctx->rx_max) || (len < ETH_HLEN)) {
+			pr_debug("invalid frame detected (ignored)"
+					"offset[%u]=%u, length=%u, skb=%p\n",
+					x, offset, len, skb_in);
+			if (!x)
+				goto error;
+			break;
+
+		} else {
+			skb = skb_clone(skb_in, GFP_ATOMIC);
+			if (!skb)
+				goto error;
+			skb->len = len;
+			skb->data = ((u8 *)skb_in->data) + offset;
+			skb_set_tail_pointer(skb, len);
+			usbnet_skb_return(dev, skb);
+		}
+	}
+	return 1;
+error:
+	return 0;
+}
+
+static void
+cdc_ncm_speed_change(struct cdc_ncm_ctx *ctx,
+		     struct usb_cdc_speed_change *data)
+{
+	uint32_t rx_speed = le32_to_cpu(data->DLBitRRate);
+	uint32_t tx_speed = le32_to_cpu(data->ULBitRate);
+
+	/*
+	 * Currently the USB-NET API does not support reporting the actual
+	 * device speed. Do print it instead.
+	 */
+	if ((tx_speed != ctx->tx_speed) || (rx_speed != ctx->rx_speed)) {
+		ctx->tx_speed = tx_speed;
+		ctx->rx_speed = rx_speed;
+
+		if ((tx_speed > 1000000) && (rx_speed > 1000000)) {
+			printk(KERN_INFO KBUILD_MODNAME
+				": %s: %u mbit/s downlink "
+				"%u mbit/s uplink\n",
+				ctx->netdev->name,
+				(unsigned int)(rx_speed / 1000000U),
+				(unsigned int)(tx_speed / 1000000U));
+		} else {
+			printk(KERN_INFO KBUILD_MODNAME
+				": %s: %u kbit/s downlink "
+				"%u kbit/s uplink\n",
+				ctx->netdev->name,
+				(unsigned int)(rx_speed / 1000U),
+				(unsigned int)(tx_speed / 1000U));
+		}
+	}
+}
+
+static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)
+{
+	struct cdc_ncm_ctx *ctx;
+	struct usb_cdc_notification *event;
+
+	ctx = (struct cdc_ncm_ctx *)dev->data[0];
+
+	if (urb->actual_length < sizeof(*event))
+		return;
+
+	/* test for split data in 8-byte chunks */
+	if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) {
+		cdc_ncm_speed_change(ctx,
+		      (struct usb_cdc_speed_change *)urb->transfer_buffer);
+		return;
+	}
+
+	event = urb->transfer_buffer;
+
+	switch (event->bNotificationType) {
+	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+		/*
+		 * According to the CDC NCM specification ch.7.1
+		 * USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be
+		 * sent by device after USB_CDC_NOTIFY_SPEED_CHANGE.
+		 */
+		ctx->connected = event->wValue;
+
+		printk(KERN_INFO KBUILD_MODNAME ": %s: network connection:"
+			" %sconnected\n",
+			ctx->netdev->name, ctx->connected ? "" : "dis");
+
+		if (ctx->connected)
+			netif_carrier_on(dev->net);
+		else {
+			netif_carrier_off(dev->net);
+			ctx->tx_speed = ctx->rx_speed = 0;
+		}
+		break;
+
+	case USB_CDC_NOTIFY_SPEED_CHANGE:
+		if (urb->actual_length < (sizeof(*event) +
+					sizeof(struct usb_cdc_speed_change)))
+			set_bit(EVENT_STS_SPLIT, &dev->flags);
+		else
+			cdc_ncm_speed_change(ctx,
+				(struct usb_cdc_speed_change *) &event[1]);
+		break;
+
+	default:
+		dev_err(&dev->udev->dev, "NCM: unexpected "
+			"notification 0x%02x!\n", event->bNotificationType);
+		break;
+	}
+}
+
+static int cdc_ncm_check_connect(struct usbnet *dev)
+{
+	struct cdc_ncm_ctx *ctx;
+
+	ctx = (struct cdc_ncm_ctx *)dev->data[0];
+	if (ctx == NULL)
+		return 1;	/* disconnected */
+
+	return !ctx->connected;
+}
+
+static int
+cdc_ncm_probe(struct usb_interface *udev, const struct usb_device_id *prod)
+{
+	return usbnet_probe(udev, prod);
+}
+
+static void cdc_ncm_disconnect(struct usb_interface *intf)
+{
+	struct usbnet *dev = usb_get_intfdata(intf);
+
+	if (dev == NULL)
+		return;		/* already disconnected */
+
+	usbnet_disconnect(intf);
+}
+
+static int cdc_ncm_manage_power(struct usbnet *dev, int status)
+{
+	dev->intf->needs_remote_wakeup = status;
+	return 0;
+}
+
+static const struct driver_info cdc_ncm_info = {
+	.description = "CDC NCM",
+	.flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET,
+	.bind = cdc_ncm_bind,
+	.unbind = cdc_ncm_unbind,
+	.check_connect = cdc_ncm_check_connect,
+	.manage_power = cdc_ncm_manage_power,
+	.status = cdc_ncm_status,
+	.rx_fixup = cdc_ncm_rx_fixup,
+	.tx_fixup = cdc_ncm_tx_fixup,
+};
+
+static struct usb_driver cdc_ncm_driver = {
+	.name = "cdc_ncm",
+	.id_table = cdc_devs,
+	.probe = cdc_ncm_probe,
+	.disconnect = cdc_ncm_disconnect,
+	.suspend = usbnet_suspend,
+	.resume = usbnet_resume,
+	.reset_resume =	usbnet_resume,
+	.supports_autosuspend = 1,
+};
+
+static const struct ethtool_ops cdc_ncm_ethtool_ops = {
+	.get_drvinfo = cdc_ncm_get_drvinfo,
+	.get_link = usbnet_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,
+};
+
+module_usb_driver(cdc_ncm_driver);
+
+MODULE_AUTHOR("Hans Petter Selasky");
+MODULE_DESCRIPTION("USB CDC NCM host driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_subset.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_subset.c
new file mode 100644
index 0000000..b403d93
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/cdc_subset.c
@@ -0,0 +1,345 @@
+/*
+ * Simple "CDC Subset" USB Networking Links
+ * Copyright (C) 2000-2005 by David Brownell
+ *
+ * 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 <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+
+
+/*
+ * This supports simple USB network links that don't require any special
+ * framing or hardware control operations.  The protocol used here is a
+ * strict subset of CDC Ethernet, with three basic differences reflecting
+ * the goal that almost any hardware should run it:
+ *
+ *  - Minimal runtime control:  one interface, no altsettings, and
+ *    no vendor or class specific control requests.  If a device is
+ *    configured, it is allowed to exchange packets with the host.
+ *    Fancier models would mean not working on some hardware.
+ *
+ *  - Minimal manufacturing control:  no IEEE "Organizationally
+ *    Unique ID" required, or an EEPROMs to store one.  Each host uses
+ *    one random "locally assigned" Ethernet address instead, which can
+ *    of course be overridden using standard tools like "ifconfig".
+ *    (With 2^46 such addresses, same-net collisions are quite rare.)
+ *
+ *  - There is no additional framing data for USB.  Packets are written
+ *    exactly as in CDC Ethernet, starting with an Ethernet header and
+ *    terminated by a short packet.  However, the host will never send a
+ *    zero length packet; some systems can't handle those robustly.
+ *
+ * Anything that can transmit and receive USB bulk packets can implement
+ * this protocol.  That includes both smart peripherals and quite a lot
+ * of "host-to-host" USB cables (which embed two devices back-to-back).
+ *
+ * Note that although Linux may use many of those host-to-host links
+ * with this "cdc_subset" framing, that doesn't mean there may not be a
+ * better approach.  Handling the "other end unplugs/replugs" scenario
+ * well tends to require chip-specific vendor requests.  Also, Windows
+ * peers at the other end of host-to-host cables may expect their own
+ * framing to be used rather than this "cdc_subset" model.
+ */
+
+#if defined(CONFIG_USB_EPSON2888) || defined(CONFIG_USB_ARMLINUX)
+/* PDA style devices are always connected if present */
+static int always_connected (struct usbnet *dev)
+{
+	return 0;
+}
+#endif
+
+#ifdef	CONFIG_USB_ALI_M5632
+#define	HAVE_HARDWARE
+
+/*-------------------------------------------------------------------------
+ *
+ * ALi M5632 driver ... does high speed
+ *
+ * NOTE that the MS-Windows drivers for this chip use some funky and
+ * (naturally) undocumented 7-byte prefix to each packet, so this is a
+ * case where we don't currently interoperate.  Also, once you unplug
+ * one end of the cable, you need to replug the other end too ... since
+ * chip docs are unavailable, there's no way to reset the relevant state
+ * short of a power cycle.
+ *
+ *-------------------------------------------------------------------------*/
+
+static const struct driver_info	ali_m5632_info = {
+	.description =	"ALi M5632",
+	.flags       = FLAG_POINTTOPOINT,
+};
+
+#endif
+
+
+#ifdef	CONFIG_USB_AN2720
+#define	HAVE_HARDWARE
+
+/*-------------------------------------------------------------------------
+ *
+ * AnchorChips 2720 driver ... http://www.cypress.com
+ *
+ * This doesn't seem to have a way to detect whether the peer is
+ * connected, or need any reset handshaking.  It's got pretty big
+ * internal buffers (handles most of a frame's worth of data).
+ * Chip data sheets don't describe any vendor control messages.
+ *
+ *-------------------------------------------------------------------------*/
+
+static const struct driver_info	an2720_info = {
+	.description =	"AnchorChips/Cypress 2720",
+	.flags       = FLAG_POINTTOPOINT,
+	// no reset available!
+	// no check_connect available!
+
+	.in = 2, .out = 2,		// direction distinguishes these
+};
+
+#endif	/* CONFIG_USB_AN2720 */
+
+
+#ifdef	CONFIG_USB_BELKIN
+#define	HAVE_HARDWARE
+
+/*-------------------------------------------------------------------------
+ *
+ * Belkin F5U104 ... two NetChip 2280 devices + Atmel AVR microcontroller
+ *
+ * ... also two eTEK designs, including one sold as "Advance USBNET"
+ *
+ *-------------------------------------------------------------------------*/
+
+static const struct driver_info	belkin_info = {
+	.description =	"Belkin, eTEK, or compatible",
+	.flags       = FLAG_POINTTOPOINT,
+};
+
+#endif	/* CONFIG_USB_BELKIN */
+
+
+
+#ifdef	CONFIG_USB_EPSON2888
+#define	HAVE_HARDWARE
+
+/*-------------------------------------------------------------------------
+ *
+ * EPSON USB clients
+ *
+ * This is the same idea as Linux PDAs (below) except the firmware in the
+ * device might not be Tux-powered.  Epson provides reference firmware that
+ * implements this interface.  Product developers can reuse or modify that
+ * code, such as by using their own product and vendor codes.
+ *
+ * Support was from Juro Bystricky <bystricky.juro@erd.epson.com>
+ *
+ *-------------------------------------------------------------------------*/
+
+static const struct driver_info	epson2888_info = {
+	.description =	"Epson USB Device",
+	.check_connect = always_connected,
+	.flags = FLAG_POINTTOPOINT,
+
+	.in = 4, .out = 3,
+};
+
+#endif	/* CONFIG_USB_EPSON2888 */
+
+
+/*-------------------------------------------------------------------------
+ *
+ * info from Jonathan McDowell <noodles@earth.li>
+ *
+ *-------------------------------------------------------------------------*/
+#ifdef CONFIG_USB_KC2190
+#define HAVE_HARDWARE
+static const struct driver_info kc2190_info = {
+	.description =  "KC Technology KC-190",
+	.flags = FLAG_POINTTOPOINT,
+};
+#endif /* CONFIG_USB_KC2190 */
+
+
+#ifdef	CONFIG_USB_ARMLINUX
+#define	HAVE_HARDWARE
+
+/*-------------------------------------------------------------------------
+ *
+ * Intel's SA-1100 chip integrates basic USB support, and is used
+ * in PDAs like some iPaqs, the Yopy, some Zaurus models, and more.
+ * When they run Linux, arch/arm/mach-sa1100/usb-eth.c may be used to
+ * network using minimal USB framing data.
+ *
+ * This describes the driver currently in standard ARM Linux kernels.
+ * The Zaurus uses a different driver (see later).
+ *
+ * PXA25x and PXA210 use XScale cores (ARM v5TE) with better USB support
+ * and different USB endpoint numbering than the SA1100 devices.  The
+ * mach-pxa/usb-eth.c driver re-uses the device ids from mach-sa1100
+ * so we rely on the endpoint descriptors.
+ *
+ *-------------------------------------------------------------------------*/
+
+static const struct driver_info	linuxdev_info = {
+	.description =	"Linux Device",
+	.check_connect = always_connected,
+	.flags = FLAG_POINTTOPOINT,
+};
+
+static const struct driver_info	yopy_info = {
+	.description =	"Yopy",
+	.check_connect = always_connected,
+	.flags = FLAG_POINTTOPOINT,
+};
+
+static const struct driver_info	blob_info = {
+	.description =	"Boot Loader OBject",
+	.check_connect = always_connected,
+	.flags = FLAG_POINTTOPOINT,
+};
+
+#endif	/* CONFIG_USB_ARMLINUX */
+
+
+/*-------------------------------------------------------------------------*/
+
+#ifndef	HAVE_HARDWARE
+#warning You need to configure some hardware for this driver
+#endif
+
+/*
+ * chip vendor names won't normally be on the cables, and
+ * may not be on the device.
+ */
+
+static const struct usb_device_id	products [] = {
+
+#ifdef	CONFIG_USB_ALI_M5632
+{
+	USB_DEVICE (0x0402, 0x5632),	// ALi defaults
+	.driver_info =	(unsigned long) &ali_m5632_info,
+},
+{
+	USB_DEVICE (0x182d,0x207c),	// SiteCom CN-124
+	.driver_info =	(unsigned long) &ali_m5632_info,
+},
+#endif
+
+#ifdef	CONFIG_USB_AN2720
+{
+	USB_DEVICE (0x0547, 0x2720),	// AnchorChips defaults
+	.driver_info =	(unsigned long) &an2720_info,
+}, {
+	USB_DEVICE (0x0547, 0x2727),	// Xircom PGUNET
+	.driver_info =	(unsigned long) &an2720_info,
+},
+#endif
+
+#ifdef	CONFIG_USB_BELKIN
+{
+	USB_DEVICE (0x050d, 0x0004),	// Belkin
+	.driver_info =	(unsigned long) &belkin_info,
+}, {
+	USB_DEVICE (0x056c, 0x8100),	// eTEK
+	.driver_info =	(unsigned long) &belkin_info,
+}, {
+	USB_DEVICE (0x0525, 0x9901),	// Advance USBNET (eTEK)
+	.driver_info =	(unsigned long) &belkin_info,
+},
+#endif
+
+#ifdef	CONFIG_USB_EPSON2888
+{
+	USB_DEVICE (0x0525, 0x2888),	// EPSON USB client
+	.driver_info	= (unsigned long) &epson2888_info,
+},
+#endif
+
+#ifdef CONFIG_USB_KC2190
+{
+	USB_DEVICE (0x050f, 0x0190),	// KC-190
+	.driver_info =	(unsigned long) &kc2190_info,
+},
+#endif
+
+#ifdef	CONFIG_USB_ARMLINUX
+/*
+ * SA-1100 using standard ARM Linux kernels, or compatible.
+ * Often used when talking to Linux PDAs (iPaq, Yopy, etc).
+ * The sa-1100 "usb-eth" driver handles the basic framing.
+ *
+ * PXA25x or PXA210 ...  these use a "usb-eth" driver much like
+ * the sa1100 one, but hardware uses different endpoint numbers.
+ *
+ * Or the Linux "Ethernet" gadget on hardware that can't talk
+ * CDC Ethernet (e.g., no altsettings), in either of two modes:
+ *  - acting just like the old "usb-eth" firmware, though
+ *    the implementation is different
+ *  - supporting RNDIS as the first/default configuration for
+ *    MS-Windows interop; Linux needs to use the other config
+ */
+{
+	// 1183 = 0x049F, both used as hex values?
+	// Compaq "Itsy" vendor/product id
+	USB_DEVICE (0x049F, 0x505A),	// usb-eth, or compatible
+	.driver_info =	(unsigned long) &linuxdev_info,
+}, {
+	USB_DEVICE (0x0E7E, 0x1001),	// G.Mate "Yopy"
+	.driver_info =	(unsigned long) &yopy_info,
+}, {
+	USB_DEVICE (0x8086, 0x07d3),	// "blob" bootloader
+	.driver_info =	(unsigned long) &blob_info,
+}, {
+	USB_DEVICE (0x1286, 0x8001),    // "blob" bootloader
+	.driver_info =  (unsigned long) &blob_info,
+}, {
+	// Linux Ethernet/RNDIS gadget, mostly on PXA, second config
+	// e.g. Gumstix, current OpenZaurus, ... or anything else
+	// that just enables this gadget option.
+	USB_DEVICE (0x0525, 0xa4a2),
+	.driver_info =	(unsigned long) &linuxdev_info,
+},
+#endif
+
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_driver cdc_subset_driver = {
+	.name =		"cdc_subset",
+	.probe =	usbnet_probe,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+	.disconnect =	usbnet_disconnect,
+	.id_table =	products,
+};
+
+module_usb_driver(cdc_subset_driver);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("Simple 'CDC Subset' USB networking links");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/cx82310_eth.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/cx82310_eth.c
new file mode 100644
index 0000000..0e05313
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/cx82310_eth.c
@@ -0,0 +1,336 @@
+/*
+ * Driver for USB ethernet port of Conexant CX82310-based ADSL routers
+ * Copyright (C) 2010 by Ondrej Zary
+ * some parts inspired by the cxacru driver
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+
+enum cx82310_cmd {
+	CMD_START		= 0x84,	/* no effect? */
+	CMD_STOP		= 0x85,	/* no effect? */
+	CMD_GET_STATUS		= 0x90,	/* returns nothing? */
+	CMD_GET_MAC_ADDR	= 0x91,	/* read MAC address */
+	CMD_GET_LINK_STATUS	= 0x92,	/* not useful, link is always up */
+	CMD_ETHERNET_MODE	= 0x99,	/* unknown, needed during init */
+};
+
+enum cx82310_status {
+	STATUS_UNDEFINED,
+	STATUS_SUCCESS,
+	STATUS_ERROR,
+	STATUS_UNSUPPORTED,
+	STATUS_UNIMPLEMENTED,
+	STATUS_PARAMETER_ERROR,
+	STATUS_DBG_LOOPBACK,
+};
+
+#define CMD_PACKET_SIZE	64
+/* first command after power on can take around 8 seconds */
+#define CMD_TIMEOUT	15000
+#define CMD_REPLY_RETRY 5
+
+#define CX82310_MTU	1514
+#define CMD_EP		0x01
+
+/*
+ * execute control command
+ *  - optionally send some data (command parameters)
+ *  - optionally wait for the reply
+ *  - optionally read some data from the reply
+ */
+static int cx82310_cmd(struct usbnet *dev, enum cx82310_cmd cmd, bool reply,
+		       u8 *wdata, int wlen, u8 *rdata, int rlen)
+{
+	int actual_len, retries, ret;
+	struct usb_device *udev = dev->udev;
+	u8 *buf = kzalloc(CMD_PACKET_SIZE, GFP_KERNEL);
+
+	if (!buf)
+		return -ENOMEM;
+
+	/* create command packet */
+	buf[0] = cmd;
+	if (wdata)
+		memcpy(buf + 4, wdata, min_t(int, wlen, CMD_PACKET_SIZE - 4));
+
+	/* send command packet */
+	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, CMD_EP), buf,
+			   CMD_PACKET_SIZE, &actual_len, CMD_TIMEOUT);
+	if (ret < 0) {
+		dev_err(&dev->udev->dev, "send command %#x: error %d\n",
+			cmd, ret);
+		goto end;
+	}
+
+	if (reply) {
+		/* wait for reply, retry if it's empty */
+		for (retries = 0; retries < CMD_REPLY_RETRY; retries++) {
+			ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, CMD_EP),
+					   buf, CMD_PACKET_SIZE, &actual_len,
+					   CMD_TIMEOUT);
+			if (ret < 0) {
+				dev_err(&dev->udev->dev,
+					"reply receive error %d\n", ret);
+				goto end;
+			}
+			if (actual_len > 0)
+				break;
+		}
+		if (actual_len == 0) {
+			dev_err(&dev->udev->dev, "no reply to command %#x\n",
+				cmd);
+			ret = -EIO;
+			goto end;
+		}
+		if (buf[0] != cmd) {
+			dev_err(&dev->udev->dev,
+				"got reply to command %#x, expected: %#x\n",
+				buf[0], cmd);
+			ret = -EIO;
+			goto end;
+		}
+		if (buf[1] != STATUS_SUCCESS) {
+			dev_err(&dev->udev->dev, "command %#x failed: %#x\n",
+				cmd, buf[1]);
+			ret = -EIO;
+			goto end;
+		}
+		if (rdata)
+			memcpy(rdata, buf + 4,
+			       min_t(int, rlen, CMD_PACKET_SIZE - 4));
+	}
+end:
+	kfree(buf);
+	return ret;
+}
+
+#define partial_len	data[0]		/* length of partial packet data */
+#define partial_rem	data[1]		/* remaining (missing) data length */
+#define partial_data	data[2]		/* partial packet data */
+
+static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int ret;
+	char buf[15];
+	struct usb_device *udev = dev->udev;
+
+	/* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */
+	if (usb_string(udev, udev->descriptor.iProduct, buf, sizeof(buf)) > 0
+	    && strcmp(buf, "USB NET CARD")) {
+		dev_info(&udev->dev, "ignoring: probably an ADSL modem\n");
+		return -ENODEV;
+	}
+
+	ret = usbnet_get_endpoints(dev, intf);
+	if (ret)
+		return ret;
+
+	/*
+	 * this must not include ethernet header as the device can send partial
+	 * packets with no header (and sometimes even empty URBs)
+	 */
+	dev->net->hard_header_len = 0;
+	/* we can send at most 1514 bytes of data (+ 2-byte header) per URB */
+	dev->hard_mtu = CX82310_MTU + 2;
+	/* we can receive URBs up to 4KB from the device */
+	dev->rx_urb_size = 4096;
+
+	dev->partial_data = (unsigned long) kmalloc(dev->hard_mtu, GFP_KERNEL);
+	if (!dev->partial_data)
+		return -ENOMEM;
+
+	/* enable ethernet mode (?) */
+	ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
+	if (ret) {
+		dev_err(&udev->dev, "unable to enable ethernet mode: %d\n",
+			ret);
+		goto err;
+	}
+
+	/* get the MAC address */
+	ret = cx82310_cmd(dev, CMD_GET_MAC_ADDR, true, NULL, 0,
+			  dev->net->dev_addr, ETH_ALEN);
+	if (ret) {
+		dev_err(&udev->dev, "unable to read MAC address: %d\n", ret);
+		goto err;
+	}
+
+	/* start (does not seem to have any effect?) */
+	ret = cx82310_cmd(dev, CMD_START, false, NULL, 0, NULL, 0);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	kfree((void *)dev->partial_data);
+	return ret;
+}
+
+static void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	kfree((void *)dev->partial_data);
+}
+
+/*
+ * RX is NOT easy - we can receive multiple packets per skb, each having 2-byte
+ * packet length at the beginning.
+ * The last packet might be incomplete (when it crosses the 4KB URB size),
+ * continuing in the next skb (without any headers).
+ * If a packet has odd length, there is one extra byte at the end (before next
+ * packet or at the end of the URB).
+ */
+static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	int len;
+	struct sk_buff *skb2;
+
+	/*
+	 * If the last skb ended with an incomplete packet, this skb contains
+	 * end of that packet at the beginning.
+	 */
+	if (dev->partial_rem) {
+		len = dev->partial_len + dev->partial_rem;
+		skb2 = alloc_skb(len, GFP_ATOMIC);
+		if (!skb2)
+			return 0;
+		skb_put(skb2, len);
+		memcpy(skb2->data, (void *)dev->partial_data,
+		       dev->partial_len);
+		memcpy(skb2->data + dev->partial_len, skb->data,
+		       dev->partial_rem);
+		usbnet_skb_return(dev, skb2);
+		skb_pull(skb, (dev->partial_rem + 1) & ~1);
+		dev->partial_rem = 0;
+		if (skb->len < 2)
+			return 1;
+	}
+
+	/* a skb can contain multiple packets */
+	while (skb->len > 1) {
+		/* first two bytes are packet length */
+		len = skb->data[0] | (skb->data[1] << 8);
+		skb_pull(skb, 2);
+
+		/* if last packet in the skb, let usbnet to process it */
+		if (len == skb->len || len + 1 == skb->len) {
+			skb_trim(skb, len);
+			break;
+		}
+
+		if (len > CX82310_MTU) {
+			dev_err(&dev->udev->dev, "RX packet too long: %d B\n",
+				len);
+			return 0;
+		}
+
+		/* incomplete packet, save it for the next skb */
+		if (len > skb->len) {
+			dev->partial_len = skb->len;
+			dev->partial_rem = len - skb->len;
+			memcpy((void *)dev->partial_data, skb->data,
+			       dev->partial_len);
+			skb_pull(skb, skb->len);
+			break;
+		}
+
+		skb2 = alloc_skb(len, GFP_ATOMIC);
+		if (!skb2)
+			return 0;
+		skb_put(skb2, len);
+		memcpy(skb2->data, skb->data, len);
+		/* process the packet */
+		usbnet_skb_return(dev, skb2);
+
+		skb_pull(skb, (len + 1) & ~1);
+	}
+
+	/* let usbnet process the last packet */
+	return 1;
+}
+
+/* TX is easy, just add 2 bytes of length at the beginning */
+static struct sk_buff *cx82310_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+				       gfp_t flags)
+{
+	int len = skb->len;
+
+	if (skb_headroom(skb) < 2) {
+		struct sk_buff *skb2 = skb_copy_expand(skb, 2, 0, flags);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+		if (!skb)
+			return NULL;
+	}
+	skb_push(skb, 2);
+
+	skb->data[0] = len;
+	skb->data[1] = len >> 8;
+
+	return skb;
+}
+
+
+static const struct driver_info	cx82310_info = {
+	.description	= "Conexant CX82310 USB ethernet",
+	.flags		= FLAG_ETHER,
+	.bind		= cx82310_bind,
+	.unbind		= cx82310_unbind,
+	.rx_fixup	= cx82310_rx_fixup,
+	.tx_fixup	= cx82310_tx_fixup,
+};
+
+#define USB_DEVICE_CLASS(vend, prod, cl, sc, pr) \
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+		       USB_DEVICE_ID_MATCH_DEV_INFO, \
+	.idVendor = (vend), \
+	.idProduct = (prod), \
+	.bDeviceClass = (cl), \
+	.bDeviceSubClass = (sc), \
+	.bDeviceProtocol = (pr)
+
+static const struct usb_device_id products[] = {
+	{
+		USB_DEVICE_CLASS(0x0572, 0xcb01, 0xff, 0, 0),
+		.driver_info = (unsigned long) &cx82310_info
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver cx82310_driver = {
+	.name		= "cx82310_eth",
+	.id_table	= products,
+	.probe		= usbnet_probe,
+	.disconnect	= usbnet_disconnect,
+	.suspend	= usbnet_suspend,
+	.resume		= usbnet_resume,
+};
+
+module_usb_driver(cx82310_driver);
+
+MODULE_AUTHOR("Ondrej Zary");
+MODULE_DESCRIPTION("Conexant CX82310-based ADSL router USB ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/dm9601.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/dm9601.c
new file mode 100644
index 0000000..1785183
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/dm9601.c
@@ -0,0 +1,695 @@
+/*
+ * Davicom DM9601 USB 1.1 10/100Mbps ethernet devices
+ *
+ * Peter Korsgaard <jacmet@sunsite.dk>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+//#define DEBUG
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/stddef.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+
+/* datasheet:
+ http://ptm2.cc.utu.fi/ftp/network/cards/DM9601/From_NET/DM9601-DS-P01-930914.pdf
+*/
+
+/* control requests */
+#define DM_READ_REGS	0x00
+#define DM_WRITE_REGS	0x01
+#define DM_READ_MEMS	0x02
+#define DM_WRITE_REG	0x03
+#define DM_WRITE_MEMS	0x05
+#define DM_WRITE_MEM	0x07
+
+/* registers */
+#define DM_NET_CTRL	0x00
+#define DM_RX_CTRL	0x05
+#define DM_SHARED_CTRL	0x0b
+#define DM_SHARED_ADDR	0x0c
+#define DM_SHARED_DATA	0x0d	/* low + high */
+#define DM_PHY_ADDR	0x10	/* 6 bytes */
+#define DM_MCAST_ADDR	0x16	/* 8 bytes */
+#define DM_GPR_CTRL	0x1e
+#define DM_GPR_DATA	0x1f
+
+#define DM_MAX_MCAST	64
+#define DM_MCAST_SIZE	8
+#define DM_EEPROM_LEN	256
+#define DM_TX_OVERHEAD	2	/* 2 byte header */
+#define DM_RX_OVERHEAD	7	/* 3 byte header + 4 byte crc tail */
+#define DM_TIMEOUT	1000
+
+
+static int dm_read(struct usbnet *dev, u8 reg, u16 length, void *data)
+{
+	void *buf;
+	int err = -ENOMEM;
+
+	netdev_dbg(dev->net, "dm_read() reg=0x%02x length=%d\n", reg, length);
+
+	buf = kmalloc(length, GFP_KERNEL);
+	if (!buf)
+		goto out;
+
+	err = usb_control_msg(dev->udev,
+			      usb_rcvctrlpipe(dev->udev, 0),
+			      DM_READ_REGS,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0, reg, buf, length, USB_CTRL_SET_TIMEOUT);
+	if (err == length)
+		memcpy(data, buf, length);
+	else if (err >= 0)
+		err = -EINVAL;
+	kfree(buf);
+
+ out:
+	return err;
+}
+
+static int dm_read_reg(struct usbnet *dev, u8 reg, u8 *value)
+{
+	return dm_read(dev, reg, 1, value);
+}
+
+static int dm_write(struct usbnet *dev, u8 reg, u16 length, void *data)
+{
+	void *buf = NULL;
+	int err = -ENOMEM;
+
+	netdev_dbg(dev->net, "dm_write() reg=0x%02x, length=%d\n", reg, length);
+
+	if (data) {
+		buf = kmemdup(data, length, GFP_KERNEL);
+		if (!buf)
+			goto out;
+	}
+
+	err = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      DM_WRITE_REGS,
+			      USB_DIR_OUT | USB_TYPE_VENDOR |USB_RECIP_DEVICE,
+			      0, reg, buf, length, USB_CTRL_SET_TIMEOUT);
+	kfree(buf);
+	if (err >= 0 && err < length)
+		err = -EINVAL;
+ out:
+	return err;
+}
+
+static int dm_write_reg(struct usbnet *dev, u8 reg, u8 value)
+{
+	netdev_dbg(dev->net, "dm_write_reg() reg=0x%02x, value=0x%02x\n",
+		   reg, value);
+	return usb_control_msg(dev->udev,
+			       usb_sndctrlpipe(dev->udev, 0),
+			       DM_WRITE_REG,
+			       USB_DIR_OUT | USB_TYPE_VENDOR |USB_RECIP_DEVICE,
+			       value, reg, NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+static void dm_write_async_callback(struct urb *urb)
+{
+	struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
+	int status = urb->status;
+
+	if (status < 0)
+		printk(KERN_DEBUG "dm_write_async_callback() failed with %d\n",
+		       status);
+
+	kfree(req);
+	usb_free_urb(urb);
+}
+
+static void dm_write_async_helper(struct usbnet *dev, u8 reg, u8 value,
+				  u16 length, void *data)
+{
+	struct usb_ctrlrequest *req;
+	struct urb *urb;
+	int status;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb) {
+		netdev_err(dev->net, "Error allocating URB in dm_write_async_helper!\n");
+		return;
+	}
+
+	req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC);
+	if (!req) {
+		netdev_err(dev->net, "Failed to allocate memory for control request\n");
+		usb_free_urb(urb);
+		return;
+	}
+
+	req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+	req->bRequest = length ? DM_WRITE_REGS : DM_WRITE_REG;
+	req->wValue = cpu_to_le16(value);
+	req->wIndex = cpu_to_le16(reg);
+	req->wLength = cpu_to_le16(length);
+
+	usb_fill_control_urb(urb, dev->udev,
+			     usb_sndctrlpipe(dev->udev, 0),
+			     (void *)req, data, length,
+			     dm_write_async_callback, req);
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0) {
+		netdev_err(dev->net, "Error submitting the control message: status=%d\n",
+			   status);
+		kfree(req);
+		usb_free_urb(urb);
+	}
+}
+
+static void dm_write_async(struct usbnet *dev, u8 reg, u16 length, void *data)
+{
+	netdev_dbg(dev->net, "dm_write_async() reg=0x%02x length=%d\n", reg, length);
+
+	dm_write_async_helper(dev, reg, 0, length, data);
+}
+
+static void dm_write_reg_async(struct usbnet *dev, u8 reg, u8 value)
+{
+	netdev_dbg(dev->net, "dm_write_reg_async() reg=0x%02x value=0x%02x\n",
+		   reg, value);
+
+	dm_write_async_helper(dev, reg, value, 0, NULL);
+}
+
+static int dm_read_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 *value)
+{
+	int ret, i;
+
+	mutex_lock(&dev->phy_mutex);
+
+	dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg);
+	dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0xc : 0x4);
+
+	for (i = 0; i < DM_TIMEOUT; i++) {
+		u8 tmp;
+
+		udelay(1);
+		ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
+		if (ret < 0)
+			goto out;
+
+		/* ready */
+		if ((tmp & 1) == 0)
+			break;
+	}
+
+	if (i == DM_TIMEOUT) {
+		netdev_err(dev->net, "%s read timed out!\n", phy ? "phy" : "eeprom");
+		ret = -EIO;
+		goto out;
+	}
+
+	dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
+	ret = dm_read(dev, DM_SHARED_DATA, 2, value);
+
+	netdev_dbg(dev->net, "read shared %d 0x%02x returned 0x%04x, %d\n",
+		   phy, reg, *value, ret);
+
+ out:
+	mutex_unlock(&dev->phy_mutex);
+	return ret;
+}
+
+static int dm_write_shared_word(struct usbnet *dev, int phy, u8 reg, __le16 value)
+{
+	int ret, i;
+
+	mutex_lock(&dev->phy_mutex);
+
+	ret = dm_write(dev, DM_SHARED_DATA, 2, &value);
+	if (ret < 0)
+		goto out;
+
+	dm_write_reg(dev, DM_SHARED_ADDR, phy ? (reg | 0x40) : reg);
+	dm_write_reg(dev, DM_SHARED_CTRL, phy ? 0x1a : 0x12);
+
+	for (i = 0; i < DM_TIMEOUT; i++) {
+		u8 tmp;
+
+		udelay(1);
+		ret = dm_read_reg(dev, DM_SHARED_CTRL, &tmp);
+		if (ret < 0)
+			goto out;
+
+		/* ready */
+		if ((tmp & 1) == 0)
+			break;
+	}
+
+	if (i == DM_TIMEOUT) {
+		netdev_err(dev->net, "%s write timed out!\n", phy ? "phy" : "eeprom");
+		ret = -EIO;
+		goto out;
+	}
+
+	dm_write_reg(dev, DM_SHARED_CTRL, 0x0);
+
+out:
+	mutex_unlock(&dev->phy_mutex);
+	return ret;
+}
+
+static int dm_read_eeprom_word(struct usbnet *dev, u8 offset, void *value)
+{
+	return dm_read_shared_word(dev, 0, offset, value);
+}
+
+
+
+static int dm9601_get_eeprom_len(struct net_device *dev)
+{
+	return DM_EEPROM_LEN;
+}
+
+static int dm9601_get_eeprom(struct net_device *net,
+			     struct ethtool_eeprom *eeprom, u8 * data)
+{
+	struct usbnet *dev = netdev_priv(net);
+	__le16 *ebuf = (__le16 *) data;
+	int i;
+
+	/* access is 16bit */
+	if ((eeprom->offset % 2) || (eeprom->len % 2))
+		return -EINVAL;
+
+	for (i = 0; i < eeprom->len / 2; i++) {
+		if (dm_read_eeprom_word(dev, eeprom->offset / 2 + i,
+					&ebuf[i]) < 0)
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int dm9601_mdio_read(struct net_device *netdev, int phy_id, int loc)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	__le16 res;
+
+	if (phy_id) {
+		netdev_dbg(dev->net, "Only internal phy supported\n");
+		return 0;
+	}
+
+	dm_read_shared_word(dev, 1, loc, &res);
+
+	netdev_dbg(dev->net,
+		   "dm9601_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 dm9601_mdio_write(struct net_device *netdev, int phy_id, int loc,
+			      int val)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	__le16 res = cpu_to_le16(val);
+
+	if (phy_id) {
+		netdev_dbg(dev->net, "Only internal phy supported\n");
+		return;
+	}
+
+	netdev_dbg(dev->net, "dm9601_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n",
+		   phy_id, loc, val);
+
+	dm_write_shared_word(dev, 1, loc, res);
+}
+
+static void dm9601_get_drvinfo(struct net_device *net,
+			       struct ethtool_drvinfo *info)
+{
+	/* Inherit standard device info */
+	usbnet_get_drvinfo(net, info);
+	info->eedump_len = DM_EEPROM_LEN;
+}
+
+static u32 dm9601_get_link(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	return mii_link_ok(&dev->mii);
+}
+
+static int dm9601_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
+}
+
+static const struct ethtool_ops dm9601_ethtool_ops = {
+	.get_drvinfo	= dm9601_get_drvinfo,
+	.get_link	= dm9601_get_link,
+	.get_msglevel	= usbnet_get_msglevel,
+	.set_msglevel	= usbnet_set_msglevel,
+	.get_eeprom_len	= dm9601_get_eeprom_len,
+	.get_eeprom	= dm9601_get_eeprom,
+	.get_settings	= usbnet_get_settings,
+	.set_settings	= usbnet_set_settings,
+	.nway_reset	= usbnet_nway_reset,
+};
+
+static void dm9601_set_multicast(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	/* We use the 20 byte dev->data for our 8 byte filter buffer
+	 * to avoid allocating memory that is tricky to free later */
+	u8 *hashes = (u8 *) & dev->data;
+	u8 rx_ctl = 0x31;
+
+	memset(hashes, 0x00, DM_MCAST_SIZE);
+	hashes[DM_MCAST_SIZE - 1] |= 0x80;	/* broadcast address */
+
+	if (net->flags & IFF_PROMISC) {
+		rx_ctl |= 0x02;
+	} else if (net->flags & IFF_ALLMULTI ||
+		   netdev_mc_count(net) > DM_MAX_MCAST) {
+		rx_ctl |= 0x08;
+	} else if (!netdev_mc_empty(net)) {
+		struct netdev_hw_addr *ha;
+
+		netdev_for_each_mc_addr(ha, net) {
+			u32 crc = ether_crc(ETH_ALEN, ha->addr) >> 26;
+			hashes[crc >> 3] |= 1 << (crc & 0x7);
+		}
+	}
+
+	dm_write_async(dev, DM_MCAST_ADDR, DM_MCAST_SIZE, hashes);
+	dm_write_reg_async(dev, DM_RX_CTRL, rx_ctl);
+}
+
+static void __dm9601_set_mac_address(struct usbnet *dev)
+{
+	dm_write_async(dev, DM_PHY_ADDR, ETH_ALEN, dev->net->dev_addr);
+}
+
+static int dm9601_set_mac_address(struct net_device *net, void *p)
+{
+	struct sockaddr *addr = p;
+	struct usbnet *dev = netdev_priv(net);
+
+	if (!is_valid_ether_addr(addr->sa_data)) {
+		dev_err(&net->dev, "not setting invalid mac address %pM\n",
+								addr->sa_data);
+		return -EINVAL;
+	}
+
+	memcpy(net->dev_addr, addr->sa_data, net->addr_len);
+	__dm9601_set_mac_address(dev);
+
+	return 0;
+}
+
+static const struct net_device_ops dm9601_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl 		= dm9601_ioctl,
+	.ndo_set_rx_mode	= dm9601_set_multicast,
+	.ndo_set_mac_address	= dm9601_set_mac_address,
+};
+
+static int dm9601_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int ret;
+	u8 mac[ETH_ALEN];
+
+	ret = usbnet_get_endpoints(dev, intf);
+	if (ret)
+		goto out;
+
+	dev->net->netdev_ops = &dm9601_netdev_ops;
+	dev->net->ethtool_ops = &dm9601_ethtool_ops;
+	dev->net->hard_header_len += DM_TX_OVERHEAD;
+	dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
+
+	/* dm9620/21a require room for 4 byte padding, even in dm9601
+	 * mode, so we need +1 to be able to receive full size
+	 * ethernet frames.
+	 */
+	dev->rx_urb_size = dev->net->mtu + ETH_HLEN + DM_RX_OVERHEAD + 1;
+
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = dm9601_mdio_read;
+	dev->mii.mdio_write = dm9601_mdio_write;
+	dev->mii.phy_id_mask = 0x1f;
+	dev->mii.reg_num_mask = 0x1f;
+
+	/* reset */
+	dm_write_reg(dev, DM_NET_CTRL, 1);
+	udelay(20);
+
+	/* read MAC */
+	if (dm_read(dev, DM_PHY_ADDR, ETH_ALEN, mac) < 0) {
+		printk(KERN_ERR "Error reading MAC address\n");
+		ret = -ENODEV;
+		goto out;
+	}
+
+	/*
+	 * Overwrite the auto-generated address only with good ones.
+	 */
+	if (is_valid_ether_addr(mac))
+		memcpy(dev->net->dev_addr, mac, ETH_ALEN);
+	else {
+		printk(KERN_WARNING
+			"dm9601: No valid MAC address in EEPROM, using %pM\n",
+			dev->net->dev_addr);
+		__dm9601_set_mac_address(dev);
+	}
+
+	/* power up phy */
+	dm_write_reg(dev, DM_GPR_CTRL, 1);
+	dm_write_reg(dev, DM_GPR_DATA, 0);
+
+	/* receive broadcast packets */
+	dm9601_set_multicast(dev->net);
+
+	dm9601_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
+	dm9601_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+			  ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP);
+	mii_nway_restart(&dev->mii);
+
+out:
+	return ret;
+}
+
+static int dm9601_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	u8 status;
+	int len;
+
+	/* format:
+	   b1: rx status
+	   b2: packet length (incl crc) low
+	   b3: packet length (incl crc) high
+	   b4..n-4: packet data
+	   bn-3..bn: ethernet crc
+	 */
+
+	if (unlikely(skb->len < DM_RX_OVERHEAD)) {
+		dev_err(&dev->udev->dev, "unexpected tiny rx frame\n");
+		return 0;
+	}
+
+	status = skb->data[0];
+	len = (skb->data[1] | (skb->data[2] << 8)) - 4;
+
+	if (unlikely(status & 0xbf)) {
+		if (status & 0x01) dev->net->stats.rx_fifo_errors++;
+		if (status & 0x02) dev->net->stats.rx_crc_errors++;
+		if (status & 0x04) dev->net->stats.rx_frame_errors++;
+		if (status & 0x20) dev->net->stats.rx_missed_errors++;
+		if (status & 0x90) dev->net->stats.rx_length_errors++;
+		return 0;
+	}
+
+	skb_pull(skb, 3);
+	skb_trim(skb, len);
+
+	return 1;
+}
+
+static struct sk_buff *dm9601_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
+				       gfp_t flags)
+{
+	int len, pad;
+
+	/* format:
+	   b1: packet length low
+	   b2: packet length high
+	   b3..n: packet data
+	*/
+
+	len = skb->len + DM_TX_OVERHEAD;
+
+	/* workaround for dm962x errata with tx fifo getting out of
+	 * sync if a USB bulk transfer retry happens right after a
+	 * packet with odd / maxpacket length by adding up to 3 bytes
+	 * padding.
+	 */
+	while ((len & 1) || !(len % dev->maxpacket))
+		len++;
+
+	len -= DM_TX_OVERHEAD; /* hw header doesn't count as part of length */
+	pad = len - skb->len;
+
+	if (skb_headroom(skb) < DM_TX_OVERHEAD || skb_tailroom(skb) < pad) {
+		struct sk_buff *skb2;
+
+		skb2 = skb_copy_expand(skb, DM_TX_OVERHEAD, pad, flags);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+		if (!skb)
+			return NULL;
+	}
+
+	__skb_push(skb, DM_TX_OVERHEAD);
+
+	if (pad) {
+		memset(skb->data + skb->len, 0, pad);
+		__skb_put(skb, pad);
+	}
+
+	skb->data[0] = len;
+	skb->data[1] = len >> 8;
+
+	return skb;
+}
+
+static void dm9601_status(struct usbnet *dev, struct urb *urb)
+{
+	int link;
+	u8 *buf;
+
+	/* format:
+	   b0: net status
+	   b1: tx status 1
+	   b2: tx status 2
+	   b3: rx status
+	   b4: rx overflow
+	   b5: rx count
+	   b6: tx count
+	   b7: gpr
+	*/
+
+	if (urb->actual_length < 8)
+		return;
+
+	buf = urb->transfer_buffer;
+
+	link = !!(buf[0] & 0x40);
+	if (netif_carrier_ok(dev->net) != link) {
+		if (link) {
+			netif_carrier_on(dev->net);
+			usbnet_defer_kevent (dev, EVENT_LINK_RESET);
+		}
+		else
+			netif_carrier_off(dev->net);
+		netdev_dbg(dev->net, "Link Status is: %d\n", link);
+	}
+}
+
+static int dm9601_link_reset(struct usbnet *dev)
+{
+	struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+
+	mii_check_media(&dev->mii, 1, 1);
+	mii_ethtool_gset(&dev->mii, &ecmd);
+
+	netdev_dbg(dev->net, "link_reset() speed: %u duplex: %d\n",
+		   ethtool_cmd_speed(&ecmd), ecmd.duplex);
+
+	return 0;
+}
+
+static const struct driver_info dm9601_info = {
+	.description	= "Davicom DM9601 USB Ethernet",
+	.flags		= FLAG_ETHER | FLAG_LINK_INTR,
+	.bind		= dm9601_bind,
+	.rx_fixup	= dm9601_rx_fixup,
+	.tx_fixup	= dm9601_tx_fixup,
+	.status		= dm9601_status,
+	.link_reset	= dm9601_link_reset,
+	.reset		= dm9601_link_reset,
+};
+
+static const struct usb_device_id products[] = {
+	{
+	 USB_DEVICE(0x07aa, 0x9601),	/* Corega FEther USB-TXC */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	 USB_DEVICE(0x0a46, 0x9601),	/* Davicom USB-100 */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	 USB_DEVICE(0x0a46, 0x6688),	/* ZT6688 USB NIC */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	 USB_DEVICE(0x0a46, 0x0268),	/* ShanTou ST268 USB NIC */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	 USB_DEVICE(0x0a46, 0x8515),	/* ADMtek ADM8515 USB NIC */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	USB_DEVICE(0x0a47, 0x9601),	/* Hirose USB-100 */
+	.driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	USB_DEVICE(0x0fe6, 0x8101),	/* DM9601 USB to Fast Ethernet Adapter */
+	.driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	 USB_DEVICE(0x0fe6, 0x9700),	/* DM9601 USB to Fast Ethernet Adapter */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{
+	 USB_DEVICE(0x0a46, 0x9000),	/* DM9000E */
+	 .driver_info = (unsigned long)&dm9601_info,
+	 },
+	{},			// END
+};
+
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver dm9601_driver = {
+	.name = "dm9601",
+	.id_table = products,
+	.probe = usbnet_probe,
+	.disconnect = usbnet_disconnect,
+	.suspend = usbnet_suspend,
+	.resume = usbnet_resume,
+};
+
+module_usb_driver(dm9601_driver);
+
+MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
+MODULE_DESCRIPTION("Davicom DM9601 USB 1.1 ethernet devices");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/gl620a.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/gl620a.c
new file mode 100644
index 0000000..71e0b99
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/gl620a.c
@@ -0,0 +1,239 @@
+/*
+ * GeneSys GL620USB-A based links
+ * Copyright (C) 2001 by Jiun-Jie Huang <huangjj@genesyslogic.com.tw>
+ * Copyright (C) 2001 by Stanislav Brabec <utx@penguin.cz>
+ *
+ * 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	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+#include <linux/gfp.h>
+
+
+/*
+ * GeneSys GL620USB-A (www.genesyslogic.com.tw)
+ *
+ * ... should partially interop with the Win32 driver for this hardware.
+ * The GeneSys docs imply there's some NDIS issue motivating this framing.
+ *
+ * Some info from GeneSys:
+ *  - GL620USB-A is full duplex; GL620USB is only half duplex for bulk.
+ *    (Some cables, like the BAFO-100c, use the half duplex version.)
+ *  - For the full duplex model, the low bit of the version code says
+ *    which side is which ("left/right").
+ *  - For the half duplex type, a control/interrupt handshake settles
+ *    the transfer direction.  (That's disabled here, partially coded.)
+ *    A control URB would block until other side writes an interrupt.
+ *
+ * Original code from Jiun-Jie Huang <huangjj@genesyslogic.com.tw>
+ * and merged into "usbnet" by Stanislav Brabec <utx@penguin.cz>.
+ */
+
+// control msg write command
+#define GENELINK_CONNECT_WRITE			0xF0
+// interrupt pipe index
+#define GENELINK_INTERRUPT_PIPE			0x03
+// interrupt read buffer size
+#define INTERRUPT_BUFSIZE			0x08
+// interrupt pipe interval value
+#define GENELINK_INTERRUPT_INTERVAL		0x10
+// max transmit packet number per transmit
+#define GL_MAX_TRANSMIT_PACKETS			32
+// max packet length
+#define GL_MAX_PACKET_LEN			1514
+// max receive buffer size
+#define GL_RCV_BUF_SIZE		\
+	(((GL_MAX_PACKET_LEN + 4) * GL_MAX_TRANSMIT_PACKETS) + 4)
+
+struct gl_packet {
+	__le32		packet_length;
+	char		packet_data [1];
+};
+
+struct gl_header {
+	__le32			packet_count;
+	struct gl_packet	packets;
+};
+
+static int genelink_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	struct gl_header	*header;
+	struct gl_packet	*packet;
+	struct sk_buff		*gl_skb;
+	u32			size;
+	u32			count;
+
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len)
+		return 0;
+
+	header = (struct gl_header *) skb->data;
+
+	// get the packet count of the received skb
+	count = le32_to_cpu(header->packet_count);
+	if (count > GL_MAX_TRANSMIT_PACKETS) {
+		dbg("genelink: invalid received packet count %u", count);
+		return 0;
+	}
+
+	// set the current packet pointer to the first packet
+	packet = &header->packets;
+
+	// decrement the length for the packet count size 4 bytes
+	skb_pull(skb, 4);
+
+	while (count > 1) {
+		// get the packet length
+		size = le32_to_cpu(packet->packet_length);
+
+		// this may be a broken packet
+		if (size > GL_MAX_PACKET_LEN) {
+			dbg("genelink: invalid rx length %d", size);
+			return 0;
+		}
+
+		// allocate the skb for the individual packet
+		gl_skb = alloc_skb(size, GFP_ATOMIC);
+		if (gl_skb) {
+
+			// copy the packet data to the new skb
+			memcpy(skb_put(gl_skb, size),
+					packet->packet_data, size);
+			usbnet_skb_return(dev, gl_skb);
+		}
+
+		// advance to the next packet
+		packet = (struct gl_packet *)&packet->packet_data[size];
+		count--;
+
+		// shift the data pointer to the next gl_packet
+		skb_pull(skb, size + 4);
+	}
+
+	// skip the packet length field 4 bytes
+	skb_pull(skb, 4);
+
+	if (skb->len > GL_MAX_PACKET_LEN) {
+		dbg("genelink: invalid rx length %d", skb->len);
+		return 0;
+	}
+	return 1;
+}
+
+static struct sk_buff *
+genelink_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	int 	padlen;
+	int	length = skb->len;
+	int	headroom = skb_headroom(skb);
+	int	tailroom = skb_tailroom(skb);
+	__le32	*packet_count;
+	__le32	*packet_len;
+
+	// FIXME:  magic numbers, bleech
+	padlen = ((skb->len + (4 + 4*1)) % 64) ? 0 : 1;
+
+	if ((!skb_cloned(skb))
+			&& ((headroom + tailroom) >= (padlen + (4 + 4*1)))) {
+		if ((headroom < (4 + 4*1)) || (tailroom < padlen)) {
+			skb->data = memmove(skb->head + (4 + 4*1),
+					     skb->data, skb->len);
+			skb_set_tail_pointer(skb, skb->len);
+		}
+	} else {
+		struct sk_buff	*skb2;
+		skb2 = skb_copy_expand(skb, (4 + 4*1) , padlen, flags);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+		if (!skb)
+			return NULL;
+	}
+
+	// attach the packet count to the header
+	packet_count = (__le32 *) skb_push(skb, (4 + 4*1));
+	packet_len = packet_count + 1;
+
+	*packet_count = cpu_to_le32(1);
+	*packet_len = cpu_to_le32(length);
+
+	// add padding byte
+	if ((skb->len % dev->maxpacket) == 0)
+		skb_put(skb, 1);
+
+	return skb;
+}
+
+static int genelink_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	dev->hard_mtu = GL_RCV_BUF_SIZE;
+	dev->net->hard_header_len += 4;
+	dev->in = usb_rcvbulkpipe(dev->udev, dev->driver_info->in);
+	dev->out = usb_sndbulkpipe(dev->udev, dev->driver_info->out);
+	return 0;
+}
+
+static const struct driver_info	genelink_info = {
+	.description =	"Genesys GeneLink",
+	.flags =	FLAG_POINTTOPOINT | FLAG_FRAMING_GL | FLAG_NO_SETINT,
+	.bind =		genelink_bind,
+	.rx_fixup =	genelink_rx_fixup,
+	.tx_fixup =	genelink_tx_fixup,
+
+	.in = 1, .out = 2,
+
+#ifdef	GENELINK_ACK
+	.check_connect =genelink_check_connect,
+#endif
+};
+
+static const struct usb_device_id	products [] = {
+
+{
+	USB_DEVICE(0x05e3, 0x0502),	// GL620USB-A
+	.driver_info =	(unsigned long) &genelink_info,
+},
+	/* NOT: USB_DEVICE(0x05e3, 0x0501),	// GL620USB
+	 * that's half duplex, not currently supported
+	 */
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver gl620a_driver = {
+	.name =		"gl620a",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+module_usb_driver(gl620a_driver);
+
+MODULE_AUTHOR("Jiun-Jie Huang");
+MODULE_DESCRIPTION("GL620-USB-A Host-to-Host Link cables");
+MODULE_LICENSE("GPL");
+
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/hso.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/hso.c
new file mode 100644
index 0000000..2d2a688
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/hso.c
@@ -0,0 +1,3380 @@
+/******************************************************************************
+ *
+ * Driver for Option High Speed Mobile Devices.
+ *
+ *  Copyright (C) 2008 Option International
+ *                     Filip Aben <f.aben@option.com>
+ *                     Denis Joseph Barrow <d.barow@option.com>
+ *                     Jan Dumon <j.dumon@option.com>
+ *  Copyright (C) 2007 Andrew Bird (Sphere Systems Ltd)
+ *  			<ajb@spheresystems.co.uk>
+ *  Copyright (C) 2008 Greg Kroah-Hartman <gregkh@suse.de>
+ *  Copyright (C) 2008 Novell, Inc.
+ *
+ *  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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ *  USA
+ *
+ *
+ *****************************************************************************/
+
+/******************************************************************************
+ *
+ * Description of the device:
+ *
+ * Interface 0:	Contains the IP network interface on the bulk end points.
+ *		The multiplexed serial ports are using the interrupt and
+ *		control endpoints.
+ *		Interrupt contains a bitmap telling which multiplexed
+ *		serialport needs servicing.
+ *
+ * Interface 1:	Diagnostics port, uses bulk only, do not submit urbs until the
+ *		port is opened, as this have a huge impact on the network port
+ *		throughput.
+ *
+ * Interface 2:	Standard modem interface - circuit switched interface, this
+ *		can be used to make a standard ppp connection however it
+ *              should not be used in conjunction with the IP network interface
+ *              enabled for USB performance reasons i.e. if using this set
+ *              ideally disable_net=1.
+ *
+ *****************************************************************************/
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <linux/ethtool.h>
+#include <linux/usb.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/kmod.h>
+#include <linux/rfkill.h>
+#include <linux/ip.h>
+#include <linux/uaccess.h>
+#include <linux/usb/cdc.h>
+#include <net/arp.h>
+#include <asm/byteorder.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+
+
+#define MOD_AUTHOR			"Option Wireless"
+#define MOD_DESCRIPTION			"USB High Speed Option driver"
+#define MOD_LICENSE			"GPL"
+
+#define HSO_MAX_NET_DEVICES		10
+#define HSO__MAX_MTU			2048
+#define DEFAULT_MTU			1500
+#define DEFAULT_MRU			1500
+
+#define CTRL_URB_RX_SIZE		1024
+#define CTRL_URB_TX_SIZE		64
+
+#define BULK_URB_RX_SIZE		4096
+#define BULK_URB_TX_SIZE		8192
+
+#define MUX_BULK_RX_BUF_SIZE		HSO__MAX_MTU
+#define MUX_BULK_TX_BUF_SIZE		HSO__MAX_MTU
+#define MUX_BULK_RX_BUF_COUNT		4
+#define USB_TYPE_OPTION_VENDOR		0x20
+
+/* These definitions are used with the struct hso_net flags element */
+/* - use *_bit operations on it. (bit indices not values.) */
+#define HSO_NET_RUNNING			0
+
+#define	HSO_NET_TX_TIMEOUT		(HZ*10)
+
+#define HSO_SERIAL_MAGIC		0x48534f31
+
+/* Number of ttys to handle */
+#define HSO_SERIAL_TTY_MINORS		256
+
+#define MAX_RX_URBS			2
+
+static inline struct hso_serial *get_serial_by_tty(struct tty_struct *tty)
+{
+	if (tty)
+		return tty->driver_data;
+	return NULL;
+}
+
+/*****************************************************************************/
+/* Debugging functions                                                       */
+/*****************************************************************************/
+#define D__(lvl_, fmt, arg...)				\
+	do {						\
+		printk(lvl_ "[%d:%s]: " fmt "\n",	\
+		       __LINE__, __func__, ## arg);	\
+	} while (0)
+
+#define D_(lvl, args...)				\
+	do {						\
+		if (lvl & debug)			\
+			D__(KERN_INFO, args);		\
+	} while (0)
+
+#define D1(args...)	D_(0x01, ##args)
+#define D2(args...)	D_(0x02, ##args)
+#define D3(args...)	D_(0x04, ##args)
+#define D4(args...)	D_(0x08, ##args)
+#define D5(args...)	D_(0x10, ##args)
+
+/*****************************************************************************/
+/* Enumerators                                                               */
+/*****************************************************************************/
+enum pkt_parse_state {
+	WAIT_IP,
+	WAIT_DATA,
+	WAIT_SYNC
+};
+
+/*****************************************************************************/
+/* Structs                                                                   */
+/*****************************************************************************/
+
+struct hso_shared_int {
+	struct usb_endpoint_descriptor *intr_endp;
+	void *shared_intr_buf;
+	struct urb *shared_intr_urb;
+	struct usb_device *usb;
+	int use_count;
+	int ref_count;
+	struct mutex shared_int_lock;
+};
+
+struct hso_net {
+	struct hso_device *parent;
+	struct net_device *net;
+	struct rfkill *rfkill;
+
+	struct usb_endpoint_descriptor *in_endp;
+	struct usb_endpoint_descriptor *out_endp;
+
+	struct urb *mux_bulk_rx_urb_pool[MUX_BULK_RX_BUF_COUNT];
+	struct urb *mux_bulk_tx_urb;
+	void *mux_bulk_rx_buf_pool[MUX_BULK_RX_BUF_COUNT];
+	void *mux_bulk_tx_buf;
+
+	struct sk_buff *skb_rx_buf;
+	struct sk_buff *skb_tx_buf;
+
+	enum pkt_parse_state rx_parse_state;
+	spinlock_t net_lock;
+
+	unsigned short rx_buf_size;
+	unsigned short rx_buf_missing;
+	struct iphdr rx_ip_hdr;
+
+	unsigned long flags;
+};
+
+enum rx_ctrl_state{
+	RX_IDLE,
+	RX_SENT,
+	RX_PENDING
+};
+
+#define BM_REQUEST_TYPE (0xa1)
+#define B_NOTIFICATION  (0x20)
+#define W_VALUE         (0x0)
+#define W_INDEX         (0x2)
+#define W_LENGTH        (0x2)
+
+#define B_OVERRUN       (0x1<<6)
+#define B_PARITY        (0x1<<5)
+#define B_FRAMING       (0x1<<4)
+#define B_RING_SIGNAL   (0x1<<3)
+#define B_BREAK         (0x1<<2)
+#define B_TX_CARRIER    (0x1<<1)
+#define B_RX_CARRIER    (0x1<<0)
+
+struct hso_serial_state_notification {
+	u8 bmRequestType;
+	u8 bNotification;
+	u16 wValue;
+	u16 wIndex;
+	u16 wLength;
+	u16 UART_state_bitmap;
+} __packed;
+
+struct hso_tiocmget {
+	struct mutex mutex;
+	wait_queue_head_t waitq;
+	int    intr_completed;
+	struct usb_endpoint_descriptor *endp;
+	struct urb *urb;
+	struct hso_serial_state_notification serial_state_notification;
+	u16    prev_UART_state_bitmap;
+	struct uart_icount icount;
+};
+
+
+struct hso_serial {
+	struct hso_device *parent;
+	int magic;
+	u8 minor;
+
+	struct hso_shared_int *shared_int;
+
+	/* rx/tx urb could be either a bulk urb or a control urb depending
+	   on which serial port it is used on. */
+	struct urb *rx_urb[MAX_RX_URBS];
+	u8 num_rx_urbs;
+	u8 *rx_data[MAX_RX_URBS];
+	u16 rx_data_length;	/* should contain allocated length */
+
+	struct urb *tx_urb;
+	u8 *tx_data;
+	u8 *tx_buffer;
+	u16 tx_data_length;	/* should contain allocated length */
+	u16 tx_data_count;
+	u16 tx_buffer_count;
+	struct usb_ctrlrequest ctrl_req_tx;
+	struct usb_ctrlrequest ctrl_req_rx;
+
+	struct usb_endpoint_descriptor *in_endp;
+	struct usb_endpoint_descriptor *out_endp;
+
+	enum rx_ctrl_state rx_state;
+	u8 rts_state;
+	u8 dtr_state;
+	unsigned tx_urb_used:1;
+
+	/* from usb_serial_port */
+	struct tty_struct *tty;
+	int open_count;
+	spinlock_t serial_lock;
+
+	int (*write_data) (struct hso_serial *serial);
+	struct hso_tiocmget  *tiocmget;
+	/* Hacks required to get flow control
+	 * working on the serial receive buffers
+	 * so as not to drop characters on the floor.
+	 */
+	int  curr_rx_urb_idx;
+	u16  curr_rx_urb_offset;
+	u8   rx_urb_filled[MAX_RX_URBS];
+	struct tasklet_struct unthrottle_tasklet;
+	struct work_struct    retry_unthrottle_workqueue;
+};
+
+struct hso_device {
+	union {
+		struct hso_serial *dev_serial;
+		struct hso_net *dev_net;
+	} port_data;
+
+	u32 port_spec;
+
+	u8 is_active;
+	u8 usb_gone;
+	struct work_struct async_get_intf;
+	struct work_struct async_put_intf;
+	struct work_struct reset_device;
+
+	struct usb_device *usb;
+	struct usb_interface *interface;
+
+	struct device *dev;
+	struct kref ref;
+	struct mutex mutex;
+};
+
+/* Type of interface */
+#define HSO_INTF_MASK		0xFF00
+#define	HSO_INTF_MUX		0x0100
+#define	HSO_INTF_BULK   	0x0200
+
+/* Type of port */
+#define HSO_PORT_MASK		0xFF
+#define HSO_PORT_NO_PORT	0x0
+#define	HSO_PORT_CONTROL	0x1
+#define	HSO_PORT_APP		0x2
+#define	HSO_PORT_GPS		0x3
+#define	HSO_PORT_PCSC		0x4
+#define	HSO_PORT_APP2		0x5
+#define HSO_PORT_GPS_CONTROL	0x6
+#define HSO_PORT_MSD		0x7
+#define HSO_PORT_VOICE		0x8
+#define HSO_PORT_DIAG2		0x9
+#define	HSO_PORT_DIAG		0x10
+#define	HSO_PORT_MODEM		0x11
+#define	HSO_PORT_NETWORK	0x12
+
+/* Additional device info */
+#define HSO_INFO_MASK		0xFF000000
+#define HSO_INFO_CRC_BUG	0x01000000
+
+/*****************************************************************************/
+/* Prototypes                                                                */
+/*****************************************************************************/
+/* Serial driver functions */
+static int hso_serial_tiocmset(struct tty_struct *tty,
+			       unsigned int set, unsigned int clear);
+static void ctrl_callback(struct urb *urb);
+static int put_rxbuf_data(struct urb *urb, struct hso_serial *serial);
+static void hso_kick_transmit(struct hso_serial *serial);
+/* Helper functions */
+static int hso_mux_submit_intr_urb(struct hso_shared_int *mux_int,
+				   struct usb_device *usb, gfp_t gfp);
+static void handle_usb_error(int status, const char *function,
+			     struct hso_device *hso_dev);
+static struct usb_endpoint_descriptor *hso_get_ep(struct usb_interface *intf,
+						  int type, int dir);
+static int hso_get_mux_ports(struct usb_interface *intf, unsigned char *ports);
+static void hso_free_interface(struct usb_interface *intf);
+static int hso_start_serial_device(struct hso_device *hso_dev, gfp_t flags);
+static int hso_stop_serial_device(struct hso_device *hso_dev);
+static int hso_start_net_device(struct hso_device *hso_dev);
+static void hso_free_shared_int(struct hso_shared_int *shared_int);
+static int hso_stop_net_device(struct hso_device *hso_dev);
+static void hso_serial_ref_free(struct kref *ref);
+static void hso_std_serial_read_bulk_callback(struct urb *urb);
+static int hso_mux_serial_read(struct hso_serial *serial);
+static void async_get_intf(struct work_struct *data);
+static void async_put_intf(struct work_struct *data);
+static int hso_put_activity(struct hso_device *hso_dev);
+static int hso_get_activity(struct hso_device *hso_dev);
+static void tiocmget_intr_callback(struct urb *urb);
+static void reset_device(struct work_struct *data);
+/*****************************************************************************/
+/* Helping functions                                                         */
+/*****************************************************************************/
+
+/* #define DEBUG */
+
+static inline struct hso_net *dev2net(struct hso_device *hso_dev)
+{
+	return hso_dev->port_data.dev_net;
+}
+
+static inline struct hso_serial *dev2ser(struct hso_device *hso_dev)
+{
+	return hso_dev->port_data.dev_serial;
+}
+
+/* Debugging functions */
+#ifdef DEBUG
+static void dbg_dump(int line_count, const char *func_name, unsigned char *buf,
+		     unsigned int len)
+{
+	static char name[255];
+
+	sprintf(name, "hso[%d:%s]", line_count, func_name);
+	print_hex_dump_bytes(name, DUMP_PREFIX_NONE, buf, len);
+}
+
+#define DUMP(buf_, len_)	\
+	dbg_dump(__LINE__, __func__, (unsigned char *)buf_, len_)
+
+#define DUMP1(buf_, len_)			\
+	do {					\
+		if (0x01 & debug)		\
+			DUMP(buf_, len_);	\
+	} while (0)
+#else
+#define DUMP(buf_, len_)
+#define DUMP1(buf_, len_)
+#endif
+
+/* module parameters */
+static int debug;
+static int tty_major;
+static int disable_net;
+
+/* driver info */
+static const char driver_name[] = "hso";
+static const char tty_filename[] = "ttyHS";
+static const char *version = __FILE__ ": " MOD_AUTHOR;
+/* the usb driver itself (registered in hso_init) */
+static struct usb_driver hso_driver;
+/* serial structures */
+static struct tty_driver *tty_drv;
+static struct hso_device *serial_table[HSO_SERIAL_TTY_MINORS];
+static struct hso_device *network_table[HSO_MAX_NET_DEVICES];
+static spinlock_t serial_table_lock;
+
+static const s32 default_port_spec[] = {
+	HSO_INTF_MUX | HSO_PORT_NETWORK,
+	HSO_INTF_BULK | HSO_PORT_DIAG,
+	HSO_INTF_BULK | HSO_PORT_MODEM,
+	0
+};
+
+static const s32 icon321_port_spec[] = {
+	HSO_INTF_MUX | HSO_PORT_NETWORK,
+	HSO_INTF_BULK | HSO_PORT_DIAG2,
+	HSO_INTF_BULK | HSO_PORT_MODEM,
+	HSO_INTF_BULK | HSO_PORT_DIAG,
+	0
+};
+
+#define default_port_device(vendor, product)	\
+	USB_DEVICE(vendor, product),	\
+		.driver_info = (kernel_ulong_t)default_port_spec
+
+#define icon321_port_device(vendor, product)	\
+	USB_DEVICE(vendor, product),	\
+		.driver_info = (kernel_ulong_t)icon321_port_spec
+
+/* list of devices we support */
+static const struct usb_device_id hso_ids[] = {
+	{default_port_device(0x0af0, 0x6711)},
+	{default_port_device(0x0af0, 0x6731)},
+	{default_port_device(0x0af0, 0x6751)},
+	{default_port_device(0x0af0, 0x6771)},
+	{default_port_device(0x0af0, 0x6791)},
+	{default_port_device(0x0af0, 0x6811)},
+	{default_port_device(0x0af0, 0x6911)},
+	{default_port_device(0x0af0, 0x6951)},
+	{default_port_device(0x0af0, 0x6971)},
+	{default_port_device(0x0af0, 0x7011)},
+	{default_port_device(0x0af0, 0x7031)},
+	{default_port_device(0x0af0, 0x7051)},
+	{default_port_device(0x0af0, 0x7071)},
+	{default_port_device(0x0af0, 0x7111)},
+	{default_port_device(0x0af0, 0x7211)},
+	{default_port_device(0x0af0, 0x7251)},
+	{default_port_device(0x0af0, 0x7271)},
+	{default_port_device(0x0af0, 0x7311)},
+	{default_port_device(0x0af0, 0xc031)},	/* Icon-Edge */
+	{icon321_port_device(0x0af0, 0xd013)},	/* Module HSxPA */
+	{icon321_port_device(0x0af0, 0xd031)},	/* Icon-321 */
+	{icon321_port_device(0x0af0, 0xd033)},	/* Icon-322 */
+	{USB_DEVICE(0x0af0, 0x7301)},		/* GE40x */
+	{USB_DEVICE(0x0af0, 0x7361)},		/* GE40x */
+	{USB_DEVICE(0x0af0, 0x7381)},		/* GE40x */
+	{USB_DEVICE(0x0af0, 0x7401)},		/* GI 0401 */
+	{USB_DEVICE(0x0af0, 0x7501)},		/* GTM 382 */
+	{USB_DEVICE(0x0af0, 0x7601)},		/* GE40x */
+	{USB_DEVICE(0x0af0, 0x7701)},
+	{USB_DEVICE(0x0af0, 0x7706)},
+	{USB_DEVICE(0x0af0, 0x7801)},
+	{USB_DEVICE(0x0af0, 0x7901)},
+	{USB_DEVICE(0x0af0, 0x7A01)},
+	{USB_DEVICE(0x0af0, 0x7A05)},
+	{USB_DEVICE(0x0af0, 0x8200)},
+	{USB_DEVICE(0x0af0, 0x8201)},
+	{USB_DEVICE(0x0af0, 0x8300)},
+	{USB_DEVICE(0x0af0, 0x8302)},
+	{USB_DEVICE(0x0af0, 0x8304)},
+	{USB_DEVICE(0x0af0, 0x8400)},
+	{USB_DEVICE(0x0af0, 0x8600)},
+	{USB_DEVICE(0x0af0, 0x8800)},
+	{USB_DEVICE(0x0af0, 0x8900)},
+	{USB_DEVICE(0x0af0, 0x9000)},
+	{USB_DEVICE(0x0af0, 0xd035)},
+	{USB_DEVICE(0x0af0, 0xd055)},
+	{USB_DEVICE(0x0af0, 0xd155)},
+	{USB_DEVICE(0x0af0, 0xd255)},
+	{USB_DEVICE(0x0af0, 0xd057)},
+	{USB_DEVICE(0x0af0, 0xd157)},
+	{USB_DEVICE(0x0af0, 0xd257)},
+	{USB_DEVICE(0x0af0, 0xd357)},
+	{USB_DEVICE(0x0af0, 0xd058)},
+	{USB_DEVICE(0x0af0, 0xc100)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, hso_ids);
+
+/* Sysfs attribute */
+static ssize_t hso_sysfs_show_porttype(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct hso_device *hso_dev = dev_get_drvdata(dev);
+	char *port_name;
+
+	if (!hso_dev)
+		return 0;
+
+	switch (hso_dev->port_spec & HSO_PORT_MASK) {
+	case HSO_PORT_CONTROL:
+		port_name = "Control";
+		break;
+	case HSO_PORT_APP:
+		port_name = "Application";
+		break;
+	case HSO_PORT_APP2:
+		port_name = "Application2";
+		break;
+	case HSO_PORT_GPS:
+		port_name = "GPS";
+		break;
+	case HSO_PORT_GPS_CONTROL:
+		port_name = "GPS Control";
+		break;
+	case HSO_PORT_PCSC:
+		port_name = "PCSC";
+		break;
+	case HSO_PORT_DIAG:
+		port_name = "Diagnostic";
+		break;
+	case HSO_PORT_DIAG2:
+		port_name = "Diagnostic2";
+		break;
+	case HSO_PORT_MODEM:
+		port_name = "Modem";
+		break;
+	case HSO_PORT_NETWORK:
+		port_name = "Network";
+		break;
+	default:
+		port_name = "Unknown";
+		break;
+	}
+
+	return sprintf(buf, "%s\n", port_name);
+}
+static DEVICE_ATTR(hsotype, S_IRUGO, hso_sysfs_show_porttype, NULL);
+
+static int hso_urb_to_index(struct hso_serial *serial, struct urb *urb)
+{
+	int idx;
+
+	for (idx = 0; idx < serial->num_rx_urbs; idx++)
+		if (serial->rx_urb[idx] == urb)
+			return idx;
+	dev_err(serial->parent->dev, "hso_urb_to_index failed\n");
+	return -1;
+}
+
+/* converts mux value to a port spec value */
+static u32 hso_mux_to_port(int mux)
+{
+	u32 result;
+
+	switch (mux) {
+	case 0x1:
+		result = HSO_PORT_CONTROL;
+		break;
+	case 0x2:
+		result = HSO_PORT_APP;
+		break;
+	case 0x4:
+		result = HSO_PORT_PCSC;
+		break;
+	case 0x8:
+		result = HSO_PORT_GPS;
+		break;
+	case 0x10:
+		result = HSO_PORT_APP2;
+		break;
+	default:
+		result = HSO_PORT_NO_PORT;
+	}
+	return result;
+}
+
+/* converts port spec value to a mux value */
+static u32 hso_port_to_mux(int port)
+{
+	u32 result;
+
+	switch (port & HSO_PORT_MASK) {
+	case HSO_PORT_CONTROL:
+		result = 0x0;
+		break;
+	case HSO_PORT_APP:
+		result = 0x1;
+		break;
+	case HSO_PORT_PCSC:
+		result = 0x2;
+		break;
+	case HSO_PORT_GPS:
+		result = 0x3;
+		break;
+	case HSO_PORT_APP2:
+		result = 0x4;
+		break;
+	default:
+		result = 0x0;
+	}
+	return result;
+}
+
+static struct hso_serial *get_serial_by_shared_int_and_type(
+					struct hso_shared_int *shared_int,
+					int mux)
+{
+	int i, port;
+
+	port = hso_mux_to_port(mux);
+
+	for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) {
+		if (serial_table[i] &&
+		    (dev2ser(serial_table[i])->shared_int == shared_int) &&
+		    ((serial_table[i]->port_spec & HSO_PORT_MASK) == port)) {
+			return dev2ser(serial_table[i]);
+		}
+	}
+
+	return NULL;
+}
+
+static struct hso_serial *get_serial_by_index(unsigned index)
+{
+	struct hso_serial *serial = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial_table_lock, flags);
+	if (serial_table[index])
+		serial = dev2ser(serial_table[index]);
+	spin_unlock_irqrestore(&serial_table_lock, flags);
+
+	return serial;
+}
+
+static int get_free_serial_index(void)
+{
+	int index;
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial_table_lock, flags);
+	for (index = 0; index < HSO_SERIAL_TTY_MINORS; index++) {
+		if (serial_table[index] == NULL) {
+			spin_unlock_irqrestore(&serial_table_lock, flags);
+			return index;
+		}
+	}
+	spin_unlock_irqrestore(&serial_table_lock, flags);
+
+	printk(KERN_ERR "%s: no free serial devices in table\n", __func__);
+	return -1;
+}
+
+static void set_serial_by_index(unsigned index, struct hso_serial *serial)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial_table_lock, flags);
+	if (serial)
+		serial_table[index] = serial->parent;
+	else
+		serial_table[index] = NULL;
+	spin_unlock_irqrestore(&serial_table_lock, flags);
+}
+
+static void handle_usb_error(int status, const char *function,
+			     struct hso_device *hso_dev)
+{
+	char *explanation;
+
+	switch (status) {
+	case -ENODEV:
+		explanation = "no device";
+		break;
+	case -ENOENT:
+		explanation = "endpoint not enabled";
+		break;
+	case -EPIPE:
+		explanation = "endpoint stalled";
+		break;
+	case -ENOSPC:
+		explanation = "not enough bandwidth";
+		break;
+	case -ESHUTDOWN:
+		explanation = "device disabled";
+		break;
+	case -EHOSTUNREACH:
+		explanation = "device suspended";
+		break;
+	case -EINVAL:
+	case -EAGAIN:
+	case -EFBIG:
+	case -EMSGSIZE:
+		explanation = "internal error";
+		break;
+	case -EILSEQ:
+	case -EPROTO:
+	case -ETIME:
+	case -ETIMEDOUT:
+		explanation = "protocol error";
+		if (hso_dev)
+			schedule_work(&hso_dev->reset_device);
+		break;
+	default:
+		explanation = "unknown status";
+		break;
+	}
+
+	/* log a meaningful explanation of an USB status */
+	D1("%s: received USB status - %s (%d)", function, explanation, status);
+}
+
+/* Network interface functions */
+
+/* called when net interface is brought up by ifconfig */
+static int hso_net_open(struct net_device *net)
+{
+	struct hso_net *odev = netdev_priv(net);
+	unsigned long flags = 0;
+
+	if (!odev) {
+		dev_err(&net->dev, "No net device !\n");
+		return -ENODEV;
+	}
+
+	odev->skb_tx_buf = NULL;
+
+	/* setup environment */
+	spin_lock_irqsave(&odev->net_lock, flags);
+	odev->rx_parse_state = WAIT_IP;
+	odev->rx_buf_size = 0;
+	odev->rx_buf_missing = sizeof(struct iphdr);
+	spin_unlock_irqrestore(&odev->net_lock, flags);
+
+	/* We are up and running. */
+	set_bit(HSO_NET_RUNNING, &odev->flags);
+	hso_start_net_device(odev->parent);
+
+	/* Tell the kernel we are ready to start receiving from it */
+	netif_start_queue(net);
+
+	return 0;
+}
+
+/* called when interface is brought down by ifconfig */
+static int hso_net_close(struct net_device *net)
+{
+	struct hso_net *odev = netdev_priv(net);
+
+	/* we don't need the queue anymore */
+	netif_stop_queue(net);
+	/* no longer running */
+	clear_bit(HSO_NET_RUNNING, &odev->flags);
+
+	hso_stop_net_device(odev->parent);
+
+	/* done */
+	return 0;
+}
+
+/* USB tells is xmit done, we should start the netqueue again */
+static void write_bulk_callback(struct urb *urb)
+{
+	struct hso_net *odev = urb->context;
+	int status = urb->status;
+
+	/* Sanity check */
+	if (!odev || !test_bit(HSO_NET_RUNNING, &odev->flags)) {
+		dev_err(&urb->dev->dev, "%s: device not running\n", __func__);
+		return;
+	}
+
+	/* Do we still have a valid kernel network device? */
+	if (!netif_device_present(odev->net)) {
+		dev_err(&urb->dev->dev, "%s: net device not present\n",
+			__func__);
+		return;
+	}
+
+	/* log status, but don't act on it, we don't need to resubmit anything
+	 * anyhow */
+	if (status)
+		handle_usb_error(status, __func__, odev->parent);
+
+	hso_put_activity(odev->parent);
+
+	/* Tell the network interface we are ready for another frame */
+	netif_wake_queue(odev->net);
+}
+
+/* called by kernel when we need to transmit a packet */
+static netdev_tx_t hso_net_start_xmit(struct sk_buff *skb,
+					    struct net_device *net)
+{
+	struct hso_net *odev = netdev_priv(net);
+	int result;
+
+	/* Tell the kernel, "No more frames 'til we are done with this one." */
+	netif_stop_queue(net);
+	if (hso_get_activity(odev->parent) == -EAGAIN) {
+		odev->skb_tx_buf = skb;
+		return NETDEV_TX_OK;
+	}
+
+	/* log if asked */
+	DUMP1(skb->data, skb->len);
+	/* Copy it from kernel memory to OUR memory */
+	memcpy(odev->mux_bulk_tx_buf, skb->data, skb->len);
+	D1("len: %d/%d", skb->len, MUX_BULK_TX_BUF_SIZE);
+
+	/* Fill in the URB for shipping it out. */
+	usb_fill_bulk_urb(odev->mux_bulk_tx_urb,
+			  odev->parent->usb,
+			  usb_sndbulkpipe(odev->parent->usb,
+					  odev->out_endp->
+					  bEndpointAddress & 0x7F),
+			  odev->mux_bulk_tx_buf, skb->len, write_bulk_callback,
+			  odev);
+
+	/* Deal with the Zero Length packet problem, I hope */
+	odev->mux_bulk_tx_urb->transfer_flags |= URB_ZERO_PACKET;
+
+	/* Send the URB on its merry way. */
+	result = usb_submit_urb(odev->mux_bulk_tx_urb, GFP_ATOMIC);
+	if (result) {
+		dev_warn(&odev->parent->interface->dev,
+			"failed mux_bulk_tx_urb %d\n", result);
+		net->stats.tx_errors++;
+		netif_start_queue(net);
+	} else {
+		net->stats.tx_packets++;
+		net->stats.tx_bytes += skb->len;
+	}
+	dev_kfree_skb(skb);
+	/* we're done */
+	return NETDEV_TX_OK;
+}
+
+static const struct ethtool_ops ops = {
+	.get_link = ethtool_op_get_link
+};
+
+/* called when a packet did not ack after watchdogtimeout */
+static void hso_net_tx_timeout(struct net_device *net)
+{
+	struct hso_net *odev = netdev_priv(net);
+
+	if (!odev)
+		return;
+
+	/* Tell syslog we are hosed. */
+	dev_warn(&net->dev, "Tx timed out.\n");
+
+	/* Tear the waiting frame off the list */
+	if (odev->mux_bulk_tx_urb &&
+	    (odev->mux_bulk_tx_urb->status == -EINPROGRESS))
+		usb_unlink_urb(odev->mux_bulk_tx_urb);
+
+	/* Update statistics */
+	net->stats.tx_errors++;
+}
+
+/* make a real packet from the received USB buffer */
+static void packetizeRx(struct hso_net *odev, unsigned char *ip_pkt,
+			unsigned int count, unsigned char is_eop)
+{
+	unsigned short temp_bytes;
+	unsigned short buffer_offset = 0;
+	unsigned short frame_len;
+	unsigned char *tmp_rx_buf;
+
+	/* log if needed */
+	D1("Rx %d bytes", count);
+	DUMP(ip_pkt, min(128, (int)count));
+
+	while (count) {
+		switch (odev->rx_parse_state) {
+		case WAIT_IP:
+			/* waiting for IP header. */
+			/* wanted bytes - size of ip header */
+			temp_bytes =
+			    (count <
+			     odev->rx_buf_missing) ? count : odev->
+			    rx_buf_missing;
+
+			memcpy(((unsigned char *)(&odev->rx_ip_hdr)) +
+			       odev->rx_buf_size, ip_pkt + buffer_offset,
+			       temp_bytes);
+
+			odev->rx_buf_size += temp_bytes;
+			buffer_offset += temp_bytes;
+			odev->rx_buf_missing -= temp_bytes;
+			count -= temp_bytes;
+
+			if (!odev->rx_buf_missing) {
+				/* header is complete allocate an sk_buffer and
+				 * continue to WAIT_DATA */
+				frame_len = ntohs(odev->rx_ip_hdr.tot_len);
+
+				if ((frame_len > DEFAULT_MRU) ||
+				    (frame_len < sizeof(struct iphdr))) {
+					dev_err(&odev->net->dev,
+						"Invalid frame (%d) length\n",
+						frame_len);
+					odev->rx_parse_state = WAIT_SYNC;
+					continue;
+				}
+				/* Allocate an sk_buff */
+				odev->skb_rx_buf = netdev_alloc_skb(odev->net,
+								    frame_len);
+				if (!odev->skb_rx_buf) {
+					/* We got no receive buffer. */
+					D1("could not allocate memory");
+					odev->rx_parse_state = WAIT_SYNC;
+					return;
+				}
+
+				/* Copy what we got so far. make room for iphdr
+				 * after tail. */
+				tmp_rx_buf =
+				    skb_put(odev->skb_rx_buf,
+					    sizeof(struct iphdr));
+				memcpy(tmp_rx_buf, (char *)&(odev->rx_ip_hdr),
+				       sizeof(struct iphdr));
+
+				/* ETH_HLEN */
+				odev->rx_buf_size = sizeof(struct iphdr);
+
+				/* Filip actually use .tot_len */
+				odev->rx_buf_missing =
+				    frame_len - sizeof(struct iphdr);
+				odev->rx_parse_state = WAIT_DATA;
+			}
+			break;
+
+		case WAIT_DATA:
+			temp_bytes = (count < odev->rx_buf_missing)
+					? count : odev->rx_buf_missing;
+
+			/* Copy the rest of the bytes that are left in the
+			 * buffer into the waiting sk_buf. */
+			/* Make room for temp_bytes after tail. */
+			tmp_rx_buf = skb_put(odev->skb_rx_buf, temp_bytes);
+			memcpy(tmp_rx_buf, ip_pkt + buffer_offset, temp_bytes);
+
+			odev->rx_buf_missing -= temp_bytes;
+			count -= temp_bytes;
+			buffer_offset += temp_bytes;
+			odev->rx_buf_size += temp_bytes;
+			if (!odev->rx_buf_missing) {
+				/* Packet is complete. Inject into stack. */
+				/* We have IP packet here */
+				odev->skb_rx_buf->protocol = cpu_to_be16(ETH_P_IP);
+				skb_reset_mac_header(odev->skb_rx_buf);
+
+				/* Ship it off to the kernel */
+				netif_rx(odev->skb_rx_buf);
+				/* No longer our buffer. */
+				odev->skb_rx_buf = NULL;
+
+				/* update out statistics */
+				odev->net->stats.rx_packets++;
+
+				odev->net->stats.rx_bytes += odev->rx_buf_size;
+
+				odev->rx_buf_size = 0;
+				odev->rx_buf_missing = sizeof(struct iphdr);
+				odev->rx_parse_state = WAIT_IP;
+			}
+			break;
+
+		case WAIT_SYNC:
+			D1(" W_S");
+			count = 0;
+			break;
+		default:
+			D1(" ");
+			count--;
+			break;
+		}
+	}
+
+	/* Recovery mechanism for WAIT_SYNC state. */
+	if (is_eop) {
+		if (odev->rx_parse_state == WAIT_SYNC) {
+			odev->rx_parse_state = WAIT_IP;
+			odev->rx_buf_size = 0;
+			odev->rx_buf_missing = sizeof(struct iphdr);
+		}
+	}
+}
+
+static void fix_crc_bug(struct urb *urb, __le16 max_packet_size)
+{
+	static const u8 crc_check[4] = { 0xDE, 0xAD, 0xBE, 0xEF };
+	u32 rest = urb->actual_length % le16_to_cpu(max_packet_size);
+
+	if (((rest == 5) || (rest == 6)) &&
+	    !memcmp(((u8 *)urb->transfer_buffer) + urb->actual_length - 4,
+		    crc_check, 4)) {
+		urb->actual_length -= 4;
+	}
+}
+
+/* Moving data from usb to kernel (in interrupt state) */
+static void read_bulk_callback(struct urb *urb)
+{
+	struct hso_net *odev = urb->context;
+	struct net_device *net;
+	int result;
+	int status = urb->status;
+
+	/* is al ok?  (Filip: Who's Al ?) */
+	if (status) {
+		handle_usb_error(status, __func__, odev->parent);
+		return;
+	}
+
+	/* Sanity check */
+	if (!odev || !test_bit(HSO_NET_RUNNING, &odev->flags)) {
+		D1("BULK IN callback but driver is not active!");
+		return;
+	}
+	usb_mark_last_busy(urb->dev);
+
+	net = odev->net;
+
+	if (!netif_device_present(net)) {
+		/* Somebody killed our network interface... */
+		return;
+	}
+
+	if (odev->parent->port_spec & HSO_INFO_CRC_BUG)
+		fix_crc_bug(urb, odev->in_endp->wMaxPacketSize);
+
+	/* do we even have a packet? */
+	if (urb->actual_length) {
+		/* Handle the IP stream, add header and push it onto network
+		 * stack if the packet is complete. */
+		spin_lock(&odev->net_lock);
+		packetizeRx(odev, urb->transfer_buffer, urb->actual_length,
+			    (urb->transfer_buffer_length >
+			     urb->actual_length) ? 1 : 0);
+		spin_unlock(&odev->net_lock);
+	}
+
+	/* We are done with this URB, resubmit it. Prep the USB to wait for
+	 * another frame. Reuse same as received. */
+	usb_fill_bulk_urb(urb,
+			  odev->parent->usb,
+			  usb_rcvbulkpipe(odev->parent->usb,
+					  odev->in_endp->
+					  bEndpointAddress & 0x7F),
+			  urb->transfer_buffer, MUX_BULK_RX_BUF_SIZE,
+			  read_bulk_callback, odev);
+
+	/* Give this to the USB subsystem so it can tell us when more data
+	 * arrives. */
+	result = usb_submit_urb(urb, GFP_ATOMIC);
+	if (result)
+		dev_warn(&odev->parent->interface->dev,
+			 "%s failed submit mux_bulk_rx_urb %d\n", __func__,
+			 result);
+}
+
+/* Serial driver functions */
+
+static void hso_init_termios(struct ktermios *termios)
+{
+	/*
+	 * The default requirements for this device are:
+	 */
+	termios->c_iflag &=
+		~(IGNBRK	/* disable ignore break */
+		| BRKINT	/* disable break causes interrupt */
+		| PARMRK	/* disable mark parity errors */
+		| ISTRIP	/* disable clear high bit of input characters */
+		| INLCR		/* disable translate NL to CR */
+		| IGNCR		/* disable ignore CR */
+		| ICRNL		/* disable translate CR to NL */
+		| IXON);	/* disable enable XON/XOFF flow control */
+
+	/* disable postprocess output characters */
+	termios->c_oflag &= ~OPOST;
+
+	termios->c_lflag &=
+		~(ECHO		/* disable echo input characters */
+		| ECHONL	/* disable echo new line */
+		| ICANON	/* disable erase, kill, werase, and rprnt
+				   special characters */
+		| ISIG		/* disable interrupt, quit, and suspend special
+				   characters */
+		| IEXTEN);	/* disable non-POSIX special characters */
+
+	termios->c_cflag &=
+		~(CSIZE		/* no size */
+		| PARENB	/* disable parity bit */
+		| CBAUD		/* clear current baud rate */
+		| CBAUDEX);	/* clear current buad rate */
+
+	termios->c_cflag |= CS8;	/* character size 8 bits */
+
+	/* baud rate 115200 */
+	tty_termios_encode_baud_rate(termios, 115200, 115200);
+}
+
+static void _hso_serial_set_termios(struct tty_struct *tty,
+				    struct ktermios *old)
+{
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	struct ktermios *termios;
+
+	if (!serial) {
+		printk(KERN_ERR "%s: no tty structures", __func__);
+		return;
+	}
+
+	D4("port %d", serial->minor);
+
+	/*
+	 *	Fix up unsupported bits
+	 */
+	termios = tty->termios;
+	termios->c_iflag &= ~IXON; /* disable enable XON/XOFF flow control */
+
+	termios->c_cflag &=
+		~(CSIZE		/* no size */
+		| PARENB	/* disable parity bit */
+		| CBAUD		/* clear current baud rate */
+		| CBAUDEX);	/* clear current buad rate */
+
+	termios->c_cflag |= CS8;	/* character size 8 bits */
+
+	/* baud rate 115200 */
+	tty_encode_baud_rate(tty, 115200, 115200);
+}
+
+static void hso_resubmit_rx_bulk_urb(struct hso_serial *serial, struct urb *urb)
+{
+	int result;
+	/* We are done with this URB, resubmit it. Prep the USB to wait for
+	 * another frame */
+	usb_fill_bulk_urb(urb, serial->parent->usb,
+			  usb_rcvbulkpipe(serial->parent->usb,
+					  serial->in_endp->
+					  bEndpointAddress & 0x7F),
+			  urb->transfer_buffer, serial->rx_data_length,
+			  hso_std_serial_read_bulk_callback, serial);
+	/* Give this to the USB subsystem so it can tell us when more data
+	 * arrives. */
+	result = usb_submit_urb(urb, GFP_ATOMIC);
+	if (result) {
+		dev_err(&urb->dev->dev, "%s failed submit serial rx_urb %d\n",
+			__func__, result);
+	}
+}
+
+
+
+
+static void put_rxbuf_data_and_resubmit_bulk_urb(struct hso_serial *serial)
+{
+	int count;
+	struct urb *curr_urb;
+
+	while (serial->rx_urb_filled[serial->curr_rx_urb_idx]) {
+		curr_urb = serial->rx_urb[serial->curr_rx_urb_idx];
+		count = put_rxbuf_data(curr_urb, serial);
+		if (count == -1)
+			return;
+		if (count == 0) {
+			serial->curr_rx_urb_idx++;
+			if (serial->curr_rx_urb_idx >= serial->num_rx_urbs)
+				serial->curr_rx_urb_idx = 0;
+			hso_resubmit_rx_bulk_urb(serial, curr_urb);
+		}
+	}
+}
+
+static void put_rxbuf_data_and_resubmit_ctrl_urb(struct hso_serial *serial)
+{
+	int count = 0;
+	struct urb *urb;
+
+	urb = serial->rx_urb[0];
+	if (serial->open_count > 0) {
+		count = put_rxbuf_data(urb, serial);
+		if (count == -1)
+			return;
+	}
+	/* Re issue a read as long as we receive data. */
+
+	if (count == 0 && ((urb->actual_length != 0) ||
+			   (serial->rx_state == RX_PENDING))) {
+		serial->rx_state = RX_SENT;
+		hso_mux_serial_read(serial);
+	} else
+		serial->rx_state = RX_IDLE;
+}
+
+
+/* read callback for Diag and CS port */
+static void hso_std_serial_read_bulk_callback(struct urb *urb)
+{
+	struct hso_serial *serial = urb->context;
+	int status = urb->status;
+
+	/* sanity check */
+	if (!serial) {
+		D1("serial == NULL");
+		return;
+	} else if (status) {
+		handle_usb_error(status, __func__, serial->parent);
+		return;
+	}
+
+	D4("\n--- Got serial_read_bulk callback %02x ---", status);
+	D1("Actual length = %d\n", urb->actual_length);
+	DUMP1(urb->transfer_buffer, urb->actual_length);
+
+	/* Anyone listening? */
+	if (serial->open_count == 0)
+		return;
+
+	if (status == 0) {
+		if (serial->parent->port_spec & HSO_INFO_CRC_BUG)
+			fix_crc_bug(urb, serial->in_endp->wMaxPacketSize);
+		/* Valid data, handle RX data */
+		spin_lock(&serial->serial_lock);
+		serial->rx_urb_filled[hso_urb_to_index(serial, urb)] = 1;
+		put_rxbuf_data_and_resubmit_bulk_urb(serial);
+		spin_unlock(&serial->serial_lock);
+	} else if (status == -ENOENT || status == -ECONNRESET) {
+		/* Unlinked - check for throttled port. */
+		D2("Port %d, successfully unlinked urb", serial->minor);
+		spin_lock(&serial->serial_lock);
+		serial->rx_urb_filled[hso_urb_to_index(serial, urb)] = 0;
+		hso_resubmit_rx_bulk_urb(serial, urb);
+		spin_unlock(&serial->serial_lock);
+	} else {
+		D2("Port %d, status = %d for read urb", serial->minor, status);
+		return;
+	}
+}
+
+/*
+ * This needs to be a tasklet otherwise we will
+ * end up recursively calling this function.
+ */
+static void hso_unthrottle_tasklet(struct hso_serial *serial)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial->serial_lock, flags);
+	if ((serial->parent->port_spec & HSO_INTF_MUX))
+		put_rxbuf_data_and_resubmit_ctrl_urb(serial);
+	else
+		put_rxbuf_data_and_resubmit_bulk_urb(serial);
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+}
+
+static	void hso_unthrottle(struct tty_struct *tty)
+{
+	struct hso_serial *serial = get_serial_by_tty(tty);
+
+	tasklet_hi_schedule(&serial->unthrottle_tasklet);
+}
+
+static void hso_unthrottle_workfunc(struct work_struct *work)
+{
+	struct hso_serial *serial =
+	    container_of(work, struct hso_serial,
+			 retry_unthrottle_workqueue);
+	hso_unthrottle_tasklet(serial);
+}
+
+/* open the requested serial port */
+static int hso_serial_open(struct tty_struct *tty, struct file *filp)
+{
+	struct hso_serial *serial = get_serial_by_index(tty->index);
+	int result;
+
+	/* sanity check */
+	if (serial == NULL || serial->magic != HSO_SERIAL_MAGIC) {
+		WARN_ON(1);
+		tty->driver_data = NULL;
+		D1("Failed to open port");
+		return -ENODEV;
+	}
+
+	mutex_lock(&serial->parent->mutex);
+	result = usb_autopm_get_interface(serial->parent->interface);
+	if (result < 0)
+		goto err_out;
+
+	D1("Opening %d", serial->minor);
+	kref_get(&serial->parent->ref);
+
+	/* setup */
+	spin_lock_irq(&serial->serial_lock);
+	tty->driver_data = serial;
+	tty_kref_put(serial->tty);
+	serial->tty = tty_kref_get(tty);
+	spin_unlock_irq(&serial->serial_lock);
+
+	/* check for port already opened, if not set the termios */
+	serial->open_count++;
+	if (serial->open_count == 1) {
+		serial->rx_state = RX_IDLE;
+		/* Force default termio settings */
+		_hso_serial_set_termios(tty, NULL);
+		tasklet_init(&serial->unthrottle_tasklet,
+			     (void (*)(unsigned long))hso_unthrottle_tasklet,
+			     (unsigned long)serial);
+		INIT_WORK(&serial->retry_unthrottle_workqueue,
+			  hso_unthrottle_workfunc);
+		result = hso_start_serial_device(serial->parent, GFP_KERNEL);
+		if (result) {
+			hso_stop_serial_device(serial->parent);
+			serial->open_count--;
+			kref_put(&serial->parent->ref, hso_serial_ref_free);
+		}
+	} else {
+		D1("Port was already open");
+	}
+
+	usb_autopm_put_interface(serial->parent->interface);
+
+	/* done */
+	if (result)
+		hso_serial_tiocmset(tty, TIOCM_RTS | TIOCM_DTR, 0);
+err_out:
+	mutex_unlock(&serial->parent->mutex);
+	return result;
+}
+
+/* close the requested serial port */
+static void hso_serial_close(struct tty_struct *tty, struct file *filp)
+{
+	struct hso_serial *serial = tty->driver_data;
+	u8 usb_gone;
+
+	D1("Closing serial port");
+
+	/* Open failed, no close cleanup required */
+	if (serial == NULL)
+		return;
+
+	mutex_lock(&serial->parent->mutex);
+	usb_gone = serial->parent->usb_gone;
+
+	if (!usb_gone)
+		usb_autopm_get_interface(serial->parent->interface);
+
+	/* reset the rts and dtr */
+	/* do the actual close */
+	serial->open_count--;
+
+	if (serial->open_count <= 0) {
+		serial->open_count = 0;
+		spin_lock_irq(&serial->serial_lock);
+		if (serial->tty == tty) {
+			serial->tty->driver_data = NULL;
+			serial->tty = NULL;
+			tty_kref_put(tty);
+		}
+		spin_unlock_irq(&serial->serial_lock);
+		if (!usb_gone)
+			hso_stop_serial_device(serial->parent);
+		tasklet_kill(&serial->unthrottle_tasklet);
+		cancel_work_sync(&serial->retry_unthrottle_workqueue);
+	}
+
+	if (!usb_gone)
+		usb_autopm_put_interface(serial->parent->interface);
+
+	mutex_unlock(&serial->parent->mutex);
+
+	kref_put(&serial->parent->ref, hso_serial_ref_free);
+}
+
+/* close the requested serial port */
+static int hso_serial_write(struct tty_struct *tty, const unsigned char *buf,
+			    int count)
+{
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	int space, tx_bytes;
+	unsigned long flags;
+
+	/* sanity check */
+	if (serial == NULL) {
+		printk(KERN_ERR "%s: serial is NULL\n", __func__);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&serial->serial_lock, flags);
+
+	space = serial->tx_data_length - serial->tx_buffer_count;
+	tx_bytes = (count < space) ? count : space;
+
+	if (!tx_bytes)
+		goto out;
+
+	memcpy(serial->tx_buffer + serial->tx_buffer_count, buf, tx_bytes);
+	serial->tx_buffer_count += tx_bytes;
+
+out:
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+
+	hso_kick_transmit(serial);
+	/* done */
+	return tx_bytes;
+}
+
+/* how much room is there for writing */
+static int hso_serial_write_room(struct tty_struct *tty)
+{
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	int room;
+	unsigned long flags;
+
+	spin_lock_irqsave(&serial->serial_lock, flags);
+	room = serial->tx_data_length - serial->tx_buffer_count;
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+
+	/* return free room */
+	return room;
+}
+
+/* setup the term */
+static void hso_serial_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	unsigned long flags;
+
+	if (old)
+		D5("Termios called with: cflags new[%d] - old[%d]",
+		   tty->termios->c_cflag, old->c_cflag);
+
+	/* the actual setup */
+	spin_lock_irqsave(&serial->serial_lock, flags);
+	if (serial->open_count)
+		_hso_serial_set_termios(tty, old);
+	else
+		tty->termios = old;
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+
+	/* done */
+}
+
+/* how many characters in the buffer */
+static int hso_serial_chars_in_buffer(struct tty_struct *tty)
+{
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	int chars;
+	unsigned long flags;
+
+	/* sanity check */
+	if (serial == NULL)
+		return 0;
+
+	spin_lock_irqsave(&serial->serial_lock, flags);
+	chars = serial->tx_buffer_count;
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+
+	return chars;
+}
+static int tiocmget_submit_urb(struct hso_serial *serial,
+			       struct hso_tiocmget *tiocmget,
+			       struct usb_device *usb)
+{
+	int result;
+
+	if (serial->parent->usb_gone)
+		return -ENODEV;
+	usb_fill_int_urb(tiocmget->urb, usb,
+			 usb_rcvintpipe(usb,
+					tiocmget->endp->
+					bEndpointAddress & 0x7F),
+			 &tiocmget->serial_state_notification,
+			 sizeof(struct hso_serial_state_notification),
+			 tiocmget_intr_callback, serial,
+			 tiocmget->endp->bInterval);
+	result = usb_submit_urb(tiocmget->urb, GFP_ATOMIC);
+	if (result) {
+		dev_warn(&usb->dev, "%s usb_submit_urb failed %d\n", __func__,
+			 result);
+	}
+	return result;
+
+}
+
+static void tiocmget_intr_callback(struct urb *urb)
+{
+	struct hso_serial *serial = urb->context;
+	struct hso_tiocmget *tiocmget;
+	int status = urb->status;
+	u16 UART_state_bitmap, prev_UART_state_bitmap;
+	struct uart_icount *icount;
+	struct hso_serial_state_notification *serial_state_notification;
+	struct usb_device *usb;
+
+	/* Sanity checks */
+	if (!serial)
+		return;
+	if (status) {
+		handle_usb_error(status, __func__, serial->parent);
+		return;
+	}
+	tiocmget = serial->tiocmget;
+	if (!tiocmget)
+		return;
+	usb = serial->parent->usb;
+	serial_state_notification = &tiocmget->serial_state_notification;
+	if (serial_state_notification->bmRequestType != BM_REQUEST_TYPE ||
+	    serial_state_notification->bNotification != B_NOTIFICATION ||
+	    le16_to_cpu(serial_state_notification->wValue) != W_VALUE ||
+	    le16_to_cpu(serial_state_notification->wIndex) != W_INDEX ||
+	    le16_to_cpu(serial_state_notification->wLength) != W_LENGTH) {
+		dev_warn(&usb->dev,
+			 "hso received invalid serial state notification\n");
+		DUMP(serial_state_notification,
+		     sizeof(struct hso_serial_state_notification));
+	} else {
+
+		UART_state_bitmap = le16_to_cpu(serial_state_notification->
+						UART_state_bitmap);
+		prev_UART_state_bitmap = tiocmget->prev_UART_state_bitmap;
+		icount = &tiocmget->icount;
+		spin_lock(&serial->serial_lock);
+		if ((UART_state_bitmap & B_OVERRUN) !=
+		   (prev_UART_state_bitmap & B_OVERRUN))
+			icount->parity++;
+		if ((UART_state_bitmap & B_PARITY) !=
+		   (prev_UART_state_bitmap & B_PARITY))
+			icount->parity++;
+		if ((UART_state_bitmap & B_FRAMING) !=
+		   (prev_UART_state_bitmap & B_FRAMING))
+			icount->frame++;
+		if ((UART_state_bitmap & B_RING_SIGNAL) &&
+		   !(prev_UART_state_bitmap & B_RING_SIGNAL))
+			icount->rng++;
+		if ((UART_state_bitmap & B_BREAK) !=
+		   (prev_UART_state_bitmap & B_BREAK))
+			icount->brk++;
+		if ((UART_state_bitmap & B_TX_CARRIER) !=
+		   (prev_UART_state_bitmap & B_TX_CARRIER))
+			icount->dsr++;
+		if ((UART_state_bitmap & B_RX_CARRIER) !=
+		   (prev_UART_state_bitmap & B_RX_CARRIER))
+			icount->dcd++;
+		tiocmget->prev_UART_state_bitmap = UART_state_bitmap;
+		spin_unlock(&serial->serial_lock);
+		tiocmget->intr_completed = 1;
+		wake_up_interruptible(&tiocmget->waitq);
+	}
+	memset(serial_state_notification, 0,
+	       sizeof(struct hso_serial_state_notification));
+	tiocmget_submit_urb(serial,
+			    tiocmget,
+			    serial->parent->usb);
+}
+
+/*
+ * next few functions largely stolen from drivers/serial/serial_core.c
+ */
+/* Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+ * - mask passed in arg for lines of interest
+ *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+ * Caller should use TIOCGICOUNT to see which one it was
+ */
+static int
+hso_wait_modem_status(struct hso_serial *serial, unsigned long arg)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	struct uart_icount cprev, cnow;
+	struct hso_tiocmget  *tiocmget;
+	int ret;
+
+	tiocmget = serial->tiocmget;
+	if (!tiocmget)
+		return -ENOENT;
+	/*
+	 * note the counters on entry
+	 */
+	spin_lock_irq(&serial->serial_lock);
+	memcpy(&cprev, &tiocmget->icount, sizeof(struct uart_icount));
+	spin_unlock_irq(&serial->serial_lock);
+	add_wait_queue(&tiocmget->waitq, &wait);
+	for (;;) {
+		spin_lock_irq(&serial->serial_lock);
+		memcpy(&cnow, &tiocmget->icount, sizeof(struct uart_icount));
+		spin_unlock_irq(&serial->serial_lock);
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+		    ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+		    ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd))) {
+			ret = 0;
+			break;
+		}
+		schedule();
+		/* see if a signal did it */
+		if (signal_pending(current)) {
+			ret = -ERESTARTSYS;
+			break;
+		}
+		cprev = cnow;
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&tiocmget->waitq, &wait);
+
+	return ret;
+}
+
+/*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ *     RI where only 0->1 is counted.
+ */
+static int hso_get_count(struct tty_struct *tty,
+		  struct serial_icounter_struct *icount)
+{
+	struct uart_icount cnow;
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	struct hso_tiocmget  *tiocmget = serial->tiocmget;
+
+	memset(icount, 0, sizeof(struct serial_icounter_struct));
+
+	if (!tiocmget)
+		 return -ENOENT;
+	spin_lock_irq(&serial->serial_lock);
+	memcpy(&cnow, &tiocmget->icount, sizeof(struct uart_icount));
+	spin_unlock_irq(&serial->serial_lock);
+
+	icount->cts         = cnow.cts;
+	icount->dsr         = cnow.dsr;
+	icount->rng         = cnow.rng;
+	icount->dcd         = cnow.dcd;
+	icount->rx          = cnow.rx;
+	icount->tx          = cnow.tx;
+	icount->frame       = cnow.frame;
+	icount->overrun     = cnow.overrun;
+	icount->parity      = cnow.parity;
+	icount->brk         = cnow.brk;
+	icount->buf_overrun = cnow.buf_overrun;
+
+	return 0;
+}
+
+
+static int hso_serial_tiocmget(struct tty_struct *tty)
+{
+	int retval;
+	struct hso_serial *serial = get_serial_by_tty(tty);
+	struct hso_tiocmget  *tiocmget;
+	u16 UART_state_bitmap;
+
+	/* sanity check */
+	if (!serial) {
+		D1("no tty structures");
+		return -EINVAL;
+	}
+	spin_lock_irq(&serial->serial_lock);
+	retval = ((serial->rts_state) ? TIOCM_RTS : 0) |
+	    ((serial->dtr_state) ? TIOCM_DTR : 0);
+	tiocmget = serial->tiocmget;
+	if (tiocmget) {
+
+		UART_state_bitmap = le16_to_cpu(
+			tiocmget->prev_UART_state_bitmap);
+		if (UART_state_bitmap & B_RING_SIGNAL)
+			retval |=  TIOCM_RNG;
+		if (UART_state_bitmap & B_RX_CARRIER)
+			retval |=  TIOCM_CD;
+		if (UART_state_bitmap & B_TX_CARRIER)
+			retval |=  TIOCM_DSR;
+	}
+	spin_unlock_irq(&serial->serial_lock);
+	return retval;
+}
+
+static int hso_serial_tiocmset(struct tty_struct *tty,
+			       unsigned int set, unsigned int clear)
+{
+	int val = 0;
+	unsigned long flags;
+	int if_num;
+	struct hso_serial *serial = get_serial_by_tty(tty);
+
+	/* sanity check */
+	if (!serial) {
+		D1("no tty structures");
+		return -EINVAL;
+	}
+
+	if ((serial->parent->port_spec & HSO_PORT_MASK) != HSO_PORT_MODEM)
+		return -EINVAL;
+
+	if_num = serial->parent->interface->altsetting->desc.bInterfaceNumber;
+
+	spin_lock_irqsave(&serial->serial_lock, flags);
+	if (set & TIOCM_RTS)
+		serial->rts_state = 1;
+	if (set & TIOCM_DTR)
+		serial->dtr_state = 1;
+
+	if (clear & TIOCM_RTS)
+		serial->rts_state = 0;
+	if (clear & TIOCM_DTR)
+		serial->dtr_state = 0;
+
+	if (serial->dtr_state)
+		val |= 0x01;
+	if (serial->rts_state)
+		val |= 0x02;
+
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+
+	return usb_control_msg(serial->parent->usb,
+			       usb_rcvctrlpipe(serial->parent->usb, 0), 0x22,
+			       0x21, val, if_num, NULL, 0,
+			       USB_CTRL_SET_TIMEOUT);
+}
+
+static int hso_serial_ioctl(struct tty_struct *tty,
+			    unsigned int cmd, unsigned long arg)
+{
+	struct hso_serial *serial =  get_serial_by_tty(tty);
+	int ret = 0;
+	D4("IOCTL cmd: %d, arg: %ld", cmd, arg);
+
+	if (!serial)
+		return -ENODEV;
+	switch (cmd) {
+	case TIOCMIWAIT:
+		ret = hso_wait_modem_status(serial, arg);
+		break;
+	default:
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+	return ret;
+}
+
+
+/* starts a transmit */
+static void hso_kick_transmit(struct hso_serial *serial)
+{
+	u8 *temp;
+	unsigned long flags;
+	int res;
+
+	spin_lock_irqsave(&serial->serial_lock, flags);
+	if (!serial->tx_buffer_count)
+		goto out;
+
+	if (serial->tx_urb_used)
+		goto out;
+
+	/* Wakeup USB interface if necessary */
+	if (hso_get_activity(serial->parent) == -EAGAIN)
+		goto out;
+
+	/* Switch pointers around to avoid memcpy */
+	temp = serial->tx_buffer;
+	serial->tx_buffer = serial->tx_data;
+	serial->tx_data = temp;
+	serial->tx_data_count = serial->tx_buffer_count;
+	serial->tx_buffer_count = 0;
+
+	/* If temp is set, it means we switched buffers */
+	if (temp && serial->write_data) {
+		res = serial->write_data(serial);
+		if (res >= 0)
+			serial->tx_urb_used = 1;
+	}
+out:
+	spin_unlock_irqrestore(&serial->serial_lock, flags);
+}
+
+/* make a request (for reading and writing data to muxed serial port) */
+static int mux_device_request(struct hso_serial *serial, u8 type, u16 port,
+			      struct urb *ctrl_urb,
+			      struct usb_ctrlrequest *ctrl_req,
+			      u8 *ctrl_urb_data, u32 size)
+{
+	int result;
+	int pipe;
+
+	/* Sanity check */
+	if (!serial || !ctrl_urb || !ctrl_req) {
+		printk(KERN_ERR "%s: Wrong arguments\n", __func__);
+		return -EINVAL;
+	}
+
+	/* initialize */
+	ctrl_req->wValue = 0;
+	ctrl_req->wIndex = cpu_to_le16(hso_port_to_mux(port));
+	ctrl_req->wLength = cpu_to_le16(size);
+
+	if (type == USB_CDC_GET_ENCAPSULATED_RESPONSE) {
+		/* Reading command */
+		ctrl_req->bRequestType = USB_DIR_IN |
+					 USB_TYPE_OPTION_VENDOR |
+					 USB_RECIP_INTERFACE;
+		ctrl_req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+		pipe = usb_rcvctrlpipe(serial->parent->usb, 0);
+	} else {
+		/* Writing command */
+		ctrl_req->bRequestType = USB_DIR_OUT |
+					 USB_TYPE_OPTION_VENDOR |
+					 USB_RECIP_INTERFACE;
+		ctrl_req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
+		pipe = usb_sndctrlpipe(serial->parent->usb, 0);
+	}
+	/* syslog */
+	D2("%s command (%02x) len: %d, port: %d",
+	   type == USB_CDC_GET_ENCAPSULATED_RESPONSE ? "Read" : "Write",
+	   ctrl_req->bRequestType, ctrl_req->wLength, port);
+
+	/* Load ctrl urb */
+	ctrl_urb->transfer_flags = 0;
+	usb_fill_control_urb(ctrl_urb,
+			     serial->parent->usb,
+			     pipe,
+			     (u8 *) ctrl_req,
+			     ctrl_urb_data, size, ctrl_callback, serial);
+	/* Send it on merry way */
+	result = usb_submit_urb(ctrl_urb, GFP_ATOMIC);
+	if (result) {
+		dev_err(&ctrl_urb->dev->dev,
+			"%s failed submit ctrl_urb %d type %d\n", __func__,
+			result, type);
+		return result;
+	}
+
+	/* done */
+	return size;
+}
+
+/* called by intr_callback when read occurs */
+static int hso_mux_serial_read(struct hso_serial *serial)
+{
+	if (!serial)
+		return -EINVAL;
+
+	/* clean data */
+	memset(serial->rx_data[0], 0, CTRL_URB_RX_SIZE);
+	/* make the request */
+
+	if (serial->num_rx_urbs != 1) {
+		dev_err(&serial->parent->interface->dev,
+			"ERROR: mux'd reads with multiple buffers "
+			"not possible\n");
+		return 0;
+	}
+	return mux_device_request(serial,
+				  USB_CDC_GET_ENCAPSULATED_RESPONSE,
+				  serial->parent->port_spec & HSO_PORT_MASK,
+				  serial->rx_urb[0],
+				  &serial->ctrl_req_rx,
+				  serial->rx_data[0], serial->rx_data_length);
+}
+
+/* used for muxed serial port callback (muxed serial read) */
+static void intr_callback(struct urb *urb)
+{
+	struct hso_shared_int *shared_int = urb->context;
+	struct hso_serial *serial;
+	unsigned char *port_req;
+	int status = urb->status;
+	int i;
+
+	usb_mark_last_busy(urb->dev);
+
+	/* sanity check */
+	if (!shared_int)
+		return;
+
+	/* status check */
+	if (status) {
+		handle_usb_error(status, __func__, NULL);
+		return;
+	}
+	D4("\n--- Got intr callback 0x%02X ---", status);
+
+	/* what request? */
+	port_req = urb->transfer_buffer;
+	D4(" port_req = 0x%.2X\n", *port_req);
+	/* loop over all muxed ports to find the one sending this */
+	for (i = 0; i < 8; i++) {
+		/* max 8 channels on MUX */
+		if (*port_req & (1 << i)) {
+			serial = get_serial_by_shared_int_and_type(shared_int,
+								   (1 << i));
+			if (serial != NULL) {
+				D1("Pending read interrupt on port %d\n", i);
+				spin_lock(&serial->serial_lock);
+				if (serial->rx_state == RX_IDLE &&
+					serial->open_count > 0) {
+					/* Setup and send a ctrl req read on
+					 * port i */
+					if (!serial->rx_urb_filled[0]) {
+						serial->rx_state = RX_SENT;
+						hso_mux_serial_read(serial);
+					} else
+						serial->rx_state = RX_PENDING;
+				} else {
+					D1("Already a read pending on "
+					   "port %d or port not open\n", i);
+				}
+				spin_unlock(&serial->serial_lock);
+			}
+		}
+	}
+	/* Resubmit interrupt urb */
+	hso_mux_submit_intr_urb(shared_int, urb->dev, GFP_ATOMIC);
+}
+
+/* called for writing to muxed serial port */
+static int hso_mux_serial_write_data(struct hso_serial *serial)
+{
+	if (NULL == serial)
+		return -EINVAL;
+
+	return mux_device_request(serial,
+				  USB_CDC_SEND_ENCAPSULATED_COMMAND,
+				  serial->parent->port_spec & HSO_PORT_MASK,
+				  serial->tx_urb,
+				  &serial->ctrl_req_tx,
+				  serial->tx_data, serial->tx_data_count);
+}
+
+/* write callback for Diag and CS port */
+static void hso_std_serial_write_bulk_callback(struct urb *urb)
+{
+	struct hso_serial *serial = urb->context;
+	int status = urb->status;
+	struct tty_struct *tty;
+
+	/* sanity check */
+	if (!serial) {
+		D1("serial == NULL");
+		return;
+	}
+
+	spin_lock(&serial->serial_lock);
+	serial->tx_urb_used = 0;
+	tty = tty_kref_get(serial->tty);
+	spin_unlock(&serial->serial_lock);
+	if (status) {
+		handle_usb_error(status, __func__, serial->parent);
+		tty_kref_put(tty);
+		return;
+	}
+	hso_put_activity(serial->parent);
+	if (tty) {
+		tty_wakeup(tty);
+		tty_kref_put(tty);
+	}
+	hso_kick_transmit(serial);
+
+	D1(" ");
+}
+
+/* called for writing diag or CS serial port */
+static int hso_std_serial_write_data(struct hso_serial *serial)
+{
+	int count = serial->tx_data_count;
+	int result;
+
+	usb_fill_bulk_urb(serial->tx_urb,
+			  serial->parent->usb,
+			  usb_sndbulkpipe(serial->parent->usb,
+					  serial->out_endp->
+					  bEndpointAddress & 0x7F),
+			  serial->tx_data, serial->tx_data_count,
+			  hso_std_serial_write_bulk_callback, serial);
+
+	result = usb_submit_urb(serial->tx_urb, GFP_ATOMIC);
+	if (result) {
+		dev_warn(&serial->parent->usb->dev,
+			 "Failed to submit urb - res %d\n", result);
+		return result;
+	}
+
+	return count;
+}
+
+/* callback after read or write on muxed serial port */
+static void ctrl_callback(struct urb *urb)
+{
+	struct hso_serial *serial = urb->context;
+	struct usb_ctrlrequest *req;
+	int status = urb->status;
+	struct tty_struct *tty;
+
+	/* sanity check */
+	if (!serial)
+		return;
+
+	spin_lock(&serial->serial_lock);
+	serial->tx_urb_used = 0;
+	tty = tty_kref_get(serial->tty);
+	spin_unlock(&serial->serial_lock);
+	if (status) {
+		handle_usb_error(status, __func__, serial->parent);
+		tty_kref_put(tty);
+		return;
+	}
+
+	/* what request? */
+	req = (struct usb_ctrlrequest *)(urb->setup_packet);
+	D4("\n--- Got muxed ctrl callback 0x%02X ---", status);
+	D4("Actual length of urb = %d\n", urb->actual_length);
+	DUMP1(urb->transfer_buffer, urb->actual_length);
+
+	if (req->bRequestType ==
+	    (USB_DIR_IN | USB_TYPE_OPTION_VENDOR | USB_RECIP_INTERFACE)) {
+		/* response to a read command */
+		serial->rx_urb_filled[0] = 1;
+		spin_lock(&serial->serial_lock);
+		put_rxbuf_data_and_resubmit_ctrl_urb(serial);
+		spin_unlock(&serial->serial_lock);
+	} else {
+		hso_put_activity(serial->parent);
+		if (tty)
+			tty_wakeup(tty);
+		/* response to a write command */
+		hso_kick_transmit(serial);
+	}
+	tty_kref_put(tty);
+}
+
+/* handle RX data for serial port */
+static int put_rxbuf_data(struct urb *urb, struct hso_serial *serial)
+{
+	struct tty_struct *tty;
+	int write_length_remaining = 0;
+	int curr_write_len;
+
+	/* Sanity check */
+	if (urb == NULL || serial == NULL) {
+		D1("serial = NULL");
+		return -2;
+	}
+
+	/* All callers to put_rxbuf_data hold serial_lock */
+	tty = tty_kref_get(serial->tty);
+
+	/* Push data to tty */
+	if (tty) {
+		write_length_remaining = urb->actual_length -
+			serial->curr_rx_urb_offset;
+		D1("data to push to tty");
+		while (write_length_remaining) {
+			if (test_bit(TTY_THROTTLED, &tty->flags)) {
+				tty_kref_put(tty);
+				return -1;
+			}
+			curr_write_len =  tty_insert_flip_string
+				(tty, urb->transfer_buffer +
+				 serial->curr_rx_urb_offset,
+				 write_length_remaining);
+			serial->curr_rx_urb_offset += curr_write_len;
+			write_length_remaining -= curr_write_len;
+			tty_flip_buffer_push(tty);
+		}
+	}
+	if (write_length_remaining == 0) {
+		serial->curr_rx_urb_offset = 0;
+		serial->rx_urb_filled[hso_urb_to_index(serial, urb)] = 0;
+	}
+	tty_kref_put(tty);
+	return write_length_remaining;
+}
+
+
+/* Base driver functions */
+
+static void hso_log_port(struct hso_device *hso_dev)
+{
+	char *port_type;
+	char port_dev[20];
+
+	switch (hso_dev->port_spec & HSO_PORT_MASK) {
+	case HSO_PORT_CONTROL:
+		port_type = "Control";
+		break;
+	case HSO_PORT_APP:
+		port_type = "Application";
+		break;
+	case HSO_PORT_GPS:
+		port_type = "GPS";
+		break;
+	case HSO_PORT_GPS_CONTROL:
+		port_type = "GPS control";
+		break;
+	case HSO_PORT_APP2:
+		port_type = "Application2";
+		break;
+	case HSO_PORT_PCSC:
+		port_type = "PCSC";
+		break;
+	case HSO_PORT_DIAG:
+		port_type = "Diagnostic";
+		break;
+	case HSO_PORT_DIAG2:
+		port_type = "Diagnostic2";
+		break;
+	case HSO_PORT_MODEM:
+		port_type = "Modem";
+		break;
+	case HSO_PORT_NETWORK:
+		port_type = "Network";
+		break;
+	default:
+		port_type = "Unknown";
+		break;
+	}
+	if ((hso_dev->port_spec & HSO_PORT_MASK) == HSO_PORT_NETWORK) {
+		sprintf(port_dev, "%s", dev2net(hso_dev)->net->name);
+	} else
+		sprintf(port_dev, "/dev/%s%d", tty_filename,
+			dev2ser(hso_dev)->minor);
+
+	dev_dbg(&hso_dev->interface->dev, "HSO: Found %s port %s\n",
+		port_type, port_dev);
+}
+
+static int hso_start_net_device(struct hso_device *hso_dev)
+{
+	int i, result = 0;
+	struct hso_net *hso_net = dev2net(hso_dev);
+
+	if (!hso_net)
+		return -ENODEV;
+
+	/* send URBs for all read buffers */
+	for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) {
+
+		/* Prep a receive URB */
+		usb_fill_bulk_urb(hso_net->mux_bulk_rx_urb_pool[i],
+				  hso_dev->usb,
+				  usb_rcvbulkpipe(hso_dev->usb,
+						  hso_net->in_endp->
+						  bEndpointAddress & 0x7F),
+				  hso_net->mux_bulk_rx_buf_pool[i],
+				  MUX_BULK_RX_BUF_SIZE, read_bulk_callback,
+				  hso_net);
+
+		/* Put it out there so the device can send us stuff */
+		result = usb_submit_urb(hso_net->mux_bulk_rx_urb_pool[i],
+					GFP_NOIO);
+		if (result)
+			dev_warn(&hso_dev->usb->dev,
+				"%s failed mux_bulk_rx_urb[%d] %d\n", __func__,
+				i, result);
+	}
+
+	return result;
+}
+
+static int hso_stop_net_device(struct hso_device *hso_dev)
+{
+	int i;
+	struct hso_net *hso_net = dev2net(hso_dev);
+
+	if (!hso_net)
+		return -ENODEV;
+
+	for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) {
+		if (hso_net->mux_bulk_rx_urb_pool[i])
+			usb_kill_urb(hso_net->mux_bulk_rx_urb_pool[i]);
+
+	}
+	if (hso_net->mux_bulk_tx_urb)
+		usb_kill_urb(hso_net->mux_bulk_tx_urb);
+
+	return 0;
+}
+
+static int hso_start_serial_device(struct hso_device *hso_dev, gfp_t flags)
+{
+	int i, result = 0;
+	struct hso_serial *serial = dev2ser(hso_dev);
+
+	if (!serial)
+		return -ENODEV;
+
+	/* If it is not the MUX port fill in and submit a bulk urb (already
+	 * allocated in hso_serial_start) */
+	if (!(serial->parent->port_spec & HSO_INTF_MUX)) {
+		for (i = 0; i < serial->num_rx_urbs; i++) {
+			usb_fill_bulk_urb(serial->rx_urb[i],
+					  serial->parent->usb,
+					  usb_rcvbulkpipe(serial->parent->usb,
+							  serial->in_endp->
+							  bEndpointAddress &
+							  0x7F),
+					  serial->rx_data[i],
+					  serial->rx_data_length,
+					  hso_std_serial_read_bulk_callback,
+					  serial);
+			result = usb_submit_urb(serial->rx_urb[i], flags);
+			if (result) {
+				dev_warn(&serial->parent->usb->dev,
+					 "Failed to submit urb - res %d\n",
+					 result);
+				break;
+			}
+		}
+	} else {
+		mutex_lock(&serial->shared_int->shared_int_lock);
+		if (!serial->shared_int->use_count) {
+			result =
+			    hso_mux_submit_intr_urb(serial->shared_int,
+						    hso_dev->usb, flags);
+		}
+		serial->shared_int->use_count++;
+		mutex_unlock(&serial->shared_int->shared_int_lock);
+	}
+	if (serial->tiocmget)
+		tiocmget_submit_urb(serial,
+				    serial->tiocmget,
+				    serial->parent->usb);
+	return result;
+}
+
+static int hso_stop_serial_device(struct hso_device *hso_dev)
+{
+	int i;
+	struct hso_serial *serial = dev2ser(hso_dev);
+	struct hso_tiocmget  *tiocmget;
+
+	if (!serial)
+		return -ENODEV;
+
+	for (i = 0; i < serial->num_rx_urbs; i++) {
+		if (serial->rx_urb[i]) {
+				usb_kill_urb(serial->rx_urb[i]);
+				serial->rx_urb_filled[i] = 0;
+		}
+	}
+	serial->curr_rx_urb_idx = 0;
+	serial->curr_rx_urb_offset = 0;
+
+	if (serial->tx_urb)
+		usb_kill_urb(serial->tx_urb);
+
+	if (serial->shared_int) {
+		mutex_lock(&serial->shared_int->shared_int_lock);
+		if (serial->shared_int->use_count &&
+		    (--serial->shared_int->use_count == 0)) {
+			struct urb *urb;
+
+			urb = serial->shared_int->shared_intr_urb;
+			if (urb)
+				usb_kill_urb(urb);
+		}
+		mutex_unlock(&serial->shared_int->shared_int_lock);
+	}
+	tiocmget = serial->tiocmget;
+	if (tiocmget) {
+		wake_up_interruptible(&tiocmget->waitq);
+		usb_kill_urb(tiocmget->urb);
+	}
+
+	return 0;
+}
+
+static void hso_serial_common_free(struct hso_serial *serial)
+{
+	int i;
+
+	if (serial->parent->dev)
+		device_remove_file(serial->parent->dev, &dev_attr_hsotype);
+
+	tty_unregister_device(tty_drv, serial->minor);
+
+	for (i = 0; i < serial->num_rx_urbs; i++) {
+		/* unlink and free RX URB */
+		usb_free_urb(serial->rx_urb[i]);
+		/* free the RX buffer */
+		kfree(serial->rx_data[i]);
+	}
+
+	/* unlink and free TX URB */
+	usb_free_urb(serial->tx_urb);
+	kfree(serial->tx_data);
+}
+
+static int hso_serial_common_create(struct hso_serial *serial, int num_urbs,
+				    int rx_size, int tx_size)
+{
+	struct device *dev;
+	int minor;
+	int i;
+
+	minor = get_free_serial_index();
+	if (minor < 0)
+		goto exit;
+
+	/* register our minor number */
+	serial->parent->dev = tty_register_device(tty_drv, minor,
+					&serial->parent->interface->dev);
+	dev = serial->parent->dev;
+	dev_set_drvdata(dev, serial->parent);
+	i = device_create_file(dev, &dev_attr_hsotype);
+
+	/* fill in specific data for later use */
+	serial->minor = minor;
+	serial->magic = HSO_SERIAL_MAGIC;
+	spin_lock_init(&serial->serial_lock);
+	serial->num_rx_urbs = num_urbs;
+
+	/* RX, allocate urb and initialize */
+
+	/* prepare our RX buffer */
+	serial->rx_data_length = rx_size;
+	for (i = 0; i < serial->num_rx_urbs; i++) {
+		serial->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!serial->rx_urb[i]) {
+			dev_err(dev, "Could not allocate urb?\n");
+			goto exit;
+		}
+		serial->rx_urb[i]->transfer_buffer = NULL;
+		serial->rx_urb[i]->transfer_buffer_length = 0;
+		serial->rx_data[i] = kzalloc(serial->rx_data_length,
+					     GFP_KERNEL);
+		if (!serial->rx_data[i]) {
+			dev_err(dev, "%s - Out of memory\n", __func__);
+			goto exit;
+		}
+	}
+
+	/* TX, allocate urb and initialize */
+	serial->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!serial->tx_urb) {
+		dev_err(dev, "Could not allocate urb?\n");
+		goto exit;
+	}
+	serial->tx_urb->transfer_buffer = NULL;
+	serial->tx_urb->transfer_buffer_length = 0;
+	/* prepare our TX buffer */
+	serial->tx_data_count = 0;
+	serial->tx_buffer_count = 0;
+	serial->tx_data_length = tx_size;
+	serial->tx_data = kzalloc(serial->tx_data_length, GFP_KERNEL);
+	if (!serial->tx_data) {
+		dev_err(dev, "%s - Out of memory\n", __func__);
+		goto exit;
+	}
+	serial->tx_buffer = kzalloc(serial->tx_data_length, GFP_KERNEL);
+	if (!serial->tx_buffer) {
+		dev_err(dev, "%s - Out of memory\n", __func__);
+		goto exit;
+	}
+
+	return 0;
+exit:
+	hso_serial_common_free(serial);
+	return -1;
+}
+
+/* Creates a general hso device */
+static struct hso_device *hso_create_device(struct usb_interface *intf,
+					    int port_spec)
+{
+	struct hso_device *hso_dev;
+
+	hso_dev = kzalloc(sizeof(*hso_dev), GFP_ATOMIC);
+	if (!hso_dev)
+		return NULL;
+
+	hso_dev->port_spec = port_spec;
+	hso_dev->usb = interface_to_usbdev(intf);
+	hso_dev->interface = intf;
+	kref_init(&hso_dev->ref);
+	mutex_init(&hso_dev->mutex);
+
+	INIT_WORK(&hso_dev->async_get_intf, async_get_intf);
+	INIT_WORK(&hso_dev->async_put_intf, async_put_intf);
+	INIT_WORK(&hso_dev->reset_device, reset_device);
+
+	return hso_dev;
+}
+
+/* Removes a network device in the network device table */
+static int remove_net_device(struct hso_device *hso_dev)
+{
+	int i;
+
+	for (i = 0; i < HSO_MAX_NET_DEVICES; i++) {
+		if (network_table[i] == hso_dev) {
+			network_table[i] = NULL;
+			break;
+		}
+	}
+	if (i == HSO_MAX_NET_DEVICES)
+		return -1;
+	return 0;
+}
+
+/* Frees our network device */
+static void hso_free_net_device(struct hso_device *hso_dev)
+{
+	int i;
+	struct hso_net *hso_net = dev2net(hso_dev);
+
+	if (!hso_net)
+		return;
+
+	remove_net_device(hso_net->parent);
+
+	if (hso_net->net)
+		unregister_netdev(hso_net->net);
+
+	/* start freeing */
+	for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) {
+		usb_free_urb(hso_net->mux_bulk_rx_urb_pool[i]);
+		kfree(hso_net->mux_bulk_rx_buf_pool[i]);
+		hso_net->mux_bulk_rx_buf_pool[i] = NULL;
+	}
+	usb_free_urb(hso_net->mux_bulk_tx_urb);
+	kfree(hso_net->mux_bulk_tx_buf);
+	hso_net->mux_bulk_tx_buf = NULL;
+
+	if (hso_net->net)
+		free_netdev(hso_net->net);
+
+	kfree(hso_dev);
+}
+
+static const struct net_device_ops hso_netdev_ops = {
+	.ndo_open	= hso_net_open,
+	.ndo_stop	= hso_net_close,
+	.ndo_start_xmit = hso_net_start_xmit,
+	.ndo_tx_timeout = hso_net_tx_timeout,
+};
+
+/* initialize the network interface */
+static void hso_net_init(struct net_device *net)
+{
+	struct hso_net *hso_net = netdev_priv(net);
+
+	D1("sizeof hso_net is %d", (int)sizeof(*hso_net));
+
+	/* fill in the other fields */
+	net->netdev_ops = &hso_netdev_ops;
+	net->watchdog_timeo = HSO_NET_TX_TIMEOUT;
+	net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+	net->type = ARPHRD_NONE;
+	net->mtu = DEFAULT_MTU - 14;
+	net->tx_queue_len = 10;
+	SET_ETHTOOL_OPS(net, &ops);
+
+	/* and initialize the semaphore */
+	spin_lock_init(&hso_net->net_lock);
+}
+
+/* Adds a network device in the network device table */
+static int add_net_device(struct hso_device *hso_dev)
+{
+	int i;
+
+	for (i = 0; i < HSO_MAX_NET_DEVICES; i++) {
+		if (network_table[i] == NULL) {
+			network_table[i] = hso_dev;
+			break;
+		}
+	}
+	if (i == HSO_MAX_NET_DEVICES)
+		return -1;
+	return 0;
+}
+
+static int hso_rfkill_set_block(void *data, bool blocked)
+{
+	struct hso_device *hso_dev = data;
+	int enabled = !blocked;
+	int rv;
+
+	mutex_lock(&hso_dev->mutex);
+	if (hso_dev->usb_gone)
+		rv = 0;
+	else
+		rv = usb_control_msg(hso_dev->usb, usb_rcvctrlpipe(hso_dev->usb, 0),
+				       enabled ? 0x82 : 0x81, 0x40, 0, 0, NULL, 0,
+				       USB_CTRL_SET_TIMEOUT);
+	mutex_unlock(&hso_dev->mutex);
+	return rv;
+}
+
+static const struct rfkill_ops hso_rfkill_ops = {
+	.set_block = hso_rfkill_set_block,
+};
+
+/* Creates and sets up everything for rfkill */
+static void hso_create_rfkill(struct hso_device *hso_dev,
+			     struct usb_interface *interface)
+{
+	struct hso_net *hso_net = dev2net(hso_dev);
+	struct device *dev = &hso_net->net->dev;
+	char *rfkn;
+
+	rfkn = kzalloc(20, GFP_KERNEL);
+	if (!rfkn)
+		dev_err(dev, "%s - Out of memory\n", __func__);
+
+	snprintf(rfkn, 20, "hso-%d",
+		 interface->altsetting->desc.bInterfaceNumber);
+
+	hso_net->rfkill = rfkill_alloc(rfkn,
+				       &interface_to_usbdev(interface)->dev,
+				       RFKILL_TYPE_WWAN,
+				       &hso_rfkill_ops, hso_dev);
+	if (!hso_net->rfkill) {
+		dev_err(dev, "%s - Out of memory\n", __func__);
+		kfree(rfkn);
+		return;
+	}
+	if (rfkill_register(hso_net->rfkill) < 0) {
+		rfkill_destroy(hso_net->rfkill);
+		kfree(rfkn);
+		hso_net->rfkill = NULL;
+		dev_err(dev, "%s - Failed to register rfkill\n", __func__);
+		return;
+	}
+}
+
+static struct device_type hso_type = {
+	.name	= "wwan",
+};
+
+/* Creates our network device */
+static struct hso_device *hso_create_net_device(struct usb_interface *interface,
+						int port_spec)
+{
+	int result, i;
+	struct net_device *net;
+	struct hso_net *hso_net;
+	struct hso_device *hso_dev;
+
+	hso_dev = hso_create_device(interface, port_spec);
+	if (!hso_dev)
+		return NULL;
+
+	/* allocate our network device, then we can put in our private data */
+	/* call hso_net_init to do the basic initialization */
+	net = alloc_netdev(sizeof(struct hso_net), "hso%d", hso_net_init);
+	if (!net) {
+		dev_err(&interface->dev, "Unable to create ethernet device\n");
+		goto exit;
+	}
+
+	hso_net = netdev_priv(net);
+
+	hso_dev->port_data.dev_net = hso_net;
+	hso_net->net = net;
+	hso_net->parent = hso_dev;
+
+	hso_net->in_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK,
+				      USB_DIR_IN);
+	if (!hso_net->in_endp) {
+		dev_err(&interface->dev, "Can't find BULK IN endpoint\n");
+		goto exit;
+	}
+	hso_net->out_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK,
+				       USB_DIR_OUT);
+	if (!hso_net->out_endp) {
+		dev_err(&interface->dev, "Can't find BULK OUT endpoint\n");
+		goto exit;
+	}
+	SET_NETDEV_DEV(net, &interface->dev);
+	SET_NETDEV_DEVTYPE(net, &hso_type);
+
+	/* registering our net device */
+	result = register_netdev(net);
+	if (result) {
+		dev_err(&interface->dev, "Failed to register device\n");
+		goto exit;
+	}
+
+	/* start allocating */
+	for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) {
+		hso_net->mux_bulk_rx_urb_pool[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!hso_net->mux_bulk_rx_urb_pool[i]) {
+			dev_err(&interface->dev, "Could not allocate rx urb\n");
+			goto exit;
+		}
+		hso_net->mux_bulk_rx_buf_pool[i] = kzalloc(MUX_BULK_RX_BUF_SIZE,
+							   GFP_KERNEL);
+		if (!hso_net->mux_bulk_rx_buf_pool[i]) {
+			dev_err(&interface->dev, "Could not allocate rx buf\n");
+			goto exit;
+		}
+	}
+	hso_net->mux_bulk_tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!hso_net->mux_bulk_tx_urb) {
+		dev_err(&interface->dev, "Could not allocate tx urb\n");
+		goto exit;
+	}
+	hso_net->mux_bulk_tx_buf = kzalloc(MUX_BULK_TX_BUF_SIZE, GFP_KERNEL);
+	if (!hso_net->mux_bulk_tx_buf) {
+		dev_err(&interface->dev, "Could not allocate tx buf\n");
+		goto exit;
+	}
+
+	add_net_device(hso_dev);
+
+	hso_log_port(hso_dev);
+
+	hso_create_rfkill(hso_dev, interface);
+
+	return hso_dev;
+exit:
+	hso_free_net_device(hso_dev);
+	return NULL;
+}
+
+static void hso_free_tiomget(struct hso_serial *serial)
+{
+	struct hso_tiocmget *tiocmget;
+	if (!serial)
+		return;
+	tiocmget = serial->tiocmget;
+	if (tiocmget) {
+		usb_free_urb(tiocmget->urb);
+		tiocmget->urb = NULL;
+		serial->tiocmget = NULL;
+		kfree(tiocmget);
+	}
+}
+
+/* Frees an AT channel ( goes for both mux and non-mux ) */
+static void hso_free_serial_device(struct hso_device *hso_dev)
+{
+	struct hso_serial *serial = dev2ser(hso_dev);
+
+	if (!serial)
+		return;
+	set_serial_by_index(serial->minor, NULL);
+
+	hso_serial_common_free(serial);
+
+	if (serial->shared_int) {
+		mutex_lock(&serial->shared_int->shared_int_lock);
+		if (--serial->shared_int->ref_count == 0)
+			hso_free_shared_int(serial->shared_int);
+		else
+			mutex_unlock(&serial->shared_int->shared_int_lock);
+	}
+	hso_free_tiomget(serial);
+	kfree(serial);
+	kfree(hso_dev);
+}
+
+/* Creates a bulk AT channel */
+static struct hso_device *hso_create_bulk_serial_device(
+			struct usb_interface *interface, int port)
+{
+	struct hso_device *hso_dev;
+	struct hso_serial *serial;
+	int num_urbs;
+	struct hso_tiocmget *tiocmget;
+
+	hso_dev = hso_create_device(interface, port);
+	if (!hso_dev)
+		return NULL;
+
+	serial = kzalloc(sizeof(*serial), GFP_KERNEL);
+	if (!serial)
+		goto exit;
+
+	serial->parent = hso_dev;
+	hso_dev->port_data.dev_serial = serial;
+
+	if ((port & HSO_PORT_MASK) == HSO_PORT_MODEM) {
+		num_urbs = 2;
+		serial->tiocmget = kzalloc(sizeof(struct hso_tiocmget),
+					   GFP_KERNEL);
+		/* it isn't going to break our heart if serial->tiocmget
+		 *  allocation fails don't bother checking this.
+		 */
+		if (serial->tiocmget) {
+			tiocmget = serial->tiocmget;
+			tiocmget->urb = usb_alloc_urb(0, GFP_KERNEL);
+			if (tiocmget->urb) {
+				mutex_init(&tiocmget->mutex);
+				init_waitqueue_head(&tiocmget->waitq);
+				tiocmget->endp = hso_get_ep(
+					interface,
+					USB_ENDPOINT_XFER_INT,
+					USB_DIR_IN);
+			} else
+				hso_free_tiomget(serial);
+		}
+	}
+	else
+		num_urbs = 1;
+
+	if (hso_serial_common_create(serial, num_urbs, BULK_URB_RX_SIZE,
+				     BULK_URB_TX_SIZE))
+		goto exit;
+
+	serial->in_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK,
+				     USB_DIR_IN);
+	if (!serial->in_endp) {
+		dev_err(&interface->dev, "Failed to find BULK IN ep\n");
+		goto exit2;
+	}
+
+	if (!
+	    (serial->out_endp =
+	     hso_get_ep(interface, USB_ENDPOINT_XFER_BULK, USB_DIR_OUT))) {
+		dev_err(&interface->dev, "Failed to find BULK IN ep\n");
+		goto exit2;
+	}
+
+	serial->write_data = hso_std_serial_write_data;
+
+	/* and record this serial */
+	set_serial_by_index(serial->minor, serial);
+
+	/* setup the proc dirs and files if needed */
+	hso_log_port(hso_dev);
+
+	/* done, return it */
+	return hso_dev;
+
+exit2:
+	hso_serial_common_free(serial);
+exit:
+	hso_free_tiomget(serial);
+	kfree(serial);
+	kfree(hso_dev);
+	return NULL;
+}
+
+/* Creates a multiplexed AT channel */
+static
+struct hso_device *hso_create_mux_serial_device(struct usb_interface *interface,
+						int port,
+						struct hso_shared_int *mux)
+{
+	struct hso_device *hso_dev;
+	struct hso_serial *serial;
+	int port_spec;
+
+	port_spec = HSO_INTF_MUX;
+	port_spec &= ~HSO_PORT_MASK;
+
+	port_spec |= hso_mux_to_port(port);
+	if ((port_spec & HSO_PORT_MASK) == HSO_PORT_NO_PORT)
+		return NULL;
+
+	hso_dev = hso_create_device(interface, port_spec);
+	if (!hso_dev)
+		return NULL;
+
+	serial = kzalloc(sizeof(*serial), GFP_KERNEL);
+	if (!serial)
+		goto exit;
+
+	hso_dev->port_data.dev_serial = serial;
+	serial->parent = hso_dev;
+
+	if (hso_serial_common_create
+	    (serial, 1, CTRL_URB_RX_SIZE, CTRL_URB_TX_SIZE))
+		goto exit;
+
+	serial->tx_data_length--;
+	serial->write_data = hso_mux_serial_write_data;
+
+	serial->shared_int = mux;
+	mutex_lock(&serial->shared_int->shared_int_lock);
+	serial->shared_int->ref_count++;
+	mutex_unlock(&serial->shared_int->shared_int_lock);
+
+	/* and record this serial */
+	set_serial_by_index(serial->minor, serial);
+
+	/* setup the proc dirs and files if needed */
+	hso_log_port(hso_dev);
+
+	/* done, return it */
+	return hso_dev;
+
+exit:
+	if (serial) {
+		tty_unregister_device(tty_drv, serial->minor);
+		kfree(serial);
+	}
+	if (hso_dev)
+		kfree(hso_dev);
+	return NULL;
+
+}
+
+static void hso_free_shared_int(struct hso_shared_int *mux)
+{
+	usb_free_urb(mux->shared_intr_urb);
+	kfree(mux->shared_intr_buf);
+	mutex_unlock(&mux->shared_int_lock);
+	kfree(mux);
+}
+
+static
+struct hso_shared_int *hso_create_shared_int(struct usb_interface *interface)
+{
+	struct hso_shared_int *mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+
+	if (!mux)
+		return NULL;
+
+	mux->intr_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_INT,
+				    USB_DIR_IN);
+	if (!mux->intr_endp) {
+		dev_err(&interface->dev, "Can't find INT IN endpoint\n");
+		goto exit;
+	}
+
+	mux->shared_intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!mux->shared_intr_urb) {
+		dev_err(&interface->dev, "Could not allocate intr urb?\n");
+		goto exit;
+	}
+	mux->shared_intr_buf =
+		kzalloc(le16_to_cpu(mux->intr_endp->wMaxPacketSize),
+			GFP_KERNEL);
+	if (!mux->shared_intr_buf) {
+		dev_err(&interface->dev, "Could not allocate intr buf?\n");
+		goto exit;
+	}
+
+	mutex_init(&mux->shared_int_lock);
+
+	return mux;
+
+exit:
+	kfree(mux->shared_intr_buf);
+	usb_free_urb(mux->shared_intr_urb);
+	kfree(mux);
+	return NULL;
+}
+
+/* Gets the port spec for a certain interface */
+static int hso_get_config_data(struct usb_interface *interface)
+{
+	struct usb_device *usbdev = interface_to_usbdev(interface);
+	u8 config_data[17];
+	u32 if_num = interface->altsetting->desc.bInterfaceNumber;
+	s32 result;
+
+	if (usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+			    0x86, 0xC0, 0, 0, config_data, 17,
+			    USB_CTRL_SET_TIMEOUT) != 0x11) {
+		return -EIO;
+	}
+
+	switch (config_data[if_num]) {
+	case 0x0:
+		result = 0;
+		break;
+	case 0x1:
+		result = HSO_PORT_DIAG;
+		break;
+	case 0x2:
+		result = HSO_PORT_GPS;
+		break;
+	case 0x3:
+		result = HSO_PORT_GPS_CONTROL;
+		break;
+	case 0x4:
+		result = HSO_PORT_APP;
+		break;
+	case 0x5:
+		result = HSO_PORT_APP2;
+		break;
+	case 0x6:
+		result = HSO_PORT_CONTROL;
+		break;
+	case 0x7:
+		result = HSO_PORT_NETWORK;
+		break;
+	case 0x8:
+		result = HSO_PORT_MODEM;
+		break;
+	case 0x9:
+		result = HSO_PORT_MSD;
+		break;
+	case 0xa:
+		result = HSO_PORT_PCSC;
+		break;
+	case 0xb:
+		result = HSO_PORT_VOICE;
+		break;
+	default:
+		result = 0;
+	}
+
+	if (result)
+		result |= HSO_INTF_BULK;
+
+	if (config_data[16] & 0x1)
+		result |= HSO_INFO_CRC_BUG;
+
+	return result;
+}
+
+/* called once for each interface upon device insertion */
+static int hso_probe(struct usb_interface *interface,
+		     const struct usb_device_id *id)
+{
+	int mux, i, if_num, port_spec;
+	unsigned char port_mask;
+	struct hso_device *hso_dev = NULL;
+	struct hso_shared_int *shared_int;
+	struct hso_device *tmp_dev = NULL;
+
+	if_num = interface->altsetting->desc.bInterfaceNumber;
+
+	/* Get the interface/port specification from either driver_info or from
+	 * the device itself */
+	if (id->driver_info)
+		port_spec = ((u32 *)(id->driver_info))[if_num];
+	else
+		port_spec = hso_get_config_data(interface);
+
+	if (interface->cur_altsetting->desc.bInterfaceClass != 0xFF) {
+		dev_err(&interface->dev, "Not our interface\n");
+		return -ENODEV;
+	}
+	/* Check if we need to switch to alt interfaces prior to port
+	 * configuration */
+	if (interface->num_altsetting > 1)
+		usb_set_interface(interface_to_usbdev(interface), if_num, 1);
+	interface->needs_remote_wakeup = 1;
+
+	/* Allocate new hso device(s) */
+	switch (port_spec & HSO_INTF_MASK) {
+	case HSO_INTF_MUX:
+		if ((port_spec & HSO_PORT_MASK) == HSO_PORT_NETWORK) {
+			/* Create the network device */
+			if (!disable_net) {
+				hso_dev = hso_create_net_device(interface,
+								port_spec);
+				if (!hso_dev)
+					goto exit;
+				tmp_dev = hso_dev;
+			}
+		}
+
+		if (hso_get_mux_ports(interface, &port_mask))
+			/* TODO: de-allocate everything */
+			goto exit;
+
+		shared_int = hso_create_shared_int(interface);
+		if (!shared_int)
+			goto exit;
+
+		for (i = 1, mux = 0; i < 0x100; i = i << 1, mux++) {
+			if (port_mask & i) {
+				hso_dev = hso_create_mux_serial_device(
+						interface, i, shared_int);
+				if (!hso_dev)
+					goto exit;
+			}
+		}
+
+		if (tmp_dev)
+			hso_dev = tmp_dev;
+		break;
+
+	case HSO_INTF_BULK:
+		/* It's a regular bulk interface */
+		if ((port_spec & HSO_PORT_MASK) == HSO_PORT_NETWORK) {
+			if (!disable_net)
+				hso_dev =
+				    hso_create_net_device(interface, port_spec);
+		} else {
+			hso_dev =
+			    hso_create_bulk_serial_device(interface, port_spec);
+		}
+		if (!hso_dev)
+			goto exit;
+		break;
+	default:
+		goto exit;
+	}
+
+	/* save our data pointer in this device */
+	usb_set_intfdata(interface, hso_dev);
+
+	/* done */
+	return 0;
+exit:
+	hso_free_interface(interface);
+	return -ENODEV;
+}
+
+/* device removed, cleaning up */
+static void hso_disconnect(struct usb_interface *interface)
+{
+	hso_free_interface(interface);
+
+	/* remove reference of our private data */
+	usb_set_intfdata(interface, NULL);
+}
+
+static void async_get_intf(struct work_struct *data)
+{
+	struct hso_device *hso_dev =
+	    container_of(data, struct hso_device, async_get_intf);
+	usb_autopm_get_interface(hso_dev->interface);
+}
+
+static void async_put_intf(struct work_struct *data)
+{
+	struct hso_device *hso_dev =
+	    container_of(data, struct hso_device, async_put_intf);
+	usb_autopm_put_interface(hso_dev->interface);
+}
+
+static int hso_get_activity(struct hso_device *hso_dev)
+{
+	if (hso_dev->usb->state == USB_STATE_SUSPENDED) {
+		if (!hso_dev->is_active) {
+			hso_dev->is_active = 1;
+			schedule_work(&hso_dev->async_get_intf);
+		}
+	}
+
+	if (hso_dev->usb->state != USB_STATE_CONFIGURED)
+		return -EAGAIN;
+
+	usb_mark_last_busy(hso_dev->usb);
+
+	return 0;
+}
+
+static int hso_put_activity(struct hso_device *hso_dev)
+{
+	if (hso_dev->usb->state != USB_STATE_SUSPENDED) {
+		if (hso_dev->is_active) {
+			hso_dev->is_active = 0;
+			schedule_work(&hso_dev->async_put_intf);
+			return -EAGAIN;
+		}
+	}
+	hso_dev->is_active = 0;
+	return 0;
+}
+
+/* called by kernel when we need to suspend device */
+static int hso_suspend(struct usb_interface *iface, pm_message_t message)
+{
+	int i, result;
+
+	/* Stop all serial ports */
+	for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) {
+		if (serial_table[i] && (serial_table[i]->interface == iface)) {
+			result = hso_stop_serial_device(serial_table[i]);
+			if (result)
+				goto out;
+		}
+	}
+
+	/* Stop all network ports */
+	for (i = 0; i < HSO_MAX_NET_DEVICES; i++) {
+		if (network_table[i] &&
+		    (network_table[i]->interface == iface)) {
+			result = hso_stop_net_device(network_table[i]);
+			if (result)
+				goto out;
+		}
+	}
+
+out:
+	return 0;
+}
+
+/* called by kernel when we need to resume device */
+static int hso_resume(struct usb_interface *iface)
+{
+	int i, result = 0;
+	struct hso_net *hso_net;
+
+	/* Start all serial ports */
+	for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) {
+		if (serial_table[i] && (serial_table[i]->interface == iface)) {
+			if (dev2ser(serial_table[i])->open_count) {
+				result =
+				    hso_start_serial_device(serial_table[i], GFP_NOIO);
+				hso_kick_transmit(dev2ser(serial_table[i]));
+				if (result)
+					goto out;
+			}
+		}
+	}
+
+	/* Start all network ports */
+	for (i = 0; i < HSO_MAX_NET_DEVICES; i++) {
+		if (network_table[i] &&
+		    (network_table[i]->interface == iface)) {
+			hso_net = dev2net(network_table[i]);
+			if (hso_net->flags & IFF_UP) {
+				/* First transmit any lingering data,
+				   then restart the device. */
+				if (hso_net->skb_tx_buf) {
+					dev_dbg(&iface->dev,
+						"Transmitting"
+						" lingering data\n");
+					hso_net_start_xmit(hso_net->skb_tx_buf,
+							   hso_net->net);
+					hso_net->skb_tx_buf = NULL;
+				}
+				result = hso_start_net_device(network_table[i]);
+				if (result)
+					goto out;
+			}
+		}
+	}
+
+out:
+	return result;
+}
+
+static void reset_device(struct work_struct *data)
+{
+	struct hso_device *hso_dev =
+	    container_of(data, struct hso_device, reset_device);
+	struct usb_device *usb = hso_dev->usb;
+	int result;
+
+	if (hso_dev->usb_gone) {
+		D1("No reset during disconnect\n");
+	} else {
+		result = usb_lock_device_for_reset(usb, hso_dev->interface);
+		if (result < 0)
+			D1("unable to lock device for reset: %d\n", result);
+		else {
+			usb_reset_device(usb);
+			usb_unlock_device(usb);
+		}
+	}
+}
+
+static void hso_serial_ref_free(struct kref *ref)
+{
+	struct hso_device *hso_dev = container_of(ref, struct hso_device, ref);
+
+	hso_free_serial_device(hso_dev);
+}
+
+static void hso_free_interface(struct usb_interface *interface)
+{
+	struct hso_serial *hso_dev;
+	struct tty_struct *tty;
+	int i;
+
+	for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) {
+		if (serial_table[i] &&
+		    (serial_table[i]->interface == interface)) {
+			hso_dev = dev2ser(serial_table[i]);
+			spin_lock_irq(&hso_dev->serial_lock);
+			tty = tty_kref_get(hso_dev->tty);
+			spin_unlock_irq(&hso_dev->serial_lock);
+			if (tty)
+				tty_hangup(tty);
+			mutex_lock(&hso_dev->parent->mutex);
+			tty_kref_put(tty);
+			hso_dev->parent->usb_gone = 1;
+			mutex_unlock(&hso_dev->parent->mutex);
+			kref_put(&serial_table[i]->ref, hso_serial_ref_free);
+		}
+	}
+
+	for (i = 0; i < HSO_MAX_NET_DEVICES; i++) {
+		if (network_table[i] &&
+		    (network_table[i]->interface == interface)) {
+			struct rfkill *rfk = dev2net(network_table[i])->rfkill;
+			/* hso_stop_net_device doesn't stop the net queue since
+			 * traffic needs to start it again when suspended */
+			netif_stop_queue(dev2net(network_table[i])->net);
+			hso_stop_net_device(network_table[i]);
+			cancel_work_sync(&network_table[i]->async_put_intf);
+			cancel_work_sync(&network_table[i]->async_get_intf);
+			if (rfk) {
+				rfkill_unregister(rfk);
+				rfkill_destroy(rfk);
+			}
+			hso_free_net_device(network_table[i]);
+		}
+	}
+}
+
+/* Helper functions */
+
+/* Get the endpoint ! */
+static struct usb_endpoint_descriptor *hso_get_ep(struct usb_interface *intf,
+						  int type, int dir)
+{
+	int i;
+	struct usb_host_interface *iface = intf->cur_altsetting;
+	struct usb_endpoint_descriptor *endp;
+
+	for (i = 0; i < iface->desc.bNumEndpoints; i++) {
+		endp = &iface->endpoint[i].desc;
+		if (((endp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir) &&
+		    (usb_endpoint_type(endp) == type))
+			return endp;
+	}
+
+	return NULL;
+}
+
+/* Get the byte that describes which ports are enabled */
+static int hso_get_mux_ports(struct usb_interface *intf, unsigned char *ports)
+{
+	int i;
+	struct usb_host_interface *iface = intf->cur_altsetting;
+
+	if (iface->extralen == 3) {
+		*ports = iface->extra[2];
+		return 0;
+	}
+
+	for (i = 0; i < iface->desc.bNumEndpoints; i++) {
+		if (iface->endpoint[i].extralen == 3) {
+			*ports = iface->endpoint[i].extra[2];
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+/* interrupt urb needs to be submitted, used for serial read of muxed port */
+static int hso_mux_submit_intr_urb(struct hso_shared_int *shared_int,
+				   struct usb_device *usb, gfp_t gfp)
+{
+	int result;
+
+	usb_fill_int_urb(shared_int->shared_intr_urb, usb,
+			 usb_rcvintpipe(usb,
+				shared_int->intr_endp->bEndpointAddress & 0x7F),
+			 shared_int->shared_intr_buf,
+			 1,
+			 intr_callback, shared_int,
+			 shared_int->intr_endp->bInterval);
+
+	result = usb_submit_urb(shared_int->shared_intr_urb, gfp);
+	if (result)
+		dev_warn(&usb->dev, "%s failed mux_intr_urb %d\n", __func__,
+			result);
+
+	return result;
+}
+
+/* operations setup of the serial interface */
+static const struct tty_operations hso_serial_ops = {
+	.open = hso_serial_open,
+	.close = hso_serial_close,
+	.write = hso_serial_write,
+	.write_room = hso_serial_write_room,
+	.ioctl = hso_serial_ioctl,
+	.set_termios = hso_serial_set_termios,
+	.chars_in_buffer = hso_serial_chars_in_buffer,
+	.tiocmget = hso_serial_tiocmget,
+	.tiocmset = hso_serial_tiocmset,
+	.get_icount = hso_get_count,
+	.unthrottle = hso_unthrottle
+};
+
+static struct usb_driver hso_driver = {
+	.name = driver_name,
+	.probe = hso_probe,
+	.disconnect = hso_disconnect,
+	.id_table = hso_ids,
+	.suspend = hso_suspend,
+	.resume = hso_resume,
+	.reset_resume = hso_resume,
+	.supports_autosuspend = 1,
+};
+
+static int __init hso_init(void)
+{
+	int i;
+	int result;
+
+	/* put it in the log */
+	printk(KERN_INFO "hso: %s\n", version);
+
+	/* Initialise the serial table semaphore and table */
+	spin_lock_init(&serial_table_lock);
+	for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++)
+		serial_table[i] = NULL;
+
+	/* allocate our driver using the proper amount of supported minors */
+	tty_drv = alloc_tty_driver(HSO_SERIAL_TTY_MINORS);
+	if (!tty_drv)
+		return -ENOMEM;
+
+	/* fill in all needed values */
+	tty_drv->magic = TTY_DRIVER_MAGIC;
+	tty_drv->driver_name = driver_name;
+	tty_drv->name = tty_filename;
+
+	/* if major number is provided as parameter, use that one */
+	if (tty_major)
+		tty_drv->major = tty_major;
+
+	tty_drv->minor_start = 0;
+	tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
+	tty_drv->subtype = SERIAL_TYPE_NORMAL;
+	tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+	tty_drv->init_termios = tty_std_termios;
+	hso_init_termios(&tty_drv->init_termios);
+	tty_set_operations(tty_drv, &hso_serial_ops);
+
+	/* register the tty driver */
+	result = tty_register_driver(tty_drv);
+	if (result) {
+		printk(KERN_ERR "%s - tty_register_driver failed(%d)\n",
+			__func__, result);
+		return result;
+	}
+
+	/* register this module as an usb driver */
+	result = usb_register(&hso_driver);
+	if (result) {
+		printk(KERN_ERR "Could not register hso driver? error: %d\n",
+			result);
+		/* cleanup serial interface */
+		tty_unregister_driver(tty_drv);
+		return result;
+	}
+
+	/* done */
+	return 0;
+}
+
+static void __exit hso_exit(void)
+{
+	printk(KERN_INFO "hso: unloaded\n");
+
+	tty_unregister_driver(tty_drv);
+	/* deregister the usb driver */
+	usb_deregister(&hso_driver);
+}
+
+/* Module definitions */
+module_init(hso_init);
+module_exit(hso_exit);
+
+MODULE_AUTHOR(MOD_AUTHOR);
+MODULE_DESCRIPTION(MOD_DESCRIPTION);
+MODULE_LICENSE(MOD_LICENSE);
+
+/* change the debug level (eg: insmod hso.ko debug=0x04) */
+MODULE_PARM_DESC(debug, "Level of debug [0x01 | 0x02 | 0x04 | 0x08 | 0x10]");
+module_param(debug, int, S_IRUGO | S_IWUSR);
+
+/* set the major tty number (eg: insmod hso.ko tty_major=245) */
+MODULE_PARM_DESC(tty_major, "Set the major tty number");
+module_param(tty_major, int, S_IRUGO | S_IWUSR);
+
+/* disable network interface (eg: insmod hso.ko disable_net=1) */
+MODULE_PARM_DESC(disable_net, "Disable the network interface");
+module_param(disable_net, int, S_IRUGO | S_IWUSR);
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/int51x1.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/int51x1.c
new file mode 100644
index 0000000..12a22a4
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/int51x1.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2009 Peter Holik
+ *
+ * Intellon usb PLC (Powerline Communications) usb net driver
+ *
+ * http://www.tandel.be/downloads/INT51X1_Datasheet.pdf
+ *
+ * Based on the work of Jan 'RedBully' Seiffert
+ */
+
+/*
+ * 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 <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/slab.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+
+#define INT51X1_VENDOR_ID	0x09e1
+#define INT51X1_PRODUCT_ID	0x5121
+
+#define INT51X1_HEADER_SIZE	2	/* 2 byte header */
+
+#define PACKET_TYPE_PROMISCUOUS		(1 << 0)
+#define PACKET_TYPE_ALL_MULTICAST	(1 << 1) /* no filter */
+#define PACKET_TYPE_DIRECTED		(1 << 2)
+#define PACKET_TYPE_BROADCAST		(1 << 3)
+#define PACKET_TYPE_MULTICAST		(1 << 4) /* filtered */
+
+#define SET_ETHERNET_PACKET_FILTER	0x43
+
+static int int51x1_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	int len;
+
+	if (!(pskb_may_pull(skb, INT51X1_HEADER_SIZE))) {
+		netdev_err(dev->net, "unexpected tiny rx frame\n");
+		return 0;
+	}
+
+	len = le16_to_cpu(*(__le16 *)&skb->data[skb->len - 2]);
+
+	skb_trim(skb, len);
+
+	return 1;
+}
+
+static struct sk_buff *int51x1_tx_fixup(struct usbnet *dev,
+		struct sk_buff *skb, gfp_t flags)
+{
+	int pack_len = skb->len;
+	int pack_with_header_len = pack_len + INT51X1_HEADER_SIZE;
+	int headroom = skb_headroom(skb);
+	int tailroom = skb_tailroom(skb);
+	int need_tail = 0;
+	__le16 *len;
+
+	/* if packet and our header is smaler than 64 pad to 64 (+ ZLP) */
+	if ((pack_with_header_len) < dev->maxpacket)
+		need_tail = dev->maxpacket - pack_with_header_len + 1;
+	/*
+	 * usbnet would send a ZLP if packetlength mod urbsize == 0 for us,
+	 * but we need to know ourself, because this would add to the length
+	 * we send down to the device...
+	 */
+	else if (!(pack_with_header_len % dev->maxpacket))
+		need_tail = 1;
+
+	if (!skb_cloned(skb) &&
+			(headroom + tailroom >= need_tail + INT51X1_HEADER_SIZE)) {
+		if (headroom < INT51X1_HEADER_SIZE || tailroom < need_tail) {
+			skb->data = memmove(skb->head + INT51X1_HEADER_SIZE,
+					skb->data, skb->len);
+			skb_set_tail_pointer(skb, skb->len);
+		}
+	} else {
+		struct sk_buff *skb2;
+
+		skb2 = skb_copy_expand(skb,
+				INT51X1_HEADER_SIZE,
+				need_tail,
+				flags);
+		dev_kfree_skb_any(skb);
+		if (!skb2)
+			return NULL;
+		skb = skb2;
+	}
+
+	pack_len += need_tail;
+	pack_len &= 0x07ff;
+
+	len = (__le16 *) __skb_push(skb, INT51X1_HEADER_SIZE);
+	*len = cpu_to_le16(pack_len);
+
+	if(need_tail)
+		memset(__skb_put(skb, need_tail), 0, need_tail);
+
+	return skb;
+}
+
+static void int51x1_async_cmd_callback(struct urb *urb)
+{
+	struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
+	int status = urb->status;
+
+	if (status < 0)
+		dev_warn(&urb->dev->dev, "async callback failed with %d\n", status);
+
+	kfree(req);
+	usb_free_urb(urb);
+}
+
+static void int51x1_set_multicast(struct net_device *netdev)
+{
+	struct usb_ctrlrequest *req;
+	int status;
+	struct urb *urb;
+	struct usbnet *dev = netdev_priv(netdev);
+	u16 filter = PACKET_TYPE_DIRECTED | PACKET_TYPE_BROADCAST;
+
+	if (netdev->flags & IFF_PROMISC) {
+		/* do not expect to see traffic of other PLCs */
+		filter |= PACKET_TYPE_PROMISCUOUS;
+		netdev_info(dev->net, "promiscuous mode enabled\n");
+	} else if (!netdev_mc_empty(netdev) ||
+		  (netdev->flags & IFF_ALLMULTI)) {
+		filter |= PACKET_TYPE_ALL_MULTICAST;
+		netdev_dbg(dev->net, "receive all multicast enabled\n");
+	} else {
+		/* ~PROMISCUOUS, ~MULTICAST */
+		netdev_dbg(dev->net, "receive own packets only\n");
+	}
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb) {
+		netdev_warn(dev->net, "Error allocating URB\n");
+		return;
+	}
+
+	req = kmalloc(sizeof(*req), GFP_ATOMIC);
+	if (!req) {
+		netdev_warn(dev->net, "Error allocating control msg\n");
+		goto out;
+	}
+
+	req->bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+	req->bRequest = SET_ETHERNET_PACKET_FILTER;
+	req->wValue = cpu_to_le16(filter);
+	req->wIndex = 0;
+	req->wLength = 0;
+
+	usb_fill_control_urb(urb, dev->udev, usb_sndctrlpipe(dev->udev, 0),
+		(void *)req, NULL, 0,
+		int51x1_async_cmd_callback,
+		(void *)req);
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0) {
+		netdev_warn(dev->net, "Error submitting control msg, sts=%d\n",
+			    status);
+		goto out1;
+	}
+	return;
+out1:
+	kfree(req);
+out:
+	usb_free_urb(urb);
+}
+
+static const struct net_device_ops int51x1_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_set_mac_address	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_set_rx_mode	= int51x1_set_multicast,
+};
+
+static int int51x1_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int status = usbnet_get_ethernet_addr(dev, 3);
+
+	if (status)
+		return status;
+
+	dev->net->hard_header_len += INT51X1_HEADER_SIZE;
+	dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
+	dev->net->netdev_ops = &int51x1_netdev_ops;
+
+	return usbnet_get_endpoints(dev, intf);
+}
+
+static const struct driver_info int51x1_info = {
+	.description = "Intellon usb powerline adapter",
+	.bind        = int51x1_bind,
+	.rx_fixup    = int51x1_rx_fixup,
+	.tx_fixup    = int51x1_tx_fixup,
+	.in          = 1,
+	.out         = 2,
+	.flags       = FLAG_ETHER,
+};
+
+static const struct usb_device_id products[] = {
+	{
+	USB_DEVICE(INT51X1_VENDOR_ID, INT51X1_PRODUCT_ID),
+		.driver_info = (unsigned long) &int51x1_info,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver int51x1_driver = {
+	.name       = "int51x1",
+	.id_table   = products,
+	.probe      = usbnet_probe,
+	.disconnect = usbnet_disconnect,
+	.suspend    = usbnet_suspend,
+	.resume     = usbnet_resume,
+};
+
+module_usb_driver(int51x1_driver);
+
+MODULE_AUTHOR("Peter Holik");
+MODULE_DESCRIPTION("Intellon usb powerline adapter");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/ipheth.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/ipheth.c
new file mode 100644
index 0000000..32e4791
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/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 USB_PRODUCT_IPHONE_4	0x1297
+#define USB_PRODUCT_IPAD 0x129a
+#define USB_PRODUCT_IPHONE_4_VZW 0x129c
+#define USB_PRODUCT_IPHONE_4S	0x12a0
+#define USB_PRODUCT_IPHONE_5	0x12a8
+
+#define IPHETH_USBINTF_CLASS    255
+#define IPHETH_USBINTF_SUBCLASS 253
+#define IPHETH_USBINTF_PROTO    1
+
+#define IPHETH_BUF_SIZE         1516
+#define IPHETH_IP_ALIGN		2	/* padding at front of URB */
+#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) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(
+		USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_4,
+		IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+		IPHETH_USBINTF_PROTO) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(
+		USB_VENDOR_APPLE, USB_PRODUCT_IPAD,
+		IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+		IPHETH_USBINTF_PROTO) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(
+		USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_4_VZW,
+		IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+		IPHETH_USBINTF_PROTO) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(
+		USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_4S,
+		IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+		IPHETH_USBINTF_PROTO) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(
+		USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_5,
+		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__, status);
+		return;
+	}
+
+	if (urb->actual_length <= IPHETH_IP_ALIGN) {
+		dev->net->stats.rx_length_errors++;
+		return;
+	}
+	len = urb->actual_length - IPHETH_IP_ALIGN;
+	buf = urb->transfer_buffer + IPHETH_IP_ALIGN;
+
+	skb = dev_alloc_skb(len);
+	if (!skb) {
+		err("%s: dev_alloc_skb: -ENOMEM", __func__);
+		dev->net->stats.rx_dropped++;
+		return;
+	}
+
+	memcpy(skb_put(skb, len), buf, len);
+	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;
+	int status = urb->status;
+
+	dev = urb->context;
+	if (dev == NULL)
+		return;
+
+	if (status != 0 &&
+	    status != -ENOENT &&
+	    status != -ECONNRESET &&
+	    status != -ESHUTDOWN)
+		err("%s: urb status: %d", __func__, 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\n", __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 u32 ipheth_ethtool_op_get_link(struct net_device *net)
+{
+	struct ipheth_device *dev = netdev_priv(net);
+	return netif_carrier_ok(dev->net);
+}
+
+static const 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,
+};
+
+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, "eth%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);
+
+	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,
+};
+
+module_usb_driver(ipheth_driver);
+
+MODULE_AUTHOR("Diego Giagio <diego@giagio.com>");
+MODULE_DESCRIPTION("Apple iPhone USB Ethernet driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/kalmia.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/kalmia.c
new file mode 100644
index 0000000..7562649
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/kalmia.c
@@ -0,0 +1,382 @@
+/*
+ * USB network interface driver for Samsung Kalmia based LTE USB modem like the
+ * Samsung GT-B3730 and GT-B3710.
+ *
+ * Copyright (C) 2011 Marius Bjoernstad Kotsbak <marius@kotsbak.com>
+ *
+ * Sponsored by Quicklink Video Distribution Services Ltd.
+ *
+ * Based on the cdc_eem module.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ctype.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/gfp.h>
+
+/*
+ * The Samsung Kalmia based LTE USB modems have a CDC ACM port for modem control
+ * handled by the "option" module and an ethernet data port handled by this
+ * module.
+ *
+ * The stick must first be switched into modem mode by usb_modeswitch
+ * or similar tool. Then the modem gets sent two initialization packets by
+ * this module, which gives the MAC address of the device. User space can then
+ * connect the modem using AT commands through the ACM port and then use
+ * DHCP on the network interface exposed by this module. Network packets are
+ * sent to and from the modem in a proprietary format discovered after watching
+ * the behavior of the windows driver for the modem.
+ *
+ * More information about the use of the modem is available in usb_modeswitch
+ * forum and the project page:
+ *
+ * http://www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?t=465
+ * https://github.com/mkotsbak/Samsung-GT-B3730-linux-driver
+ */
+
+/* #define	DEBUG */
+/* #define	VERBOSE */
+
+#define KALMIA_HEADER_LENGTH 6
+#define KALMIA_ALIGN_SIZE 4
+#define KALMIA_USB_TIMEOUT 10000
+
+/*-------------------------------------------------------------------------*/
+
+static int
+kalmia_send_init_packet(struct usbnet *dev, u8 *init_msg, u8 init_msg_len,
+	u8 *buffer, u8 expected_len)
+{
+	int act_len;
+	int status;
+
+	netdev_dbg(dev->net, "Sending init packet");
+
+	status = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 0x02),
+		init_msg, init_msg_len, &act_len, KALMIA_USB_TIMEOUT);
+	if (status != 0) {
+		netdev_err(dev->net,
+			"Error sending init packet. Status %i, length %i\n",
+			status, act_len);
+		return status;
+	}
+	else if (act_len != init_msg_len) {
+		netdev_err(dev->net,
+			"Did not send all of init packet. Bytes sent: %i",
+			act_len);
+	}
+	else {
+		netdev_dbg(dev->net, "Successfully sent init packet.");
+	}
+
+	status = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, 0x81),
+		buffer, expected_len, &act_len, KALMIA_USB_TIMEOUT);
+
+	if (status != 0)
+		netdev_err(dev->net,
+			"Error receiving init result. Status %i, length %i\n",
+			status, act_len);
+	else if (act_len != expected_len)
+		netdev_err(dev->net, "Unexpected init result length: %i\n",
+			act_len);
+
+	return status;
+}
+
+static int
+kalmia_init_and_get_ethernet_addr(struct usbnet *dev, u8 *ethernet_addr)
+{
+	static const char init_msg_1[] =
+		{ 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+		0x00, 0x00 };
+	static const char init_msg_2[] =
+		{ 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf4,
+		0x00, 0x00 };
+	static const int buflen = 28;
+	char *usb_buf;
+	int status;
+
+	usb_buf = kmalloc(buflen, GFP_DMA | GFP_KERNEL);
+	if (!usb_buf)
+		return -ENOMEM;
+
+	memcpy(usb_buf, init_msg_1, 12);
+	status = kalmia_send_init_packet(dev, usb_buf, sizeof(init_msg_1)
+		/ sizeof(init_msg_1[0]), usb_buf, 24);
+	if (status != 0)
+		return status;
+
+	memcpy(usb_buf, init_msg_2, 12);
+	status = kalmia_send_init_packet(dev, usb_buf, sizeof(init_msg_2)
+		/ sizeof(init_msg_2[0]), usb_buf, 28);
+	if (status != 0)
+		return status;
+
+	memcpy(ethernet_addr, usb_buf + 10, ETH_ALEN);
+
+	kfree(usb_buf);
+	return status;
+}
+
+static int
+kalmia_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int status;
+	u8 ethernet_addr[ETH_ALEN];
+
+	/* Don't bind to AT command interface */
+	if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
+		return -EINVAL;
+
+	dev->in = usb_rcvbulkpipe(dev->udev, 0x81 & USB_ENDPOINT_NUMBER_MASK);
+	dev->out = usb_sndbulkpipe(dev->udev, 0x02 & USB_ENDPOINT_NUMBER_MASK);
+	dev->status = NULL;
+
+	dev->net->hard_header_len += KALMIA_HEADER_LENGTH;
+	dev->hard_mtu = 1400;
+	dev->rx_urb_size = dev->hard_mtu * 10; // Found as optimal after testing
+
+	status = kalmia_init_and_get_ethernet_addr(dev, ethernet_addr);
+
+	if (status < 0) {
+		usb_set_intfdata(intf, NULL);
+		usb_driver_release_interface(driver_of(intf), intf);
+		return status;
+	}
+
+	memcpy(dev->net->dev_addr, ethernet_addr, ETH_ALEN);
+	memcpy(dev->net->perm_addr, ethernet_addr, ETH_ALEN);
+
+	return status;
+}
+
+static struct sk_buff *
+kalmia_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	struct sk_buff *skb2 = NULL;
+	u16 content_len;
+	unsigned char *header_start;
+	unsigned char ether_type_1, ether_type_2;
+	u8 remainder, padlen = 0;
+
+	if (!skb_cloned(skb)) {
+		int headroom = skb_headroom(skb);
+		int tailroom = skb_tailroom(skb);
+
+		if ((tailroom >= KALMIA_ALIGN_SIZE) && (headroom
+			>= KALMIA_HEADER_LENGTH))
+			goto done;
+
+		if ((headroom + tailroom) > (KALMIA_HEADER_LENGTH
+			+ KALMIA_ALIGN_SIZE)) {
+			skb->data = memmove(skb->head + KALMIA_HEADER_LENGTH,
+				skb->data, skb->len);
+			skb_set_tail_pointer(skb, skb->len);
+			goto done;
+		}
+	}
+
+	skb2 = skb_copy_expand(skb, KALMIA_HEADER_LENGTH,
+		KALMIA_ALIGN_SIZE, flags);
+	if (!skb2)
+		return NULL;
+
+	dev_kfree_skb_any(skb);
+	skb = skb2;
+
+done:
+	header_start = skb_push(skb, KALMIA_HEADER_LENGTH);
+	ether_type_1 = header_start[KALMIA_HEADER_LENGTH + 12];
+	ether_type_2 = header_start[KALMIA_HEADER_LENGTH + 13];
+
+	netdev_dbg(dev->net, "Sending etherType: %02x%02x", ether_type_1,
+		ether_type_2);
+
+	/* According to empiric data for data packages */
+	header_start[0] = 0x57;
+	header_start[1] = 0x44;
+	content_len = skb->len - KALMIA_HEADER_LENGTH;
+
+	put_unaligned_le16(content_len, &header_start[2]);
+	header_start[4] = ether_type_1;
+	header_start[5] = ether_type_2;
+
+	/* Align to 4 bytes by padding with zeros */
+	remainder = skb->len % KALMIA_ALIGN_SIZE;
+	if (remainder > 0) {
+		padlen = KALMIA_ALIGN_SIZE - remainder;
+		memset(skb_put(skb, padlen), 0, padlen);
+	}
+
+	netdev_dbg(
+		dev->net,
+		"Sending package with length %i and padding %i. Header: %02x:%02x:%02x:%02x:%02x:%02x.",
+		content_len, padlen, header_start[0], header_start[1],
+		header_start[2], header_start[3], header_start[4],
+		header_start[5]);
+
+	return skb;
+}
+
+static int
+kalmia_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	/*
+	 * Our task here is to strip off framing, leaving skb with one
+	 * data frame for the usbnet framework code to process.
+	 */
+	static const u8 HEADER_END_OF_USB_PACKET[] =
+		{ 0x57, 0x5a, 0x00, 0x00, 0x08, 0x00 };
+	static const u8 EXPECTED_UNKNOWN_HEADER_1[] =
+		{ 0x57, 0x43, 0x1e, 0x00, 0x15, 0x02 };
+	static const u8 EXPECTED_UNKNOWN_HEADER_2[] =
+		{ 0x57, 0x50, 0x0e, 0x00, 0x00, 0x00 };
+	int i = 0;
+
+	/* incomplete header? */
+	if (skb->len < KALMIA_HEADER_LENGTH)
+		return 0;
+
+	do {
+		struct sk_buff *skb2 = NULL;
+		u8 *header_start;
+		u16 usb_packet_length, ether_packet_length;
+		int is_last;
+
+		header_start = skb->data;
+
+		if (unlikely(header_start[0] != 0x57 || header_start[1] != 0x44)) {
+			if (!memcmp(header_start, EXPECTED_UNKNOWN_HEADER_1,
+				sizeof(EXPECTED_UNKNOWN_HEADER_1)) || !memcmp(
+				header_start, EXPECTED_UNKNOWN_HEADER_2,
+				sizeof(EXPECTED_UNKNOWN_HEADER_2))) {
+				netdev_dbg(
+					dev->net,
+					"Received expected unknown frame header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
+					header_start[0], header_start[1],
+					header_start[2], header_start[3],
+					header_start[4], header_start[5],
+					skb->len - KALMIA_HEADER_LENGTH);
+			}
+			else {
+				netdev_err(
+					dev->net,
+					"Received unknown frame header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
+					header_start[0], header_start[1],
+					header_start[2], header_start[3],
+					header_start[4], header_start[5],
+					skb->len - KALMIA_HEADER_LENGTH);
+				return 0;
+			}
+		}
+		else
+			netdev_dbg(
+				dev->net,
+				"Received header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
+				header_start[0], header_start[1], header_start[2],
+				header_start[3], header_start[4], header_start[5],
+				skb->len - KALMIA_HEADER_LENGTH);
+
+		/* subtract start header and end header */
+		usb_packet_length = skb->len - (2 * KALMIA_HEADER_LENGTH);
+		ether_packet_length = get_unaligned_le16(&header_start[2]);
+		skb_pull(skb, KALMIA_HEADER_LENGTH);
+
+		/* Some small packets misses end marker */
+		if (usb_packet_length < ether_packet_length) {
+			ether_packet_length = usb_packet_length
+				+ KALMIA_HEADER_LENGTH;
+			is_last = true;
+		}
+		else {
+			netdev_dbg(dev->net, "Correct package length #%i", i
+				+ 1);
+
+			is_last = (memcmp(skb->data + ether_packet_length,
+				HEADER_END_OF_USB_PACKET,
+				sizeof(HEADER_END_OF_USB_PACKET)) == 0);
+			if (!is_last) {
+				header_start = skb->data + ether_packet_length;
+				netdev_dbg(
+					dev->net,
+					"End header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
+					header_start[0], header_start[1],
+					header_start[2], header_start[3],
+					header_start[4], header_start[5],
+					skb->len - KALMIA_HEADER_LENGTH);
+			}
+		}
+
+		if (is_last) {
+			skb2 = skb;
+		}
+		else {
+			skb2 = skb_clone(skb, GFP_ATOMIC);
+			if (unlikely(!skb2))
+				return 0;
+		}
+
+		skb_trim(skb2, ether_packet_length);
+
+		if (is_last) {
+			return 1;
+		}
+		else {
+			usbnet_skb_return(dev, skb2);
+			skb_pull(skb, ether_packet_length);
+		}
+
+		i++;
+	}
+	while (skb->len);
+
+	return 1;
+}
+
+static const struct driver_info kalmia_info = {
+	.description = "Samsung Kalmia LTE USB dongle",
+	.flags = FLAG_WWAN,
+	.bind = kalmia_bind,
+	.rx_fixup = kalmia_rx_fixup,
+	.tx_fixup = kalmia_tx_fixup
+};
+
+/*-------------------------------------------------------------------------*/
+
+static const struct usb_device_id products[] = {
+	/* The unswitched USB ID, to get the module auto loaded: */
+	{ USB_DEVICE(0x04e8, 0x689a) },
+	/* The stick swithed into modem (by e.g. usb_modeswitch): */
+	{ USB_DEVICE(0x04e8, 0x6889),
+		.driver_info = (unsigned long) &kalmia_info, },
+	{ /* EMPTY == end of list */} };
+MODULE_DEVICE_TABLE( usb, products);
+
+static struct usb_driver kalmia_driver = {
+	.name = "kalmia",
+	.id_table = products,
+	.probe = usbnet_probe,
+	.disconnect = usbnet_disconnect,
+	.suspend = usbnet_suspend,
+	.resume = usbnet_resume
+};
+
+module_usb_driver(kalmia_driver);
+
+MODULE_AUTHOR("Marius Bjoernstad Kotsbak <marius@kotsbak.com>");
+MODULE_DESCRIPTION("Samsung Kalmia USB network driver");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/kaweth.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/kaweth.c
new file mode 100644
index 0000000..7a6ccd6
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/kaweth.c
@@ -0,0 +1,1321 @@
+/****************************************************************
+ *
+ *     kaweth.c - driver for KL5KUSB101 based USB->Ethernet
+ *
+ *     (c) 2000 Interlan Communications
+ *     (c) 2000 Stephane Alnet
+ *     (C) 2001 Brad Hards
+ *     (C) 2002 Oliver Neukum
+ *
+ *     Original author: The Zapman <zapman@interlan.net>
+ *     Inspired by, and much credit goes to Michael Rothwell
+ *     <rothwell@interlan.net> for the test equipment, help, and patience
+ *     Based off of (and with thanks to) Petko Manolov's pegaus.c driver.
+ *     Also many thanks to Joel Silverman and Ed Surprenant at Kawasaki
+ *     for providing the firmware and driver resources.
+ *
+ *     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, 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.
+ *
+ ****************************************************************/
+
+/* TODO:
+ * Develop test procedures for USB net interfaces
+ * Run test procedures
+ * Fix bugs from previous two steps
+ * Snoop other OSs for any tricks we're not doing
+ * Reduce arbitrary timeouts
+ * Smart multicast support
+ * Temporary MAC change support
+ * Tunable SOFs parameter - ioctl()?
+ * Ethernet stats collection
+ * Code formatting improvements
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/usb.h>
+#include <linux/types.h>
+#include <linux/ethtool.h>
+#include <linux/dma-mapping.h>
+#include <linux/wait.h>
+#include <linux/firmware.h>
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+
+#undef DEBUG
+
+#define KAWETH_MTU			1514
+#define KAWETH_BUF_SIZE			1664
+#define KAWETH_TX_TIMEOUT		(5 * HZ)
+#define KAWETH_SCRATCH_SIZE		32
+#define KAWETH_FIRMWARE_BUF_SIZE	4096
+#define KAWETH_CONTROL_TIMEOUT		(30000)
+
+#define KAWETH_STATUS_BROKEN		0x0000001
+#define KAWETH_STATUS_CLOSING		0x0000002
+#define KAWETH_STATUS_SUSPENDING	0x0000004
+
+#define KAWETH_STATUS_BLOCKED (KAWETH_STATUS_CLOSING | KAWETH_STATUS_SUSPENDING)
+
+#define KAWETH_PACKET_FILTER_PROMISCUOUS	0x01
+#define KAWETH_PACKET_FILTER_ALL_MULTICAST	0x02
+#define KAWETH_PACKET_FILTER_DIRECTED		0x04
+#define KAWETH_PACKET_FILTER_BROADCAST		0x08
+#define KAWETH_PACKET_FILTER_MULTICAST		0x10
+
+/* Table 7 */
+#define KAWETH_COMMAND_GET_ETHERNET_DESC	0x00
+#define KAWETH_COMMAND_MULTICAST_FILTERS        0x01
+#define KAWETH_COMMAND_SET_PACKET_FILTER	0x02
+#define KAWETH_COMMAND_STATISTICS               0x03
+#define KAWETH_COMMAND_SET_TEMP_MAC     	0x06
+#define KAWETH_COMMAND_GET_TEMP_MAC             0x07
+#define KAWETH_COMMAND_SET_URB_SIZE		0x08
+#define KAWETH_COMMAND_SET_SOFS_WAIT		0x09
+#define KAWETH_COMMAND_SCAN			0xFF
+
+#define KAWETH_SOFS_TO_WAIT			0x05
+
+#define INTBUFFERSIZE				4
+
+#define STATE_OFFSET				0
+#define STATE_MASK				0x40
+#define	STATE_SHIFT				5
+
+#define IS_BLOCKED(s) (s & KAWETH_STATUS_BLOCKED)
+
+
+MODULE_AUTHOR("Michael Zappe <zapman@interlan.net>, Stephane Alnet <stephane@u-picardie.fr>, Brad Hards <bhards@bigpond.net.au> and Oliver Neukum <oliver@neukum.org>");
+MODULE_DESCRIPTION("KL5USB101 USB Ethernet driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("kaweth/new_code.bin");
+MODULE_FIRMWARE("kaweth/new_code_fix.bin");
+MODULE_FIRMWARE("kaweth/trigger_code.bin");
+MODULE_FIRMWARE("kaweth/trigger_code_fix.bin");
+
+static const char driver_name[] = "kaweth";
+
+static int kaweth_probe(
+		struct usb_interface *intf,
+		const struct usb_device_id *id	/* from id_table */
+	);
+static void kaweth_disconnect(struct usb_interface *intf);
+static int kaweth_internal_control_msg(struct usb_device *usb_dev,
+				       unsigned int pipe,
+				       struct usb_ctrlrequest *cmd, void *data,
+				       int len, int timeout);
+static int kaweth_suspend(struct usb_interface *intf, pm_message_t message);
+static int kaweth_resume(struct usb_interface *intf);
+
+/****************************************************************
+ *     usb_device_id
+ ****************************************************************/
+static struct usb_device_id usb_klsi_table[] = {
+	{ USB_DEVICE(0x03e8, 0x0008) }, /* AOX Endpoints USB Ethernet */
+	{ USB_DEVICE(0x04bb, 0x0901) }, /* I-O DATA USB-ET/T */
+	{ USB_DEVICE(0x0506, 0x03e8) }, /* 3Com 3C19250 */
+	{ USB_DEVICE(0x0506, 0x11f8) }, /* 3Com 3C460 */
+	{ USB_DEVICE(0x0557, 0x2002) }, /* ATEN USB Ethernet */
+	{ USB_DEVICE(0x0557, 0x4000) }, /* D-Link DSB-650C */
+	{ USB_DEVICE(0x0565, 0x0002) }, /* Peracom Enet */
+	{ USB_DEVICE(0x0565, 0x0003) }, /* Optus@Home UEP1045A */
+	{ USB_DEVICE(0x0565, 0x0005) }, /* Peracom Enet2 */
+	{ USB_DEVICE(0x05e9, 0x0008) }, /* KLSI KL5KUSB101B */
+	{ USB_DEVICE(0x05e9, 0x0009) }, /* KLSI KL5KUSB101B (Board change) */
+	{ USB_DEVICE(0x066b, 0x2202) }, /* Linksys USB10T */
+	{ USB_DEVICE(0x06e1, 0x0008) }, /* ADS USB-10BT */
+	{ USB_DEVICE(0x06e1, 0x0009) }, /* ADS USB-10BT */
+	{ 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 */
+	{ USB_DEVICE(0x085a, 0x0009) }, /* PortGear Ethernet Adapter */
+	{ USB_DEVICE(0x087d, 0x5704) }, /* Jaton USB Ethernet Device Adapter */
+	{ USB_DEVICE(0x0951, 0x0008) }, /* Kingston Technology USB Ethernet Adapter */
+	{ USB_DEVICE(0x095a, 0x3003) }, /* Portsmith Express Ethernet Adapter */
+	{ USB_DEVICE(0x10bd, 0x1427) }, /* ASANTE USB To Ethernet Adapter */
+	{ USB_DEVICE(0x1342, 0x0204) }, /* Mobility USB-Ethernet Adapter */
+	{ USB_DEVICE(0x13d2, 0x0400) }, /* Shark Pocket Adapter */
+	{ USB_DEVICE(0x1485, 0x0001) },	/* Silicom U2E */
+	{ USB_DEVICE(0x1485, 0x0002) }, /* Psion Dacom Gold Port Ethernet */
+	{ USB_DEVICE(0x1645, 0x0005) }, /* Entrega E45 */
+	{ USB_DEVICE(0x1645, 0x0008) }, /* Entrega USB Ethernet Adapter */
+	{ USB_DEVICE(0x1645, 0x8005) }, /* PortGear Ethernet Adapter */
+	{ USB_DEVICE(0x1668, 0x0323) }, /* Actiontec USB Ethernet */
+	{ USB_DEVICE(0x2001, 0x4000) }, /* D-link DSB-650C */
+	{} /* Null terminator */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_klsi_table);
+
+/****************************************************************
+ *     kaweth_driver
+ ****************************************************************/
+static struct usb_driver kaweth_driver = {
+	.name =		driver_name,
+	.probe =	kaweth_probe,
+	.disconnect =	kaweth_disconnect,
+	.suspend =	kaweth_suspend,
+	.resume =	kaweth_resume,
+	.id_table =     usb_klsi_table,
+	.supports_autosuspend =	1,
+};
+
+typedef __u8 eth_addr_t[6];
+
+/****************************************************************
+ *     usb_eth_dev
+ ****************************************************************/
+struct usb_eth_dev {
+	char *name;
+	__u16 vendor;
+	__u16 device;
+	void *pdata;
+};
+
+/****************************************************************
+ *     kaweth_ethernet_configuration
+ *     Refer Table 8
+ ****************************************************************/
+struct kaweth_ethernet_configuration
+{
+	__u8 size;
+	__u8 reserved1;
+	__u8 reserved2;
+	eth_addr_t hw_addr;
+	__u32 statistics_mask;
+	__le16 segment_size;
+	__u16 max_multicast_filters;
+	__u8 reserved3;
+} __packed;
+
+/****************************************************************
+ *     kaweth_device
+ ****************************************************************/
+struct kaweth_device
+{
+	spinlock_t device_lock;
+
+	__u32 status;
+	int end;
+	int suspend_lowmem_rx;
+	int suspend_lowmem_ctrl;
+	int linkstate;
+	int opened;
+	struct delayed_work lowmem_work;
+
+	struct usb_device *dev;
+	struct usb_interface *intf;
+	struct net_device *net;
+	wait_queue_head_t term_wait;
+
+	struct urb *rx_urb;
+	struct urb *tx_urb;
+	struct urb *irq_urb;
+
+	dma_addr_t intbufferhandle;
+	__u8 *intbuffer;
+	dma_addr_t rxbufferhandle;
+	__u8 *rx_buf;
+
+	
+	struct sk_buff *tx_skb;
+
+	__u8 *firmware_buf;
+	__u8 scratch[KAWETH_SCRATCH_SIZE];
+	__u16 packet_filter_bitmap;
+
+	struct kaweth_ethernet_configuration configuration;
+
+	struct net_device_stats stats;
+};
+
+/****************************************************************
+ *     kaweth_control
+ ****************************************************************/
+static int kaweth_control(struct kaweth_device *kaweth,
+			  unsigned int pipe,
+			  __u8 request,
+			  __u8 requesttype,
+			  __u16 value,
+			  __u16 index,
+			  void *data,
+			  __u16 size,
+			  int timeout)
+{
+	struct usb_ctrlrequest *dr;
+	int retval;
+
+	dbg("kaweth_control()");
+
+	if(in_interrupt()) {
+		dbg("in_interrupt()");
+		return -EBUSY;
+	}
+
+	dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC);
+
+	if (!dr) {
+		dbg("kmalloc() failed");
+		return -ENOMEM;
+	}
+
+	dr->bRequestType = requesttype;
+	dr->bRequest = request;
+	dr->wValue = cpu_to_le16(value);
+	dr->wIndex = cpu_to_le16(index);
+	dr->wLength = cpu_to_le16(size);
+
+	retval = kaweth_internal_control_msg(kaweth->dev,
+					     pipe,
+					     dr,
+					     data,
+					     size,
+					     timeout);
+
+	kfree(dr);
+	return retval;
+}
+
+/****************************************************************
+ *     kaweth_read_configuration
+ ****************************************************************/
+static int kaweth_read_configuration(struct kaweth_device *kaweth)
+{
+	int retval;
+
+	dbg("Reading kaweth configuration");
+
+	retval = kaweth_control(kaweth,
+				usb_rcvctrlpipe(kaweth->dev, 0),
+				KAWETH_COMMAND_GET_ETHERNET_DESC,
+				USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE,
+				0,
+				0,
+				(void *)&kaweth->configuration,
+				sizeof(kaweth->configuration),
+				KAWETH_CONTROL_TIMEOUT);
+
+	return retval;
+}
+
+/****************************************************************
+ *     kaweth_set_urb_size
+ ****************************************************************/
+static int kaweth_set_urb_size(struct kaweth_device *kaweth, __u16 urb_size)
+{
+	int retval;
+
+	dbg("Setting URB size to %d", (unsigned)urb_size);
+
+	retval = kaweth_control(kaweth,
+				usb_sndctrlpipe(kaweth->dev, 0),
+				KAWETH_COMMAND_SET_URB_SIZE,
+				USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+				urb_size,
+				0,
+				(void *)&kaweth->scratch,
+				0,
+				KAWETH_CONTROL_TIMEOUT);
+
+	return retval;
+}
+
+/****************************************************************
+ *     kaweth_set_sofs_wait
+ ****************************************************************/
+static int kaweth_set_sofs_wait(struct kaweth_device *kaweth, __u16 sofs_wait)
+{
+	int retval;
+
+	dbg("Set SOFS wait to %d", (unsigned)sofs_wait);
+
+	retval = kaweth_control(kaweth,
+				usb_sndctrlpipe(kaweth->dev, 0),
+				KAWETH_COMMAND_SET_SOFS_WAIT,
+				USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+				sofs_wait,
+				0,
+				(void *)&kaweth->scratch,
+				0,
+				KAWETH_CONTROL_TIMEOUT);
+
+	return retval;
+}
+
+/****************************************************************
+ *     kaweth_set_receive_filter
+ ****************************************************************/
+static int kaweth_set_receive_filter(struct kaweth_device *kaweth,
+				     __u16 receive_filter)
+{
+	int retval;
+
+	dbg("Set receive filter to %d", (unsigned)receive_filter);
+
+	retval = kaweth_control(kaweth,
+				usb_sndctrlpipe(kaweth->dev, 0),
+				KAWETH_COMMAND_SET_PACKET_FILTER,
+				USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+				receive_filter,
+				0,
+				(void *)&kaweth->scratch,
+				0,
+				KAWETH_CONTROL_TIMEOUT);
+
+	return retval;
+}
+
+/****************************************************************
+ *     kaweth_download_firmware
+ ****************************************************************/
+static int kaweth_download_firmware(struct kaweth_device *kaweth,
+				    const char *fwname,
+				    __u8 interrupt,
+				    __u8 type)
+{
+	const struct firmware *fw;
+	int data_len;
+	int ret;
+
+	ret = request_firmware(&fw, fwname, &kaweth->dev->dev);
+	if (ret) {
+		err("Firmware request failed\n");
+		return ret;
+	}
+
+	if (fw->size > KAWETH_FIRMWARE_BUF_SIZE) {
+		err("Firmware too big: %zu", fw->size);
+		release_firmware(fw);
+		return -ENOSPC;
+	}
+	data_len = fw->size;
+	memcpy(kaweth->firmware_buf, fw->data, fw->size);
+
+	release_firmware(fw);
+
+	kaweth->firmware_buf[2] = (data_len & 0xFF) - 7;
+	kaweth->firmware_buf[3] = data_len >> 8;
+	kaweth->firmware_buf[4] = type;
+	kaweth->firmware_buf[5] = interrupt;
+
+	dbg("High: %i, Low:%i", kaweth->firmware_buf[3],
+		   kaweth->firmware_buf[2]);
+
+	dbg("Downloading firmware at %p to kaweth device at %p",
+	    fw->data, kaweth);
+	dbg("Firmware length: %d", data_len);
+
+	return kaweth_control(kaweth,
+		              usb_sndctrlpipe(kaweth->dev, 0),
+			      KAWETH_COMMAND_SCAN,
+			      USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+			      0,
+			      0,
+			      (void *)kaweth->firmware_buf,
+			      data_len,
+			      KAWETH_CONTROL_TIMEOUT);
+}
+
+/****************************************************************
+ *     kaweth_trigger_firmware
+ ****************************************************************/
+static int kaweth_trigger_firmware(struct kaweth_device *kaweth,
+				   __u8 interrupt)
+{
+	kaweth->firmware_buf[0] = 0xB6;
+	kaweth->firmware_buf[1] = 0xC3;
+	kaweth->firmware_buf[2] = 0x01;
+	kaweth->firmware_buf[3] = 0x00;
+	kaweth->firmware_buf[4] = 0x06;
+	kaweth->firmware_buf[5] = interrupt;
+	kaweth->firmware_buf[6] = 0x00;
+	kaweth->firmware_buf[7] = 0x00;
+
+	dbg("Triggering firmware");
+
+	return kaweth_control(kaweth,
+			      usb_sndctrlpipe(kaweth->dev, 0),
+			      KAWETH_COMMAND_SCAN,
+			      USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+			      0,
+			      0,
+			      (void *)kaweth->firmware_buf,
+			      8,
+			      KAWETH_CONTROL_TIMEOUT);
+}
+
+/****************************************************************
+ *     kaweth_reset
+ ****************************************************************/
+static int kaweth_reset(struct kaweth_device *kaweth)
+{
+	int result;
+
+	dbg("kaweth_reset(%p)", kaweth);
+	result = usb_reset_configuration(kaweth->dev);
+	mdelay(10);
+
+	dbg("kaweth_reset() returns %d.",result);
+
+	return result;
+}
+
+static void kaweth_usb_receive(struct urb *);
+static int kaweth_resubmit_rx_urb(struct kaweth_device *, gfp_t);
+
+/****************************************************************
+	int_callback
+*****************************************************************/
+
+static void kaweth_resubmit_int_urb(struct kaweth_device *kaweth, gfp_t mf)
+{
+	int status;
+
+	status = usb_submit_urb (kaweth->irq_urb, mf);
+	if (unlikely(status == -ENOMEM)) {
+		kaweth->suspend_lowmem_ctrl = 1;
+		schedule_delayed_work(&kaweth->lowmem_work, HZ/4);
+	} else {
+		kaweth->suspend_lowmem_ctrl = 0;
+	}
+
+	if (status)
+		err ("can't resubmit intr, %s-%s, status %d",
+				kaweth->dev->bus->bus_name,
+				kaweth->dev->devpath, status);
+}
+
+static void int_callback(struct urb *u)
+{
+	struct kaweth_device *kaweth = u->context;
+	int act_state;
+	int status = u->status;
+
+	switch (status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		goto resubmit;
+	}
+
+	/* we check the link state to report changes */
+	if (kaweth->linkstate != (act_state = ( kaweth->intbuffer[STATE_OFFSET] | STATE_MASK) >> STATE_SHIFT)) {
+		if (act_state)
+			netif_carrier_on(kaweth->net);
+		else
+			netif_carrier_off(kaweth->net);
+
+		kaweth->linkstate = act_state;
+	}
+resubmit:
+	kaweth_resubmit_int_urb(kaweth, GFP_ATOMIC);
+}
+
+static void kaweth_resubmit_tl(struct work_struct *work)
+{
+	struct kaweth_device *kaweth =
+		container_of(work, struct kaweth_device, lowmem_work.work);
+
+	if (IS_BLOCKED(kaweth->status))
+		return;
+
+	if (kaweth->suspend_lowmem_rx)
+		kaweth_resubmit_rx_urb(kaweth, GFP_NOIO);
+
+	if (kaweth->suspend_lowmem_ctrl)
+		kaweth_resubmit_int_urb(kaweth, GFP_NOIO);
+}
+
+
+/****************************************************************
+ *     kaweth_resubmit_rx_urb
+ ****************************************************************/
+static int kaweth_resubmit_rx_urb(struct kaweth_device *kaweth,
+						gfp_t mem_flags)
+{
+	int result;
+
+	usb_fill_bulk_urb(kaweth->rx_urb,
+		      kaweth->dev,
+		      usb_rcvbulkpipe(kaweth->dev, 1),
+		      kaweth->rx_buf,
+		      KAWETH_BUF_SIZE,
+		      kaweth_usb_receive,
+		      kaweth);
+	kaweth->rx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	kaweth->rx_urb->transfer_dma = kaweth->rxbufferhandle;
+
+	if((result = usb_submit_urb(kaweth->rx_urb, mem_flags))) {
+		if (result == -ENOMEM) {
+			kaweth->suspend_lowmem_rx = 1;
+			schedule_delayed_work(&kaweth->lowmem_work, HZ/4);
+		}
+		err("resubmitting rx_urb %d failed", result);
+	} else {
+		kaweth->suspend_lowmem_rx = 0;
+	}
+
+	return result;
+}
+
+static void kaweth_async_set_rx_mode(struct kaweth_device *kaweth);
+
+/****************************************************************
+ *     kaweth_usb_receive
+ ****************************************************************/
+static void kaweth_usb_receive(struct urb *urb)
+{
+	struct kaweth_device *kaweth = urb->context;
+	struct net_device *net = kaweth->net;
+	int status = urb->status;
+
+	int count = urb->actual_length;
+	int count2 = urb->transfer_buffer_length;
+
+	__u16 pkt_len = le16_to_cpup((__le16 *)kaweth->rx_buf);
+
+	struct sk_buff *skb;
+
+	if (unlikely(status == -EPIPE)) {
+		kaweth->stats.rx_errors++;
+		kaweth->end = 1;
+		wake_up(&kaweth->term_wait);
+		dbg("Status was -EPIPE.");
+		return;
+	}
+	if (unlikely(status == -ECONNRESET || status == -ESHUTDOWN)) {
+		/* we are killed - set a flag and wake the disconnect handler */
+		kaweth->end = 1;
+		wake_up(&kaweth->term_wait);
+		dbg("Status was -ECONNRESET or -ESHUTDOWN.");
+		return;
+	}
+	if (unlikely(status == -EPROTO || status == -ETIME ||
+		     status == -EILSEQ)) {
+		kaweth->stats.rx_errors++;
+		dbg("Status was -EPROTO, -ETIME, or -EILSEQ.");
+		return;
+	}
+	if (unlikely(status == -EOVERFLOW)) {
+		kaweth->stats.rx_errors++;
+		dbg("Status was -EOVERFLOW.");
+	}
+	spin_lock(&kaweth->device_lock);
+	if (IS_BLOCKED(kaweth->status)) {
+		spin_unlock(&kaweth->device_lock);
+		return;
+	}
+	spin_unlock(&kaweth->device_lock);
+
+	if(status && status != -EREMOTEIO && count != 1) {
+		err("%s RX status: %d count: %d packet_len: %d",
+                           net->name,
+			   status,
+			   count,
+			   (int)pkt_len);
+		kaweth_resubmit_rx_urb(kaweth, GFP_ATOMIC);
+                return;
+	}
+
+	if(kaweth->net && (count > 2)) {
+		if(pkt_len > (count - 2)) {
+			err("Packet length too long for USB frame (pkt_len: %x, count: %x)",pkt_len, count);
+			err("Packet len & 2047: %x", pkt_len & 2047);
+			err("Count 2: %x", count2);
+		        kaweth_resubmit_rx_urb(kaweth, GFP_ATOMIC);
+                        return;
+                }
+
+		if(!(skb = dev_alloc_skb(pkt_len+2))) {
+		        kaweth_resubmit_rx_urb(kaweth, GFP_ATOMIC);
+                        return;
+		}
+
+		skb_reserve(skb, 2);    /* Align IP on 16 byte boundaries */
+
+		skb_copy_to_linear_data(skb, kaweth->rx_buf + 2, pkt_len);
+
+		skb_put(skb, pkt_len);
+
+		skb->protocol = eth_type_trans(skb, net);
+
+		netif_rx(skb);
+
+		kaweth->stats.rx_packets++;
+		kaweth->stats.rx_bytes += pkt_len;
+	}
+
+	kaweth_resubmit_rx_urb(kaweth, GFP_ATOMIC);
+}
+
+/****************************************************************
+ *     kaweth_open
+ ****************************************************************/
+static int kaweth_open(struct net_device *net)
+{
+	struct kaweth_device *kaweth = netdev_priv(net);
+	int res;
+
+	dbg("Opening network device.");
+
+	res = usb_autopm_get_interface(kaweth->intf);
+	if (res) {
+		err("Interface cannot be resumed.");
+		return -EIO;
+	}
+	res = kaweth_resubmit_rx_urb(kaweth, GFP_KERNEL);
+	if (res)
+		goto err_out;
+
+	usb_fill_int_urb(
+		kaweth->irq_urb,
+		kaweth->dev,
+		usb_rcvintpipe(kaweth->dev, 3),
+		kaweth->intbuffer,
+		INTBUFFERSIZE,
+		int_callback,
+		kaweth,
+		250); /* overriding the descriptor */
+	kaweth->irq_urb->transfer_dma = kaweth->intbufferhandle;
+	kaweth->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	res = usb_submit_urb(kaweth->irq_urb, GFP_KERNEL);
+	if (res) {
+		usb_kill_urb(kaweth->rx_urb);
+		goto err_out;
+	}
+	kaweth->opened = 1;
+
+	netif_start_queue(net);
+
+	kaweth_async_set_rx_mode(kaweth);
+	return 0;
+
+err_out:
+	usb_autopm_put_interface(kaweth->intf);
+	return -EIO;
+}
+
+/****************************************************************
+ *     kaweth_kill_urbs
+ ****************************************************************/
+static void kaweth_kill_urbs(struct kaweth_device *kaweth)
+{
+	usb_kill_urb(kaweth->irq_urb);
+	usb_kill_urb(kaweth->rx_urb);
+	usb_kill_urb(kaweth->tx_urb);
+
+	cancel_delayed_work_sync(&kaweth->lowmem_work);
+
+	/* a scheduled work may have resubmitted,
+	   we hit them again */
+	usb_kill_urb(kaweth->irq_urb);
+	usb_kill_urb(kaweth->rx_urb);
+}
+
+/****************************************************************
+ *     kaweth_close
+ ****************************************************************/
+static int kaweth_close(struct net_device *net)
+{
+	struct kaweth_device *kaweth = netdev_priv(net);
+
+	netif_stop_queue(net);
+	kaweth->opened = 0;
+
+	kaweth->status |= KAWETH_STATUS_CLOSING;
+
+	kaweth_kill_urbs(kaweth);
+
+	kaweth->status &= ~KAWETH_STATUS_CLOSING;
+
+	usb_autopm_put_interface(kaweth->intf);
+
+	return 0;
+}
+
+static u32 kaweth_get_link(struct net_device *dev)
+{
+	struct kaweth_device *kaweth = netdev_priv(dev);
+
+	return kaweth->linkstate;
+}
+
+static const struct ethtool_ops ops = {
+	.get_link	= kaweth_get_link
+};
+
+/****************************************************************
+ *     kaweth_usb_transmit_complete
+ ****************************************************************/
+static void kaweth_usb_transmit_complete(struct urb *urb)
+{
+	struct kaweth_device *kaweth = urb->context;
+	struct sk_buff *skb = kaweth->tx_skb;
+	int status = urb->status;
+
+	if (unlikely(status != 0))
+		if (status != -ENOENT)
+			dbg("%s: TX status %d.", kaweth->net->name, status);
+
+	netif_wake_queue(kaweth->net);
+	dev_kfree_skb_irq(skb);
+}
+
+/****************************************************************
+ *     kaweth_start_xmit
+ ****************************************************************/
+static netdev_tx_t kaweth_start_xmit(struct sk_buff *skb,
+					   struct net_device *net)
+{
+	struct kaweth_device *kaweth = netdev_priv(net);
+	__le16 *private_header;
+
+	int res;
+
+	spin_lock_irq(&kaweth->device_lock);
+
+	kaweth_async_set_rx_mode(kaweth);
+	netif_stop_queue(net);
+	if (IS_BLOCKED(kaweth->status)) {
+		goto skip;
+	}
+
+	/* We now decide whether we can put our special header into the sk_buff */
+	if (skb_cloned(skb) || skb_headroom(skb) < 2) {
+		/* no such luck - we make our own */
+		struct sk_buff *copied_skb;
+		copied_skb = skb_copy_expand(skb, 2, 0, GFP_ATOMIC);
+		dev_kfree_skb_irq(skb);
+		skb = copied_skb;
+		if (!copied_skb) {
+			kaweth->stats.tx_errors++;
+			netif_start_queue(net);
+			spin_unlock_irq(&kaweth->device_lock);
+			return NETDEV_TX_OK;
+		}
+	}
+
+	private_header = (__le16 *)__skb_push(skb, 2);
+	*private_header = cpu_to_le16(skb->len-2);
+	kaweth->tx_skb = skb;
+
+	usb_fill_bulk_urb(kaweth->tx_urb,
+		      kaweth->dev,
+		      usb_sndbulkpipe(kaweth->dev, 2),
+		      private_header,
+		      skb->len,
+		      kaweth_usb_transmit_complete,
+		      kaweth);
+	kaweth->end = 0;
+
+	if((res = usb_submit_urb(kaweth->tx_urb, GFP_ATOMIC)))
+	{
+		dev_warn(&net->dev, "kaweth failed tx_urb %d\n", res);
+skip:
+		kaweth->stats.tx_errors++;
+
+		netif_start_queue(net);
+		dev_kfree_skb_irq(skb);
+	}
+	else
+	{
+		kaweth->stats.tx_packets++;
+		kaweth->stats.tx_bytes += skb->len;
+	}
+
+	spin_unlock_irq(&kaweth->device_lock);
+
+	return NETDEV_TX_OK;
+}
+
+/****************************************************************
+ *     kaweth_set_rx_mode
+ ****************************************************************/
+static void kaweth_set_rx_mode(struct net_device *net)
+{
+	struct kaweth_device *kaweth = netdev_priv(net);
+
+	__u16 packet_filter_bitmap = KAWETH_PACKET_FILTER_DIRECTED |
+                                     KAWETH_PACKET_FILTER_BROADCAST |
+		                     KAWETH_PACKET_FILTER_MULTICAST;
+
+	dbg("Setting Rx mode to %d", packet_filter_bitmap);
+
+	netif_stop_queue(net);
+
+	if (net->flags & IFF_PROMISC) {
+		packet_filter_bitmap |= KAWETH_PACKET_FILTER_PROMISCUOUS;
+	}
+	else if (!netdev_mc_empty(net) || (net->flags & IFF_ALLMULTI)) {
+		packet_filter_bitmap |= KAWETH_PACKET_FILTER_ALL_MULTICAST;
+	}
+
+	kaweth->packet_filter_bitmap = packet_filter_bitmap;
+	netif_wake_queue(net);
+}
+
+/****************************************************************
+ *     kaweth_async_set_rx_mode
+ ****************************************************************/
+static void kaweth_async_set_rx_mode(struct kaweth_device *kaweth)
+{
+	int result;
+	__u16 packet_filter_bitmap = kaweth->packet_filter_bitmap;
+
+	kaweth->packet_filter_bitmap = 0;
+	if (packet_filter_bitmap == 0)
+		return;
+
+	if (in_interrupt())
+		return;
+
+	result = kaweth_control(kaweth,
+				usb_sndctrlpipe(kaweth->dev, 0),
+				KAWETH_COMMAND_SET_PACKET_FILTER,
+				USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+				packet_filter_bitmap,
+				0,
+				(void *)&kaweth->scratch,
+				0,
+				KAWETH_CONTROL_TIMEOUT);
+
+	if(result < 0) {
+		err("Failed to set Rx mode: %d", result);
+	}
+	else {
+		dbg("Set Rx mode to %d", packet_filter_bitmap);
+	}
+}
+
+/****************************************************************
+ *     kaweth_netdev_stats
+ ****************************************************************/
+static struct net_device_stats *kaweth_netdev_stats(struct net_device *dev)
+{
+	struct kaweth_device *kaweth = netdev_priv(dev);
+	return &kaweth->stats;
+}
+
+/****************************************************************
+ *     kaweth_tx_timeout
+ ****************************************************************/
+static void kaweth_tx_timeout(struct net_device *net)
+{
+	struct kaweth_device *kaweth = netdev_priv(net);
+
+	dev_warn(&net->dev, "%s: Tx timed out. Resetting.\n", net->name);
+	kaweth->stats.tx_errors++;
+	net->trans_start = jiffies;
+
+	usb_unlink_urb(kaweth->tx_urb);
+}
+
+/****************************************************************
+ *     kaweth_suspend
+ ****************************************************************/
+static int kaweth_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct kaweth_device *kaweth = usb_get_intfdata(intf);
+	unsigned long flags;
+
+	dbg("Suspending device");
+	spin_lock_irqsave(&kaweth->device_lock, flags);
+	kaweth->status |= KAWETH_STATUS_SUSPENDING;
+	spin_unlock_irqrestore(&kaweth->device_lock, flags);
+
+	kaweth_kill_urbs(kaweth);
+	return 0;
+}
+
+/****************************************************************
+ *     kaweth_resume
+ ****************************************************************/
+static int kaweth_resume(struct usb_interface *intf)
+{
+	struct kaweth_device *kaweth = usb_get_intfdata(intf);
+	unsigned long flags;
+
+	dbg("Resuming device");
+	spin_lock_irqsave(&kaweth->device_lock, flags);
+	kaweth->status &= ~KAWETH_STATUS_SUSPENDING;
+	spin_unlock_irqrestore(&kaweth->device_lock, flags);
+
+	if (!kaweth->opened)
+		return 0;
+	kaweth_resubmit_rx_urb(kaweth, GFP_NOIO);
+	kaweth_resubmit_int_urb(kaweth, GFP_NOIO);
+
+	return 0;
+}
+
+/****************************************************************
+ *     kaweth_probe
+ ****************************************************************/
+
+
+static const struct net_device_ops kaweth_netdev_ops = {
+	.ndo_open =			kaweth_open,
+	.ndo_stop =			kaweth_close,
+	.ndo_start_xmit =		kaweth_start_xmit,
+	.ndo_tx_timeout =		kaweth_tx_timeout,
+	.ndo_set_rx_mode =		kaweth_set_rx_mode,
+	.ndo_get_stats =		kaweth_netdev_stats,
+	.ndo_change_mtu =		eth_change_mtu,
+	.ndo_set_mac_address =		eth_mac_addr,
+	.ndo_validate_addr =		eth_validate_addr,
+};
+
+static int kaweth_probe(
+		struct usb_interface *intf,
+		const struct usb_device_id *id      /* from id_table */
+	)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct kaweth_device *kaweth;
+	struct net_device *netdev;
+	const eth_addr_t bcast_addr = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+	int result = 0;
+
+	dbg("Kawasaki Device Probe (Device number:%d): 0x%4.4x:0x%4.4x:0x%4.4x",
+		 dev->devnum,
+		 le16_to_cpu(dev->descriptor.idVendor),
+		 le16_to_cpu(dev->descriptor.idProduct),
+		 le16_to_cpu(dev->descriptor.bcdDevice));
+
+	dbg("Device at %p", dev);
+
+	dbg("Descriptor length: %x type: %x",
+		 (int)dev->descriptor.bLength,
+		 (int)dev->descriptor.bDescriptorType);
+
+	netdev = alloc_etherdev(sizeof(*kaweth));
+	if (!netdev)
+		return -ENOMEM;
+
+	kaweth = netdev_priv(netdev);
+	kaweth->dev = dev;
+	kaweth->net = netdev;
+
+	spin_lock_init(&kaweth->device_lock);
+	init_waitqueue_head(&kaweth->term_wait);
+
+	dbg("Resetting.");
+
+	kaweth_reset(kaweth);
+
+	/*
+	 * If high byte of bcdDevice is nonzero, firmware is already
+	 * downloaded. Don't try to do it again, or we'll hang the device.
+	 */
+
+	if (le16_to_cpu(dev->descriptor.bcdDevice) >> 8) {
+		dev_info(&intf->dev, "Firmware present in device.\n");
+	} else {
+		/* Download the firmware */
+		dev_info(&intf->dev, "Downloading firmware...\n");
+		kaweth->firmware_buf = (__u8 *)__get_free_page(GFP_KERNEL);
+		if ((result = kaweth_download_firmware(kaweth,
+						      "kaweth/new_code.bin",
+						      100,
+						      2)) < 0) {
+			err("Error downloading firmware (%d)", result);
+			goto err_fw;
+		}
+
+		if ((result = kaweth_download_firmware(kaweth,
+						      "kaweth/new_code_fix.bin",
+						      100,
+						      3)) < 0) {
+			err("Error downloading firmware fix (%d)", result);
+			goto err_fw;
+		}
+
+		if ((result = kaweth_download_firmware(kaweth,
+						      "kaweth/trigger_code.bin",
+						      126,
+						      2)) < 0) {
+			err("Error downloading trigger code (%d)", result);
+			goto err_fw;
+
+		}
+
+		if ((result = kaweth_download_firmware(kaweth,
+						      "kaweth/trigger_code_fix.bin",
+						      126,
+						      3)) < 0) {
+			err("Error downloading trigger code fix (%d)", result);
+			goto err_fw;
+		}
+
+
+		if ((result = kaweth_trigger_firmware(kaweth, 126)) < 0) {
+			err("Error triggering firmware (%d)", result);
+			goto err_fw;
+		}
+
+		/* Device will now disappear for a moment...  */
+		dev_info(&intf->dev, "Firmware loaded.  I'll be back...\n");
+err_fw:
+		free_page((unsigned long)kaweth->firmware_buf);
+		free_netdev(netdev);
+		return -EIO;
+	}
+
+	result = kaweth_read_configuration(kaweth);
+
+	if(result < 0) {
+		err("Error reading configuration (%d), no net device created", result);
+		goto err_free_netdev;
+	}
+
+	dev_info(&intf->dev, "Statistics collection: %x\n", kaweth->configuration.statistics_mask);
+	dev_info(&intf->dev, "Multicast filter limit: %x\n", kaweth->configuration.max_multicast_filters & ((1 << 15) - 1));
+	dev_info(&intf->dev, "MTU: %d\n", le16_to_cpu(kaweth->configuration.segment_size));
+	dev_info(&intf->dev, "Read MAC address %pM\n", kaweth->configuration.hw_addr);
+
+	if(!memcmp(&kaweth->configuration.hw_addr,
+                   &bcast_addr,
+		   sizeof(bcast_addr))) {
+		err("Firmware not functioning properly, no net device created");
+		goto err_free_netdev;
+	}
+
+	if(kaweth_set_urb_size(kaweth, KAWETH_BUF_SIZE) < 0) {
+		dbg("Error setting URB size");
+		goto err_free_netdev;
+	}
+
+	if(kaweth_set_sofs_wait(kaweth, KAWETH_SOFS_TO_WAIT) < 0) {
+		err("Error setting SOFS wait");
+		goto err_free_netdev;
+	}
+
+	result = kaweth_set_receive_filter(kaweth,
+                                           KAWETH_PACKET_FILTER_DIRECTED |
+                                           KAWETH_PACKET_FILTER_BROADCAST |
+                                           KAWETH_PACKET_FILTER_MULTICAST);
+
+	if(result < 0) {
+		err("Error setting receive filter");
+		goto err_free_netdev;
+	}
+
+	dbg("Initializing net device.");
+
+	kaweth->intf = intf;
+
+	kaweth->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!kaweth->tx_urb)
+		goto err_free_netdev;
+	kaweth->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!kaweth->rx_urb)
+		goto err_only_tx;
+	kaweth->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!kaweth->irq_urb)
+		goto err_tx_and_rx;
+
+	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_alloc_coherent(	kaweth->dev,
+						KAWETH_BUF_SIZE,
+						GFP_KERNEL,
+						&kaweth->rxbufferhandle);
+	if (!kaweth->rx_buf)
+		goto err_all_but_rxbuf;
+
+	memcpy(netdev->broadcast, &bcast_addr, sizeof(bcast_addr));
+	memcpy(netdev->dev_addr, &kaweth->configuration.hw_addr,
+               sizeof(kaweth->configuration.hw_addr));
+
+	netdev->netdev_ops = &kaweth_netdev_ops;
+	netdev->watchdog_timeo = KAWETH_TX_TIMEOUT;
+	netdev->mtu = le16_to_cpu(kaweth->configuration.segment_size);
+	SET_ETHTOOL_OPS(netdev, &ops);
+
+	/* kaweth is zeroed as part of alloc_netdev */
+	INIT_DELAYED_WORK(&kaweth->lowmem_work, kaweth_resubmit_tl);
+	usb_set_intfdata(intf, kaweth);
+
+#if 0
+// dma_supported() is deeply broken on almost all architectures
+	if (dma_supported (&intf->dev, 0xffffffffffffffffULL))
+		kaweth->net->features |= NETIF_F_HIGHDMA;
+#endif
+
+	SET_NETDEV_DEV(netdev, &intf->dev);
+	if (register_netdev(netdev) != 0) {
+		err("Error registering netdev.");
+		goto err_intfdata;
+	}
+
+	dev_info(&intf->dev, "kaweth interface created at %s\n",
+		 kaweth->net->name);
+
+	dbg("Kaweth probe returning.");
+
+	return 0;
+
+err_intfdata:
+	usb_set_intfdata(intf, NULL);
+	usb_free_coherent(kaweth->dev, KAWETH_BUF_SIZE, (void *)kaweth->rx_buf, kaweth->rxbufferhandle);
+err_all_but_rxbuf:
+	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:
+	usb_free_urb(kaweth->rx_urb);
+err_only_tx:
+	usb_free_urb(kaweth->tx_urb);
+err_free_netdev:
+	free_netdev(netdev);
+
+	return -EIO;
+}
+
+/****************************************************************
+ *     kaweth_disconnect
+ ****************************************************************/
+static void kaweth_disconnect(struct usb_interface *intf)
+{
+	struct kaweth_device *kaweth = usb_get_intfdata(intf);
+	struct net_device *netdev;
+
+	dev_info(&intf->dev, "Unregistering\n");
+
+	usb_set_intfdata(intf, NULL);
+	if (!kaweth) {
+		dev_warn(&intf->dev, "unregistering non-existent device\n");
+		return;
+	}
+	netdev = kaweth->net;
+
+	dbg("Unregistering net device");
+	unregister_netdev(netdev);
+
+	usb_free_urb(kaweth->rx_urb);
+	usb_free_urb(kaweth->tx_urb);
+	usb_free_urb(kaweth->irq_urb);
+
+	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);
+}
+
+
+// FIXME this completion stuff is a modified clone of
+// an OLD version of some stuff in usb.c ...
+struct usb_api_data {
+	wait_queue_head_t wqh;
+	int done;
+};
+
+/*-------------------------------------------------------------------*
+ * completion handler for compatibility wrappers (sync control/bulk) *
+ *-------------------------------------------------------------------*/
+static void usb_api_blocking_completion(struct urb *urb)
+{
+        struct usb_api_data *awd = (struct usb_api_data *)urb->context;
+
+	awd->done=1;
+	wake_up(&awd->wqh);
+}
+
+/*-------------------------------------------------------------------*
+ *                         COMPATIBILITY STUFF                       *
+ *-------------------------------------------------------------------*/
+
+// Starts urb and waits for completion or timeout
+static int usb_start_wait_urb(struct urb *urb, int timeout, int* actual_length)
+{
+	struct usb_api_data awd;
+        int status;
+
+        init_waitqueue_head(&awd.wqh);
+        awd.done = 0;
+
+        urb->context = &awd;
+        status = usb_submit_urb(urb, GFP_NOIO);
+        if (status) {
+                // something went wrong
+                usb_free_urb(urb);
+                return status;
+        }
+
+	if (!wait_event_timeout(awd.wqh, awd.done, timeout)) {
+                // timeout
+                dev_warn(&urb->dev->dev, "usb_control/bulk_msg: timeout\n");
+                usb_kill_urb(urb);  // remove urb safely
+                status = -ETIMEDOUT;
+        }
+	else {
+                status = urb->status;
+	}
+
+        if (actual_length) {
+                *actual_length = urb->actual_length;
+	}
+
+        usb_free_urb(urb);
+        return status;
+}
+
+/*-------------------------------------------------------------------*/
+// returns status (negative) or length (positive)
+static int kaweth_internal_control_msg(struct usb_device *usb_dev,
+				       unsigned int pipe,
+				       struct usb_ctrlrequest *cmd, void *data,
+				       int len, int timeout)
+{
+        struct urb *urb;
+        int retv;
+        int length = 0; /* shut up GCC */
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+        if (!urb)
+                return -ENOMEM;
+
+        usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char*)cmd, data,
+			 len, usb_api_blocking_completion, NULL);
+
+        retv = usb_start_wait_urb(urb, timeout, &length);
+        if (retv < 0) {
+                return retv;
+	}
+        else {
+                return length;
+	}
+}
+
+module_usb_driver(kaweth_driver);
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/lg-vl600.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/lg-vl600.c
new file mode 100644
index 0000000..45a981f
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/lg-vl600.c
@@ -0,0 +1,353 @@
+/*
+ * Ethernet interface part of the LG VL600 LTE modem (4G dongle)
+ *
+ * Copyright (C) 2011 Intel Corporation
+ * Author: Andrzej Zaborowski <balrogg@gmail.com>
+ *
+ * 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 <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <linux/inetdevice.h>
+#include <linux/module.h>
+
+/*
+ * The device has a CDC ACM port for modem control (it claims to be
+ * CDC ACM anyway) and a CDC Ethernet port for actual network data.
+ * It will however ignore data on both ports that is not encapsulated
+ * in a specific way, any data returned is also encapsulated the same
+ * way.  The headers don't seem to follow any popular standard.
+ *
+ * This driver adds and strips these headers from the ethernet frames
+ * sent/received from the CDC Ethernet port.  The proprietary header
+ * replaces the standard ethernet header in a packet so only actual
+ * ethernet frames are allowed.  The headers allow some form of
+ * multiplexing by using non standard values of the .h_proto field.
+ * Windows/Mac drivers do send a couple of such frames to the device
+ * during initialisation, with protocol set to 0x0906 or 0x0b06 and (what
+ * seems to be) a flag in the .dummy_flags.  This doesn't seem necessary
+ * for modem operation but can possibly be used for GPS or other funcitons.
+ */
+
+struct vl600_frame_hdr {
+	__le32 len;
+	__le32 serial;
+	__le32 pkt_cnt;
+	__le32 dummy_flags;
+	__le32 dummy;
+	__le32 magic;
+} __attribute__((packed));
+
+struct vl600_pkt_hdr {
+	__le32 dummy[2];
+	__le32 len;
+	__be16 h_proto;
+} __attribute__((packed));
+
+struct vl600_state {
+	struct sk_buff *current_rx_buf;
+};
+
+static int vl600_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int ret;
+	struct vl600_state *s = kzalloc(sizeof(struct vl600_state), GFP_KERNEL);
+
+	if (!s)
+		return -ENOMEM;
+
+	ret = usbnet_cdc_bind(dev, intf);
+	if (ret) {
+		kfree(s);
+		return ret;
+	}
+
+	dev->driver_priv = s;
+
+	/* ARP packets don't go through, but they're also of no use.  The
+	 * subnet has only two hosts anyway: us and the gateway / DHCP
+	 * server (probably simulated by modem firmware or network operator)
+	 * whose address changes everytime we connect to the intarwebz and
+	 * who doesn't bother answering ARP requests either.  So hardware
+	 * addresses have no meaning, the destination and the source of every
+	 * packet depend only on whether it is on the IN or OUT endpoint.  */
+	dev->net->flags |= IFF_NOARP;
+	/* IPv6 NDP relies on multicast.  Enable it by default. */
+	dev->net->flags |= IFF_MULTICAST;
+
+	return ret;
+}
+
+static void vl600_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct vl600_state *s = dev->driver_priv;
+
+	if (s->current_rx_buf)
+		dev_kfree_skb(s->current_rx_buf);
+
+	kfree(s);
+
+	return usbnet_cdc_unbind(dev, intf);
+}
+
+static int vl600_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	struct vl600_frame_hdr *frame;
+	struct vl600_pkt_hdr *packet;
+	struct ethhdr *ethhdr;
+	int packet_len, count;
+	struct sk_buff *buf = skb;
+	struct sk_buff *clone;
+	struct vl600_state *s = dev->driver_priv;
+
+	/* Frame lengths are generally 4B multiplies but every couple of
+	 * hours there's an odd number of bytes sized yet correct frame,
+	 * so don't require this.  */
+
+	/* Allow a packet (or multiple packets batched together) to be
+	 * split across many frames.  We don't allow a new batch to
+	 * begin in the same frame another one is ending however, and no
+	 * leading or trailing pad bytes.  */
+	if (s->current_rx_buf) {
+		frame = (struct vl600_frame_hdr *) s->current_rx_buf->data;
+		if (skb->len + s->current_rx_buf->len >
+				le32_to_cpup(&frame->len)) {
+			netif_err(dev, ifup, dev->net, "Fragment too long\n");
+			dev->net->stats.rx_length_errors++;
+			goto error;
+		}
+
+		buf = s->current_rx_buf;
+		memcpy(skb_put(buf, skb->len), skb->data, skb->len);
+	} else if (skb->len < 4) {
+		netif_err(dev, ifup, dev->net, "Frame too short\n");
+		dev->net->stats.rx_length_errors++;
+		goto error;
+	}
+
+	frame = (struct vl600_frame_hdr *) buf->data;
+	/* Yes, check that frame->magic == 0x53544448 (or 0x44544d48),
+	 * otherwise we may run out of memory w/a bad packet */
+	if (ntohl(frame->magic) != 0x53544448 &&
+			ntohl(frame->magic) != 0x44544d48)
+		goto error;
+
+	if (buf->len < sizeof(*frame) ||
+			buf->len != le32_to_cpup(&frame->len)) {
+		/* Save this fragment for later assembly */
+		if (s->current_rx_buf)
+			return 0;
+
+		s->current_rx_buf = skb_copy_expand(skb, 0,
+				le32_to_cpup(&frame->len), GFP_ATOMIC);
+		if (!s->current_rx_buf) {
+			netif_err(dev, ifup, dev->net, "Reserving %i bytes "
+					"for packet assembly failed.\n",
+					le32_to_cpup(&frame->len));
+			dev->net->stats.rx_errors++;
+		}
+
+		return 0;
+	}
+
+	count = le32_to_cpup(&frame->pkt_cnt);
+
+	skb_pull(buf, sizeof(*frame));
+
+	while (count--) {
+		if (buf->len < sizeof(*packet)) {
+			netif_err(dev, ifup, dev->net, "Packet too short\n");
+			goto error;
+		}
+
+		packet = (struct vl600_pkt_hdr *) buf->data;
+		packet_len = sizeof(*packet) + le32_to_cpup(&packet->len);
+		if (packet_len > buf->len) {
+			netif_err(dev, ifup, dev->net,
+					"Bad packet length stored in header\n");
+			goto error;
+		}
+
+		/* Packet header is same size as the ethernet header
+		 * (sizeof(*packet) == sizeof(*ethhdr)), additionally
+		 * the h_proto field is in the same place so we just leave it
+		 * alone and fill in the remaining fields.
+		 */
+		ethhdr = (struct ethhdr *) skb->data;
+		if (be16_to_cpup(&ethhdr->h_proto) == ETH_P_ARP &&
+				buf->len > 0x26) {
+			/* Copy the addresses from packet contents */
+			memcpy(ethhdr->h_source,
+					&buf->data[sizeof(*ethhdr) + 0x8],
+					ETH_ALEN);
+			memcpy(ethhdr->h_dest,
+					&buf->data[sizeof(*ethhdr) + 0x12],
+					ETH_ALEN);
+		} else {
+			memset(ethhdr->h_source, 0, ETH_ALEN);
+			memcpy(ethhdr->h_dest, dev->net->dev_addr, ETH_ALEN);
+
+			/* Inbound IPv6 packets have an IPv4 ethertype (0x800)
+			 * for some reason.  Peek at the L3 header to check
+			 * for IPv6 packets, and set the ethertype to IPv6
+			 * (0x86dd) so Linux can understand it.
+			 */
+			if ((buf->data[sizeof(*ethhdr)] & 0xf0) == 0x60)
+				ethhdr->h_proto = __constant_htons(ETH_P_IPV6);
+		}
+
+		if (count) {
+			/* Not the last packet in this batch */
+			clone = skb_clone(buf, GFP_ATOMIC);
+			if (!clone)
+				goto error;
+
+			skb_trim(clone, packet_len);
+			usbnet_skb_return(dev, clone);
+
+			skb_pull(buf, (packet_len + 3) & ~3);
+		} else {
+			skb_trim(buf, packet_len);
+
+			if (s->current_rx_buf) {
+				usbnet_skb_return(dev, buf);
+				s->current_rx_buf = NULL;
+				return 0;
+			}
+
+			return 1;
+		}
+	}
+
+error:
+	if (s->current_rx_buf) {
+		dev_kfree_skb_any(s->current_rx_buf);
+		s->current_rx_buf = NULL;
+	}
+	dev->net->stats.rx_errors++;
+	return 0;
+}
+
+static struct sk_buff *vl600_tx_fixup(struct usbnet *dev,
+		struct sk_buff *skb, gfp_t flags)
+{
+	struct sk_buff *ret;
+	struct vl600_frame_hdr *frame;
+	struct vl600_pkt_hdr *packet;
+	static uint32_t serial = 1;
+	int orig_len = skb->len - sizeof(struct ethhdr);
+	int full_len = (skb->len + sizeof(struct vl600_frame_hdr) + 3) & ~3;
+
+	frame = (struct vl600_frame_hdr *) skb->data;
+	if (skb->len > sizeof(*frame) && skb->len == le32_to_cpup(&frame->len))
+		return skb; /* Already encapsulated? */
+
+	if (skb->len < sizeof(struct ethhdr))
+		/* Drop, device can only deal with ethernet packets */
+		return NULL;
+
+	if (!skb_cloned(skb)) {
+		int headroom = skb_headroom(skb);
+		int tailroom = skb_tailroom(skb);
+
+		if (tailroom >= full_len - skb->len - sizeof(*frame) &&
+				headroom >= sizeof(*frame))
+			/* There's enough head and tail room */
+			goto encapsulate;
+
+		if (headroom + tailroom + skb->len >= full_len) {
+			/* There's enough total room, just readjust */
+			skb->data = memmove(skb->head + sizeof(*frame),
+					skb->data, skb->len);
+			skb_set_tail_pointer(skb, skb->len);
+			goto encapsulate;
+		}
+	}
+
+	/* Alloc a new skb with the required size */
+	ret = skb_copy_expand(skb, sizeof(struct vl600_frame_hdr), full_len -
+			skb->len - sizeof(struct vl600_frame_hdr), flags);
+	dev_kfree_skb_any(skb);
+	if (!ret)
+		return ret;
+	skb = ret;
+
+encapsulate:
+	/* Packet header is same size as ethernet packet header
+	 * (sizeof(*packet) == sizeof(struct ethhdr)), additionally the
+	 * h_proto field is in the same place so we just leave it alone and
+	 * overwrite the remaining fields.
+	 */
+	packet = (struct vl600_pkt_hdr *) skb->data;
+	/* The VL600 wants IPv6 packets to have an IPv4 ethertype
+	 * Since this modem only supports IPv4 and IPv6, just set all
+	 * frames to 0x0800 (ETH_P_IP)
+	 */
+	packet->h_proto = htons(ETH_P_IP);
+	memset(&packet->dummy, 0, sizeof(packet->dummy));
+	packet->len = cpu_to_le32(orig_len);
+
+	frame = (struct vl600_frame_hdr *) skb_push(skb, sizeof(*frame));
+	memset(frame, 0, sizeof(*frame));
+	frame->len = cpu_to_le32(full_len);
+	frame->serial = cpu_to_le32(serial++);
+	frame->pkt_cnt = cpu_to_le32(1);
+
+	if (skb->len < full_len) /* Pad */
+		skb_put(skb, full_len - skb->len);
+
+	return skb;
+}
+
+static const struct driver_info	vl600_info = {
+	.description	= "LG VL600 modem",
+	.flags		= FLAG_RX_ASSEMBLE | FLAG_WWAN,
+	.bind		= vl600_bind,
+	.unbind		= vl600_unbind,
+	.status		= usbnet_cdc_status,
+	.rx_fixup	= vl600_rx_fixup,
+	.tx_fixup	= vl600_tx_fixup,
+};
+
+static const struct usb_device_id products[] = {
+	{
+		USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM,
+				USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
+		.driver_info	= (unsigned long) &vl600_info,
+	},
+	{},	/* End */
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver lg_vl600_driver = {
+	.name		= "lg-vl600",
+	.id_table	= products,
+	.probe		= usbnet_probe,
+	.disconnect	= usbnet_disconnect,
+	.suspend	= usbnet_suspend,
+	.resume		= usbnet_resume,
+};
+
+module_usb_driver(lg_vl600_driver);
+
+MODULE_AUTHOR("Anrzej Zaborowski");
+MODULE_DESCRIPTION("LG-VL600 modem's ethernet link");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/mcs7830.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/mcs7830.c
new file mode 100644
index 0000000..21a9377
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/mcs7830.c
@@ -0,0 +1,699 @@
+/*
+ * MOSCHIP MCS7830 based (7730/7830/7832) USB 2.0 Ethernet Devices
+ *
+ * based on usbnet.c, asix.c and the vendor provided mcs7830 driver
+ *
+ * Copyright (C) 2010 Andreas Mohr <andi@lisas.de>
+ * Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>
+ * Copyright (C) 2003-2005 David Hollis <dhollis@davehollis.com>
+ * Copyright (C) 2005 Phil Chang <pchang23@sbcglobal.net>
+ * Copyright (c) 2002-2003 TiVo Inc.
+ *
+ * Definitions gathered from MOSCHIP, Data Sheet_7830DA.pdf (thanks!).
+ *
+ * 2010-12-19: add 7832 USB PID ("functionality same as MCS7830"),
+ *             per active notification by manufacturer
+ *
+ * TODO:
+ * - support HIF_REG_CONFIG_SLEEPMODE/HIF_REG_CONFIG_TXENABLE (via autopm?)
+ * - implement ethtool_ops get_pauseparam/set_pauseparam
+ *   via HIF_REG_PAUSE_THRESHOLD (>= revision C only!)
+ * - implement get_eeprom/[set_eeprom]
+ * - switch PHY on/off on ifup/ifdown (perhaps in usbnet.c, via MII)
+ * - mcs7830_get_regs() handling is weird: for rev 2 we return 32 regs,
+ *   can access only ~ 24, remaining user buffer is uninitialized garbage
+ * - anything else?
+ *
+ *
+ * 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 <linux/crc32.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+
+/* requests */
+#define MCS7830_RD_BMREQ	(USB_DIR_IN  | USB_TYPE_VENDOR | \
+				 USB_RECIP_DEVICE)
+#define MCS7830_WR_BMREQ	(USB_DIR_OUT | USB_TYPE_VENDOR | \
+				 USB_RECIP_DEVICE)
+#define MCS7830_RD_BREQ		0x0E
+#define MCS7830_WR_BREQ		0x0D
+
+#define MCS7830_CTRL_TIMEOUT	1000
+#define MCS7830_MAX_MCAST	64
+
+#define MCS7830_VENDOR_ID	0x9710
+#define MCS7832_PRODUCT_ID	0x7832
+#define MCS7830_PRODUCT_ID	0x7830
+#define MCS7730_PRODUCT_ID	0x7730
+
+#define SITECOM_VENDOR_ID	0x0DF6
+#define LN_030_PRODUCT_ID	0x0021
+
+#define MCS7830_MII_ADVERTISE	(ADVERTISE_PAUSE_CAP | ADVERTISE_100FULL | \
+				 ADVERTISE_100HALF | ADVERTISE_10FULL | \
+				 ADVERTISE_10HALF | ADVERTISE_CSMA)
+
+/* HIF_REG_XX corresponding index value */
+enum {
+	HIF_REG_MULTICAST_HASH			= 0x00,
+	HIF_REG_PACKET_GAP1			= 0x08,
+	HIF_REG_PACKET_GAP2			= 0x09,
+	HIF_REG_PHY_DATA			= 0x0a,
+	HIF_REG_PHY_CMD1			= 0x0c,
+	   HIF_REG_PHY_CMD1_READ		= 0x40,
+	   HIF_REG_PHY_CMD1_WRITE		= 0x20,
+	   HIF_REG_PHY_CMD1_PHYADDR		= 0x01,
+	HIF_REG_PHY_CMD2			= 0x0d,
+	   HIF_REG_PHY_CMD2_PEND_FLAG_BIT	= 0x80,
+	   HIF_REG_PHY_CMD2_READY_FLAG_BIT	= 0x40,
+	HIF_REG_CONFIG				= 0x0e,
+	/* hmm, spec sez: "R/W", "Except bit 3" (likely TXENABLE). */
+	   HIF_REG_CONFIG_CFG			= 0x80,
+	   HIF_REG_CONFIG_SPEED100		= 0x40,
+	   HIF_REG_CONFIG_FULLDUPLEX_ENABLE	= 0x20,
+	   HIF_REG_CONFIG_RXENABLE		= 0x10,
+	   HIF_REG_CONFIG_TXENABLE		= 0x08,
+	   HIF_REG_CONFIG_SLEEPMODE		= 0x04,
+	   HIF_REG_CONFIG_ALLMULTICAST		= 0x02,
+	   HIF_REG_CONFIG_PROMISCUOUS		= 0x01,
+	HIF_REG_ETHERNET_ADDR			= 0x0f,
+	HIF_REG_FRAME_DROP_COUNTER		= 0x15, /* 0..ff; reset: 0 */
+	HIF_REG_PAUSE_THRESHOLD			= 0x16,
+	   HIF_REG_PAUSE_THRESHOLD_DEFAULT	= 0,
+};
+
+/* Trailing status byte in Ethernet Rx frame */
+enum {
+	MCS7830_RX_SHORT_FRAME		= 0x01, /* < 64 bytes */
+	MCS7830_RX_LENGTH_ERROR		= 0x02, /* framelen != Ethernet length field */
+	MCS7830_RX_ALIGNMENT_ERROR	= 0x04, /* non-even number of nibbles */
+	MCS7830_RX_CRC_ERROR		= 0x08,
+	MCS7830_RX_LARGE_FRAME		= 0x10, /* > 1518 bytes */
+	MCS7830_RX_FRAME_CORRECT	= 0x20, /* frame is correct */
+	/* [7:6] reserved */
+};
+
+struct mcs7830_data {
+	u8 multi_filter[8];
+	u8 config;
+};
+
+static const char driver_name[] = "MOSCHIP usb-ethernet driver";
+
+static int mcs7830_get_reg(struct usbnet *dev, u16 index, u16 size, void *data)
+{
+	struct usb_device *xdev = dev->udev;
+	int ret;
+	void *buffer;
+
+	buffer = kmalloc(size, GFP_NOIO);
+	if (buffer == NULL)
+		return -ENOMEM;
+
+	ret = usb_control_msg(xdev, usb_rcvctrlpipe(xdev, 0), MCS7830_RD_BREQ,
+			      MCS7830_RD_BMREQ, 0x0000, index, buffer,
+			      size, MCS7830_CTRL_TIMEOUT);
+	memcpy(data, buffer, size);
+	kfree(buffer);
+
+	return ret;
+}
+
+static int mcs7830_set_reg(struct usbnet *dev, u16 index, u16 size, const void *data)
+{
+	struct usb_device *xdev = dev->udev;
+	int ret;
+	void *buffer;
+
+	buffer = kmemdup(data, size, GFP_NOIO);
+	if (buffer == NULL)
+		return -ENOMEM;
+
+	ret = usb_control_msg(xdev, usb_sndctrlpipe(xdev, 0), MCS7830_WR_BREQ,
+			      MCS7830_WR_BMREQ, 0x0000, index, buffer,
+			      size, MCS7830_CTRL_TIMEOUT);
+	kfree(buffer);
+	return ret;
+}
+
+static void mcs7830_async_cmd_callback(struct urb *urb)
+{
+	struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;
+	int status = urb->status;
+
+	if (status < 0)
+		printk(KERN_DEBUG "%s() failed with %d\n",
+		       __func__, status);
+
+	kfree(req);
+	usb_free_urb(urb);
+}
+
+static void mcs7830_set_reg_async(struct usbnet *dev, u16 index, u16 size, void *data)
+{
+	struct usb_ctrlrequest *req;
+	int ret;
+	struct urb *urb;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb) {
+		dev_dbg(&dev->udev->dev,
+			"Error allocating URB in write_cmd_async!\n");
+		return;
+	}
+
+	req = kmalloc(sizeof *req, GFP_ATOMIC);
+	if (!req) {
+		dev_err(&dev->udev->dev,
+			"Failed to allocate memory for control request\n");
+		goto out;
+	}
+	req->bRequestType = MCS7830_WR_BMREQ;
+	req->bRequest = MCS7830_WR_BREQ;
+	req->wValue = 0;
+	req->wIndex = cpu_to_le16(index);
+	req->wLength = cpu_to_le16(size);
+
+	usb_fill_control_urb(urb, dev->udev,
+			     usb_sndctrlpipe(dev->udev, 0),
+			     (void *)req, data, size,
+			     mcs7830_async_cmd_callback, req);
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret < 0) {
+		dev_err(&dev->udev->dev,
+			"Error submitting the control message: ret=%d\n", ret);
+		goto out;
+	}
+	return;
+out:
+	kfree(req);
+	usb_free_urb(urb);
+}
+
+static int mcs7830_hif_get_mac_address(struct usbnet *dev, unsigned char *addr)
+{
+	int ret = mcs7830_get_reg(dev, HIF_REG_ETHERNET_ADDR, ETH_ALEN, addr);
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+static int mcs7830_hif_set_mac_address(struct usbnet *dev, unsigned char *addr)
+{
+	int ret = mcs7830_set_reg(dev, HIF_REG_ETHERNET_ADDR, ETH_ALEN, addr);
+
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+static int mcs7830_set_mac_address(struct net_device *netdev, void *p)
+{
+	int ret;
+	struct usbnet *dev = netdev_priv(netdev);
+	struct sockaddr *addr = p;
+
+	if (netif_running(netdev))
+		return -EBUSY;
+
+	if (!is_valid_ether_addr(addr->sa_data))
+		return -EADDRNOTAVAIL;
+
+	ret = mcs7830_hif_set_mac_address(dev, addr->sa_data);
+
+	if (ret < 0)
+		return ret;
+
+	/* it worked --> adopt it on netdev side */
+	memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len);
+
+	return 0;
+}
+
+static int mcs7830_read_phy(struct usbnet *dev, u8 index)
+{
+	int ret;
+	int i;
+	__le16 val;
+
+	u8 cmd[2] = {
+		HIF_REG_PHY_CMD1_READ | HIF_REG_PHY_CMD1_PHYADDR,
+		HIF_REG_PHY_CMD2_PEND_FLAG_BIT | index,
+	};
+
+	mutex_lock(&dev->phy_mutex);
+	/* write the MII command */
+	ret = mcs7830_set_reg(dev, HIF_REG_PHY_CMD1, 2, cmd);
+	if (ret < 0)
+		goto out;
+
+	/* wait for the data to become valid, should be within < 1ms */
+	for (i = 0; i < 10; i++) {
+		ret = mcs7830_get_reg(dev, HIF_REG_PHY_CMD1, 2, cmd);
+		if ((ret < 0) || (cmd[1] & HIF_REG_PHY_CMD2_READY_FLAG_BIT))
+			break;
+		ret = -EIO;
+		msleep(1);
+	}
+	if (ret < 0)
+		goto out;
+
+	/* read actual register contents */
+	ret = mcs7830_get_reg(dev, HIF_REG_PHY_DATA, 2, &val);
+	if (ret < 0)
+		goto out;
+	ret = le16_to_cpu(val);
+	dev_dbg(&dev->udev->dev, "read PHY reg %02x: %04x (%d tries)\n",
+		index, val, i);
+out:
+	mutex_unlock(&dev->phy_mutex);
+	return ret;
+}
+
+static int mcs7830_write_phy(struct usbnet *dev, u8 index, u16 val)
+{
+	int ret;
+	int i;
+	__le16 le_val;
+
+	u8 cmd[2] = {
+		HIF_REG_PHY_CMD1_WRITE | HIF_REG_PHY_CMD1_PHYADDR,
+		HIF_REG_PHY_CMD2_PEND_FLAG_BIT | (index & 0x1F),
+	};
+
+	mutex_lock(&dev->phy_mutex);
+
+	/* write the new register contents */
+	le_val = cpu_to_le16(val);
+	ret = mcs7830_set_reg(dev, HIF_REG_PHY_DATA, 2, &le_val);
+	if (ret < 0)
+		goto out;
+
+	/* write the MII command */
+	ret = mcs7830_set_reg(dev, HIF_REG_PHY_CMD1, 2, cmd);
+	if (ret < 0)
+		goto out;
+
+	/* wait for the command to be accepted by the PHY */
+	for (i = 0; i < 10; i++) {
+		ret = mcs7830_get_reg(dev, HIF_REG_PHY_CMD1, 2, cmd);
+		if ((ret < 0) || (cmd[1] & HIF_REG_PHY_CMD2_READY_FLAG_BIT))
+			break;
+		ret = -EIO;
+		msleep(1);
+	}
+	if (ret < 0)
+		goto out;
+
+	ret = 0;
+	dev_dbg(&dev->udev->dev, "write PHY reg %02x: %04x (%d tries)\n",
+		index, val, i);
+out:
+	mutex_unlock(&dev->phy_mutex);
+	return ret;
+}
+
+/*
+ * This algorithm comes from the original mcs7830 version 1.4 driver,
+ * not sure if it is needed.
+ */
+static int mcs7830_set_autoneg(struct usbnet *dev, int ptrUserPhyMode)
+{
+	int ret;
+	/* Enable all media types */
+	ret = mcs7830_write_phy(dev, MII_ADVERTISE, MCS7830_MII_ADVERTISE);
+
+	/* First reset BMCR */
+	if (!ret)
+		ret = mcs7830_write_phy(dev, MII_BMCR, 0x0000);
+	/* Enable Auto Neg */
+	if (!ret)
+		ret = mcs7830_write_phy(dev, MII_BMCR, BMCR_ANENABLE);
+	/* Restart Auto Neg (Keep the Enable Auto Neg Bit Set) */
+	if (!ret)
+		ret = mcs7830_write_phy(dev, MII_BMCR,
+				BMCR_ANENABLE | BMCR_ANRESTART	);
+	return ret;
+}
+
+
+/*
+ * if we can read register 22, the chip revision is C or higher
+ */
+static int mcs7830_get_rev(struct usbnet *dev)
+{
+	u8 dummy[2];
+	int ret;
+	ret = mcs7830_get_reg(dev, HIF_REG_FRAME_DROP_COUNTER, 2, dummy);
+	if (ret > 0)
+		return 2; /* Rev C or later */
+	return 1; /* earlier revision */
+}
+
+/*
+ * On rev. C we need to set the pause threshold
+ */
+static void mcs7830_rev_C_fixup(struct usbnet *dev)
+{
+	u8 pause_threshold = HIF_REG_PAUSE_THRESHOLD_DEFAULT;
+	int retry;
+
+	for (retry = 0; retry < 2; retry++) {
+		if (mcs7830_get_rev(dev) == 2) {
+			dev_info(&dev->udev->dev, "applying rev.C fixup\n");
+			mcs7830_set_reg(dev, HIF_REG_PAUSE_THRESHOLD,
+					1, &pause_threshold);
+		}
+		msleep(1);
+	}
+}
+
+static int mcs7830_mdio_read(struct net_device *netdev, int phy_id,
+			     int location)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	return mcs7830_read_phy(dev, location);
+}
+
+static void mcs7830_mdio_write(struct net_device *netdev, int phy_id,
+				int location, int val)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	mcs7830_write_phy(dev, location, val);
+}
+
+static int mcs7830_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
+{
+	struct usbnet *dev = netdev_priv(net);
+	return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
+}
+
+static inline struct mcs7830_data *mcs7830_get_data(struct usbnet *dev)
+{
+	return (struct mcs7830_data *)&dev->data;
+}
+
+static void mcs7830_hif_update_multicast_hash(struct usbnet *dev)
+{
+	struct mcs7830_data *data = mcs7830_get_data(dev);
+	mcs7830_set_reg_async(dev, HIF_REG_MULTICAST_HASH,
+				sizeof data->multi_filter,
+				data->multi_filter);
+}
+
+static void mcs7830_hif_update_config(struct usbnet *dev)
+{
+	/* implementation specific to data->config
+           (argument needs to be heap-based anyway - USB DMA!) */
+	struct mcs7830_data *data = mcs7830_get_data(dev);
+	mcs7830_set_reg_async(dev, HIF_REG_CONFIG, 1, &data->config);
+}
+
+static void mcs7830_data_set_multicast(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct mcs7830_data *data = mcs7830_get_data(dev);
+
+	memset(data->multi_filter, 0, sizeof data->multi_filter);
+
+	data->config = HIF_REG_CONFIG_TXENABLE;
+
+	/* this should not be needed, but it doesn't work otherwise */
+	data->config |= HIF_REG_CONFIG_ALLMULTICAST;
+
+	if (net->flags & IFF_PROMISC) {
+		data->config |= HIF_REG_CONFIG_PROMISCUOUS;
+	} else if (net->flags & IFF_ALLMULTI ||
+		   netdev_mc_count(net) > MCS7830_MAX_MCAST) {
+		data->config |= HIF_REG_CONFIG_ALLMULTICAST;
+	} else if (netdev_mc_empty(net)) {
+		/* just broadcast and directed */
+	} else {
+		/* We use the 20 byte dev->data
+		 * for our 8 byte filter buffer
+		 * to avoid allocating memory that
+		 * is tricky to free later */
+		struct netdev_hw_addr *ha;
+		u32 crc_bits;
+
+		/* Build the multicast hash filter. */
+		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);
+		}
+	}
+}
+
+static int mcs7830_apply_base_config(struct usbnet *dev)
+{
+	int ret;
+
+	/* re-configure known MAC (suspend case etc.) */
+	ret = mcs7830_hif_set_mac_address(dev, dev->net->dev_addr);
+	if (ret) {
+		dev_info(&dev->udev->dev, "Cannot set MAC address\n");
+		goto out;
+	}
+
+	/* Set up PHY */
+	ret = mcs7830_set_autoneg(dev, 0);
+	if (ret) {
+		dev_info(&dev->udev->dev, "Cannot set autoneg\n");
+		goto out;
+	}
+
+	mcs7830_hif_update_multicast_hash(dev);
+	mcs7830_hif_update_config(dev);
+
+	mcs7830_rev_C_fixup(dev);
+	ret = 0;
+out:
+	return ret;
+}
+
+/* credits go to asix_set_multicast */
+static void mcs7830_set_multicast(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	mcs7830_data_set_multicast(net);
+
+	mcs7830_hif_update_multicast_hash(dev);
+	mcs7830_hif_update_config(dev);
+}
+
+static int mcs7830_get_regs_len(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	switch (mcs7830_get_rev(dev)) {
+	case 1:
+		return 21;
+	case 2:
+		return 32;
+	}
+	return 0;
+}
+
+static void mcs7830_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *drvinfo)
+{
+	usbnet_get_drvinfo(net, drvinfo);
+	drvinfo->regdump_len = mcs7830_get_regs_len(net);
+}
+
+static void mcs7830_get_regs(struct net_device *net, struct ethtool_regs *regs, void *data)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	regs->version = mcs7830_get_rev(dev);
+	mcs7830_get_reg(dev, 0, regs->len, data);
+}
+
+static const struct ethtool_ops mcs7830_ethtool_ops = {
+	.get_drvinfo		= mcs7830_get_drvinfo,
+	.get_regs_len		= mcs7830_get_regs_len,
+	.get_regs		= mcs7830_get_regs,
+
+	/* common usbnet calls */
+	.get_link		= usbnet_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,
+};
+
+static const struct net_device_ops mcs7830_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl 		= mcs7830_ioctl,
+	.ndo_set_rx_mode	= mcs7830_set_multicast,
+	.ndo_set_mac_address	= mcs7830_set_mac_address,
+};
+
+static int mcs7830_bind(struct usbnet *dev, struct usb_interface *udev)
+{
+	struct net_device *net = dev->net;
+	int ret;
+	int retry;
+
+	/* Initial startup: Gather MAC address setting from EEPROM */
+	ret = -EINVAL;
+	for (retry = 0; retry < 5 && ret; retry++)
+		ret = mcs7830_hif_get_mac_address(dev, net->dev_addr);
+	if (ret) {
+		dev_warn(&dev->udev->dev, "Cannot read MAC address\n");
+		goto out;
+	}
+
+	mcs7830_data_set_multicast(net);
+
+	ret = mcs7830_apply_base_config(dev);
+	if (ret)
+		goto out;
+
+	net->ethtool_ops = &mcs7830_ethtool_ops;
+	net->netdev_ops = &mcs7830_netdev_ops;
+
+	/* reserve space for the status byte on rx */
+	dev->rx_urb_size = ETH_FRAME_LEN + 1;
+
+	dev->mii.mdio_read = mcs7830_mdio_read;
+	dev->mii.mdio_write = mcs7830_mdio_write;
+	dev->mii.dev = net;
+	dev->mii.phy_id_mask = 0x3f;
+	dev->mii.reg_num_mask = 0x1f;
+	dev->mii.phy_id = *((u8 *) net->dev_addr + 1);
+
+	ret = usbnet_get_endpoints(dev, udev);
+out:
+	return ret;
+}
+
+/* The chip always appends a status byte that we need to strip */
+static int mcs7830_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	u8 status;
+
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len) {
+		dev_err(&dev->udev->dev, "unexpected tiny rx frame\n");
+		return 0;
+	}
+
+	skb_trim(skb, skb->len - 1);
+	status = skb->data[skb->len];
+
+	if (status != MCS7830_RX_FRAME_CORRECT) {
+		dev_dbg(&dev->udev->dev, "rx fixup status %x\n", status);
+
+		/* hmm, perhaps usbnet.c already sees a globally visible
+		   frame error and increments rx_errors on its own already? */
+		dev->net->stats.rx_errors++;
+
+		if (status &	(MCS7830_RX_SHORT_FRAME
+				|MCS7830_RX_LENGTH_ERROR
+				|MCS7830_RX_LARGE_FRAME))
+			dev->net->stats.rx_length_errors++;
+		if (status & MCS7830_RX_ALIGNMENT_ERROR)
+			dev->net->stats.rx_frame_errors++;
+		if (status & MCS7830_RX_CRC_ERROR)
+			dev->net->stats.rx_crc_errors++;
+	}
+
+	return skb->len > 0;
+}
+
+static const struct driver_info moschip_info = {
+	.description	= "MOSCHIP 7830/7832/7730 usb-NET adapter",
+	.bind		= mcs7830_bind,
+	.rx_fixup	= mcs7830_rx_fixup,
+	.flags		= FLAG_ETHER,
+	.in		= 1,
+	.out		= 2,
+};
+
+static const struct driver_info sitecom_info = {
+	.description    = "Sitecom LN-30 usb-NET adapter",
+	.bind		= mcs7830_bind,
+	.rx_fixup	= mcs7830_rx_fixup,
+	.flags		= FLAG_ETHER,
+	.in		= 1,
+	.out		= 2,
+};
+
+static const struct usb_device_id products[] = {
+	{
+		USB_DEVICE(MCS7830_VENDOR_ID, MCS7832_PRODUCT_ID),
+		.driver_info = (unsigned long) &moschip_info,
+	},
+	{
+		USB_DEVICE(MCS7830_VENDOR_ID, MCS7830_PRODUCT_ID),
+		.driver_info = (unsigned long) &moschip_info,
+	},
+	{
+		USB_DEVICE(MCS7830_VENDOR_ID, MCS7730_PRODUCT_ID),
+		.driver_info = (unsigned long) &moschip_info,
+	},
+	{
+		USB_DEVICE(SITECOM_VENDOR_ID, LN_030_PRODUCT_ID),
+		.driver_info = (unsigned long) &sitecom_info,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static int mcs7830_reset_resume (struct usb_interface *intf)
+{
+ 	/* YES, this function is successful enough that ethtool -d
+           does show same output pre-/post-suspend */
+
+	struct usbnet		*dev = usb_get_intfdata(intf);
+
+	mcs7830_apply_base_config(dev);
+
+	usbnet_resume(intf);
+
+	return 0;
+}
+
+static struct usb_driver mcs7830_driver = {
+	.name = driver_name,
+	.id_table = products,
+	.probe = usbnet_probe,
+	.disconnect = usbnet_disconnect,
+	.suspend = usbnet_suspend,
+	.resume = usbnet_resume,
+	.reset_resume = mcs7830_reset_resume,
+};
+
+module_usb_driver(mcs7830_driver);
+
+MODULE_DESCRIPTION("USB to network adapter MCS7830)");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/net1080.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/net1080.c
new file mode 100644
index 0000000..006a142
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/net1080.c
@@ -0,0 +1,600 @@
+/*
+ * Net1080 based USB host-to-host cables
+ * Copyright (C) 2000-2005 by David Brownell
+ *
+ * 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	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+
+#include <asm/unaligned.h>
+
+
+/*
+ * Netchip 1080 driver ... http://www.netchip.com
+ * (Sept 2004:  End-of-life announcement has been sent.)
+ * Used in (some) LapLink cables
+ */
+
+#define frame_errors	data[1]
+
+/*
+ * NetChip framing of ethernet packets, supporting additional error
+ * checks for links that may drop bulk packets from inside messages.
+ * Odd USB length == always short read for last usb packet.
+ *	- nc_header
+ *	- Ethernet header (14 bytes)
+ *	- payload
+ *	- (optional padding byte, if needed so length becomes odd)
+ *	- nc_trailer
+ *
+ * This framing is to be avoided for non-NetChip devices.
+ */
+
+struct nc_header {		// packed:
+	__le16	hdr_len;		// sizeof nc_header (LE, all)
+	__le16	packet_len;		// payload size (including ethhdr)
+	__le16	packet_id;		// detects dropped packets
+#define MIN_HEADER	6
+
+	// all else is optional, and must start with:
+	// __le16	vendorId;	// from usb-if
+	// __le16	productId;
+} __packed;
+
+#define	PAD_BYTE	((unsigned char)0xAC)
+
+struct nc_trailer {
+	__le16	packet_id;
+} __packed;
+
+// packets may use FLAG_FRAMING_NC and optional pad
+#define FRAMED_SIZE(mtu) (sizeof (struct nc_header) \
+				+ sizeof (struct ethhdr) \
+				+ (mtu) \
+				+ 1 \
+				+ sizeof (struct nc_trailer))
+
+#define MIN_FRAMED	FRAMED_SIZE(0)
+
+/* packets _could_ be up to 64KB... */
+#define NC_MAX_PACKET	32767
+
+
+/*
+ * Zero means no timeout; else, how long a 64 byte bulk packet may be queued
+ * before the hardware drops it.  If that's done, the driver will need to
+ * frame network packets to guard against the dropped USB packets.  The win32
+ * driver sets this for both sides of the link.
+ */
+#define	NC_READ_TTL_MS	((u8)255)	// ms
+
+/*
+ * We ignore most registers and EEPROM contents.
+ */
+#define	REG_USBCTL	((u8)0x04)
+#define REG_TTL		((u8)0x10)
+#define REG_STATUS	((u8)0x11)
+
+/*
+ * Vendor specific requests to read/write data
+ */
+#define	REQUEST_REGISTER	((u8)0x10)
+#define	REQUEST_EEPROM		((u8)0x11)
+
+static int
+nc_vendor_read(struct usbnet *dev, u8 req, u8 regnum, u16 *retval_ptr)
+{
+	int status = usb_control_msg(dev->udev,
+		usb_rcvctrlpipe(dev->udev, 0),
+		req,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		0, regnum,
+		retval_ptr, sizeof *retval_ptr,
+		USB_CTRL_GET_TIMEOUT);
+	if (status > 0)
+		status = 0;
+	if (!status)
+		le16_to_cpus(retval_ptr);
+	return status;
+}
+
+static inline int
+nc_register_read(struct usbnet *dev, u8 regnum, u16 *retval_ptr)
+{
+	return nc_vendor_read(dev, REQUEST_REGISTER, regnum, retval_ptr);
+}
+
+// no retval ... can become async, usable in_interrupt()
+static void
+nc_vendor_write(struct usbnet *dev, u8 req, u8 regnum, u16 value)
+{
+	usb_control_msg(dev->udev,
+		usb_sndctrlpipe(dev->udev, 0),
+		req,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		value, regnum,
+		NULL, 0,			// data is in setup packet
+		USB_CTRL_SET_TIMEOUT);
+}
+
+static inline void
+nc_register_write(struct usbnet *dev, u8 regnum, u16 value)
+{
+	nc_vendor_write(dev, REQUEST_REGISTER, regnum, value);
+}
+
+
+#if 0
+static void nc_dump_registers(struct usbnet *dev)
+{
+	u8	reg;
+	u16	*vp = kmalloc(sizeof (u16));
+
+	if (!vp) {
+		dbg("no memory?");
+		return;
+	}
+
+	dbg("%s registers:", dev->net->name);
+	for (reg = 0; reg < 0x20; reg++) {
+		int retval;
+
+		// reading some registers is trouble
+		if (reg >= 0x08 && reg <= 0xf)
+			continue;
+		if (reg >= 0x12 && reg <= 0x1e)
+			continue;
+
+		retval = nc_register_read(dev, reg, vp);
+		if (retval < 0)
+			dbg("%s reg [0x%x] ==> error %d",
+				dev->net->name, reg, retval);
+		else
+			dbg("%s reg [0x%x] = 0x%x",
+				dev->net->name, reg, *vp);
+	}
+	kfree(vp);
+}
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Control register
+ */
+
+#define	USBCTL_WRITABLE_MASK	0x1f0f
+// bits 15-13 reserved, r/o
+#define	USBCTL_ENABLE_LANG	(1 << 12)
+#define	USBCTL_ENABLE_MFGR	(1 << 11)
+#define	USBCTL_ENABLE_PROD	(1 << 10)
+#define	USBCTL_ENABLE_SERIAL	(1 << 9)
+#define	USBCTL_ENABLE_DEFAULTS	(1 << 8)
+// bits 7-4 reserved, r/o
+#define	USBCTL_FLUSH_OTHER	(1 << 3)
+#define	USBCTL_FLUSH_THIS	(1 << 2)
+#define	USBCTL_DISCONN_OTHER	(1 << 1)
+#define	USBCTL_DISCONN_THIS	(1 << 0)
+
+static inline void nc_dump_usbctl(struct usbnet *dev, u16 usbctl)
+{
+	netif_dbg(dev, link, dev->net,
+		  "net1080 %s-%s usbctl 0x%x:%s%s%s%s%s; this%s%s; other%s%s; r/o 0x%x\n",
+		  dev->udev->bus->bus_name, dev->udev->devpath,
+		  usbctl,
+		  (usbctl & USBCTL_ENABLE_LANG) ? " lang" : "",
+		  (usbctl & USBCTL_ENABLE_MFGR) ? " mfgr" : "",
+		  (usbctl & USBCTL_ENABLE_PROD) ? " prod" : "",
+		  (usbctl & USBCTL_ENABLE_SERIAL) ? " serial" : "",
+		  (usbctl & USBCTL_ENABLE_DEFAULTS) ? " defaults" : "",
+
+		  (usbctl & USBCTL_FLUSH_THIS) ? " FLUSH" : "",
+		  (usbctl & USBCTL_DISCONN_THIS) ? " DIS" : "",
+
+		  (usbctl & USBCTL_FLUSH_OTHER) ? " FLUSH" : "",
+		  (usbctl & USBCTL_DISCONN_OTHER) ? " DIS" : "",
+
+		  usbctl & ~USBCTL_WRITABLE_MASK);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Status register
+ */
+
+#define	STATUS_PORT_A		(1 << 15)
+
+#define	STATUS_CONN_OTHER	(1 << 14)
+#define	STATUS_SUSPEND_OTHER	(1 << 13)
+#define	STATUS_MAILBOX_OTHER	(1 << 12)
+#define	STATUS_PACKETS_OTHER(n)	(((n) >> 8) & 0x03)
+
+#define	STATUS_CONN_THIS	(1 << 6)
+#define	STATUS_SUSPEND_THIS	(1 << 5)
+#define	STATUS_MAILBOX_THIS	(1 << 4)
+#define	STATUS_PACKETS_THIS(n)	(((n) >> 0) & 0x03)
+
+#define	STATUS_UNSPEC_MASK	0x0c8c
+#define	STATUS_NOISE_MASK 	((u16)~(0x0303|STATUS_UNSPEC_MASK))
+
+
+static inline void nc_dump_status(struct usbnet *dev, u16 status)
+{
+	netif_dbg(dev, link, dev->net,
+		  "net1080 %s-%s status 0x%x: this (%c) PKT=%d%s%s%s; other PKT=%d%s%s%s; unspec 0x%x\n",
+		  dev->udev->bus->bus_name, dev->udev->devpath,
+		  status,
+
+		  // XXX the packet counts don't seem right
+		  // (1 at reset, not 0); maybe UNSPEC too
+
+		  (status & STATUS_PORT_A) ? 'A' : 'B',
+		  STATUS_PACKETS_THIS(status),
+		  (status & STATUS_CONN_THIS) ? " CON" : "",
+		  (status & STATUS_SUSPEND_THIS) ? " SUS" : "",
+		  (status & STATUS_MAILBOX_THIS) ? " MBOX" : "",
+
+		  STATUS_PACKETS_OTHER(status),
+		  (status & STATUS_CONN_OTHER) ? " CON" : "",
+		  (status & STATUS_SUSPEND_OTHER) ? " SUS" : "",
+		  (status & STATUS_MAILBOX_OTHER) ? " MBOX" : "",
+
+		  status & STATUS_UNSPEC_MASK);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * TTL register
+ */
+
+#define	TTL_THIS(ttl)	(0x00ff & ttl)
+#define	TTL_OTHER(ttl)	(0x00ff & (ttl >> 8))
+#define MK_TTL(this,other)	((u16)(((other)<<8)|(0x00ff&(this))))
+
+static inline void nc_dump_ttl(struct usbnet *dev, u16 ttl)
+{
+	netif_dbg(dev, link, dev->net, "net1080 %s-%s ttl 0x%x this = %d, other = %d\n",
+		  dev->udev->bus->bus_name, dev->udev->devpath,
+		  ttl, TTL_THIS(ttl), TTL_OTHER(ttl));
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int net1080_reset(struct usbnet *dev)
+{
+	u16		usbctl, status, ttl;
+	u16		*vp = kmalloc(sizeof (u16), GFP_KERNEL);
+	int		retval;
+
+	if (!vp)
+		return -ENOMEM;
+
+	// nc_dump_registers(dev);
+
+	if ((retval = nc_register_read(dev, REG_STATUS, vp)) < 0) {
+		dbg("can't read %s-%s status: %d",
+			dev->udev->bus->bus_name, dev->udev->devpath, retval);
+		goto done;
+	}
+	status = *vp;
+	nc_dump_status(dev, status);
+
+	if ((retval = nc_register_read(dev, REG_USBCTL, vp)) < 0) {
+		dbg("can't read USBCTL, %d", retval);
+		goto done;
+	}
+	usbctl = *vp;
+	nc_dump_usbctl(dev, usbctl);
+
+	nc_register_write(dev, REG_USBCTL,
+			USBCTL_FLUSH_THIS | USBCTL_FLUSH_OTHER);
+
+	if ((retval = nc_register_read(dev, REG_TTL, vp)) < 0) {
+		dbg("can't read TTL, %d", retval);
+		goto done;
+	}
+	ttl = *vp;
+	// nc_dump_ttl(dev, ttl);
+
+	nc_register_write(dev, REG_TTL,
+			MK_TTL(NC_READ_TTL_MS, TTL_OTHER(ttl)) );
+	dbg("%s: assigned TTL, %d ms", dev->net->name, NC_READ_TTL_MS);
+
+	netif_info(dev, link, dev->net, "port %c, peer %sconnected\n",
+		   (status & STATUS_PORT_A) ? 'A' : 'B',
+		   (status & STATUS_CONN_OTHER) ? "" : "dis");
+	retval = 0;
+
+done:
+	kfree(vp);
+	return retval;
+}
+
+static int net1080_check_connect(struct usbnet *dev)
+{
+	int			retval;
+	u16			status;
+	u16			*vp = kmalloc(sizeof (u16), GFP_KERNEL);
+
+	if (!vp)
+		return -ENOMEM;
+	retval = nc_register_read(dev, REG_STATUS, vp);
+	status = *vp;
+	kfree(vp);
+	if (retval != 0) {
+		dbg("%s net1080_check_conn read - %d", dev->net->name, retval);
+		return retval;
+	}
+	if ((status & STATUS_CONN_OTHER) != STATUS_CONN_OTHER)
+		return -ENOLINK;
+	return 0;
+}
+
+static void nc_flush_complete(struct urb *urb)
+{
+	kfree(urb->context);
+	usb_free_urb(urb);
+}
+
+static void nc_ensure_sync(struct usbnet *dev)
+{
+	dev->frame_errors++;
+	if (dev->frame_errors > 5) {
+		struct urb		*urb;
+		struct usb_ctrlrequest	*req;
+		int			status;
+
+		/* Send a flush */
+		urb = usb_alloc_urb(0, GFP_ATOMIC);
+		if (!urb)
+			return;
+
+		req = kmalloc(sizeof *req, GFP_ATOMIC);
+		if (!req) {
+			usb_free_urb(urb);
+			return;
+		}
+
+		req->bRequestType = USB_DIR_OUT
+			| USB_TYPE_VENDOR
+			| USB_RECIP_DEVICE;
+		req->bRequest = REQUEST_REGISTER;
+		req->wValue = cpu_to_le16(USBCTL_FLUSH_THIS
+				| USBCTL_FLUSH_OTHER);
+		req->wIndex = cpu_to_le16(REG_USBCTL);
+		req->wLength = cpu_to_le16(0);
+
+		/* queue an async control request, we don't need
+		 * to do anything when it finishes except clean up.
+		 */
+		usb_fill_control_urb(urb, dev->udev,
+			usb_sndctrlpipe(dev->udev, 0),
+			(unsigned char *) req,
+			NULL, 0,
+			nc_flush_complete, req);
+		status = usb_submit_urb(urb, GFP_ATOMIC);
+		if (status) {
+			kfree(req);
+			usb_free_urb(urb);
+			return;
+		}
+
+		netif_dbg(dev, rx_err, dev->net,
+			  "flush net1080; too many framing errors\n");
+		dev->frame_errors = 0;
+	}
+}
+
+static int net1080_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	struct nc_header	*header;
+	struct nc_trailer	*trailer;
+	u16			hdr_len, packet_len;
+
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len)
+		return 0;
+
+	if (!(skb->len & 0x01)) {
+#ifdef DEBUG
+		struct net_device	*net = dev->net;
+		dbg("rx framesize %d range %d..%d mtu %d", skb->len,
+			net->hard_header_len, dev->hard_mtu, net->mtu);
+#endif
+		dev->net->stats.rx_frame_errors++;
+		nc_ensure_sync(dev);
+		return 0;
+	}
+
+	header = (struct nc_header *) skb->data;
+	hdr_len = le16_to_cpup(&header->hdr_len);
+	packet_len = le16_to_cpup(&header->packet_len);
+	if (FRAMED_SIZE(packet_len) > NC_MAX_PACKET) {
+		dev->net->stats.rx_frame_errors++;
+		dbg("packet too big, %d", packet_len);
+		nc_ensure_sync(dev);
+		return 0;
+	} else if (hdr_len < MIN_HEADER) {
+		dev->net->stats.rx_frame_errors++;
+		dbg("header too short, %d", hdr_len);
+		nc_ensure_sync(dev);
+		return 0;
+	} else if (hdr_len > MIN_HEADER) {
+		// out of band data for us?
+		dbg("header OOB, %d bytes", hdr_len - MIN_HEADER);
+		nc_ensure_sync(dev);
+		// switch (vendor/product ids) { ... }
+	}
+	skb_pull(skb, hdr_len);
+
+	trailer = (struct nc_trailer *)
+		(skb->data + skb->len - sizeof *trailer);
+	skb_trim(skb, skb->len - sizeof *trailer);
+
+	if ((packet_len & 0x01) == 0) {
+		if (skb->data [packet_len] != PAD_BYTE) {
+			dev->net->stats.rx_frame_errors++;
+			dbg("bad pad");
+			return 0;
+		}
+		skb_trim(skb, skb->len - 1);
+	}
+	if (skb->len != packet_len) {
+		dev->net->stats.rx_frame_errors++;
+		dbg("bad packet len %d (expected %d)",
+			skb->len, packet_len);
+		nc_ensure_sync(dev);
+		return 0;
+	}
+	if (header->packet_id != get_unaligned(&trailer->packet_id)) {
+		dev->net->stats.rx_fifo_errors++;
+		dbg("(2+ dropped) rx packet_id mismatch 0x%x 0x%x",
+			le16_to_cpu(header->packet_id),
+			le16_to_cpu(trailer->packet_id));
+		return 0;
+	}
+#if 0
+	netdev_dbg(dev->net, "frame <rx h %d p %d id %d\n", header->hdr_len,
+		   header->packet_len, header->packet_id);
+#endif
+	dev->frame_errors = 0;
+	return 1;
+}
+
+static struct sk_buff *
+net1080_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	struct sk_buff		*skb2;
+	struct nc_header	*header = NULL;
+	struct nc_trailer	*trailer = NULL;
+	int			padlen = sizeof (struct nc_trailer);
+	int			len = skb->len;
+
+	if (!((len + padlen + sizeof (struct nc_header)) & 0x01))
+		padlen++;
+	if (!skb_cloned(skb)) {
+		int	headroom = skb_headroom(skb);
+		int	tailroom = skb_tailroom(skb);
+
+		if (padlen <= tailroom &&
+		    sizeof(struct nc_header) <= headroom)
+			/* There's enough head and tail room */
+			goto encapsulate;
+
+		if ((sizeof (struct nc_header) + padlen) <
+				(headroom + tailroom)) {
+			/* There's enough total room, so just readjust */
+			skb->data = memmove(skb->head
+						+ sizeof (struct nc_header),
+					    skb->data, skb->len);
+			skb_set_tail_pointer(skb, len);
+			goto encapsulate;
+		}
+	}
+
+	/* Create a new skb to use with the correct size */
+	skb2 = skb_copy_expand(skb,
+				sizeof (struct nc_header),
+				padlen,
+				flags);
+	dev_kfree_skb_any(skb);
+	if (!skb2)
+		return skb2;
+	skb = skb2;
+
+encapsulate:
+	/* header first */
+	header = (struct nc_header *) skb_push(skb, sizeof *header);
+	header->hdr_len = cpu_to_le16(sizeof (*header));
+	header->packet_len = cpu_to_le16(len);
+	header->packet_id = cpu_to_le16((u16)dev->xid++);
+
+	/* maybe pad; then trailer */
+	if (!((skb->len + sizeof *trailer) & 0x01))
+		*skb_put(skb, 1) = PAD_BYTE;
+	trailer = (struct nc_trailer *) skb_put(skb, sizeof *trailer);
+	put_unaligned(header->packet_id, &trailer->packet_id);
+#if 0
+	netdev_dbg(dev->net, "frame >tx h %d p %d id %d\n",
+		   header->hdr_len, header->packet_len,
+		   header->packet_id);
+#endif
+	return skb;
+}
+
+static int net1080_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	unsigned	extra = sizeof (struct nc_header)
+				+ 1
+				+ sizeof (struct nc_trailer);
+
+	dev->net->hard_header_len += extra;
+	dev->rx_urb_size = dev->net->hard_header_len + dev->net->mtu;
+	dev->hard_mtu = NC_MAX_PACKET;
+	return usbnet_get_endpoints (dev, intf);
+}
+
+static const struct driver_info	net1080_info = {
+	.description =	"NetChip TurboCONNECT",
+	.flags =	FLAG_POINTTOPOINT | FLAG_FRAMING_NC,
+	.bind =		net1080_bind,
+	.reset =	net1080_reset,
+	.check_connect = net1080_check_connect,
+	.rx_fixup =	net1080_rx_fixup,
+	.tx_fixup =	net1080_tx_fixup,
+};
+
+static const struct usb_device_id	products [] = {
+{
+	USB_DEVICE(0x0525, 0x1080),	// NetChip ref design
+	.driver_info =	(unsigned long) &net1080_info,
+}, {
+	USB_DEVICE(0x06D0, 0x0622),	// Laplink Gold
+	.driver_info =	(unsigned long) &net1080_info,
+},
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver net1080_driver = {
+	.name =		"net1080",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+module_usb_driver(net1080_driver);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("NetChip 1080 based USB Host-to-Host Links");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/pegasus.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/pegasus.c
new file mode 100644
index 0000000..7523930
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/pegasus.c
@@ -0,0 +1,1539 @@
+/*
+ *  Copyright (c) 1999-2005 Petko Manolov (petkan@users.sourceforge.net)
+ *
+ * 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.
+ *
+ *	ChangeLog:
+ *		....	Most of the time spent on reading sources & docs.
+ *		v0.2.x	First official release for the Linux kernel.
+ *		v0.3.0	Beutified and structured, some bugs fixed.
+ *		v0.3.x	URBifying bulk requests and bugfixing. First relatively
+ *			stable release. Still can touch device's registers only
+ *			from top-halves.
+ *		v0.4.0	Control messages remained unurbified are now URBs.
+ *			Now we can touch the HW at any time.
+ *		v0.4.9	Control urbs again use process context to wait. Argh...
+ *			Some long standing bugs (enable_net_traffic) fixed.
+ *			Also nasty trick about resubmiting control urb from
+ *			interrupt context used. Please let me know how it
+ *			behaves. Pegasus II support added since this version.
+ *			TODO: suppressing HCD warnings spewage on disconnect.
+ *		v0.4.13	Ethernet address is now set at probe(), not at open()
+ *			time as this seems to break dhcpd.
+ *		v0.5.0	branch to 2.5.x kernels
+ *		v0.5.1	ethtool support added
+ *		v0.5.5	rx socket buffers are in a pool and the their allocation
+ *			is out of the interrupt routine.
+ */
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/module.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include "pegasus.h"
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.6.14 (2006/09/27)"
+#define DRIVER_AUTHOR "Petko Manolov <petkan@users.sourceforge.net>"
+#define DRIVER_DESC "Pegasus/Pegasus II USB Ethernet driver"
+
+static const char driver_name[] = "pegasus";
+
+#undef	PEGASUS_WRITE_EEPROM
+#define	BMSR_MEDIA	(BMSR_10HALF | BMSR_10FULL | BMSR_100HALF | \
+			BMSR_100FULL | BMSR_ANEGCAPABLE)
+
+static bool loopback;
+static bool mii_mode;
+static char *devid;
+
+static struct usb_eth_dev usb_dev_id[] = {
+#define	PEGASUS_DEV(pn, vid, pid, flags)	\
+	{.name = pn, .vendor = vid, .device = pid, .private = flags},
+#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \
+	PEGASUS_DEV(pn, vid, pid, flags)
+#include "pegasus.h"
+#undef	PEGASUS_DEV
+#undef	PEGASUS_DEV_CLASS
+	{NULL, 0, 0, 0},
+	{NULL, 0, 0, 0}
+};
+
+static struct usb_device_id pegasus_ids[] = {
+#define	PEGASUS_DEV(pn, vid, pid, flags) \
+	{.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid},
+/*
+ * The Belkin F8T012xx1 bluetooth adaptor has the same vendor and product
+ * IDs as the Belkin F5D5050, so we need to teach the pegasus driver to
+ * ignore adaptors belonging to the "Wireless" class 0xE0. For this one
+ * case anyway, seeing as the pegasus is for "Wired" adaptors.
+ */
+#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \
+	{.match_flags = (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS), \
+	.idVendor = vid, .idProduct = pid, .bDeviceClass = dclass},
+#include "pegasus.h"
+#undef	PEGASUS_DEV
+#undef	PEGASUS_DEV_CLASS
+	{},
+	{}
+};
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+module_param(loopback, bool, 0);
+module_param(mii_mode, bool, 0);
+module_param(devid, charp, 0);
+MODULE_PARM_DESC(loopback, "Enable MAC loopback mode (bit 0)");
+MODULE_PARM_DESC(mii_mode, "Enable HomePNA mode (bit 0),default=MII mode = 0");
+MODULE_PARM_DESC(devid, "The format is: 'DEV_name:VendorID:DeviceID:Flags'");
+
+/* use ethtool to change the level for any given device */
+static int msg_level = -1;
+module_param(msg_level, int, 0);
+MODULE_PARM_DESC(msg_level, "Override default message level");
+
+MODULE_DEVICE_TABLE(usb, pegasus_ids);
+static const struct net_device_ops pegasus_netdev_ops;
+
+static int update_eth_regs_async(pegasus_t *);
+/* Aargh!!! I _really_ hate such tweaks */
+static void ctrl_callback(struct urb *urb)
+{
+	pegasus_t *pegasus = urb->context;
+	int status = urb->status;
+
+	if (!pegasus)
+		return;
+
+	switch (status) {
+	case 0:
+		if (pegasus->flags & ETH_REGS_CHANGE) {
+			pegasus->flags &= ~ETH_REGS_CHANGE;
+			pegasus->flags |= ETH_REGS_CHANGED;
+			update_eth_regs_async(pegasus);
+			return;
+		}
+		break;
+	case -EINPROGRESS:
+		return;
+	case -ENOENT:
+		break;
+	default:
+		if (net_ratelimit())
+			netif_dbg(pegasus, drv, pegasus->net,
+				  "%s, status %d\n", __func__, status);
+		break;
+	}
+	pegasus->flags &= ~ETH_REGS_CHANGED;
+	wake_up(&pegasus->ctrl_wait);
+}
+
+static int get_registers(pegasus_t *pegasus, __u16 indx, __u16 size,
+			 void *data)
+{
+	int ret;
+	char *buffer;
+	DECLARE_WAITQUEUE(wait, current);
+
+	buffer = kmalloc(size, GFP_KERNEL);
+	if (!buffer) {
+		netif_warn(pegasus, drv, pegasus->net,
+			   "out of memory in %s\n", __func__);
+		return -ENOMEM;
+	}
+	add_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	while (pegasus->flags & ETH_REGS_CHANGED)
+		schedule();
+	remove_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_RUNNING);
+
+	pegasus->dr.bRequestType = PEGASUS_REQT_READ;
+	pegasus->dr.bRequest = PEGASUS_REQ_GET_REGS;
+	pegasus->dr.wValue = cpu_to_le16(0);
+	pegasus->dr.wIndex = cpu_to_le16(indx);
+	pegasus->dr.wLength = cpu_to_le16(size);
+	pegasus->ctrl_urb->transfer_buffer_length = size;
+
+	usb_fill_control_urb(pegasus->ctrl_urb, pegasus->usb,
+			     usb_rcvctrlpipe(pegasus->usb, 0),
+			     (char *) &pegasus->dr,
+			     buffer, size, ctrl_callback, pegasus);
+
+	add_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+
+	/* using ATOMIC, we'd never wake up if we slept */
+	if ((ret = usb_submit_urb(pegasus->ctrl_urb, GFP_ATOMIC))) {
+		set_current_state(TASK_RUNNING);
+		if (ret == -ENODEV)
+			netif_device_detach(pegasus->net);
+		if (net_ratelimit())
+			netif_err(pegasus, drv, pegasus->net,
+				  "%s, status %d\n", __func__, ret);
+		goto out;
+	}
+
+	schedule();
+out:
+	remove_wait_queue(&pegasus->ctrl_wait, &wait);
+	memcpy(data, buffer, size);
+	kfree(buffer);
+
+	return ret;
+}
+
+static int set_registers(pegasus_t *pegasus, __u16 indx, __u16 size,
+			 void *data)
+{
+	int ret;
+	char *buffer;
+	DECLARE_WAITQUEUE(wait, current);
+
+	buffer = kmemdup(data, size, GFP_KERNEL);
+	if (!buffer) {
+		netif_warn(pegasus, drv, pegasus->net,
+			   "out of memory in %s\n", __func__);
+		return -ENOMEM;
+	}
+
+	add_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	while (pegasus->flags & ETH_REGS_CHANGED)
+		schedule();
+	remove_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_RUNNING);
+
+	pegasus->dr.bRequestType = PEGASUS_REQT_WRITE;
+	pegasus->dr.bRequest = PEGASUS_REQ_SET_REGS;
+	pegasus->dr.wValue = cpu_to_le16(0);
+	pegasus->dr.wIndex = cpu_to_le16(indx);
+	pegasus->dr.wLength = cpu_to_le16(size);
+	pegasus->ctrl_urb->transfer_buffer_length = size;
+
+	usb_fill_control_urb(pegasus->ctrl_urb, pegasus->usb,
+			     usb_sndctrlpipe(pegasus->usb, 0),
+			     (char *) &pegasus->dr,
+			     buffer, size, ctrl_callback, pegasus);
+
+	add_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+
+	if ((ret = usb_submit_urb(pegasus->ctrl_urb, GFP_ATOMIC))) {
+		if (ret == -ENODEV)
+			netif_device_detach(pegasus->net);
+		netif_err(pegasus, drv, pegasus->net,
+			  "%s, status %d\n", __func__, ret);
+		goto out;
+	}
+
+	schedule();
+out:
+	remove_wait_queue(&pegasus->ctrl_wait, &wait);
+	kfree(buffer);
+
+	return ret;
+}
+
+static int set_register(pegasus_t *pegasus, __u16 indx, __u8 data)
+{
+	int ret;
+	char *tmp;
+	DECLARE_WAITQUEUE(wait, current);
+
+	tmp = kmemdup(&data, 1, GFP_KERNEL);
+	if (!tmp) {
+		netif_warn(pegasus, drv, pegasus->net,
+			   "out of memory in %s\n", __func__);
+		return -ENOMEM;
+	}
+	add_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	while (pegasus->flags & ETH_REGS_CHANGED)
+		schedule();
+	remove_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_RUNNING);
+
+	pegasus->dr.bRequestType = PEGASUS_REQT_WRITE;
+	pegasus->dr.bRequest = PEGASUS_REQ_SET_REG;
+	pegasus->dr.wValue = cpu_to_le16(data);
+	pegasus->dr.wIndex = cpu_to_le16(indx);
+	pegasus->dr.wLength = cpu_to_le16(1);
+	pegasus->ctrl_urb->transfer_buffer_length = 1;
+
+	usb_fill_control_urb(pegasus->ctrl_urb, pegasus->usb,
+			     usb_sndctrlpipe(pegasus->usb, 0),
+			     (char *) &pegasus->dr,
+			     tmp, 1, ctrl_callback, pegasus);
+
+	add_wait_queue(&pegasus->ctrl_wait, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+
+	if ((ret = usb_submit_urb(pegasus->ctrl_urb, GFP_ATOMIC))) {
+		if (ret == -ENODEV)
+			netif_device_detach(pegasus->net);
+		if (net_ratelimit())
+			netif_err(pegasus, drv, pegasus->net,
+				  "%s, status %d\n", __func__, ret);
+		goto out;
+	}
+
+	schedule();
+out:
+	remove_wait_queue(&pegasus->ctrl_wait, &wait);
+	kfree(tmp);
+
+	return ret;
+}
+
+static int update_eth_regs_async(pegasus_t *pegasus)
+{
+	int ret;
+
+	pegasus->dr.bRequestType = PEGASUS_REQT_WRITE;
+	pegasus->dr.bRequest = PEGASUS_REQ_SET_REGS;
+	pegasus->dr.wValue = cpu_to_le16(0);
+	pegasus->dr.wIndex = cpu_to_le16(EthCtrl0);
+	pegasus->dr.wLength = cpu_to_le16(3);
+	pegasus->ctrl_urb->transfer_buffer_length = 3;
+
+	usb_fill_control_urb(pegasus->ctrl_urb, pegasus->usb,
+			     usb_sndctrlpipe(pegasus->usb, 0),
+			     (char *) &pegasus->dr,
+			     pegasus->eth_regs, 3, ctrl_callback, pegasus);
+
+	if ((ret = usb_submit_urb(pegasus->ctrl_urb, GFP_ATOMIC))) {
+		if (ret == -ENODEV)
+			netif_device_detach(pegasus->net);
+		netif_err(pegasus, drv, pegasus->net,
+			  "%s, status %d\n", __func__, ret);
+	}
+
+	return ret;
+}
+
+/* Returns 0 on success, error on failure */
+static int read_mii_word(pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd)
+{
+	int i;
+	__u8 data[4] = { phy, 0, 0, indx };
+	__le16 regdi;
+	int ret;
+
+	set_register(pegasus, PhyCtrl, 0);
+	set_registers(pegasus, PhyAddr, sizeof(data), data);
+	set_register(pegasus, PhyCtrl, (indx | PHY_READ));
+	for (i = 0; i < REG_TIMEOUT; i++) {
+		ret = get_registers(pegasus, PhyCtrl, 1, data);
+		if (ret == -ESHUTDOWN)
+			goto fail;
+		if (data[0] & PHY_DONE)
+			break;
+	}
+
+	if (i >= REG_TIMEOUT)
+		goto fail;
+
+	ret = get_registers(pegasus, PhyData, 2, &regdi);
+	*regd = le16_to_cpu(regdi);
+	return ret;
+
+fail:
+	netif_warn(pegasus, drv, pegasus->net, "%s failed\n", __func__);
+
+	return ret;
+}
+
+static int mdio_read(struct net_device *dev, int phy_id, int loc)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	u16 res;
+
+	read_mii_word(pegasus, phy_id, loc, &res);
+	return (int)res;
+}
+
+static int write_mii_word(pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 regd)
+{
+	int i;
+	__u8 data[4] = { phy, 0, 0, indx };
+	int ret;
+
+	data[1] = (u8) regd;
+	data[2] = (u8) (regd >> 8);
+	set_register(pegasus, PhyCtrl, 0);
+	set_registers(pegasus, PhyAddr, sizeof(data), data);
+	set_register(pegasus, PhyCtrl, (indx | PHY_WRITE));
+	for (i = 0; i < REG_TIMEOUT; i++) {
+		ret = get_registers(pegasus, PhyCtrl, 1, data);
+		if (ret == -ESHUTDOWN)
+			goto fail;
+		if (data[0] & PHY_DONE)
+			break;
+	}
+
+	if (i >= REG_TIMEOUT)
+		goto fail;
+
+	return ret;
+
+fail:
+	netif_warn(pegasus, drv, pegasus->net, "%s failed\n", __func__);
+	return -ETIMEDOUT;
+}
+
+static void mdio_write(struct net_device *dev, int phy_id, int loc, int val)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+
+	write_mii_word(pegasus, phy_id, loc, val);
+}
+
+static int read_eprom_word(pegasus_t *pegasus, __u8 index, __u16 *retdata)
+{
+	int i;
+	__u8 tmp;
+	__le16 retdatai;
+	int ret;
+
+	set_register(pegasus, EpromCtrl, 0);
+	set_register(pegasus, EpromOffset, index);
+	set_register(pegasus, EpromCtrl, EPROM_READ);
+
+	for (i = 0; i < REG_TIMEOUT; i++) {
+		ret = get_registers(pegasus, EpromCtrl, 1, &tmp);
+		if (tmp & EPROM_DONE)
+			break;
+		if (ret == -ESHUTDOWN)
+			goto fail;
+	}
+	if (i >= REG_TIMEOUT)
+		goto fail;
+
+	ret = get_registers(pegasus, EpromData, 2, &retdatai);
+	*retdata = le16_to_cpu(retdatai);
+	return ret;
+
+fail:
+	netif_warn(pegasus, drv, pegasus->net, "%s failed\n", __func__);
+	return -ETIMEDOUT;
+}
+
+#ifdef	PEGASUS_WRITE_EEPROM
+static inline void enable_eprom_write(pegasus_t *pegasus)
+{
+	__u8 tmp;
+	int ret;
+
+	get_registers(pegasus, EthCtrl2, 1, &tmp);
+	set_register(pegasus, EthCtrl2, tmp | EPROM_WR_ENABLE);
+}
+
+static inline void disable_eprom_write(pegasus_t *pegasus)
+{
+	__u8 tmp;
+	int ret;
+
+	get_registers(pegasus, EthCtrl2, 1, &tmp);
+	set_register(pegasus, EpromCtrl, 0);
+	set_register(pegasus, EthCtrl2, tmp & ~EPROM_WR_ENABLE);
+}
+
+static int write_eprom_word(pegasus_t *pegasus, __u8 index, __u16 data)
+{
+	int i;
+	__u8 tmp, d[4] = { 0x3f, 0, 0, EPROM_WRITE };
+	int ret;
+	__le16 le_data = cpu_to_le16(data);
+
+	set_registers(pegasus, EpromOffset, 4, d);
+	enable_eprom_write(pegasus);
+	set_register(pegasus, EpromOffset, index);
+	set_registers(pegasus, EpromData, 2, &le_data);
+	set_register(pegasus, EpromCtrl, EPROM_WRITE);
+
+	for (i = 0; i < REG_TIMEOUT; i++) {
+		ret = get_registers(pegasus, EpromCtrl, 1, &tmp);
+		if (ret == -ESHUTDOWN)
+			goto fail;
+		if (tmp & EPROM_DONE)
+			break;
+	}
+	disable_eprom_write(pegasus);
+	if (i >= REG_TIMEOUT)
+		goto fail;
+
+	return ret;
+
+fail:
+	netif_warn(pegasus, drv, pegasus->net, "%s failed\n", __func__);
+	return -ETIMEDOUT;
+}
+#endif				/* PEGASUS_WRITE_EEPROM */
+
+static inline void get_node_id(pegasus_t *pegasus, __u8 *id)
+{
+	int i;
+	__u16 w16;
+
+	for (i = 0; i < 3; i++) {
+		read_eprom_word(pegasus, i, &w16);
+		((__le16 *) id)[i] = cpu_to_le16(w16);
+	}
+}
+
+static void set_ethernet_addr(pegasus_t *pegasus)
+{
+	__u8 node_id[6];
+
+	if (pegasus->features & PEGASUS_II) {
+		get_registers(pegasus, 0x10, sizeof(node_id), node_id);
+	} else {
+		get_node_id(pegasus, node_id);
+		set_registers(pegasus, EthID, sizeof(node_id), node_id);
+	}
+	memcpy(pegasus->net->dev_addr, node_id, sizeof(node_id));
+}
+
+static inline int reset_mac(pegasus_t *pegasus)
+{
+	__u8 data = 0x8;
+	int i;
+
+	set_register(pegasus, EthCtrl1, data);
+	for (i = 0; i < REG_TIMEOUT; i++) {
+		get_registers(pegasus, EthCtrl1, 1, &data);
+		if (~data & 0x08) {
+			if (loopback)
+				break;
+			if (mii_mode && (pegasus->features & HAS_HOME_PNA))
+				set_register(pegasus, Gpio1, 0x34);
+			else
+				set_register(pegasus, Gpio1, 0x26);
+			set_register(pegasus, Gpio0, pegasus->features);
+			set_register(pegasus, Gpio0, DEFAULT_GPIO_SET);
+			break;
+		}
+	}
+	if (i == REG_TIMEOUT)
+		return -ETIMEDOUT;
+
+	if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
+	    usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) {
+		set_register(pegasus, Gpio0, 0x24);
+		set_register(pegasus, Gpio0, 0x26);
+	}
+	if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_ELCON) {
+		__u16 auxmode;
+		read_mii_word(pegasus, 3, 0x1b, &auxmode);
+		write_mii_word(pegasus, 3, 0x1b, auxmode | 4);
+	}
+
+	return 0;
+}
+
+static int enable_net_traffic(struct net_device *dev, struct usb_device *usb)
+{
+	__u16 linkpart;
+	__u8 data[4];
+	pegasus_t *pegasus = netdev_priv(dev);
+	int ret;
+
+	read_mii_word(pegasus, pegasus->phy, MII_LPA, &linkpart);
+	data[0] = 0xc9;
+	data[1] = 0;
+	if (linkpart & (ADVERTISE_100FULL | ADVERTISE_10FULL))
+		data[1] |= 0x20;	/* set full duplex */
+	if (linkpart & (ADVERTISE_100FULL | ADVERTISE_100HALF))
+		data[1] |= 0x10;	/* set 100 Mbps */
+	if (mii_mode)
+		data[1] = 0;
+	data[2] = loopback ? 0x09 : 0x01;
+
+	memcpy(pegasus->eth_regs, data, sizeof(data));
+	ret = set_registers(pegasus, EthCtrl0, 3, data);
+
+	if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS ||
+	    usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS2 ||
+	    usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) {
+		u16 auxmode;
+		read_mii_word(pegasus, 0, 0x1b, &auxmode);
+		write_mii_word(pegasus, 0, 0x1b, auxmode | 4);
+	}
+
+	return ret;
+}
+
+static void fill_skb_pool(pegasus_t *pegasus)
+{
+	int i;
+
+	for (i = 0; i < RX_SKBS; i++) {
+		if (pegasus->rx_pool[i])
+			continue;
+		pegasus->rx_pool[i] = dev_alloc_skb(PEGASUS_MTU + 2);
+		/*
+		 ** we give up if the allocation fail. the tasklet will be
+		 ** rescheduled again anyway...
+		 */
+		if (pegasus->rx_pool[i] == NULL)
+			return;
+		skb_reserve(pegasus->rx_pool[i], 2);
+	}
+}
+
+static void free_skb_pool(pegasus_t *pegasus)
+{
+	int i;
+
+	for (i = 0; i < RX_SKBS; i++) {
+		if (pegasus->rx_pool[i]) {
+			dev_kfree_skb(pegasus->rx_pool[i]);
+			pegasus->rx_pool[i] = NULL;
+		}
+	}
+}
+
+static inline struct sk_buff *pull_skb(pegasus_t * pegasus)
+{
+	int i;
+	struct sk_buff *skb;
+
+	for (i = 0; i < RX_SKBS; i++) {
+		if (likely(pegasus->rx_pool[i] != NULL)) {
+			skb = pegasus->rx_pool[i];
+			pegasus->rx_pool[i] = NULL;
+			return skb;
+		}
+	}
+	return NULL;
+}
+
+static void read_bulk_callback(struct urb *urb)
+{
+	pegasus_t *pegasus = urb->context;
+	struct net_device *net;
+	int rx_status, count = urb->actual_length;
+	int status = urb->status;
+	u8 *buf = urb->transfer_buffer;
+	__u16 pkt_len;
+
+	if (!pegasus)
+		return;
+
+	net = pegasus->net;
+	if (!netif_device_present(net) || !netif_running(net))
+		return;
+
+	switch (status) {
+	case 0:
+		break;
+	case -ETIME:
+		netif_dbg(pegasus, rx_err, net, "reset MAC\n");
+		pegasus->flags &= ~PEGASUS_RX_BUSY;
+		break;
+	case -EPIPE:		/* stall, or disconnect from TT */
+		/* FIXME schedule work to clear the halt */
+		netif_warn(pegasus, rx_err, net, "no rx stall recovery\n");
+		return;
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		netif_dbg(pegasus, ifdown, net, "rx unlink, %d\n", status);
+		return;
+	default:
+		netif_dbg(pegasus, rx_err, net, "RX status %d\n", status);
+		goto goon;
+	}
+
+	if (!count || count < 4)
+		goto goon;
+
+	rx_status = buf[count - 2];
+	if (rx_status & 0x1e) {
+		netif_dbg(pegasus, rx_err, net,
+			  "RX packet error %x\n", rx_status);
+		pegasus->stats.rx_errors++;
+		if (rx_status & 0x06)	/* long or runt	*/
+			pegasus->stats.rx_length_errors++;
+		if (rx_status & 0x08)
+			pegasus->stats.rx_crc_errors++;
+		if (rx_status & 0x10)	/* extra bits	*/
+			pegasus->stats.rx_frame_errors++;
+		goto goon;
+	}
+	if (pegasus->chip == 0x8513) {
+		pkt_len = le32_to_cpu(*(__le32 *)urb->transfer_buffer);
+		pkt_len &= 0x0fff;
+		pegasus->rx_skb->data += 2;
+	} else {
+		pkt_len = buf[count - 3] << 8;
+		pkt_len += buf[count - 4];
+		pkt_len &= 0xfff;
+		pkt_len -= 8;
+	}
+
+	/*
+	 * If the packet is unreasonably long, quietly drop it rather than
+	 * kernel panicing by calling skb_put.
+	 */
+	if (pkt_len > PEGASUS_MTU)
+		goto goon;
+
+	/*
+	 * at this point we are sure pegasus->rx_skb != NULL
+	 * so we go ahead and pass up the packet.
+	 */
+	skb_put(pegasus->rx_skb, pkt_len);
+	pegasus->rx_skb->protocol = eth_type_trans(pegasus->rx_skb, net);
+	netif_rx(pegasus->rx_skb);
+	pegasus->stats.rx_packets++;
+	pegasus->stats.rx_bytes += pkt_len;
+
+	if (pegasus->flags & PEGASUS_UNPLUG)
+		return;
+
+	spin_lock(&pegasus->rx_pool_lock);
+	pegasus->rx_skb = pull_skb(pegasus);
+	spin_unlock(&pegasus->rx_pool_lock);
+
+	if (pegasus->rx_skb == NULL)
+		goto tl_sched;
+goon:
+	usb_fill_bulk_urb(pegasus->rx_urb, pegasus->usb,
+			  usb_rcvbulkpipe(pegasus->usb, 1),
+			  pegasus->rx_skb->data, PEGASUS_MTU + 8,
+			  read_bulk_callback, pegasus);
+	rx_status = usb_submit_urb(pegasus->rx_urb, GFP_ATOMIC);
+	if (rx_status == -ENODEV)
+		netif_device_detach(pegasus->net);
+	else if (rx_status) {
+		pegasus->flags |= PEGASUS_RX_URB_FAIL;
+		goto tl_sched;
+	} else {
+		pegasus->flags &= ~PEGASUS_RX_URB_FAIL;
+	}
+
+	return;
+
+tl_sched:
+	tasklet_schedule(&pegasus->rx_tl);
+}
+
+static void rx_fixup(unsigned long data)
+{
+	pegasus_t *pegasus;
+	unsigned long flags;
+	int status;
+
+	pegasus = (pegasus_t *) data;
+	if (pegasus->flags & PEGASUS_UNPLUG)
+		return;
+
+	spin_lock_irqsave(&pegasus->rx_pool_lock, flags);
+	fill_skb_pool(pegasus);
+	if (pegasus->flags & PEGASUS_RX_URB_FAIL)
+		if (pegasus->rx_skb)
+			goto try_again;
+	if (pegasus->rx_skb == NULL)
+		pegasus->rx_skb = pull_skb(pegasus);
+	if (pegasus->rx_skb == NULL) {
+		netif_warn(pegasus, rx_err, pegasus->net, "low on memory\n");
+		tasklet_schedule(&pegasus->rx_tl);
+		goto done;
+	}
+	usb_fill_bulk_urb(pegasus->rx_urb, pegasus->usb,
+			  usb_rcvbulkpipe(pegasus->usb, 1),
+			  pegasus->rx_skb->data, PEGASUS_MTU + 8,
+			  read_bulk_callback, pegasus);
+try_again:
+	status = usb_submit_urb(pegasus->rx_urb, GFP_ATOMIC);
+	if (status == -ENODEV)
+		netif_device_detach(pegasus->net);
+	else if (status) {
+		pegasus->flags |= PEGASUS_RX_URB_FAIL;
+		tasklet_schedule(&pegasus->rx_tl);
+	} else {
+		pegasus->flags &= ~PEGASUS_RX_URB_FAIL;
+	}
+done:
+	spin_unlock_irqrestore(&pegasus->rx_pool_lock, flags);
+}
+
+static void write_bulk_callback(struct urb *urb)
+{
+	pegasus_t *pegasus = urb->context;
+	struct net_device *net;
+	int status = urb->status;
+
+	if (!pegasus)
+		return;
+
+	net = pegasus->net;
+
+	if (!netif_device_present(net) || !netif_running(net))
+		return;
+
+	switch (status) {
+	case -EPIPE:
+		/* FIXME schedule_work() to clear the tx halt */
+		netif_stop_queue(net);
+		netif_warn(pegasus, tx_err, net, "no tx stall recovery\n");
+		return;
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		netif_dbg(pegasus, ifdown, net, "tx unlink, %d\n", status);
+		return;
+	default:
+		netif_info(pegasus, tx_err, net, "TX status %d\n", status);
+		/* FALL THROUGH */
+	case 0:
+		break;
+	}
+
+	net->trans_start = jiffies; /* prevent tx timeout */
+	netif_wake_queue(net);
+}
+
+static void intr_callback(struct urb *urb)
+{
+	pegasus_t *pegasus = urb->context;
+	struct net_device *net;
+	int res, status = urb->status;
+
+	if (!pegasus)
+		return;
+	net = pegasus->net;
+
+	switch (status) {
+	case 0:
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:
+		/* some Pegasus-I products report LOTS of data
+		 * toggle errors... avoid log spamming
+		 */
+		netif_dbg(pegasus, timer, net, "intr status %d\n", status);
+	}
+
+	if (urb->actual_length >= 6) {
+		u8 *d = urb->transfer_buffer;
+
+		/* byte 0 == tx_status1, reg 2B */
+		if (d[0] & (TX_UNDERRUN|EXCESSIVE_COL
+					|LATE_COL|JABBER_TIMEOUT)) {
+			pegasus->stats.tx_errors++;
+			if (d[0] & TX_UNDERRUN)
+				pegasus->stats.tx_fifo_errors++;
+			if (d[0] & (EXCESSIVE_COL | JABBER_TIMEOUT))
+				pegasus->stats.tx_aborted_errors++;
+			if (d[0] & LATE_COL)
+				pegasus->stats.tx_window_errors++;
+		}
+
+		/* d[5].LINK_STATUS lies on some adapters.
+		 * d[0].NO_CARRIER kicks in only with failed TX.
+		 * ... so monitoring with MII may be safest.
+		 */
+
+		/* bytes 3-4 == rx_lostpkt, reg 2E/2F */
+		pegasus->stats.rx_missed_errors += ((d[3] & 0x7f) << 8) | d[4];
+	}
+
+	res = usb_submit_urb(urb, GFP_ATOMIC);
+	if (res == -ENODEV)
+		netif_device_detach(pegasus->net);
+	if (res)
+		netif_err(pegasus, timer, net,
+			  "can't resubmit interrupt urb, %d\n", res);
+}
+
+static void pegasus_tx_timeout(struct net_device *net)
+{
+	pegasus_t *pegasus = netdev_priv(net);
+	netif_warn(pegasus, timer, net, "tx timeout\n");
+	usb_unlink_urb(pegasus->tx_urb);
+	pegasus->stats.tx_errors++;
+}
+
+static netdev_tx_t pegasus_start_xmit(struct sk_buff *skb,
+					    struct net_device *net)
+{
+	pegasus_t *pegasus = netdev_priv(net);
+	int count = ((skb->len + 2) & 0x3f) ? skb->len + 2 : skb->len + 3;
+	int res;
+	__u16 l16 = skb->len;
+
+	netif_stop_queue(net);
+
+	((__le16 *) pegasus->tx_buff)[0] = cpu_to_le16(l16);
+	skb_copy_from_linear_data(skb, pegasus->tx_buff + 2, skb->len);
+	usb_fill_bulk_urb(pegasus->tx_urb, pegasus->usb,
+			  usb_sndbulkpipe(pegasus->usb, 2),
+			  pegasus->tx_buff, count,
+			  write_bulk_callback, pegasus);
+	if ((res = usb_submit_urb(pegasus->tx_urb, GFP_ATOMIC))) {
+		netif_warn(pegasus, tx_err, net, "fail tx, %d\n", res);
+		switch (res) {
+		case -EPIPE:		/* stall, or disconnect from TT */
+			/* cleanup should already have been scheduled */
+			break;
+		case -ENODEV:		/* disconnect() upcoming */
+		case -EPERM:
+			netif_device_detach(pegasus->net);
+			break;
+		default:
+			pegasus->stats.tx_errors++;
+			netif_start_queue(net);
+		}
+	} else {
+		pegasus->stats.tx_packets++;
+		pegasus->stats.tx_bytes += skb->len;
+	}
+	dev_kfree_skb(skb);
+
+	return NETDEV_TX_OK;
+}
+
+static struct net_device_stats *pegasus_netdev_stats(struct net_device *dev)
+{
+	return &((pegasus_t *) netdev_priv(dev))->stats;
+}
+
+static inline void disable_net_traffic(pegasus_t *pegasus)
+{
+	__le16 tmp = cpu_to_le16(0);
+
+	set_registers(pegasus, EthCtrl0, sizeof(tmp), &tmp);
+}
+
+static inline void get_interrupt_interval(pegasus_t *pegasus)
+{
+	u16 data;
+	u8 interval;
+
+	read_eprom_word(pegasus, 4, &data);
+	interval = data >> 8;
+	if (pegasus->usb->speed != USB_SPEED_HIGH) {
+		if (interval < 0x80) {
+			netif_info(pegasus, timer, pegasus->net,
+				   "intr interval changed from %ums to %ums\n",
+				   interval, 0x80);
+			interval = 0x80;
+			data = (data & 0x00FF) | ((u16)interval << 8);
+#ifdef PEGASUS_WRITE_EEPROM
+			write_eprom_word(pegasus, 4, data);
+#endif
+		}
+	}
+	pegasus->intr_interval = interval;
+}
+
+static void set_carrier(struct net_device *net)
+{
+	pegasus_t *pegasus = netdev_priv(net);
+	u16 tmp;
+
+	if (read_mii_word(pegasus, pegasus->phy, MII_BMSR, &tmp))
+		return;
+
+	if (tmp & BMSR_LSTATUS)
+		netif_carrier_on(net);
+	else
+		netif_carrier_off(net);
+}
+
+static void free_all_urbs(pegasus_t *pegasus)
+{
+	usb_free_urb(pegasus->intr_urb);
+	usb_free_urb(pegasus->tx_urb);
+	usb_free_urb(pegasus->rx_urb);
+	usb_free_urb(pegasus->ctrl_urb);
+}
+
+static void unlink_all_urbs(pegasus_t *pegasus)
+{
+	usb_kill_urb(pegasus->intr_urb);
+	usb_kill_urb(pegasus->tx_urb);
+	usb_kill_urb(pegasus->rx_urb);
+	usb_kill_urb(pegasus->ctrl_urb);
+}
+
+static int alloc_urbs(pegasus_t *pegasus)
+{
+	pegasus->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pegasus->ctrl_urb)
+		return 0;
+	pegasus->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pegasus->rx_urb) {
+		usb_free_urb(pegasus->ctrl_urb);
+		return 0;
+	}
+	pegasus->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pegasus->tx_urb) {
+		usb_free_urb(pegasus->rx_urb);
+		usb_free_urb(pegasus->ctrl_urb);
+		return 0;
+	}
+	pegasus->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pegasus->intr_urb) {
+		usb_free_urb(pegasus->tx_urb);
+		usb_free_urb(pegasus->rx_urb);
+		usb_free_urb(pegasus->ctrl_urb);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int pegasus_open(struct net_device *net)
+{
+	pegasus_t *pegasus = netdev_priv(net);
+	int res;
+
+	if (pegasus->rx_skb == NULL)
+		pegasus->rx_skb = pull_skb(pegasus);
+	/*
+	 ** Note: no point to free the pool.  it is empty :-)
+	 */
+	if (!pegasus->rx_skb)
+		return -ENOMEM;
+
+	res = set_registers(pegasus, EthID, 6, net->dev_addr);
+
+	usb_fill_bulk_urb(pegasus->rx_urb, pegasus->usb,
+			  usb_rcvbulkpipe(pegasus->usb, 1),
+			  pegasus->rx_skb->data, PEGASUS_MTU + 8,
+			  read_bulk_callback, pegasus);
+	if ((res = usb_submit_urb(pegasus->rx_urb, GFP_KERNEL))) {
+		if (res == -ENODEV)
+			netif_device_detach(pegasus->net);
+		netif_dbg(pegasus, ifup, net, "failed rx_urb, %d\n", res);
+		goto exit;
+	}
+
+	usb_fill_int_urb(pegasus->intr_urb, pegasus->usb,
+			 usb_rcvintpipe(pegasus->usb, 3),
+			 pegasus->intr_buff, sizeof(pegasus->intr_buff),
+			 intr_callback, pegasus, pegasus->intr_interval);
+	if ((res = usb_submit_urb(pegasus->intr_urb, GFP_KERNEL))) {
+		if (res == -ENODEV)
+			netif_device_detach(pegasus->net);
+		netif_dbg(pegasus, ifup, net, "failed intr_urb, %d\n", res);
+		usb_kill_urb(pegasus->rx_urb);
+		goto exit;
+	}
+	if ((res = enable_net_traffic(net, pegasus->usb))) {
+		netif_dbg(pegasus, ifup, net,
+			  "can't enable_net_traffic() - %d\n", res);
+		res = -EIO;
+		usb_kill_urb(pegasus->rx_urb);
+		usb_kill_urb(pegasus->intr_urb);
+		free_skb_pool(pegasus);
+		goto exit;
+	}
+	set_carrier(net);
+	netif_start_queue(net);
+	netif_dbg(pegasus, ifup, net, "open\n");
+	res = 0;
+exit:
+	return res;
+}
+
+static int pegasus_close(struct net_device *net)
+{
+	pegasus_t *pegasus = netdev_priv(net);
+
+	netif_stop_queue(net);
+	if (!(pegasus->flags & PEGASUS_UNPLUG))
+		disable_net_traffic(pegasus);
+	tasklet_kill(&pegasus->rx_tl);
+	unlink_all_urbs(pegasus);
+
+	return 0;
+}
+
+static void pegasus_get_drvinfo(struct net_device *dev,
+				struct ethtool_drvinfo *info)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	strncpy(info->driver, driver_name, sizeof(info->driver) - 1);
+	strncpy(info->version, DRIVER_VERSION, sizeof(info->version) - 1);
+	usb_make_path(pegasus->usb, info->bus_info, sizeof(info->bus_info));
+}
+
+/* also handles three patterns of some kind in hardware */
+#define	WOL_SUPPORTED	(WAKE_MAGIC|WAKE_PHY)
+
+static void
+pegasus_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	pegasus_t	*pegasus = netdev_priv(dev);
+
+	wol->supported = WAKE_MAGIC | WAKE_PHY;
+	wol->wolopts = pegasus->wolopts;
+}
+
+static int
+pegasus_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+	pegasus_t	*pegasus = netdev_priv(dev);
+	u8		reg78 = 0x04;
+
+	if (wol->wolopts & ~WOL_SUPPORTED)
+		return -EINVAL;
+
+	if (wol->wolopts & WAKE_MAGIC)
+		reg78 |= 0x80;
+	if (wol->wolopts & WAKE_PHY)
+		reg78 |= 0x40;
+	/* FIXME this 0x10 bit still needs to get set in the chip... */
+	if (wol->wolopts)
+		pegasus->eth_regs[0] |= 0x10;
+	else
+		pegasus->eth_regs[0] &= ~0x10;
+	pegasus->wolopts = wol->wolopts;
+	return set_register(pegasus, WakeupControl, reg78);
+}
+
+static inline void pegasus_reset_wol(struct net_device *dev)
+{
+	struct ethtool_wolinfo wol;
+
+	memset(&wol, 0, sizeof wol);
+	(void) pegasus_set_wol(dev, &wol);
+}
+
+static int
+pegasus_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
+{
+	pegasus_t *pegasus;
+
+	pegasus = netdev_priv(dev);
+	mii_ethtool_gset(&pegasus->mii, ecmd);
+	return 0;
+}
+
+static int
+pegasus_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	return mii_ethtool_sset(&pegasus->mii, ecmd);
+}
+
+static int pegasus_nway_reset(struct net_device *dev)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	return mii_nway_restart(&pegasus->mii);
+}
+
+static u32 pegasus_get_link(struct net_device *dev)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	return mii_link_ok(&pegasus->mii);
+}
+
+static u32 pegasus_get_msglevel(struct net_device *dev)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	return pegasus->msg_enable;
+}
+
+static void pegasus_set_msglevel(struct net_device *dev, u32 v)
+{
+	pegasus_t *pegasus = netdev_priv(dev);
+	pegasus->msg_enable = v;
+}
+
+static const struct ethtool_ops ops = {
+	.get_drvinfo = pegasus_get_drvinfo,
+	.get_settings = pegasus_get_settings,
+	.set_settings = pegasus_set_settings,
+	.nway_reset = pegasus_nway_reset,
+	.get_link = pegasus_get_link,
+	.get_msglevel = pegasus_get_msglevel,
+	.set_msglevel = pegasus_set_msglevel,
+	.get_wol = pegasus_get_wol,
+	.set_wol = pegasus_set_wol,
+};
+
+static int pegasus_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
+{
+	__u16 *data = (__u16 *) &rq->ifr_ifru;
+	pegasus_t *pegasus = netdev_priv(net);
+	int res;
+
+	switch (cmd) {
+	case SIOCDEVPRIVATE:
+		data[0] = pegasus->phy;
+	case SIOCDEVPRIVATE + 1:
+		read_mii_word(pegasus, data[0], data[1] & 0x1f, &data[3]);
+		res = 0;
+		break;
+	case SIOCDEVPRIVATE + 2:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		write_mii_word(pegasus, pegasus->phy, data[1] & 0x1f, data[2]);
+		res = 0;
+		break;
+	default:
+		res = -EOPNOTSUPP;
+	}
+	return res;
+}
+
+static void pegasus_set_multicast(struct net_device *net)
+{
+	pegasus_t *pegasus = netdev_priv(net);
+
+	if (net->flags & IFF_PROMISC) {
+		pegasus->eth_regs[EthCtrl2] |= RX_PROMISCUOUS;
+		netif_info(pegasus, link, net, "Promiscuous mode enabled\n");
+	} else if (!netdev_mc_empty(net) || (net->flags & IFF_ALLMULTI)) {
+		pegasus->eth_regs[EthCtrl0] |= RX_MULTICAST;
+		pegasus->eth_regs[EthCtrl2] &= ~RX_PROMISCUOUS;
+		netif_dbg(pegasus, link, net, "set allmulti\n");
+	} else {
+		pegasus->eth_regs[EthCtrl0] &= ~RX_MULTICAST;
+		pegasus->eth_regs[EthCtrl2] &= ~RX_PROMISCUOUS;
+	}
+
+	pegasus->ctrl_urb->status = 0;
+
+	pegasus->flags |= ETH_REGS_CHANGE;
+	ctrl_callback(pegasus->ctrl_urb);
+}
+
+static __u8 mii_phy_probe(pegasus_t *pegasus)
+{
+	int i;
+	__u16 tmp;
+
+	for (i = 0; i < 32; i++) {
+		read_mii_word(pegasus, i, MII_BMSR, &tmp);
+		if (tmp == 0 || tmp == 0xffff || (tmp & BMSR_MEDIA) == 0)
+			continue;
+		else
+			return i;
+	}
+
+	return 0xff;
+}
+
+static inline void setup_pegasus_II(pegasus_t *pegasus)
+{
+	__u8 data = 0xa5;
+
+	set_register(pegasus, Reg1d, 0);
+	set_register(pegasus, Reg7b, 1);
+	mdelay(100);
+	if ((pegasus->features & HAS_HOME_PNA) && mii_mode)
+		set_register(pegasus, Reg7b, 0);
+	else
+		set_register(pegasus, Reg7b, 2);
+
+	set_register(pegasus, 0x83, data);
+	get_registers(pegasus, 0x83, 1, &data);
+
+	if (data == 0xa5)
+		pegasus->chip = 0x8513;
+	else
+		pegasus->chip = 0;
+
+	set_register(pegasus, 0x80, 0xc0);
+	set_register(pegasus, 0x83, 0xff);
+	set_register(pegasus, 0x84, 0x01);
+
+	if (pegasus->features & HAS_HOME_PNA && mii_mode)
+		set_register(pegasus, Reg81, 6);
+	else
+		set_register(pegasus, Reg81, 2);
+}
+
+
+static int pegasus_count;
+static struct workqueue_struct *pegasus_workqueue;
+#define CARRIER_CHECK_DELAY (2 * HZ)
+
+static void check_carrier(struct work_struct *work)
+{
+	pegasus_t *pegasus = container_of(work, pegasus_t, carrier_check.work);
+	set_carrier(pegasus->net);
+	if (!(pegasus->flags & PEGASUS_UNPLUG)) {
+		queue_delayed_work(pegasus_workqueue, &pegasus->carrier_check,
+			CARRIER_CHECK_DELAY);
+	}
+}
+
+static int pegasus_blacklisted(struct usb_device *udev)
+{
+	struct usb_device_descriptor *udd = &udev->descriptor;
+
+	/* Special quirk to keep the driver from handling the Belkin Bluetooth
+	 * dongle which happens to have the same ID.
+	 */
+	if ((udd->idVendor == cpu_to_le16(VENDOR_BELKIN)) &&
+	    (udd->idProduct == cpu_to_le16(0x0121)) &&
+	    (udd->bDeviceClass == USB_CLASS_WIRELESS_CONTROLLER) &&
+	    (udd->bDeviceProtocol == 1))
+		return 1;
+
+	return 0;
+}
+
+/* we rely on probe() and remove() being serialized so we
+ * don't need extra locking on pegasus_count.
+ */
+static void pegasus_dec_workqueue(void)
+{
+	pegasus_count--;
+	if (pegasus_count == 0) {
+		destroy_workqueue(pegasus_workqueue);
+		pegasus_workqueue = NULL;
+	}
+}
+
+static int pegasus_probe(struct usb_interface *intf,
+			 const struct usb_device_id *id)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct net_device *net;
+	pegasus_t *pegasus;
+	int dev_index = id - pegasus_ids;
+	int res = -ENOMEM;
+
+	if (pegasus_blacklisted(dev))
+		return -ENODEV;
+
+	if (pegasus_count == 0) {
+		pegasus_workqueue = create_singlethread_workqueue("pegasus");
+		if (!pegasus_workqueue)
+			return -ENOMEM;
+	}
+	pegasus_count++;
+
+	usb_get_dev(dev);
+
+	net = alloc_etherdev(sizeof(struct pegasus));
+	if (!net)
+		goto out;
+
+	pegasus = netdev_priv(net);
+	pegasus->dev_index = dev_index;
+	init_waitqueue_head(&pegasus->ctrl_wait);
+
+	if (!alloc_urbs(pegasus)) {
+		dev_err(&intf->dev, "can't allocate %s\n", "urbs");
+		goto out1;
+	}
+
+	tasklet_init(&pegasus->rx_tl, rx_fixup, (unsigned long) pegasus);
+
+	INIT_DELAYED_WORK(&pegasus->carrier_check, check_carrier);
+
+	pegasus->intf = intf;
+	pegasus->usb = dev;
+	pegasus->net = net;
+
+
+	net->watchdog_timeo = PEGASUS_TX_TIMEOUT;
+	net->netdev_ops = &pegasus_netdev_ops;
+	SET_ETHTOOL_OPS(net, &ops);
+	pegasus->mii.dev = net;
+	pegasus->mii.mdio_read = mdio_read;
+	pegasus->mii.mdio_write = mdio_write;
+	pegasus->mii.phy_id_mask = 0x1f;
+	pegasus->mii.reg_num_mask = 0x1f;
+	spin_lock_init(&pegasus->rx_pool_lock);
+	pegasus->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV
+				| NETIF_MSG_PROBE | NETIF_MSG_LINK);
+
+	pegasus->features = usb_dev_id[dev_index].private;
+	get_interrupt_interval(pegasus);
+	if (reset_mac(pegasus)) {
+		dev_err(&intf->dev, "can't reset MAC\n");
+		res = -EIO;
+		goto out2;
+	}
+	set_ethernet_addr(pegasus);
+	fill_skb_pool(pegasus);
+	if (pegasus->features & PEGASUS_II) {
+		dev_info(&intf->dev, "setup Pegasus II specific registers\n");
+		setup_pegasus_II(pegasus);
+	}
+	pegasus->phy = mii_phy_probe(pegasus);
+	if (pegasus->phy == 0xff) {
+		dev_warn(&intf->dev, "can't locate MII phy, using default\n");
+		pegasus->phy = 1;
+	}
+	pegasus->mii.phy_id = pegasus->phy;
+	usb_set_intfdata(intf, pegasus);
+	SET_NETDEV_DEV(net, &intf->dev);
+	pegasus_reset_wol(net);
+	res = register_netdev(net);
+	if (res)
+		goto out3;
+	queue_delayed_work(pegasus_workqueue, &pegasus->carrier_check,
+				CARRIER_CHECK_DELAY);
+
+	dev_info(&intf->dev, "%s, %s, %pM\n",
+		 net->name,
+		 usb_dev_id[dev_index].name,
+		 net->dev_addr);
+	return 0;
+
+out3:
+	usb_set_intfdata(intf, NULL);
+	free_skb_pool(pegasus);
+out2:
+	free_all_urbs(pegasus);
+out1:
+	free_netdev(net);
+out:
+	usb_put_dev(dev);
+	pegasus_dec_workqueue();
+	return res;
+}
+
+static void pegasus_disconnect(struct usb_interface *intf)
+{
+	struct pegasus *pegasus = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (!pegasus) {
+		dev_dbg(&intf->dev, "unregistering non-bound device?\n");
+		return;
+	}
+
+	pegasus->flags |= PEGASUS_UNPLUG;
+	cancel_delayed_work(&pegasus->carrier_check);
+	unregister_netdev(pegasus->net);
+	usb_put_dev(interface_to_usbdev(intf));
+	unlink_all_urbs(pegasus);
+	free_all_urbs(pegasus);
+	free_skb_pool(pegasus);
+	if (pegasus->rx_skb != NULL) {
+		dev_kfree_skb(pegasus->rx_skb);
+		pegasus->rx_skb = NULL;
+	}
+	free_netdev(pegasus->net);
+	pegasus_dec_workqueue();
+}
+
+static int pegasus_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct pegasus *pegasus = usb_get_intfdata(intf);
+
+	netif_device_detach(pegasus->net);
+	cancel_delayed_work(&pegasus->carrier_check);
+	if (netif_running(pegasus->net)) {
+		usb_kill_urb(pegasus->rx_urb);
+		usb_kill_urb(pegasus->intr_urb);
+	}
+	return 0;
+}
+
+static int pegasus_resume(struct usb_interface *intf)
+{
+	struct pegasus *pegasus = usb_get_intfdata(intf);
+
+	netif_device_attach(pegasus->net);
+	if (netif_running(pegasus->net)) {
+		pegasus->rx_urb->status = 0;
+		pegasus->rx_urb->actual_length = 0;
+		read_bulk_callback(pegasus->rx_urb);
+
+		pegasus->intr_urb->status = 0;
+		pegasus->intr_urb->actual_length = 0;
+		intr_callback(pegasus->intr_urb);
+	}
+	queue_delayed_work(pegasus_workqueue, &pegasus->carrier_check,
+				CARRIER_CHECK_DELAY);
+	return 0;
+}
+
+static const struct net_device_ops pegasus_netdev_ops = {
+	.ndo_open =			pegasus_open,
+	.ndo_stop =			pegasus_close,
+	.ndo_do_ioctl =			pegasus_ioctl,
+	.ndo_start_xmit =		pegasus_start_xmit,
+	.ndo_set_rx_mode =		pegasus_set_multicast,
+	.ndo_get_stats =		pegasus_netdev_stats,
+	.ndo_tx_timeout =		pegasus_tx_timeout,
+	.ndo_change_mtu =		eth_change_mtu,
+	.ndo_set_mac_address =		eth_mac_addr,
+	.ndo_validate_addr =		eth_validate_addr,
+};
+
+static struct usb_driver pegasus_driver = {
+	.name = driver_name,
+	.probe = pegasus_probe,
+	.disconnect = pegasus_disconnect,
+	.id_table = pegasus_ids,
+	.suspend = pegasus_suspend,
+	.resume = pegasus_resume,
+};
+
+static void __init parse_id(char *id)
+{
+	unsigned int vendor_id = 0, device_id = 0, flags = 0, i = 0;
+	char *token, *name = NULL;
+
+	if ((token = strsep(&id, ":")) != NULL)
+		name = token;
+	/* name now points to a null terminated string*/
+	if ((token = strsep(&id, ":")) != NULL)
+		vendor_id = simple_strtoul(token, NULL, 16);
+	if ((token = strsep(&id, ":")) != NULL)
+		device_id = simple_strtoul(token, NULL, 16);
+	flags = simple_strtoul(id, NULL, 16);
+	pr_info("%s: new device %s, vendor ID 0x%04x, device ID 0x%04x, flags: 0x%x\n",
+		driver_name, name, vendor_id, device_id, flags);
+
+	if (vendor_id > 0x10000 || vendor_id == 0)
+		return;
+	if (device_id > 0x10000 || device_id == 0)
+		return;
+
+	for (i = 0; usb_dev_id[i].name; i++);
+	usb_dev_id[i].name = name;
+	usb_dev_id[i].vendor = vendor_id;
+	usb_dev_id[i].device = device_id;
+	usb_dev_id[i].private = flags;
+	pegasus_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE;
+	pegasus_ids[i].idVendor = vendor_id;
+	pegasus_ids[i].idProduct = device_id;
+}
+
+static int __init pegasus_init(void)
+{
+	pr_info("%s: %s, " DRIVER_DESC "\n", driver_name, DRIVER_VERSION);
+	if (devid)
+		parse_id(devid);
+	return usb_register(&pegasus_driver);
+}
+
+static void __exit pegasus_exit(void)
+{
+	usb_deregister(&pegasus_driver);
+}
+
+module_init(pegasus_init);
+module_exit(pegasus_exit);
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/pegasus.h b/ap/os/linux/linux-3.4.x/drivers/net/usb/pegasus.h
new file mode 100644
index 0000000..65b78b3
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/pegasus.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 1999-2003 Petko Manolov - Petkan (petkan@users.sourceforge.net)
+ *
+ * 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.
+ */
+
+
+#ifndef	PEGASUS_DEV
+
+#define	PEGASUS_II		0x80000000
+#define	HAS_HOME_PNA		0x40000000
+
+#define	PEGASUS_MTU		1536
+#define	RX_SKBS			4
+
+#define	EPROM_WRITE		0x01
+#define	EPROM_READ		0x02
+#define	EPROM_DONE		0x04
+#define	EPROM_WR_ENABLE		0x10
+#define	EPROM_LOAD		0x20
+
+#define	PHY_DONE		0x80
+#define	PHY_READ		0x40
+#define	PHY_WRITE		0x20
+#define	DEFAULT_GPIO_RESET	0x24
+#define	DEFAULT_GPIO_SET	0x26
+
+#define	PEGASUS_PRESENT		0x00000001
+#define	PEGASUS_TX_BUSY		0x00000004
+#define	PEGASUS_RX_BUSY		0x00000008
+#define	CTRL_URB_RUNNING	0x00000010
+#define	CTRL_URB_SLEEP		0x00000020
+#define	PEGASUS_UNPLUG		0x00000040
+#define	PEGASUS_RX_URB_FAIL	0x00000080
+#define	ETH_REGS_CHANGE		0x40000000
+#define	ETH_REGS_CHANGED	0x80000000
+
+#define	RX_MULTICAST		2
+#define	RX_PROMISCUOUS		4
+
+#define	REG_TIMEOUT		(HZ)
+#define	PEGASUS_TX_TIMEOUT	(HZ*10)
+
+#define	TX_UNDERRUN		0x80
+#define	EXCESSIVE_COL		0x40
+#define	LATE_COL		0x20
+#define	NO_CARRIER		0x10
+#define	LOSS_CARRIER		0x08
+#define	JABBER_TIMEOUT		0x04
+
+#define	LINK_STATUS		0x01
+
+#define	PEGASUS_REQT_READ	0xc0
+#define	PEGASUS_REQT_WRITE	0x40
+#define	PEGASUS_REQ_GET_REGS	0xf0
+#define	PEGASUS_REQ_SET_REGS	0xf1
+#define	PEGASUS_REQ_SET_REG	PEGASUS_REQ_SET_REGS
+
+enum pegasus_registers {
+	EthCtrl0 = 0,
+	EthCtrl1 = 1,
+	EthCtrl2 = 2,
+	EthID = 0x10,
+	Reg1d = 0x1d,
+	EpromOffset = 0x20,
+	EpromData = 0x21,	/* 0x21 low, 0x22 high byte */
+	EpromCtrl = 0x23,
+	PhyAddr = 0x25,
+	PhyData = 0x26,		/* 0x26 low, 0x27 high byte */
+	PhyCtrl = 0x28,
+	UsbStst = 0x2a,
+	EthTxStat0 = 0x2b,
+	EthTxStat1 = 0x2c,
+	EthRxStat = 0x2d,
+	WakeupControl = 0x78,
+	Reg7b = 0x7b,
+	Gpio0 = 0x7e,
+	Gpio1 = 0x7f,
+	Reg81 = 0x81,
+};
+
+
+typedef struct pegasus {
+	struct usb_device	*usb;
+	struct usb_interface	*intf;
+	struct net_device	*net;
+	struct net_device_stats	stats;
+	struct mii_if_info	mii;
+	unsigned		flags;
+	unsigned		features;
+	u32			msg_enable;
+	u32			wolopts;
+	int			dev_index;
+	int			intr_interval;
+	struct tasklet_struct	rx_tl;
+	struct delayed_work	carrier_check;
+	struct urb		*ctrl_urb, *rx_urb, *tx_urb, *intr_urb;
+	struct sk_buff		*rx_pool[RX_SKBS];
+	struct sk_buff		*rx_skb;
+	struct usb_ctrlrequest	dr;
+	wait_queue_head_t	ctrl_wait;
+	spinlock_t		rx_pool_lock;
+	int			chip;
+	unsigned char		intr_buff[8];
+	__u8			tx_buff[PEGASUS_MTU];
+	__u8			eth_regs[4];
+	__u8			phy;
+	__u8			gpio_res;
+} pegasus_t;
+
+
+struct usb_eth_dev {
+	char	*name;
+	__u16	vendor;
+	__u16	device;
+	__u32	private; /* LSB is gpio reset value */
+};
+
+#define	VENDOR_3COM		0x0506
+#define	VENDOR_ABOCOM		0x07b8
+#define	VENDOR_ACCTON		0x083a
+#define	VENDOR_ADMTEK		0x07a6
+#define	VENDOR_AEILAB		0x3334
+#define	VENDOR_ALLIEDTEL	0x07c9
+#define	VENDOR_ATEN		0x0557
+#define	VENDOR_BELKIN		0x050d
+#define	VENDOR_BILLIONTON	0x08dd
+#define	VENDOR_COMPAQ		0x049f
+#define	VENDOR_COREGA		0x07aa
+#define	VENDOR_DLINK		0x2001
+#define	VENDOR_ELCON		0x0db7
+#define	VENDOR_ELECOM		0x056e
+#define	VENDOR_ELSA		0x05cc
+#define	VENDOR_GIGABYTE		0x1044
+#define	VENDOR_HAWKING		0x0e66
+#define	VENDOR_HP		0x03f0
+#define	VENDOR_IODATA		0x04bb
+#define	VENDOR_KINGSTON		0x0951
+#define	VENDOR_LANEED		0x056e
+#define	VENDOR_LINKSYS		0x066b
+#define	VENDOR_LINKSYS2		0x077b
+#define	VENDOR_MELCO		0x0411
+#define	VENDOR_MICROSOFT	0x045e
+#define	VENDOR_MOBILITY		0x1342
+#define	VENDOR_NETGEAR		0x0846
+#define	VENDOR_OCT		0x0b39
+#define	VENDOR_SMARTBRIDGES	0x08d1
+#define	VENDOR_SMC		0x0707
+#define	VENDOR_SOHOWARE		0x15e8
+#define	VENDOR_SIEMENS		0x067c
+
+
+#else	/* PEGASUS_DEV */
+
+PEGASUS_DEV("3Com USB Ethernet 3C460B", VENDOR_3COM, 0x4601,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("ATEN USB Ethernet UC-110T", VENDOR_ATEN, 0x2007,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("USB HPNA/Ethernet", VENDOR_ABOCOM, 0x110c,
+		DEFAULT_GPIO_RESET | PEGASUS_II | HAS_HOME_PNA)
+PEGASUS_DEV("USB HPNA/Ethernet", VENDOR_ABOCOM, 0x4104,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("USB HPNA/Ethernet", VENDOR_ABOCOM, 0x4004,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("USB HPNA/Ethernet", VENDOR_ABOCOM, 0x4007,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("USB 10/100 Fast Ethernet", VENDOR_ABOCOM, 0x4102,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("USB 10/100 Fast Ethernet", VENDOR_ABOCOM, 0x4002,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("USB 10/100 Fast Ethernet", VENDOR_ABOCOM, 0x400b,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("USB 10/100 Fast Ethernet", VENDOR_ABOCOM, 0x400c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("USB 10/100 Fast Ethernet", VENDOR_ABOCOM, 0xabc1,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("USB 10/100 Fast Ethernet", VENDOR_ABOCOM, 0x200c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Accton USB 10/100 Ethernet Adapter", VENDOR_ACCTON, 0x1046,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("SpeedStream USB 10/100 Ethernet", VENDOR_ACCTON, 0x5046,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Philips USB 10/100 Ethernet", VENDOR_ACCTON, 0xb004,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("ADMtek ADM8511 \"Pegasus II\" USB Ethernet",
+		VENDOR_ADMTEK, 0x8511,
+		DEFAULT_GPIO_RESET | PEGASUS_II | HAS_HOME_PNA)
+PEGASUS_DEV("ADMtek ADM8513 \"Pegasus II\" USB Ethernet",
+		VENDOR_ADMTEK, 0x8513,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("ADMtek ADM8515 \"Pegasus II\" USB-2.0 Ethernet",
+		VENDOR_ADMTEK, 0x8515,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("ADMtek AN986 \"Pegasus\" USB Ethernet (evaluation board)",
+		VENDOR_ADMTEK, 0x0986,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("AN986A USB MAC", VENDOR_ADMTEK, 1986,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("AEI USB Fast Ethernet Adapter", VENDOR_AEILAB, 0x1701,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Allied Telesyn Int. AT-USB100", VENDOR_ALLIEDTEL, 0xb100,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+/*
+ * Distinguish between this Belkin adaptor and the Belkin bluetooth adaptors
+ * with the same product IDs by checking the device class too.
+ */
+PEGASUS_DEV_CLASS("Belkin F5D5050 USB Ethernet", VENDOR_BELKIN, 0x0121, 0x00,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Belkin F5U122 10/100 USB Ethernet", VENDOR_BELKIN, 0x0122,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Billionton USB-100", VENDOR_BILLIONTON, 0x0986,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Billionton USBLP-100", VENDOR_BILLIONTON, 0x0987,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("iPAQ Networking 10/100 USB", VENDOR_COMPAQ, 0x8511,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Billionton USBEL-100", VENDOR_BILLIONTON, 0x0988,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Billionton USBE-100", VENDOR_BILLIONTON, 0x8511,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Corega FEther USB-TX", VENDOR_COREGA, 0x0004,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Corega FEther USB-TXS", VENDOR_COREGA, 0x000d,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("D-Link DSB-650TX", VENDOR_DLINK, 0x4001,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("D-Link DSB-650TX", VENDOR_DLINK, 0x4002,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("D-Link DSB-650TX", VENDOR_DLINK, 0x4102,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("D-Link DSB-650TX", VENDOR_DLINK, 0x400b,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("D-Link DSB-650TX", VENDOR_DLINK, 0x200c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("D-Link DSB-650TX(PNA)", VENDOR_DLINK, 0x4003,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("D-Link DSB-650", VENDOR_DLINK, 0xabc1,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("GOLDPFEIL USB Adapter", VENDOR_ELCON, 0x0002,
+		DEFAULT_GPIO_RESET | PEGASUS_II | HAS_HOME_PNA)
+PEGASUS_DEV("ELECOM USB Ethernet LD-USB20", VENDOR_ELECOM,  0x4010,
+		DEFAULT_GPIO_RESET  | PEGASUS_II)
+PEGASUS_DEV("EasiDock Ethernet", VENDOR_MOBILITY, 0x0304,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Elsa Micolink USB2Ethernet", VENDOR_ELSA, 0x3000,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("GIGABYTE GN-BR402W Wireless Router", VENDOR_GIGABYTE, 0x8002,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Hawking UF100 10/100 Ethernet", VENDOR_HAWKING, 0x400c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("HP hn210c Ethernet USB", VENDOR_HP, 0x811c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+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, 0x093a,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Kingston KNU101TX Ethernet", VENDOR_KINGSTON, 0x000a,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("LANEED USB Ethernet LD-USB/TX", VENDOR_LANEED, 0x4002,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("LANEED USB Ethernet LD-USBL/TX", VENDOR_LANEED, 0x4005,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("LANEED USB Ethernet LD-USB/TX", VENDOR_LANEED, 0x400b,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("LANEED USB Ethernet LD-USB/T", VENDOR_LANEED, 0xabc1,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("LANEED USB Ethernet LD-USB/TX", VENDOR_LANEED, 0x200c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Linksys USB10TX", VENDOR_LINKSYS, 0x2202,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Linksys USB100TX", VENDOR_LINKSYS, 0x2203,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Linksys USB100TX", VENDOR_LINKSYS, 0x2204,
+		DEFAULT_GPIO_RESET | HAS_HOME_PNA)
+PEGASUS_DEV("Linksys USB10T Ethernet Adapter", VENDOR_LINKSYS, 0x2206,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Linksys USBVPN1", VENDOR_LINKSYS2, 0x08b4,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("Linksys USB USB100TX", VENDOR_LINKSYS, 0x400b,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Linksys USB10TX", VENDOR_LINKSYS, 0x200c,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("MELCO/BUFFALO LUA-TX", VENDOR_MELCO, 0x0001,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("MELCO/BUFFALO LUA-TX", VENDOR_MELCO, 0x0005,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("MELCO/BUFFALO LUA2-TX", VENDOR_MELCO, 0x0009,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("Microsoft MN-110", VENDOR_MICROSOFT, 0x007a,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("NETGEAR FA101", VENDOR_NETGEAR, 0x1020,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("OCT Inc.", VENDOR_OCT, 0x0109,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("OCT USB TO Ethernet", VENDOR_OCT, 0x0901,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("smartNIC 2 PnP Adapter", VENDOR_SMARTBRIDGES, 0x0003,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("SMC 202 USB Ethernet", VENDOR_SMC, 0x0200,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("SMC 2206 USB Ethernet", VENDOR_SMC, 0x0201,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("SOHOware NUB100 Ethernet", VENDOR_SOHOWARE, 0x9100,
+		DEFAULT_GPIO_RESET)
+PEGASUS_DEV("SOHOware NUB110 Ethernet", VENDOR_SOHOWARE, 0x9110,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+PEGASUS_DEV("SpeedStream USB 10/100 Ethernet", VENDOR_SIEMENS, 0x1001,
+		DEFAULT_GPIO_RESET | PEGASUS_II)
+
+
+#endif	/* PEGASUS_DEV */
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/plusb.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/plusb.c
new file mode 100644
index 0000000..b2b035e
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/plusb.c
@@ -0,0 +1,161 @@
+/*
+ * PL-2301/2302 USB host-to-host link cables
+ * Copyright (C) 2000-2005 by David Brownell
+ *
+ * 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	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+
+
+/*
+ * Prolific PL-2301/PL-2302 driver ... http://www.prolific.com.tw/ 
+ *
+ * The protocol and handshaking used here should be bug-compatible
+ * with the Linux 2.2 "plusb" driver, by Deti Fliegl.
+ *
+ * HEADS UP:  this handshaking isn't all that robust.  This driver
+ * gets confused easily if you unplug one end of the cable then
+ * try to connect it again; you'll need to restart both ends. The
+ * "naplink" software (used by some PlayStation/2 deveopers) does
+ * the handshaking much better!   Also, sometimes this hardware
+ * seems to get wedged under load.  Prolific docs are weak, and
+ * don't identify differences between PL2301 and PL2302, much less
+ * anything to explain the different PL2302 versions observed.
+ *
+ * NOTE:  pl2501 has several modes, including pl2301 and pl2302
+ * compatibility.   Some docs suggest the difference between 2301
+ * and 2302 is only to make MS-Windows use a different driver...
+ *
+ * pl25a1 glue based on patch from Tony Gibbs.  Prolific "docs" on
+ * this chip are as usual incomplete about what control messages
+ * are supported.
+ */
+
+/*
+ * Bits 0-4 can be used for software handshaking; they're set from
+ * one end, cleared from the other, "read" with the interrupt byte.
+ */
+#define	PL_S_EN		(1<<7)		/* (feature only) suspend enable */
+/* reserved bit -- rx ready (6) ? */
+#define	PL_TX_READY	(1<<5)		/* (interrupt only) transmit ready */
+#define	PL_RESET_OUT	(1<<4)		/* reset output pipe */
+#define	PL_RESET_IN	(1<<3)		/* reset input pipe */
+#define	PL_TX_C		(1<<2)		/* transmission complete */
+#define	PL_TX_REQ	(1<<1)		/* transmission received */
+#define	PL_PEER_E	(1<<0)		/* peer exists */
+
+static inline int
+pl_vendor_req(struct usbnet *dev, u8 req, u8 val, u8 index)
+{
+	return usb_control_msg(dev->udev,
+		usb_rcvctrlpipe(dev->udev, 0),
+		req,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		val, index,
+		NULL, 0,
+		USB_CTRL_GET_TIMEOUT);
+}
+
+static inline int
+pl_clear_QuickLink_features(struct usbnet *dev, int val)
+{
+	return pl_vendor_req(dev, 1, (u8) val, 0);
+}
+
+static inline int
+pl_set_QuickLink_features(struct usbnet *dev, int val)
+{
+	return pl_vendor_req(dev, 3, (u8) val, 0);
+}
+
+static int pl_reset(struct usbnet *dev)
+{
+	int status;
+
+	/* some units seem to need this reset, others reject it utterly.
+	 * FIXME be more like "naplink" or windows drivers.
+	 */
+	status = pl_set_QuickLink_features(dev,
+		PL_S_EN|PL_RESET_OUT|PL_RESET_IN|PL_PEER_E);
+	if (status != 0 && netif_msg_probe(dev))
+		netif_dbg(dev, link, dev->net, "pl_reset --> %d\n", status);
+	return 0;
+}
+
+static const struct driver_info	prolific_info = {
+	.description =	"Prolific PL-2301/PL-2302/PL-25A1",
+	.flags =	FLAG_POINTTOPOINT | FLAG_NO_SETINT,
+		/* some PL-2302 versions seem to fail usb_set_interface() */
+	.reset =	pl_reset,
+};
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Proilific's name won't normally be on the cables, and
+ * may not be on the device.
+ */
+
+static const struct usb_device_id	products [] = {
+
+/* full speed cables */
+{
+	USB_DEVICE(0x067b, 0x0000),	// PL-2301
+	.driver_info =	(unsigned long) &prolific_info,
+}, {
+	USB_DEVICE(0x067b, 0x0001),	// PL-2302
+	.driver_info =	(unsigned long) &prolific_info,
+},
+
+/* high speed cables */
+{
+	USB_DEVICE(0x067b, 0x25a1),     /* PL-25A1, no eeprom */
+	.driver_info =  (unsigned long) &prolific_info,
+}, {
+	USB_DEVICE(0x050d, 0x258a),     /* Belkin F5U258/F5U279 (PL-25A1) */
+	.driver_info =  (unsigned long) &prolific_info,
+},
+
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver plusb_driver = {
+	.name =		"plusb",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+module_usb_driver(plusb_driver);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("Prolific PL-2301/2302/25A1 USB Host to Host Link Driver");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/qmi_wwan.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/qmi_wwan.c
new file mode 100644
index 0000000..326defa
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/qmi_wwan.c
@@ -0,0 +1,759 @@
+/*
+ * Copyright (c) 2012  Bjørn Mork <bjorn@mork.no>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+#include <linux/etherdevice.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/usb/cdc-wdm.h>
+
+/* The name of the CDC Device Management driver */
+#define DM_DRIVER "cdc_wdm"
+
+/*
+ * This driver supports wwan (3G/LTE/?) devices using a vendor
+ * specific management protocol called Qualcomm MSM Interface (QMI) -
+ * in addition to the more common AT commands over serial interface
+ * management
+ *
+ * QMI is wrapped in CDC, using CDC encapsulated commands on the
+ * control ("master") interface of a two-interface CDC Union
+ * resembling standard CDC ECM.  The devices do not use the control
+ * interface for any other CDC messages.  Most likely because the
+ * management protocol is used in place of the standard CDC
+ * notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE
+ *
+ * Handling a protocol like QMI is out of the scope for any driver.
+ * It can be exported as a character device using the cdc-wdm driver,
+ * which will enable userspace applications ("modem managers") to
+ * handle it.  This may be required to use the network interface
+ * provided by the driver.
+ *
+ * These devices may alternatively/additionally be configured using AT
+ * commands on any of the serial interfaces driven by the option driver
+ *
+ * This driver binds only to the data ("slave") interface to enable
+ * the cdc-wdm driver to bind to the control interface.  It still
+ * parses the CDC functional descriptors on the control interface to
+ *  a) verify that this is indeed a handled interface (CDC Union
+ *     header lists it as slave)
+ *  b) get MAC address and other ethernet config from the CDC Ethernet
+ *     header
+ *  c) enable user bind requests against the control interface, which
+ *     is the common way to bind to CDC Ethernet Control Model type
+ *     interfaces
+ *  d) provide a hint to the user about which interface is the
+ *     corresponding management interface
+ */
+
+static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	int status = -1;
+	struct usb_interface *control = NULL;
+	u8 *buf = intf->cur_altsetting->extra;
+	int len = intf->cur_altsetting->extralen;
+	struct usb_interface_descriptor *desc = &intf->cur_altsetting->desc;
+	struct usb_cdc_union_desc *cdc_union = NULL;
+	struct usb_cdc_ether_desc *cdc_ether = NULL;
+	u32 required = 1 << USB_CDC_HEADER_TYPE | 1 << USB_CDC_UNION_TYPE;
+	u32 found = 0;
+	atomic_t *pmcount = (void *)&dev->data[1];
+
+	atomic_set(pmcount, 0);
+
+	/*
+	 * assume a data interface has no additional descriptors and
+	 * that the control and data interface are numbered
+	 * consecutively - this holds for the Huawei device at least
+	 */
+	if (len == 0 && desc->bInterfaceNumber > 0) {
+		control = usb_ifnum_to_if(dev->udev, desc->bInterfaceNumber - 1);
+		if (!control)
+			goto err;
+
+		buf = control->cur_altsetting->extra;
+		len = control->cur_altsetting->extralen;
+		dev_dbg(&intf->dev, "guessing \"control\" => %s, \"data\" => this\n",
+			dev_name(&control->dev));
+	}
+
+	while (len > 3) {
+		struct usb_descriptor_header *h = (void *)buf;
+
+		/* ignore any misplaced descriptors */
+		if (h->bDescriptorType != USB_DT_CS_INTERFACE)
+			goto next_desc;
+
+		/* buf[2] is CDC descriptor subtype */
+		switch (buf[2]) {
+		case USB_CDC_HEADER_TYPE:
+			if (found & 1 << USB_CDC_HEADER_TYPE) {
+				dev_dbg(&intf->dev, "extra CDC header\n");
+				goto err;
+			}
+			if (h->bLength != sizeof(struct usb_cdc_header_desc)) {
+				dev_dbg(&intf->dev, "CDC header len %u\n", h->bLength);
+				goto err;
+			}
+			break;
+		case USB_CDC_UNION_TYPE:
+			if (found & 1 << USB_CDC_UNION_TYPE) {
+				dev_dbg(&intf->dev, "extra CDC union\n");
+				goto err;
+			}
+			if (h->bLength != sizeof(struct usb_cdc_union_desc)) {
+				dev_dbg(&intf->dev, "CDC union len %u\n", h->bLength);
+				goto err;
+			}
+			cdc_union = (struct usb_cdc_union_desc *)buf;
+			break;
+		case USB_CDC_ETHERNET_TYPE:
+			if (found & 1 << USB_CDC_ETHERNET_TYPE) {
+				dev_dbg(&intf->dev, "extra CDC ether\n");
+				goto err;
+			}
+			if (h->bLength != sizeof(struct usb_cdc_ether_desc)) {
+				dev_dbg(&intf->dev, "CDC ether len %u\n",  h->bLength);
+				goto err;
+			}
+			cdc_ether = (struct usb_cdc_ether_desc *)buf;
+			break;
+		}
+
+		/*
+		 * Remember which CDC functional descriptors we've seen.  Works
+		 * for all types we care about, of which USB_CDC_ETHERNET_TYPE
+		 * (0x0f) is the highest numbered
+		 */
+		if (buf[2] < 32)
+			found |= 1 << buf[2];
+
+next_desc:
+		len -= h->bLength;
+		buf += h->bLength;
+	}
+
+	/* did we find all the required ones? */
+	if ((found & required) != required) {
+		dev_err(&intf->dev, "CDC functional descriptors missing\n");
+		goto err;
+	}
+
+	/* give the user a helpful hint if trying to bind to the wrong interface */
+	if (cdc_union && desc->bInterfaceNumber == cdc_union->bMasterInterface0) {
+		dev_err(&intf->dev, "leaving \"control\" interface for " DM_DRIVER " - try binding to %s instead!\n",
+			dev_name(&usb_ifnum_to_if(dev->udev, cdc_union->bSlaveInterface0)->dev));
+		goto err;
+	}
+
+	/* errors aren't fatal - we can live with the dynamic address */
+	if (cdc_ether  && cdc_ether->wMaxSegmentSize) {
+		dev->hard_mtu = le16_to_cpu(cdc_ether->wMaxSegmentSize);
+		usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress);
+	}
+
+	/* success! point the user to the management interface */
+	if (control)
+		dev_info(&intf->dev, "Use \"" DM_DRIVER "\" for QMI interface %s\n",
+			dev_name(&control->dev));
+
+	/* XXX: add a sysfs symlink somewhere to help management applications find it? */
+
+	/* collect bulk endpoints now that we know intf == "data" interface */
+	status = usbnet_get_endpoints(dev, intf);
+
+err:
+	return status;
+}
+
+/* default ethernet address used by the modem */
+static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3};
+
+/* Make up an ethernet header if the packet doesn't have one.
+ *
+ * A firmware bug common among several devices cause them to send raw
+ * IP packets under some circumstances.  There is no way for the
+ * driver/host to know when this will happen.  And even when the bug
+ * hits, some packets will still arrive with an intact header.
+ *
+ * The supported devices are only capably of sending IPv4, IPv6 and
+ * ARP packets on a point-to-point link. Any packet with an ethernet
+ * header will have either our address or a broadcast/multicast
+ * address as destination.  ARP packets will always have a header.
+ *
+ * This means that this function will reliably add the appropriate
+ * header iff necessary, provided our hardware address does not start
+ * with 4 or 6.
+ *
+ * Another common firmware bug results in all packets being addressed
+ * to 00:a0:c6:00:00:00 despite the host address being different.
+ * This function will also fixup such packets.
+ */
+static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	__be16 proto;
+
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len)
+		return 0;
+
+	switch (skb->data[0] & 0xf0) {
+	case 0x40:
+		proto = htons(ETH_P_IP);
+		break;
+	case 0x60:
+		proto = htons(ETH_P_IPV6);
+		break;
+	case 0x00:
+		if (is_multicast_ether_addr(skb->data))
+			return 1;
+		/* possibly bogus destination - rewrite just in case */
+		skb_reset_mac_header(skb);
+		goto fix_dest;
+	default:
+		/* pass along other packets without modifications */
+		return 1;
+	}
+	if (skb_headroom(skb) < ETH_HLEN)
+		return 0;
+	skb_push(skb, ETH_HLEN);
+	skb_reset_mac_header(skb);
+	eth_hdr(skb)->h_proto = proto;
+	memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
+fix_dest:
+	memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
+	return 1;
+}
+
+/* very simplistic detection of IPv4 or IPv6 headers */
+static bool possibly_iphdr(const char *data)
+{
+	return (data[0] & 0xd0) == 0x40;
+}
+
+/* disallow addresses which may be confused with IP headers */
+static int qmi_wwan_mac_addr(struct net_device *dev, void *p)
+{
+	struct sockaddr *addr = p;
+
+	if (!is_valid_ether_addr(addr->sa_data) ||
+	    possibly_iphdr(addr->sa_data))
+		return -EADDRNOTAVAIL;
+	memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
+	return 0;
+}
+
+static const struct net_device_ops qmi_wwan_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_set_mac_address	= qmi_wwan_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+/* using a counter to merge subdriver requests with our own into a combined state */
+static int qmi_wwan_manage_power(struct usbnet *dev, int on)
+{
+	atomic_t *pmcount = (void *)&dev->data[1];
+	int rv = 0;
+
+	dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(pmcount), on);
+
+	if ((on && atomic_add_return(1, pmcount) == 1) || (!on && atomic_dec_and_test(pmcount))) {
+		/* need autopm_get/put here to ensure the usbcore sees the new value */
+		rv = usb_autopm_get_interface(dev->intf);
+		if (rv < 0)
+			goto err;
+		dev->intf->needs_remote_wakeup = on;
+		usb_autopm_put_interface(dev->intf);
+	}
+err:
+	return rv;
+}
+
+static int qmi_wwan_cdc_wdm_manage_power(struct usb_interface *intf, int on)
+{
+	struct usbnet *dev = usb_get_intfdata(intf);
+
+	/* can be called while disconnecting */
+	if (!dev)
+		return 0;
+	return qmi_wwan_manage_power(dev, on);
+}
+
+/* Some devices combine the "control" and "data" functions into a
+ * single interface with all three endpoints: interrupt + bulk in and
+ * out
+ *
+ * Setting up cdc-wdm as a subdriver owning the interrupt endpoint
+ * will let it provide userspace access to the encapsulated QMI
+ * protocol without interfering with the usbnet operations.
+  */
+static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf)
+{
+	int rv;
+	struct usb_driver *subdriver = NULL;
+	atomic_t *pmcount = (void *)&dev->data[1];
+
+	/* ZTE makes devices where the interface descriptors and endpoint
+	 * configurations of two or more interfaces are identical, even
+	 * though the functions are completely different.  If set, then
+	 * driver_info->data is a bitmap of acceptable interface numbers
+	 * allowing us to bind to one such interface without binding to
+	 * all of them
+	 */
+	if (dev->driver_info->data &&
+	    !test_bit(intf->cur_altsetting->desc.bInterfaceNumber, &dev->driver_info->data)) {
+		dev_info(&intf->dev, "not on our whitelist - ignored");
+		rv = -ENODEV;
+		goto err;
+	}
+
+	atomic_set(pmcount, 0);
+
+	/* collect all three endpoints */
+	rv = usbnet_get_endpoints(dev, intf);
+	if (rv < 0)
+		goto err;
+
+	/* require interrupt endpoint for subdriver */
+	if (!dev->status) {
+		rv = -EINVAL;
+		goto err;
+	}
+
+	subdriver = usb_cdc_wdm_register(intf, &dev->status->desc, 512, &qmi_wwan_cdc_wdm_manage_power);
+	if (IS_ERR(subdriver)) {
+		rv = PTR_ERR(subdriver);
+		goto err;
+	}
+
+	/* can't let usbnet use the interrupt endpoint */
+	dev->status = NULL;
+
+	/* save subdriver struct for suspend/resume wrappers */
+	dev->data[0] = (unsigned long)subdriver;
+
+	/* Never use the same address on both ends of the link, even
+	 * if the buggy firmware told us to.
+	 */
+	if (!compare_ether_addr(dev->net->dev_addr, default_modem_addr))
+		eth_hw_addr_random(dev->net);
+
+	/* make MAC addr easily distinguishable from an IP header */
+	if (possibly_iphdr(dev->net->dev_addr)) {
+		dev->net->dev_addr[0] |= 0x02;	/* set local assignment bit */
+		dev->net->dev_addr[0] &= 0xbf;	/* clear "IP" bit */
+	}
+	dev->net->netdev_ops = &qmi_wwan_netdev_ops;
+err:
+	return rv;
+}
+
+static void qmi_wwan_unbind_shared(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct usb_driver *subdriver = (void *)dev->data[0];
+
+	if (subdriver && subdriver->disconnect)
+		subdriver->disconnect(intf);
+
+	dev->data[0] = (unsigned long)NULL;
+}
+
+/* suspend/resume wrappers calling both usbnet and the cdc-wdm
+ * subdriver if present.
+ *
+ * NOTE: cdc-wdm also supports pre/post_reset, but we cannot provide
+ * wrappers for those without adding usbnet reset support first.
+ */
+static int qmi_wwan_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct usbnet *dev = usb_get_intfdata(intf);
+	struct usb_driver *subdriver = (void *)dev->data[0];
+	int ret;
+
+	ret = usbnet_suspend(intf, message);
+	if (ret < 0)
+		goto err;
+
+	if (subdriver && subdriver->suspend)
+		ret = subdriver->suspend(intf, message);
+	if (ret < 0)
+		usbnet_resume(intf);
+err:
+	return ret;
+}
+
+static int qmi_wwan_resume(struct usb_interface *intf)
+{
+	struct usbnet *dev = usb_get_intfdata(intf);
+	struct usb_driver *subdriver = (void *)dev->data[0];
+	int ret = 0;
+
+	if (subdriver && subdriver->resume)
+		ret = subdriver->resume(intf);
+	if (ret < 0)
+		goto err;
+	ret = usbnet_resume(intf);
+	if (ret < 0 && subdriver && subdriver->resume && subdriver->suspend)
+		subdriver->suspend(intf, PMSG_SUSPEND);
+err:
+	return ret;
+}
+
+
+static const struct driver_info	qmi_wwan_info = {
+	.description	= "QMI speaking wwan device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind,
+	.manage_power	= qmi_wwan_manage_power,
+};
+
+static const struct driver_info	qmi_wwan_shared = {
+	.description	= "QMI speaking wwan device with combined interface",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.rx_fixup       = qmi_wwan_rx_fixup,
+};
+
+static const struct driver_info	qmi_wwan_force_int0 = {
+	.description	= "Qualcomm WWAN/QMI device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.data		= BIT(0), /* interface whitelist bitmap */
+};
+
+static const struct driver_info	qmi_wwan_force_int1 = {
+	.description	= "Qualcomm WWAN/QMI device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.data		= BIT(1), /* interface whitelist bitmap */
+};
+
+static const struct driver_info qmi_wwan_force_int2 = {
+	.description	= "Qualcomm WWAN/QMI device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.data		= BIT(2), /* interface whitelist bitmap */
+};
+
+static const struct driver_info	qmi_wwan_force_int3 = {
+	.description	= "Qualcomm WWAN/QMI device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.data		= BIT(3), /* interface whitelist bitmap */
+};
+
+static const struct driver_info	qmi_wwan_force_int4 = {
+	.description	= "Qualcomm WWAN/QMI device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.data		= BIT(4), /* interface whitelist bitmap */
+};
+
+/* Sierra Wireless provide equally useless interface descriptors
+ * Devices in QMI mode can be switched between two different
+ * configurations:
+ *   a) USB interface #8 is QMI/wwan
+ *   b) USB interfaces #8, #19 and #20 are QMI/wwan
+ *
+ * Both configurations provide a number of other interfaces (serial++),
+ * some of which have the same endpoint configuration as we expect, so
+ * a whitelist or blacklist is necessary.
+ *
+ * FIXME: The below whitelist should include BIT(20).  It does not
+ * because I cannot get it to work...
+ */
+static const struct driver_info	qmi_wwan_sierra = {
+	.description	= "Sierra Wireless wwan/QMI device",
+	.flags		= FLAG_WWAN,
+	.bind		= qmi_wwan_bind_shared,
+	.unbind		= qmi_wwan_unbind_shared,
+	.manage_power	= qmi_wwan_manage_power,
+	.data		= BIT(8) | BIT(19), /* interface whitelist bitmap */
+};
+
+#define HUAWEI_VENDOR_ID	0x12D1
+
+/* Gobi 1000 QMI/wwan interface number is 3 according to qcserial */
+#define QMI_GOBI1K_DEVICE(vend, prod) \
+	USB_DEVICE(vend, prod), \
+	.driver_info = (unsigned long)&qmi_wwan_force_int3
+
+/* Gobi 2000 and Gobi 3000 QMI/wwan interface number is 0 according to qcserial */
+#define QMI_GOBI_DEVICE(vend, prod) \
+	USB_DEVICE(vend, prod), \
+	.driver_info = (unsigned long)&qmi_wwan_force_int0
+
+static const struct usb_device_id products[] = {
+	{	/* Huawei E392, E398 and possibly others sharing both device id and more... */
+		.match_flags        = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = HUAWEI_VENDOR_ID,
+		.bInterfaceClass    = USB_CLASS_VENDOR_SPEC,
+		.bInterfaceSubClass = 1,
+		.bInterfaceProtocol = 8, /* NOTE: This is the *slave* interface of the CDC Union! */
+		.driver_info        = (unsigned long)&qmi_wwan_info,
+	},
+	{	/* Vodafone/Huawei K5005 (12d1:14c8) and similar modems */
+		.match_flags        = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = HUAWEI_VENDOR_ID,
+		.bInterfaceClass    = USB_CLASS_VENDOR_SPEC,
+		.bInterfaceSubClass = 1,
+		.bInterfaceProtocol = 56, /* NOTE: This is the *slave* interface of the CDC Union! */
+		.driver_info        = (unsigned long)&qmi_wwan_info,
+	},
+	{	/* Huawei E392, E398 and possibly others in "Windows mode"
+		 * using a combined control and data interface without any CDC
+		 * functional descriptors
+		 */
+		.match_flags        = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = HUAWEI_VENDOR_ID,
+		.bInterfaceClass    = USB_CLASS_VENDOR_SPEC,
+		.bInterfaceSubClass = 1,
+		.bInterfaceProtocol = 17,
+		.driver_info        = (unsigned long)&qmi_wwan_shared,
+	},
+	{	/* Pantech UML290 */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x106c,
+		.idProduct          = 0x3718,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xf0,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_shared,
+	},
+	{	/* Pantech UML290 - newer firmware */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x106c,
+		.idProduct          = 0x3718,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xf1,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_shared,
+	},
+	{	/* ZTE MF820D */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x0167,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE MF821D */
+		.match_flags        = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x0326,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE (Vodafone) K3520-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x0055,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int1,
+	},
+	{	/* ZTE (Vodafone) K3565-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x0063,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE (Vodafone) K3570-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x1008,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE (Vodafone) K3571-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x1010,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE (Vodafone) K3765-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x2002,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE (Vodafone) K4505-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x0104,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int4,
+	},
+	{	/* ZTE (Vodafone) K5006-Z */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x1018,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int3,
+	},
+	{	/* ZTE MF60 */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x19d2,
+		.idProduct          = 0x1402,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_force_int2,
+	},
+	{	/* Sierra Wireless MC77xx in QMI mode */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x1199,
+		.idProduct          = 0x68a2,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_sierra,
+	},
+	{	/* Sierra Wireless MC7700 */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x0f3d,
+		.idProduct          = 0x68a2,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_sierra,
+	},
+	{	/* Sierra Wireless MC7750 */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x114f,
+		.idProduct          = 0x68a2,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_sierra,
+	},
+	{	/* Sierra Wireless EM7700 */
+		.match_flags	    = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor           = 0x1199,
+		.idProduct          = 0x901c,
+		.bInterfaceClass    = 0xff,
+		.bInterfaceSubClass = 0xff,
+		.bInterfaceProtocol = 0xff,
+		.driver_info        = (unsigned long)&qmi_wwan_sierra,
+	},
+
+	/* Gobi 1000 devices */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9212)},	/* Acer Gobi Modem Device */
+	{QMI_GOBI1K_DEVICE(0x03f0, 0x1f1d)},	/* HP un2400 Gobi Modem Device */
+	{QMI_GOBI1K_DEVICE(0x03f0, 0x371d)},	/* HP un2430 Mobile Broadband Module */
+	{QMI_GOBI1K_DEVICE(0x04da, 0x250d)},	/* Panasonic Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x413c, 0x8172)},	/* Dell Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x1410, 0xa001)},	/* Novatel Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x0b05, 0x1776)},	/* Asus Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x19d2, 0xfff3)},	/* ONDA Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9001)},	/* Generic Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9002)},	/* Generic Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9202)},	/* Generic Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9203)},	/* Generic Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9222)},	/* Generic Gobi Modem device */
+	{QMI_GOBI1K_DEVICE(0x05c6, 0x9009)},	/* Generic Gobi Modem device */
+
+	/* Gobi 2000 and 3000 devices */
+	{QMI_GOBI_DEVICE(0x413c, 0x8186)},	/* Dell Gobi 2000 Modem device (N0218, VU936) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x920b)},	/* Generic Gobi 2000 Modem device */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9225)},	/* Sony Gobi 2000 Modem device (N0279, VU730) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9245)},	/* Samsung Gobi 2000 Modem device (VL176) */
+	{QMI_GOBI_DEVICE(0x03f0, 0x251d)},	/* HP Gobi 2000 Modem device (VP412) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9215)},	/* Acer Gobi 2000 Modem device (VP413) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9265)},	/* Asus Gobi 2000 Modem device (VR305) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9235)},	/* Top Global Gobi 2000 Modem device (VR306) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9275)},	/* iRex Technologies Gobi 2000 Modem device (VR307) */
+	{QMI_GOBI_DEVICE(0x1199, 0x68a5)},	/* Sierra Wireless Modem */
+	{QMI_GOBI_DEVICE(0x1199, 0x68a9)},	/* Sierra Wireless Modem */
+	{QMI_GOBI_DEVICE(0x1199, 0x9001)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9002)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9003)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9004)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9005)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9006)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9007)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9008)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9009)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x900a)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9011)},	/* Sierra Wireless Gobi 2000 Modem device (MC8305) */
+	{QMI_GOBI_DEVICE(0x16d8, 0x8002)},	/* CMDTech Gobi 2000 Modem device (VU922) */
+	{QMI_GOBI_DEVICE(0x05c6, 0x9205)},	/* Gobi 2000 Modem device */
+	{QMI_GOBI_DEVICE(0x1199, 0x9013)},	/* Sierra Wireless Gobi 3000 Modem device (MC8355) */
+	{QMI_GOBI_DEVICE(0x1199, 0x9015)},	/* Sierra Wireless Gobi 3000 Modem device */
+	{QMI_GOBI_DEVICE(0x1199, 0x9019)},	/* Sierra Wireless Gobi 3000 Modem device */
+	{QMI_GOBI_DEVICE(0x1199, 0x901b)},	/* Sierra Wireless MC7770 */
+
+	{ }					/* END */
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver qmi_wwan_driver = {
+	.name		      = "qmi_wwan",
+	.id_table	      = products,
+	.probe		      =	usbnet_probe,
+	.disconnect	      = usbnet_disconnect,
+	.suspend	      = qmi_wwan_suspend,
+	.resume		      =	qmi_wwan_resume,
+	.reset_resume         = qmi_wwan_resume,
+	.supports_autosuspend = 1,
+};
+
+static int __init qmi_wwan_init(void)
+{
+	return usb_register(&qmi_wwan_driver);
+}
+module_init(qmi_wwan_init);
+
+static void __exit qmi_wwan_exit(void)
+{
+	usb_deregister(&qmi_wwan_driver);
+}
+module_exit(qmi_wwan_exit);
+
+MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>");
+MODULE_DESCRIPTION("Qualcomm MSM Interface (QMI) WWAN driver");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/rndis_host.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/rndis_host.c
new file mode 100644
index 0000000..bf4c097
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/rndis_host.c
@@ -0,0 +1,646 @@
+/*
+ * Host Side support for RNDIS Networking Links
+ * Copyright (C) 2005 by David Brownell
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+#include <linux/usb/rndis_host.h>
+
+
+/*
+ * RNDIS is NDIS remoted over USB.  It's a MSFT variant of CDC ACM ... of
+ * course ACM was intended for modems, not Ethernet links!  USB's standard
+ * for Ethernet links is "CDC Ethernet", which is significantly simpler.
+ *
+ * NOTE that Microsoft's "RNDIS 1.0" specification is incomplete.  Issues
+ * include:
+ *    - Power management in particular relies on information that's scattered
+ *	through other documentation, and which is incomplete or incorrect even
+ *	there.
+ *    - There are various undocumented protocol requirements, such as the
+ *	need to send unused garbage in control-OUT messages.
+ *    - In some cases, MS-Windows will emit undocumented requests; this
+ *	matters more to peripheral implementations than host ones.
+ *
+ * Moreover there's a no-open-specs variant of RNDIS called "ActiveSync".
+ *
+ * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in
+ * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and
+ * currently rare) "Ethernet Emulation Model" (EEM).
+ */
+
+/*
+ * RNDIS notifications from device: command completion; "reverse"
+ * keepalives; etc
+ */
+void rndis_status(struct usbnet *dev, struct urb *urb)
+{
+	netdev_dbg(dev->net, "rndis status urb, len %d stat %d\n",
+		   urb->actual_length, urb->status);
+	// FIXME for keepalives, respond immediately (asynchronously)
+	// if not an RNDIS status, do like cdc_status(dev,urb) does
+}
+EXPORT_SYMBOL_GPL(rndis_status);
+
+/*
+ * RNDIS indicate messages.
+ */
+static void rndis_msg_indicate(struct usbnet *dev, struct rndis_indicate *msg,
+				int buflen)
+{
+	struct cdc_state *info = (void *)&dev->data;
+	struct device *udev = &info->control->dev;
+
+	if (dev->driver_info->indication) {
+		dev->driver_info->indication(dev, msg, buflen);
+	} else {
+		switch (msg->status) {
+		case RNDIS_STATUS_MEDIA_CONNECT:
+			dev_info(udev, "rndis media connect\n");
+			break;
+		case RNDIS_STATUS_MEDIA_DISCONNECT:
+			dev_info(udev, "rndis media disconnect\n");
+			break;
+		default:
+			dev_info(udev, "rndis indication: 0x%08x\n",
+					le32_to_cpu(msg->status));
+		}
+	}
+}
+
+/*
+ * RPC done RNDIS-style.  Caller guarantees:
+ * - message is properly byteswapped
+ * - there's no other request pending
+ * - buf can hold up to 1KB response (required by RNDIS spec)
+ * On return, the first few entries are already byteswapped.
+ *
+ * Call context is likely probe(), before interface name is known,
+ * which is why we won't try to use it in the diagnostics.
+ */
+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;
+
+	/* REVISIT when this gets called from contexts other than probe() or
+	 * disconnect(): either serialize, or dispatch responses on xid
+	 */
+
+	/* Issue the request; xid is unique, don't bother byteswapping it */
+	if (likely(buf->msg_type != RNDIS_MSG_HALT &&
+		   buf->msg_type != RNDIS_MSG_RESET)) {
+		xid = dev->xid++;
+		if (!xid)
+			xid = dev->xid++;
+		buf->request_id = (__force __le32) xid;
+	}
+	master_ifnum = info->control->cur_altsetting->desc.bInterfaceNumber;
+	retval = usb_control_msg(dev->udev,
+		usb_sndctrlpipe(dev->udev, 0),
+		USB_CDC_SEND_ENCAPSULATED_COMMAND,
+		USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+		0, master_ifnum,
+		buf, le32_to_cpu(buf->msg_len),
+		RNDIS_CONTROL_TIMEOUT_MS);
+	if (unlikely(retval < 0 || xid == 0))
+		return retval;
+
+	/* Some devices don't respond on the control channel until
+	 * polled on the status channel, so do that first. */
+	if (dev->driver_info->data & RNDIS_DRIVER_DATA_POLL_STATUS) {
+		retval = usb_interrupt_msg(
+			dev->udev,
+			usb_rcvintpipe(dev->udev,
+				       dev->status->desc.bEndpointAddress),
+			&notification, sizeof(notification), &partial,
+			RNDIS_CONTROL_TIMEOUT_MS);
+		if (unlikely(retval < 0))
+			return retval;
+	}
+
+	/* 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);
+		retval = 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, master_ifnum,
+			buf, buflen,
+			RNDIS_CONTROL_TIMEOUT_MS);
+		if (likely(retval >= 8)) {
+			msg_len = le32_to_cpu(buf->msg_len);
+			request_id = (__force u32) buf->request_id;
+			if (likely(buf->msg_type == rsp)) {
+				if (likely(request_id == xid)) {
+					if (unlikely(rsp == RNDIS_MSG_RESET_C))
+						return 0;
+					if (likely(RNDIS_STATUS_SUCCESS
+							== buf->status))
+						return 0;
+					dev_dbg(&info->control->dev,
+						"rndis reply status %08x\n",
+						le32_to_cpu(buf->status));
+					return -EL3RST;
+				}
+				dev_dbg(&info->control->dev,
+					"rndis reply id %d expected %d\n",
+					request_id, xid);
+				/* then likely retry */
+			} else switch (buf->msg_type) {
+			case RNDIS_MSG_INDICATE:	/* fault/event */
+				rndis_msg_indicate(dev, (void *)buf, buflen);
+
+				break;
+			case RNDIS_MSG_KEEPALIVE: {	/* ping */
+				struct rndis_keepalive_c *msg = (void *)buf;
+
+				msg->msg_type = RNDIS_MSG_KEEPALIVE_C;
+				msg->msg_len = cpu_to_le32(sizeof *msg);
+				msg->status = RNDIS_STATUS_SUCCESS;
+				retval = usb_control_msg(dev->udev,
+					usb_sndctrlpipe(dev->udev, 0),
+					USB_CDC_SEND_ENCAPSULATED_COMMAND,
+					USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+					0, master_ifnum,
+					msg, sizeof *msg,
+					RNDIS_CONTROL_TIMEOUT_MS);
+				if (unlikely(retval < 0))
+					dev_dbg(&info->control->dev,
+						"rndis keepalive err %d\n",
+						retval);
+				}
+				break;
+			default:
+				dev_dbg(&info->control->dev,
+					"unexpected rndis msg %08x len %d\n",
+					le32_to_cpu(buf->msg_type), msg_len);
+			}
+		} else {
+			/* device probably issued a protocol stall; ignore */
+			dev_dbg(&info->control->dev,
+				"rndis response error, code %d\n", retval);
+		}
+		msleep(20);
+	}
+	dev_dbg(&info->control->dev, "rndis response timeout\n");
+	return -ETIMEDOUT;
+}
+EXPORT_SYMBOL_GPL(rndis_command);
+
+/*
+ * rndis_query:
+ *
+ * Performs a query for @oid along with 0 or more bytes of payload as
+ * specified by @in_len. If @reply_len is not set to -1 then the reply
+ * length is checked against this value, resulting in an error if it
+ * doesn't match.
+ *
+ * NOTE: Adding a payload exactly or greater than the size of the expected
+ * response payload is an evident requirement MSFT added for ActiveSync.
+ *
+ * The only exception is for OIDs that return a variably sized response,
+ * in which case no payload should be added.  This undocumented (and
+ * nonsensical!) issue was found by sniffing protocol requests from the
+ * ActiveSync 4.1 Windows driver.
+ */
+static int rndis_query(struct usbnet *dev, struct usb_interface *intf,
+		void *buf, __le32 oid, u32 in_len,
+		void **reply, int *reply_len)
+{
+	int retval;
+	union {
+		void			*buf;
+		struct rndis_msg_hdr	*header;
+		struct rndis_query	*get;
+		struct rndis_query_c	*get_c;
+	} u;
+	u32 off, len;
+
+	u.buf = buf;
+
+	memset(u.get, 0, sizeof *u.get + in_len);
+	u.get->msg_type = RNDIS_MSG_QUERY;
+	u.get->msg_len = cpu_to_le32(sizeof *u.get + in_len);
+	u.get->oid = oid;
+	u.get->len = cpu_to_le32(in_len);
+	u.get->offset = cpu_to_le32(20);
+
+	retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE);
+	if (unlikely(retval < 0)) {
+		dev_err(&intf->dev, "RNDIS_MSG_QUERY(0x%08x) failed, %d\n",
+				oid, retval);
+		return retval;
+	}
+
+	off = le32_to_cpu(u.get_c->offset);
+	len = le32_to_cpu(u.get_c->len);
+	if (unlikely((8 + off + len) > CONTROL_BUFFER_SIZE))
+		goto response_error;
+
+	if (*reply_len != -1 && len != *reply_len)
+		goto response_error;
+
+	*reply = (unsigned char *) &u.get_c->request_id + off;
+	*reply_len = len;
+
+	return retval;
+
+response_error:
+	dev_err(&intf->dev, "RNDIS_MSG_QUERY(0x%08x) "
+			"invalid response - off %d len %d\n",
+		oid, off, len);
+	return -EDOM;
+}
+
+/* same as usbnet_netdev_ops but MTU change not allowed */
+static const struct net_device_ops rndis_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+int
+generic_rndis_bind(struct usbnet *dev, struct usb_interface *intf, int flags)
+{
+	int			retval;
+	struct net_device	*net = dev->net;
+	struct cdc_state	*info = (void *) &dev->data;
+	union {
+		void			*buf;
+		struct rndis_msg_hdr	*header;
+		struct rndis_init	*init;
+		struct rndis_init_c	*init_c;
+		struct rndis_query	*get;
+		struct rndis_query_c	*get_c;
+		struct rndis_set	*set;
+		struct rndis_set_c	*set_c;
+		struct rndis_halt	*halt;
+	} u;
+	u32			tmp;
+	__le32			phym_unspec, *phym;
+	int			reply_len;
+	unsigned char		*bp;
+
+	/* we can't rely on i/o from stack working, or stack allocation */
+	u.buf = kmalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL);
+	if (!u.buf)
+		return -ENOMEM;
+	retval = usbnet_generic_cdc_bind(dev, intf);
+	if (retval < 0)
+		goto fail;
+
+	u.init->msg_type = RNDIS_MSG_INIT;
+	u.init->msg_len = cpu_to_le32(sizeof *u.init);
+	u.init->major_version = cpu_to_le32(1);
+	u.init->minor_version = cpu_to_le32(0);
+
+	/* max transfer (in spec) is 0x4000 at full speed, but for
+	 * TX we'll stick to one Ethernet packet plus RNDIS framing.
+	 * For RX we handle drivers that zero-pad to end-of-packet.
+	 * Don't let userspace change these settings.
+	 *
+	 * NOTE: there still seems to be wierdness here, as if we need
+	 * to do some more things to make sure WinCE targets accept this.
+	 * They default to jumbograms of 8KB or 16KB, which is absurd
+	 * for such low data rates and which is also more than Linux
+	 * can usually expect to allocate for SKB data...
+	 */
+	net->hard_header_len += sizeof (struct rndis_data_hdr);
+	dev->hard_mtu = net->mtu + net->hard_header_len;
+
+	dev->maxpacket = usb_maxpacket(dev->udev, dev->out, 1);
+	if (dev->maxpacket == 0) {
+		netif_dbg(dev, probe, dev->net,
+			  "dev->maxpacket can't be 0\n");
+		retval = -EINVAL;
+		goto fail_and_release;
+	}
+
+	dev->rx_urb_size = dev->hard_mtu + (dev->maxpacket + 1);
+	dev->rx_urb_size &= ~(dev->maxpacket - 1);
+	u.init->max_transfer_size = cpu_to_le32(dev->rx_urb_size);
+
+	net->netdev_ops = &rndis_netdev_ops;
+
+	retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE);
+	if (unlikely(retval < 0)) {
+		/* it might not even be an RNDIS device!! */
+		dev_err(&intf->dev, "RNDIS init failed, %d\n", retval);
+		goto fail_and_release;
+	}
+	tmp = le32_to_cpu(u.init_c->max_transfer_size);
+	if (tmp < dev->hard_mtu) {
+		if (tmp <= net->hard_header_len) {
+			dev_err(&intf->dev,
+				"dev can't take %u byte packets (max %u)\n",
+				dev->hard_mtu, tmp);
+			retval = -EINVAL;
+			goto halt_fail_and_release;
+		}
+		dev_warn(&intf->dev,
+			 "dev can't take %u byte packets (max %u), "
+			 "adjusting MTU to %u\n",
+			 dev->hard_mtu, tmp, tmp - net->hard_header_len);
+		dev->hard_mtu = tmp;
+		net->mtu = dev->hard_mtu - net->hard_header_len;
+	}
+
+	/* REVISIT:  peripheral "alignment" request is ignored ... */
+	dev_dbg(&intf->dev,
+		"hard mtu %u (%u from dev), rx buflen %Zu, align %d\n",
+		dev->hard_mtu, tmp, dev->rx_urb_size,
+		1 << le32_to_cpu(u.init_c->packet_alignment));
+
+	/* module has some device initialization code needs to be done right
+	 * after RNDIS_INIT */
+	if (dev->driver_info->early_init &&
+			dev->driver_info->early_init(dev) != 0)
+		goto halt_fail_and_release;
+
+	/* Check physical medium */
+	phym = NULL;
+	reply_len = sizeof *phym;
+	retval = rndis_query(dev, intf, u.buf, OID_GEN_PHYSICAL_MEDIUM,
+			0, (void **) &phym, &reply_len);
+	if (retval != 0 || !phym) {
+		/* OID is optional so don't fail here. */
+		phym_unspec = RNDIS_PHYSICAL_MEDIUM_UNSPECIFIED;
+		phym = &phym_unspec;
+	}
+	if ((flags & FLAG_RNDIS_PHYM_WIRELESS) &&
+			*phym != RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN) {
+		netif_dbg(dev, probe, dev->net,
+			  "driver requires wireless physical medium, but device is not\n");
+		retval = -ENODEV;
+		goto halt_fail_and_release;
+	}
+	if ((flags & FLAG_RNDIS_PHYM_NOT_WIRELESS) &&
+			*phym == RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN) {
+		netif_dbg(dev, probe, dev->net,
+			  "driver requires non-wireless physical medium, but device is wireless.\n");
+		retval = -ENODEV;
+		goto halt_fail_and_release;
+	}
+
+	/* Get designated host ethernet address */
+	reply_len = ETH_ALEN;
+	retval = rndis_query(dev, intf, u.buf, OID_802_3_PERMANENT_ADDRESS,
+			48, (void **) &bp, &reply_len);
+	if (unlikely(retval< 0)) {
+		dev_err(&intf->dev, "rndis get ethaddr, %d\n", retval);
+		goto halt_fail_and_release;
+	}
+	memcpy(net->dev_addr, bp, ETH_ALEN);
+	memcpy(net->perm_addr, bp, ETH_ALEN);
+
+	/* set a nonzero filter to enable data transfers */
+	memset(u.set, 0, sizeof *u.set);
+	u.set->msg_type = RNDIS_MSG_SET;
+	u.set->msg_len = cpu_to_le32(4 + sizeof *u.set);
+	u.set->oid = OID_GEN_CURRENT_PACKET_FILTER;
+	u.set->len = cpu_to_le32(4);
+	u.set->offset = cpu_to_le32((sizeof *u.set) - 8);
+	*(__le32 *)(u.buf + sizeof *u.set) = RNDIS_DEFAULT_FILTER;
+
+	retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE);
+	if (unlikely(retval < 0)) {
+		dev_err(&intf->dev, "rndis set packet filter, %d\n", retval);
+		goto halt_fail_and_release;
+	}
+
+	retval = 0;
+
+	kfree(u.buf);
+	return retval;
+
+halt_fail_and_release:
+	memset(u.halt, 0, sizeof *u.halt);
+	u.halt->msg_type = RNDIS_MSG_HALT;
+	u.halt->msg_len = cpu_to_le32(sizeof *u.halt);
+	(void) rndis_command(dev, (void *)u.halt, CONTROL_BUFFER_SIZE);
+fail_and_release:
+	usb_set_intfdata(info->data, NULL);
+	usb_driver_release_interface(driver_of(intf), info->data);
+	info->data = NULL;
+fail:
+	kfree(u.buf);
+	return retval;
+}
+EXPORT_SYMBOL_GPL(generic_rndis_bind);
+
+static int rndis_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	return generic_rndis_bind(dev, intf, FLAG_RNDIS_PHYM_NOT_WIRELESS);
+}
+
+void rndis_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct rndis_halt	*halt;
+
+	/* try to clear any rndis state/activity (no i/o from stack!) */
+	halt = kzalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL);
+	if (halt) {
+		halt->msg_type = RNDIS_MSG_HALT;
+		halt->msg_len = cpu_to_le32(sizeof *halt);
+		(void) rndis_command(dev, (void *)halt, CONTROL_BUFFER_SIZE);
+		kfree(halt);
+	}
+
+	usbnet_cdc_unbind(dev, intf);
+}
+EXPORT_SYMBOL_GPL(rndis_unbind);
+
+/*
+ * DATA -- host must not write zlps
+ */
+int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len)
+		return 0;
+
+	/* peripheral may have batched packets to us... */
+	while (likely(skb->len)) {
+		struct rndis_data_hdr	*hdr = (void *)skb->data;
+		struct sk_buff		*skb2;
+		u32			msg_len, data_offset, data_len;
+
+		msg_len = le32_to_cpu(hdr->msg_len);
+		data_offset = le32_to_cpu(hdr->data_offset);
+		data_len = le32_to_cpu(hdr->data_len);
+
+		/* don't choke if we see oob, per-packet data, etc */
+		if (unlikely(hdr->msg_type != RNDIS_MSG_PACKET ||
+			     skb->len < msg_len ||
+			     (data_offset + data_len + 8) > msg_len)) {
+			dev->net->stats.rx_frame_errors++;
+			netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n",
+				   le32_to_cpu(hdr->msg_type),
+				   msg_len, data_offset, data_len, skb->len);
+			return 0;
+		}
+		skb_pull(skb, 8 + data_offset);
+
+		/* at most one packet left? */
+		if (likely((data_len - skb->len) <= sizeof *hdr)) {
+			skb_trim(skb, data_len);
+			break;
+		}
+
+		/* try to return all the packets in the batch */
+		skb2 = skb_clone(skb, GFP_ATOMIC);
+		if (unlikely(!skb2))
+			break;
+		skb_pull(skb, msg_len - sizeof *hdr);
+		skb_trim(skb2, data_len);
+		usbnet_skb_return(dev, skb2);
+	}
+
+	/* caller will usbnet_skb_return the remaining packet */
+	return 1;
+}
+EXPORT_SYMBOL_GPL(rndis_rx_fixup);
+
+struct sk_buff *
+rndis_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	struct rndis_data_hdr	*hdr;
+	struct sk_buff		*skb2;
+	unsigned		len = skb->len;
+
+	if (likely(!skb_cloned(skb))) {
+		int	room = skb_headroom(skb);
+
+		/* enough head room as-is? */
+		if (unlikely((sizeof *hdr) <= room))
+			goto fill;
+
+		/* enough room, but needs to be readjusted? */
+		room += skb_tailroom(skb);
+		if (likely((sizeof *hdr) <= room)) {
+			skb->data = memmove(skb->head + sizeof *hdr,
+					    skb->data, len);
+			skb_set_tail_pointer(skb, len);
+			goto fill;
+		}
+	}
+
+	/* create a new skb, with the correct size (and tailpad) */
+	skb2 = skb_copy_expand(skb, sizeof *hdr, 1, flags);
+	dev_kfree_skb_any(skb);
+	if (unlikely(!skb2))
+		return skb2;
+	skb = skb2;
+
+	/* fill out the RNDIS header.  we won't bother trying to batch
+	 * packets; Linux minimizes wasted bandwidth through tx queues.
+	 */
+fill:
+	hdr = (void *) __skb_push(skb, sizeof *hdr);
+	memset(hdr, 0, sizeof *hdr);
+	hdr->msg_type = RNDIS_MSG_PACKET;
+	hdr->msg_len = cpu_to_le32(skb->len);
+	hdr->data_offset = cpu_to_le32(sizeof(*hdr) - 8);
+	hdr->data_len = cpu_to_le32(len);
+
+	/* FIXME make the last packet always be short ... */
+	return skb;
+}
+EXPORT_SYMBOL_GPL(rndis_tx_fixup);
+
+
+static const struct driver_info	rndis_info = {
+	.description =	"RNDIS device",
+	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
+	.bind =		rndis_bind,
+	.unbind =	rndis_unbind,
+	.status =	rndis_status,
+	.rx_fixup =	rndis_rx_fixup,
+	.tx_fixup =	rndis_tx_fixup,
+};
+
+static const struct driver_info	rndis_poll_status_info = {
+	.description =	"RNDIS device (poll status before control)",
+	.flags =	FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
+	.data =		RNDIS_DRIVER_DATA_POLL_STATUS,
+	.bind =		rndis_bind,
+	.unbind =	rndis_unbind,
+	.status =	rndis_status,
+	.rx_fixup =	rndis_rx_fixup,
+	.tx_fixup =	rndis_tx_fixup,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static const struct usb_device_id	products [] = {
+{
+	/* 2Wire HomePortal 1000SW */
+	USB_DEVICE_AND_INTERFACE_INFO(0x1630, 0x0042,
+				      USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
+	.driver_info = (unsigned long) &rndis_poll_status_info,
+}, {
+	/* RNDIS is MSFT's un-official variant of CDC ACM */
+	USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff),
+	.driver_info = (unsigned long) &rndis_info,
+}, {
+	/* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */
+	USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1),
+	.driver_info = (unsigned long) &rndis_poll_status_info,
+}, {
+	/* RNDIS for tethering */
+	USB_INTERFACE_INFO(USB_CLASS_WIRELESS_CONTROLLER, 1, 3),
+	.driver_info = (unsigned long) &rndis_info,
+},
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver rndis_driver = {
+	.name =		"rndis_host",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+module_usb_driver(rndis_driver);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("USB Host side RNDIS driver");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/rtl8150.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/rtl8150.c
new file mode 100644
index 0000000..d363b31
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/rtl8150.c
@@ -0,0 +1,957 @@
+/*
+ *  Copyright (c) 2002 Petko Manolov (petkan@users.sourceforge.net)
+ *
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/usb.h>
+#include <asm/uaccess.h>
+
+/* Version Information */
+#define DRIVER_VERSION "v0.6.2 (2004/08/27)"
+#define DRIVER_AUTHOR "Petko Manolov <petkan@users.sourceforge.net>"
+#define DRIVER_DESC "rtl8150 based usb-ethernet driver"
+
+#define	IDR			0x0120
+#define	MAR			0x0126
+#define	CR			0x012e
+#define	TCR			0x012f
+#define	RCR			0x0130
+#define	TSR			0x0132
+#define	RSR			0x0133
+#define	CON0			0x0135
+#define	CON1			0x0136
+#define	MSR			0x0137
+#define	PHYADD			0x0138
+#define	PHYDAT			0x0139
+#define	PHYCNT			0x013b
+#define	GPPC			0x013d
+#define	BMCR			0x0140
+#define	BMSR			0x0142
+#define	ANAR			0x0144
+#define	ANLP			0x0146
+#define	AER			0x0148
+#define CSCR			0x014C  /* This one has the link status */
+#define CSCR_LINK_STATUS	(1 << 3)
+
+#define	IDR_EEPROM		0x1202
+
+#define	PHY_READ		0
+#define	PHY_WRITE		0x20
+#define	PHY_GO			0x40
+
+#define	MII_TIMEOUT		10
+#define	INTBUFSIZE		8
+
+#define	RTL8150_REQT_READ	0xc0
+#define	RTL8150_REQT_WRITE	0x40
+#define	RTL8150_REQ_GET_REGS	0x05
+#define	RTL8150_REQ_SET_REGS	0x05
+
+
+/* Transmit status register errors */
+#define TSR_ECOL		(1<<5)
+#define TSR_LCOL		(1<<4)
+#define TSR_LOSS_CRS		(1<<3)
+#define TSR_JBR			(1<<2)
+#define TSR_ERRORS		(TSR_ECOL | TSR_LCOL | TSR_LOSS_CRS | TSR_JBR)
+/* Receive status register errors */
+#define RSR_CRC			(1<<2)
+#define RSR_FAE			(1<<1)
+#define RSR_ERRORS		(RSR_CRC | RSR_FAE)
+
+/* Media status register definitions */
+#define MSR_DUPLEX		(1<<4)
+#define MSR_SPEED		(1<<3)
+#define MSR_LINK		(1<<2)
+
+/* Interrupt pipe data */
+#define INT_TSR			0x00
+#define INT_RSR			0x01
+#define INT_MSR			0x02
+#define INT_WAKSR		0x03
+#define INT_TXOK_CNT		0x04
+#define INT_RXLOST_CNT		0x05
+#define INT_CRERR_CNT		0x06
+#define INT_COL_CNT		0x07
+
+
+#define	RTL8150_MTU		1540
+#define	RTL8150_TX_TIMEOUT	(HZ)
+#define	RX_SKB_POOL_SIZE	4
+
+/* rtl8150 flags */
+#define	RTL8150_HW_CRC		0
+#define	RX_REG_SET		1
+#define	RTL8150_UNPLUG		2
+#define	RX_URB_FAIL		3
+
+/* Define these values to match your device */
+#define	VENDOR_ID_REALTEK		0x0bda
+#define	VENDOR_ID_MELCO			0x0411
+#define	VENDOR_ID_MICRONET		0x3980
+#define	VENDOR_ID_LONGSHINE		0x07b8
+#define	VENDOR_ID_OQO			0x1557
+#define	VENDOR_ID_ZYXEL			0x0586
+
+#define PRODUCT_ID_RTL8150		0x8150
+#define	PRODUCT_ID_LUAKTX		0x0012
+#define	PRODUCT_ID_LCS8138TX		0x401a
+#define PRODUCT_ID_SP128AR		0x0003
+#define	PRODUCT_ID_PRESTIGE		0x401a
+
+#undef	EEPROM_WRITE
+
+/* table of devices that work with this driver */
+static struct usb_device_id rtl8150_table[] = {
+	{USB_DEVICE(VENDOR_ID_REALTEK, PRODUCT_ID_RTL8150)},
+	{USB_DEVICE(VENDOR_ID_MELCO, PRODUCT_ID_LUAKTX)},
+	{USB_DEVICE(VENDOR_ID_MICRONET, PRODUCT_ID_SP128AR)},
+	{USB_DEVICE(VENDOR_ID_LONGSHINE, PRODUCT_ID_LCS8138TX)},
+	{USB_DEVICE(VENDOR_ID_OQO, PRODUCT_ID_RTL8150)},
+	{USB_DEVICE(VENDOR_ID_ZYXEL, PRODUCT_ID_PRESTIGE)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, rtl8150_table);
+
+struct rtl8150 {
+	unsigned long flags;
+	struct usb_device *udev;
+	struct tasklet_struct tl;
+	struct net_device *netdev;
+	struct urb *rx_urb, *tx_urb, *intr_urb, *ctrl_urb;
+	struct sk_buff *tx_skb, *rx_skb;
+	struct sk_buff *rx_skb_pool[RX_SKB_POOL_SIZE];
+	spinlock_t rx_pool_lock;
+	struct usb_ctrlrequest dr;
+	int intr_interval;
+	__le16 rx_creg;
+	u8 *intr_buff;
+	u8 phy;
+};
+
+typedef struct rtl8150 rtl8150_t;
+
+static const char driver_name [] = "rtl8150";
+
+/*
+**
+**	device related part of the code
+**
+*/
+static int get_registers(rtl8150_t * dev, u16 indx, u16 size, void *data)
+{
+	return usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+			       RTL8150_REQ_GET_REGS, RTL8150_REQT_READ,
+			       indx, 0, data, size, 500);
+}
+
+static int set_registers(rtl8150_t * dev, u16 indx, u16 size, void *data)
+{
+	return usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+			       RTL8150_REQ_SET_REGS, RTL8150_REQT_WRITE,
+			       indx, 0, data, size, 500);
+}
+
+static void ctrl_callback(struct urb *urb)
+{
+	rtl8150_t *dev;
+	int status = urb->status;
+
+	switch (status) {
+	case 0:
+		break;
+	case -EINPROGRESS:
+		break;
+	case -ENOENT:
+		break;
+	default:
+		if (printk_ratelimit())
+			dev_warn(&urb->dev->dev, "ctrl urb status %d\n", status);
+	}
+	dev = urb->context;
+	clear_bit(RX_REG_SET, &dev->flags);
+}
+
+static int async_set_registers(rtl8150_t * dev, u16 indx, u16 size)
+{
+	int ret;
+
+	if (test_bit(RX_REG_SET, &dev->flags))
+		return -EAGAIN;
+
+	dev->dr.bRequestType = RTL8150_REQT_WRITE;
+	dev->dr.bRequest = RTL8150_REQ_SET_REGS;
+	dev->dr.wValue = cpu_to_le16(indx);
+	dev->dr.wIndex = 0;
+	dev->dr.wLength = cpu_to_le16(size);
+	dev->ctrl_urb->transfer_buffer_length = size;
+	usb_fill_control_urb(dev->ctrl_urb, dev->udev,
+			 usb_sndctrlpipe(dev->udev, 0), (char *) &dev->dr,
+			 &dev->rx_creg, size, ctrl_callback, dev);
+	if ((ret = usb_submit_urb(dev->ctrl_urb, GFP_ATOMIC))) {
+		if (ret == -ENODEV)
+			netif_device_detach(dev->netdev);
+		err("control request submission failed: %d", ret);
+	} else
+		set_bit(RX_REG_SET, &dev->flags);
+
+	return ret;
+}
+
+static int read_mii_word(rtl8150_t * dev, u8 phy, __u8 indx, u16 * reg)
+{
+	int i;
+	u8 data[3], tmp;
+
+	data[0] = phy;
+	data[1] = data[2] = 0;
+	tmp = indx | PHY_READ | PHY_GO;
+	i = 0;
+
+	set_registers(dev, PHYADD, sizeof(data), data);
+	set_registers(dev, PHYCNT, 1, &tmp);
+	do {
+		get_registers(dev, PHYCNT, 1, data);
+	} while ((data[0] & PHY_GO) && (i++ < MII_TIMEOUT));
+
+	if (i <= MII_TIMEOUT) {
+		get_registers(dev, PHYDAT, 2, data);
+		*reg = data[0] | (data[1] << 8);
+		return 0;
+	} else
+		return 1;
+}
+
+static int write_mii_word(rtl8150_t * dev, u8 phy, __u8 indx, u16 reg)
+{
+	int i;
+	u8 data[3], tmp;
+
+	data[0] = phy;
+	data[1] = reg & 0xff;
+	data[2] = (reg >> 8) & 0xff;
+	tmp = indx | PHY_WRITE | PHY_GO;
+	i = 0;
+
+	set_registers(dev, PHYADD, sizeof(data), data);
+	set_registers(dev, PHYCNT, 1, &tmp);
+	do {
+		get_registers(dev, PHYCNT, 1, data);
+	} while ((data[0] & PHY_GO) && (i++ < MII_TIMEOUT));
+
+	if (i <= MII_TIMEOUT)
+		return 0;
+	else
+		return 1;
+}
+
+static inline void set_ethernet_addr(rtl8150_t * dev)
+{
+	u8 node_id[6];
+
+	get_registers(dev, IDR, sizeof(node_id), node_id);
+	memcpy(dev->netdev->dev_addr, node_id, sizeof(node_id));
+}
+
+static int rtl8150_set_mac_address(struct net_device *netdev, void *p)
+{
+	struct sockaddr *addr = p;
+	rtl8150_t *dev = netdev_priv(netdev);
+
+	if (netif_running(netdev))
+		return -EBUSY;
+
+	memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len);
+	dbg("%s: Setting MAC address to %pM\n", netdev->name, netdev->dev_addr);
+	/* Set the IDR registers. */
+	set_registers(dev, IDR, netdev->addr_len, netdev->dev_addr);
+#ifdef EEPROM_WRITE
+	{
+	int i;
+	u8 cr;
+	/* Get the CR contents. */
+	get_registers(dev, CR, 1, &cr);
+	/* Set the WEPROM bit (eeprom write enable). */
+	cr |= 0x20;
+	set_registers(dev, CR, 1, &cr);
+	/* Write the MAC address into eeprom. Eeprom writes must be word-sized,
+	   so we need to split them up. */
+	for (i = 0; i * 2 < netdev->addr_len; i++) {
+		set_registers(dev, IDR_EEPROM + (i * 2), 2,
+		netdev->dev_addr + (i * 2));
+	}
+	/* Clear the WEPROM bit (preventing accidental eeprom writes). */
+	cr &= 0xdf;
+	set_registers(dev, CR, 1, &cr);
+	}
+#endif
+	return 0;
+}
+
+static int rtl8150_reset(rtl8150_t * dev)
+{
+	u8 data = 0x10;
+	int i = HZ;
+
+	set_registers(dev, CR, 1, &data);
+	do {
+		get_registers(dev, CR, 1, &data);
+	} while ((data & 0x10) && --i);
+
+	return (i > 0) ? 1 : 0;
+}
+
+static int alloc_all_urbs(rtl8150_t * dev)
+{
+	dev->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->rx_urb)
+		return 0;
+	dev->tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->tx_urb) {
+		usb_free_urb(dev->rx_urb);
+		return 0;
+	}
+	dev->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->intr_urb) {
+		usb_free_urb(dev->rx_urb);
+		usb_free_urb(dev->tx_urb);
+		return 0;
+	}
+	dev->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->ctrl_urb) {
+		usb_free_urb(dev->rx_urb);
+		usb_free_urb(dev->tx_urb);
+		usb_free_urb(dev->intr_urb);
+		return 0;
+	}
+
+	return 1;
+}
+
+static void free_all_urbs(rtl8150_t * dev)
+{
+	usb_free_urb(dev->rx_urb);
+	usb_free_urb(dev->tx_urb);
+	usb_free_urb(dev->intr_urb);
+	usb_free_urb(dev->ctrl_urb);
+}
+
+static void unlink_all_urbs(rtl8150_t * dev)
+{
+	usb_kill_urb(dev->rx_urb);
+	usb_kill_urb(dev->tx_urb);
+	usb_kill_urb(dev->intr_urb);
+	usb_kill_urb(dev->ctrl_urb);
+}
+
+static inline struct sk_buff *pull_skb(rtl8150_t *dev)
+{
+	struct sk_buff *skb;
+	int i;
+
+	for (i = 0; i < RX_SKB_POOL_SIZE; i++) {
+		if (dev->rx_skb_pool[i]) {
+			skb = dev->rx_skb_pool[i];
+			dev->rx_skb_pool[i] = NULL;
+			return skb;
+		}
+	}
+	return NULL;
+}
+
+static void read_bulk_callback(struct urb *urb)
+{
+	rtl8150_t *dev;
+	unsigned pkt_len, res;
+	struct sk_buff *skb;
+	struct net_device *netdev;
+	u16 rx_stat;
+	int status = urb->status;
+	int result;
+
+	dev = urb->context;
+	if (!dev)
+		return;
+	if (test_bit(RTL8150_UNPLUG, &dev->flags))
+		return;
+	netdev = dev->netdev;
+	if (!netif_device_present(netdev))
+		return;
+
+	switch (status) {
+	case 0:
+		break;
+	case -ENOENT:
+		return;	/* the urb is in unlink state */
+	case -ETIME:
+		if (printk_ratelimit())
+			dev_warn(&urb->dev->dev, "may be reset is needed?..\n");
+		goto goon;
+	default:
+		if (printk_ratelimit())
+			dev_warn(&urb->dev->dev, "Rx status %d\n", status);
+		goto goon;
+	}
+
+	if (!dev->rx_skb)
+		goto resched;
+	/* protect against short packets (tell me why we got some?!?) */
+	if (urb->actual_length < 4)
+		goto goon;
+
+	res = urb->actual_length;
+	rx_stat = le16_to_cpu(*(__le16 *)(urb->transfer_buffer + res - 4));
+	pkt_len = res - 4;
+
+	skb_put(dev->rx_skb, pkt_len);
+	dev->rx_skb->protocol = eth_type_trans(dev->rx_skb, netdev);
+	netif_rx(dev->rx_skb);
+	netdev->stats.rx_packets++;
+	netdev->stats.rx_bytes += pkt_len;
+
+	spin_lock(&dev->rx_pool_lock);
+	skb = pull_skb(dev);
+	spin_unlock(&dev->rx_pool_lock);
+	if (!skb)
+		goto resched;
+
+	dev->rx_skb = skb;
+goon:
+	usb_fill_bulk_urb(dev->rx_urb, dev->udev, usb_rcvbulkpipe(dev->udev, 1),
+		      dev->rx_skb->data, RTL8150_MTU, read_bulk_callback, dev);
+	result = usb_submit_urb(dev->rx_urb, GFP_ATOMIC);
+	if (result == -ENODEV)
+		netif_device_detach(dev->netdev);
+	else if (result) {
+		set_bit(RX_URB_FAIL, &dev->flags);
+		goto resched;
+	} else {
+		clear_bit(RX_URB_FAIL, &dev->flags);
+	}
+
+	return;
+resched:
+	tasklet_schedule(&dev->tl);
+}
+
+static void write_bulk_callback(struct urb *urb)
+{
+	rtl8150_t *dev;
+	int status = urb->status;
+
+	dev = urb->context;
+	if (!dev)
+		return;
+	dev_kfree_skb_irq(dev->tx_skb);
+	if (!netif_device_present(dev->netdev))
+		return;
+	if (status)
+		dev_info(&urb->dev->dev, "%s: Tx status %d\n",
+			 dev->netdev->name, status);
+	dev->netdev->trans_start = jiffies;
+	netif_wake_queue(dev->netdev);
+}
+
+static void intr_callback(struct urb *urb)
+{
+	rtl8150_t *dev;
+	__u8 *d;
+	int status = urb->status;
+	int res;
+
+	dev = urb->context;
+	if (!dev)
+		return;
+	switch (status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:
+		dev_info(&urb->dev->dev, "%s: intr status %d\n",
+			 dev->netdev->name, status);
+		goto resubmit;
+	}
+
+	d = urb->transfer_buffer;
+	if (d[0] & TSR_ERRORS) {
+		dev->netdev->stats.tx_errors++;
+		if (d[INT_TSR] & (TSR_ECOL | TSR_JBR))
+			dev->netdev->stats.tx_aborted_errors++;
+		if (d[INT_TSR] & TSR_LCOL)
+			dev->netdev->stats.tx_window_errors++;
+		if (d[INT_TSR] & TSR_LOSS_CRS)
+			dev->netdev->stats.tx_carrier_errors++;
+	}
+	/* Report link status changes to the network stack */
+	if ((d[INT_MSR] & MSR_LINK) == 0) {
+		if (netif_carrier_ok(dev->netdev)) {
+			netif_carrier_off(dev->netdev);
+			dbg("%s: LINK LOST\n", __func__);
+		}
+	} else {
+		if (!netif_carrier_ok(dev->netdev)) {
+			netif_carrier_on(dev->netdev);
+			dbg("%s: LINK CAME BACK\n", __func__);
+		}
+	}
+
+resubmit:
+	res = usb_submit_urb (urb, GFP_ATOMIC);
+	if (res == -ENODEV)
+		netif_device_detach(dev->netdev);
+	else if (res)
+		err ("can't resubmit intr, %s-%s/input0, status %d",
+				dev->udev->bus->bus_name,
+				dev->udev->devpath, res);
+}
+
+static int rtl8150_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	rtl8150_t *dev = usb_get_intfdata(intf);
+
+	netif_device_detach(dev->netdev);
+
+	if (netif_running(dev->netdev)) {
+		usb_kill_urb(dev->rx_urb);
+		usb_kill_urb(dev->intr_urb);
+	}
+	return 0;
+}
+
+static int rtl8150_resume(struct usb_interface *intf)
+{
+	rtl8150_t *dev = usb_get_intfdata(intf);
+
+	netif_device_attach(dev->netdev);
+	if (netif_running(dev->netdev)) {
+		dev->rx_urb->status = 0;
+		dev->rx_urb->actual_length = 0;
+		read_bulk_callback(dev->rx_urb);
+
+		dev->intr_urb->status = 0;
+		dev->intr_urb->actual_length = 0;
+		intr_callback(dev->intr_urb);
+	}
+	return 0;
+}
+
+/*
+**
+**	network related part of the code
+**
+*/
+
+static void fill_skb_pool(rtl8150_t *dev)
+{
+	struct sk_buff *skb;
+	int i;
+
+	for (i = 0; i < RX_SKB_POOL_SIZE; i++) {
+		if (dev->rx_skb_pool[i])
+			continue;
+		skb = dev_alloc_skb(RTL8150_MTU + 2);
+		if (!skb) {
+			return;
+		}
+		skb_reserve(skb, 2);
+		dev->rx_skb_pool[i] = skb;
+	}
+}
+
+static void free_skb_pool(rtl8150_t *dev)
+{
+	int i;
+
+	for (i = 0; i < RX_SKB_POOL_SIZE; i++)
+		if (dev->rx_skb_pool[i])
+			dev_kfree_skb(dev->rx_skb_pool[i]);
+}
+
+static void rx_fixup(unsigned long data)
+{
+	struct rtl8150 *dev = (struct rtl8150 *)data;
+	struct sk_buff *skb;
+	int status;
+
+	spin_lock_irq(&dev->rx_pool_lock);
+	fill_skb_pool(dev);
+	spin_unlock_irq(&dev->rx_pool_lock);
+	if (test_bit(RX_URB_FAIL, &dev->flags))
+		if (dev->rx_skb)
+			goto try_again;
+	spin_lock_irq(&dev->rx_pool_lock);
+	skb = pull_skb(dev);
+	spin_unlock_irq(&dev->rx_pool_lock);
+	if (skb == NULL)
+		goto tlsched;
+	dev->rx_skb = skb;
+	usb_fill_bulk_urb(dev->rx_urb, dev->udev, usb_rcvbulkpipe(dev->udev, 1),
+		      dev->rx_skb->data, RTL8150_MTU, read_bulk_callback, dev);
+try_again:
+	status = usb_submit_urb(dev->rx_urb, GFP_ATOMIC);
+	if (status == -ENODEV) {
+		netif_device_detach(dev->netdev);
+	} else if (status) {
+		set_bit(RX_URB_FAIL, &dev->flags);
+		goto tlsched;
+	} else {
+		clear_bit(RX_URB_FAIL, &dev->flags);
+	}
+
+	return;
+tlsched:
+	tasklet_schedule(&dev->tl);
+}
+
+static int enable_net_traffic(rtl8150_t * dev)
+{
+	u8 cr, tcr, rcr, msr;
+
+	if (!rtl8150_reset(dev)) {
+		dev_warn(&dev->udev->dev, "device reset failed\n");
+	}
+	/* RCR bit7=1 attach Rx info at the end;  =0 HW CRC (which is broken) */
+	rcr = 0x9e;
+	dev->rx_creg = cpu_to_le16(rcr);
+	tcr = 0xd8;
+	cr = 0x0c;
+	if (!(rcr & 0x80))
+		set_bit(RTL8150_HW_CRC, &dev->flags);
+	set_registers(dev, RCR, 1, &rcr);
+	set_registers(dev, TCR, 1, &tcr);
+	set_registers(dev, CR, 1, &cr);
+	get_registers(dev, MSR, 1, &msr);
+
+	return 0;
+}
+
+static void disable_net_traffic(rtl8150_t * dev)
+{
+	u8 cr;
+
+	get_registers(dev, CR, 1, &cr);
+	cr &= 0xf3;
+	set_registers(dev, CR, 1, &cr);
+}
+
+static void rtl8150_tx_timeout(struct net_device *netdev)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	dev_warn(&netdev->dev, "Tx timeout.\n");
+	usb_unlink_urb(dev->tx_urb);
+	netdev->stats.tx_errors++;
+}
+
+static void rtl8150_set_multicast(struct net_device *netdev)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	netif_stop_queue(netdev);
+	if (netdev->flags & IFF_PROMISC) {
+		dev->rx_creg |= cpu_to_le16(0x0001);
+		dev_info(&netdev->dev, "%s: promiscuous mode\n", netdev->name);
+	} else if (!netdev_mc_empty(netdev) ||
+		   (netdev->flags & IFF_ALLMULTI)) {
+		dev->rx_creg &= cpu_to_le16(0xfffe);
+		dev->rx_creg |= cpu_to_le16(0x0002);
+		dev_info(&netdev->dev, "%s: allmulti set\n", netdev->name);
+	} else {
+		/* ~RX_MULTICAST, ~RX_PROMISCUOUS */
+		dev->rx_creg &= cpu_to_le16(0x00fc);
+	}
+	async_set_registers(dev, RCR, 2);
+	netif_wake_queue(netdev);
+}
+
+static netdev_tx_t rtl8150_start_xmit(struct sk_buff *skb,
+					    struct net_device *netdev)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	int count, res;
+
+	netif_stop_queue(netdev);
+	count = (skb->len < 60) ? 60 : skb->len;
+	count = (count & 0x3f) ? count : count + 1;
+	dev->tx_skb = skb;
+	usb_fill_bulk_urb(dev->tx_urb, dev->udev, usb_sndbulkpipe(dev->udev, 2),
+		      skb->data, count, write_bulk_callback, dev);
+	if ((res = usb_submit_urb(dev->tx_urb, GFP_ATOMIC))) {
+		/* Can we get/handle EPIPE here? */
+		if (res == -ENODEV)
+			netif_device_detach(dev->netdev);
+		else {
+			dev_warn(&netdev->dev, "failed tx_urb %d\n", res);
+			netdev->stats.tx_errors++;
+			netif_start_queue(netdev);
+		}
+	} else {
+		netdev->stats.tx_packets++;
+		netdev->stats.tx_bytes += skb->len;
+		netdev->trans_start = jiffies;
+	}
+
+	return NETDEV_TX_OK;
+}
+
+
+static void set_carrier(struct net_device *netdev)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	short tmp;
+
+	get_registers(dev, CSCR, 2, &tmp);
+	if (tmp & CSCR_LINK_STATUS)
+		netif_carrier_on(netdev);
+	else
+		netif_carrier_off(netdev);
+}
+
+static int rtl8150_open(struct net_device *netdev)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	int res;
+
+	if (dev->rx_skb == NULL)
+		dev->rx_skb = pull_skb(dev);
+	if (!dev->rx_skb)
+		return -ENOMEM;
+
+	set_registers(dev, IDR, 6, netdev->dev_addr);
+
+	usb_fill_bulk_urb(dev->rx_urb, dev->udev, usb_rcvbulkpipe(dev->udev, 1),
+		      dev->rx_skb->data, RTL8150_MTU, read_bulk_callback, dev);
+	if ((res = usb_submit_urb(dev->rx_urb, GFP_KERNEL))) {
+		if (res == -ENODEV)
+			netif_device_detach(dev->netdev);
+		dev_warn(&netdev->dev, "rx_urb submit failed: %d\n", res);
+		return res;
+	}
+	usb_fill_int_urb(dev->intr_urb, dev->udev, usb_rcvintpipe(dev->udev, 3),
+		     dev->intr_buff, INTBUFSIZE, intr_callback,
+		     dev, dev->intr_interval);
+	if ((res = usb_submit_urb(dev->intr_urb, GFP_KERNEL))) {
+		if (res == -ENODEV)
+			netif_device_detach(dev->netdev);
+		dev_warn(&netdev->dev, "intr_urb submit failed: %d\n", res);
+		usb_kill_urb(dev->rx_urb);
+		return res;
+	}
+	enable_net_traffic(dev);
+	set_carrier(netdev);
+	netif_start_queue(netdev);
+
+	return res;
+}
+
+static int rtl8150_close(struct net_device *netdev)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	int res = 0;
+
+	netif_stop_queue(netdev);
+	if (!test_bit(RTL8150_UNPLUG, &dev->flags))
+		disable_net_traffic(dev);
+	unlink_all_urbs(dev);
+
+	return res;
+}
+
+static void rtl8150_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+
+	strncpy(info->driver, driver_name, ETHTOOL_BUSINFO_LEN);
+	strncpy(info->version, DRIVER_VERSION, ETHTOOL_BUSINFO_LEN);
+	usb_make_path(dev->udev, info->bus_info, sizeof info->bus_info);
+}
+
+static int rtl8150_get_settings(struct net_device *netdev, struct ethtool_cmd *ecmd)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	short lpa, bmcr;
+
+	ecmd->supported = (SUPPORTED_10baseT_Half |
+			  SUPPORTED_10baseT_Full |
+			  SUPPORTED_100baseT_Half |
+			  SUPPORTED_100baseT_Full |
+			  SUPPORTED_Autoneg |
+			  SUPPORTED_TP | SUPPORTED_MII);
+	ecmd->port = PORT_TP;
+	ecmd->transceiver = XCVR_INTERNAL;
+	ecmd->phy_address = dev->phy;
+	get_registers(dev, BMCR, 2, &bmcr);
+	get_registers(dev, ANLP, 2, &lpa);
+	if (bmcr & BMCR_ANENABLE) {
+		u32 speed = ((lpa & (LPA_100HALF | LPA_100FULL)) ?
+			     SPEED_100 : SPEED_10);
+		ethtool_cmd_speed_set(ecmd, speed);
+		ecmd->autoneg = AUTONEG_ENABLE;
+		if (speed == SPEED_100)
+			ecmd->duplex = (lpa & LPA_100FULL) ?
+			    DUPLEX_FULL : DUPLEX_HALF;
+		else
+			ecmd->duplex = (lpa & LPA_10FULL) ?
+			    DUPLEX_FULL : DUPLEX_HALF;
+	} else {
+		ecmd->autoneg = AUTONEG_DISABLE;
+		ethtool_cmd_speed_set(ecmd, ((bmcr & BMCR_SPEED100) ?
+					     SPEED_100 : SPEED_10));
+		ecmd->duplex = (bmcr & BMCR_FULLDPLX) ?
+		    DUPLEX_FULL : DUPLEX_HALF;
+	}
+	return 0;
+}
+
+static const struct ethtool_ops ops = {
+	.get_drvinfo = rtl8150_get_drvinfo,
+	.get_settings = rtl8150_get_settings,
+	.get_link = ethtool_op_get_link
+};
+
+static int rtl8150_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
+{
+	rtl8150_t *dev = netdev_priv(netdev);
+	u16 *data = (u16 *) & rq->ifr_ifru;
+	int res = 0;
+
+	switch (cmd) {
+	case SIOCDEVPRIVATE:
+		data[0] = dev->phy;
+	case SIOCDEVPRIVATE + 1:
+		read_mii_word(dev, dev->phy, (data[1] & 0x1f), &data[3]);
+		break;
+	case SIOCDEVPRIVATE + 2:
+		if (!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		write_mii_word(dev, dev->phy, (data[1] & 0x1f), data[2]);
+		break;
+	default:
+		res = -EOPNOTSUPP;
+	}
+
+	return res;
+}
+
+static const struct net_device_ops rtl8150_netdev_ops = {
+	.ndo_open		= rtl8150_open,
+	.ndo_stop		= rtl8150_close,
+	.ndo_do_ioctl		= rtl8150_ioctl,
+	.ndo_start_xmit		= rtl8150_start_xmit,
+	.ndo_tx_timeout		= rtl8150_tx_timeout,
+	.ndo_set_rx_mode	= rtl8150_set_multicast,
+	.ndo_set_mac_address	= rtl8150_set_mac_address,
+
+	.ndo_change_mtu		= eth_change_mtu,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+static int rtl8150_probe(struct usb_interface *intf,
+			 const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	rtl8150_t *dev;
+	struct net_device *netdev;
+
+	netdev = alloc_etherdev(sizeof(rtl8150_t));
+	if (!netdev)
+		return -ENOMEM;
+
+	dev = netdev_priv(netdev);
+
+	dev->intr_buff = kmalloc(INTBUFSIZE, GFP_KERNEL);
+	if (!dev->intr_buff) {
+		free_netdev(netdev);
+		return -ENOMEM;
+	}
+
+	tasklet_init(&dev->tl, rx_fixup, (unsigned long)dev);
+	spin_lock_init(&dev->rx_pool_lock);
+
+	dev->udev = udev;
+	dev->netdev = netdev;
+	netdev->netdev_ops = &rtl8150_netdev_ops;
+	netdev->watchdog_timeo = RTL8150_TX_TIMEOUT;
+	SET_ETHTOOL_OPS(netdev, &ops);
+	dev->intr_interval = 100;	/* 100ms */
+
+	if (!alloc_all_urbs(dev)) {
+		err("out of memory");
+		goto out;
+	}
+	if (!rtl8150_reset(dev)) {
+		err("couldn't reset the device");
+		goto out1;
+	}
+	fill_skb_pool(dev);
+	set_ethernet_addr(dev);
+
+	usb_set_intfdata(intf, dev);
+	SET_NETDEV_DEV(netdev, &intf->dev);
+	if (register_netdev(netdev) != 0) {
+		err("couldn't register the device");
+		goto out2;
+	}
+
+	dev_info(&intf->dev, "%s: rtl8150 is detected\n", netdev->name);
+
+	return 0;
+
+out2:
+	usb_set_intfdata(intf, NULL);
+	free_skb_pool(dev);
+out1:
+	free_all_urbs(dev);
+out:
+	kfree(dev->intr_buff);
+	free_netdev(netdev);
+	return -EIO;
+}
+
+static void rtl8150_disconnect(struct usb_interface *intf)
+{
+	rtl8150_t *dev = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (dev) {
+		set_bit(RTL8150_UNPLUG, &dev->flags);
+		tasklet_kill(&dev->tl);
+		unregister_netdev(dev->netdev);
+		unlink_all_urbs(dev);
+		free_all_urbs(dev);
+		free_skb_pool(dev);
+		if (dev->rx_skb)
+			dev_kfree_skb(dev->rx_skb);
+		kfree(dev->intr_buff);
+		free_netdev(dev->netdev);
+	}
+}
+
+static struct usb_driver rtl8150_driver = {
+	.name		= driver_name,
+	.probe		= rtl8150_probe,
+	.disconnect	= rtl8150_disconnect,
+	.id_table	= rtl8150_table,
+	.suspend	= rtl8150_suspend,
+	.resume		= rtl8150_resume
+};
+
+module_usb_driver(rtl8150_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/sierra_net.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/sierra_net.c
new file mode 100644
index 0000000..8789bc5
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/sierra_net.c
@@ -0,0 +1,998 @@
+/*
+ * 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];
+} __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 const 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 = le16_to_cpu(*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(&eth->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 and work */
+	del_timer_sync(&priv->sync_timer);
+	cancel_work_sync(&priv->sierra_net_kevent);
+
+	/* 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;
+
+	BUILD_BUG_ON(FIELD_SIZEOF(struct usbnet, data)
+				< sizeof(struct cdc_state));
+
+	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_direct_ip = {
+	.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_direct_ip = {
+	.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_direct_ip,
+};
+
+static const struct usb_device_id products[] = {
+	{USB_DEVICE(0x1199, 0x68A3), /* Sierra Wireless USB-to-WWAN modem */
+	.driver_info = (unsigned long) &sierra_net_info_direct_ip},
+	{USB_DEVICE(0x0F3D, 0x68A3), /* AT&T Direct IP modem */
+	.driver_info = (unsigned long) &sierra_net_info_direct_ip},
+	{USB_DEVICE(0x1199, 0x68AA), /* Sierra Wireless Direct IP LTE modem */
+	.driver_info = (unsigned long) &sierra_net_info_direct_ip},
+	{USB_DEVICE(0x0F3D, 0x68AA), /* AT&T Direct IP LTE modem */
+	.driver_info = (unsigned long) &sierra_net_info_direct_ip},
+
+	{}, /* 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,
+};
+
+module_usb_driver(sierra_net_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc75xx.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc75xx.c
new file mode 100644
index 0000000..5a82fcd
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc75xx.c
@@ -0,0 +1,1262 @@
+ /***************************************************************************
+ *
+ * Copyright (C) 2007-2010 SMSC
+ *
+ * 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 <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+#include "smsc75xx.h"
+
+#define SMSC_CHIPNAME			"smsc75xx"
+#define SMSC_DRIVER_VERSION		"1.0.0"
+#define HS_USB_PKT_SIZE			(512)
+#define FS_USB_PKT_SIZE			(64)
+#define DEFAULT_HS_BURST_CAP_SIZE	(16 * 1024 + 5 * HS_USB_PKT_SIZE)
+#define DEFAULT_FS_BURST_CAP_SIZE	(6 * 1024 + 33 * FS_USB_PKT_SIZE)
+#define DEFAULT_BULK_IN_DELAY		(0x00002000)
+#define MAX_SINGLE_PACKET_SIZE		(9000)
+#define LAN75XX_EEPROM_MAGIC		(0x7500)
+#define EEPROM_MAC_OFFSET		(0x01)
+#define DEFAULT_TX_CSUM_ENABLE		(true)
+#define DEFAULT_RX_CSUM_ENABLE		(true)
+#define SMSC75XX_INTERNAL_PHY_ID	(1)
+#define SMSC75XX_TX_OVERHEAD		(8)
+#define MAX_RX_FIFO_SIZE		(20 * 1024)
+#define MAX_TX_FIFO_SIZE		(12 * 1024)
+#define USB_VENDOR_ID_SMSC		(0x0424)
+#define USB_PRODUCT_ID_LAN7500		(0x7500)
+#define USB_PRODUCT_ID_LAN7505		(0x7505)
+#define RXW_PADDING			2
+
+#define check_warn(ret, fmt, args...) \
+	({ if (ret < 0) netdev_warn(dev->net, fmt, ##args); })
+
+#define check_warn_return(ret, fmt, args...) \
+	({ if (ret < 0) { netdev_warn(dev->net, fmt, ##args); return ret; } })
+
+#define check_warn_goto_done(ret, fmt, args...) \
+	({ if (ret < 0) { netdev_warn(dev->net, fmt, ##args); goto done; } })
+
+struct smsc75xx_priv {
+	struct usbnet *dev;
+	u32 rfe_ctl;
+	u32 multicast_hash_table[DP_SEL_VHF_HASH_LEN];
+	struct mutex dataport_mutex;
+	spinlock_t rfe_ctl_lock;
+	struct work_struct set_multicast;
+};
+
+struct usb_context {
+	struct usb_ctrlrequest req;
+	struct usbnet *dev;
+};
+
+static bool turbo_mode = true;
+module_param(turbo_mode, bool, 0644);
+MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction");
+
+static int __must_check smsc75xx_read_reg(struct usbnet *dev, u32 index,
+					  u32 *data)
+{
+	u32 *buf = kmalloc(4, GFP_KERNEL);
+	int ret;
+
+	BUG_ON(!dev);
+
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+		USB_VENDOR_REQUEST_READ_REGISTER,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		00, index, buf, 4, USB_CTRL_GET_TIMEOUT);
+
+	if (unlikely(ret < 0))
+		netdev_warn(dev->net,
+			"Failed to read reg index 0x%08x: %d", index, ret);
+
+	le32_to_cpus(buf);
+	*data = *buf;
+	kfree(buf);
+
+	return ret;
+}
+
+static int __must_check smsc75xx_write_reg(struct usbnet *dev, u32 index,
+					   u32 data)
+{
+	u32 *buf = kmalloc(4, GFP_KERNEL);
+	int ret;
+
+	BUG_ON(!dev);
+
+	if (!buf)
+		return -ENOMEM;
+
+	*buf = data;
+	cpu_to_le32s(buf);
+
+	ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+		USB_VENDOR_REQUEST_WRITE_REGISTER,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		00, index, buf, 4, USB_CTRL_SET_TIMEOUT);
+
+	if (unlikely(ret < 0))
+		netdev_warn(dev->net,
+			"Failed to write reg index 0x%08x: %d", index, ret);
+
+	kfree(buf);
+
+	return ret;
+}
+
+/* Loop until the read is completed with timeout
+ * called with phy_mutex held */
+static int smsc75xx_phy_wait_not_busy(struct usbnet *dev)
+{
+	unsigned long start_time = jiffies;
+	u32 val;
+	int ret;
+
+	do {
+		ret = smsc75xx_read_reg(dev, MII_ACCESS, &val);
+		check_warn_return(ret, "Error reading MII_ACCESS");
+
+		if (!(val & MII_ACCESS_BUSY))
+			return 0;
+	} while (!time_after(jiffies, start_time + HZ));
+
+	return -EIO;
+}
+
+static int smsc75xx_mdio_read(struct net_device *netdev, int phy_id, int idx)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	u32 val, addr;
+	int ret;
+
+	mutex_lock(&dev->phy_mutex);
+
+	/* confirm MII not busy */
+	ret = smsc75xx_phy_wait_not_busy(dev);
+	check_warn_goto_done(ret, "MII is busy in smsc75xx_mdio_read");
+
+	/* set the address, index & direction (read from PHY) */
+	phy_id &= dev->mii.phy_id_mask;
+	idx &= dev->mii.reg_num_mask;
+	addr = ((phy_id << MII_ACCESS_PHY_ADDR_SHIFT) & MII_ACCESS_PHY_ADDR)
+		| ((idx << MII_ACCESS_REG_ADDR_SHIFT) & MII_ACCESS_REG_ADDR)
+		| MII_ACCESS_READ | MII_ACCESS_BUSY;
+	ret = smsc75xx_write_reg(dev, MII_ACCESS, addr);
+	check_warn_goto_done(ret, "Error writing MII_ACCESS");
+
+	ret = smsc75xx_phy_wait_not_busy(dev);
+	check_warn_goto_done(ret, "Timed out reading MII reg %02X", idx);
+
+	ret = smsc75xx_read_reg(dev, MII_DATA, &val);
+	check_warn_goto_done(ret, "Error reading MII_DATA");
+
+	ret = (u16)(val & 0xFFFF);
+
+done:
+	mutex_unlock(&dev->phy_mutex);
+	return ret;
+}
+
+static void smsc75xx_mdio_write(struct net_device *netdev, int phy_id, int idx,
+				int regval)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	u32 val, addr;
+	int ret;
+
+	mutex_lock(&dev->phy_mutex);
+
+	/* confirm MII not busy */
+	ret = smsc75xx_phy_wait_not_busy(dev);
+	check_warn_goto_done(ret, "MII is busy in smsc75xx_mdio_write");
+
+	val = regval;
+	ret = smsc75xx_write_reg(dev, MII_DATA, val);
+	check_warn_goto_done(ret, "Error writing MII_DATA");
+
+	/* set the address, index & direction (write to PHY) */
+	phy_id &= dev->mii.phy_id_mask;
+	idx &= dev->mii.reg_num_mask;
+	addr = ((phy_id << MII_ACCESS_PHY_ADDR_SHIFT) & MII_ACCESS_PHY_ADDR)
+		| ((idx << MII_ACCESS_REG_ADDR_SHIFT) & MII_ACCESS_REG_ADDR)
+		| MII_ACCESS_WRITE | MII_ACCESS_BUSY;
+	ret = smsc75xx_write_reg(dev, MII_ACCESS, addr);
+	check_warn_goto_done(ret, "Error writing MII_ACCESS");
+
+	ret = smsc75xx_phy_wait_not_busy(dev);
+	check_warn_goto_done(ret, "Timed out writing MII reg %02X", idx);
+
+done:
+	mutex_unlock(&dev->phy_mutex);
+}
+
+static int smsc75xx_wait_eeprom(struct usbnet *dev)
+{
+	unsigned long start_time = jiffies;
+	u32 val;
+	int ret;
+
+	do {
+		ret = smsc75xx_read_reg(dev, E2P_CMD, &val);
+		check_warn_return(ret, "Error reading E2P_CMD");
+
+		if (!(val & E2P_CMD_BUSY) || (val & E2P_CMD_TIMEOUT))
+			break;
+		udelay(40);
+	} while (!time_after(jiffies, start_time + HZ));
+
+	if (val & (E2P_CMD_TIMEOUT | E2P_CMD_BUSY)) {
+		netdev_warn(dev->net, "EEPROM read operation timeout");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int smsc75xx_eeprom_confirm_not_busy(struct usbnet *dev)
+{
+	unsigned long start_time = jiffies;
+	u32 val;
+	int ret;
+
+	do {
+		ret = smsc75xx_read_reg(dev, E2P_CMD, &val);
+		check_warn_return(ret, "Error reading E2P_CMD");
+
+		if (!(val & E2P_CMD_BUSY))
+			return 0;
+
+		udelay(40);
+	} while (!time_after(jiffies, start_time + HZ));
+
+	netdev_warn(dev->net, "EEPROM is busy");
+	return -EIO;
+}
+
+static int smsc75xx_read_eeprom(struct usbnet *dev, u32 offset, u32 length,
+				u8 *data)
+{
+	u32 val;
+	int i, ret;
+
+	BUG_ON(!dev);
+	BUG_ON(!data);
+
+	ret = smsc75xx_eeprom_confirm_not_busy(dev);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < length; i++) {
+		val = E2P_CMD_BUSY | E2P_CMD_READ | (offset & E2P_CMD_ADDR);
+		ret = smsc75xx_write_reg(dev, E2P_CMD, val);
+		check_warn_return(ret, "Error writing E2P_CMD");
+
+		ret = smsc75xx_wait_eeprom(dev);
+		if (ret < 0)
+			return ret;
+
+		ret = smsc75xx_read_reg(dev, E2P_DATA, &val);
+		check_warn_return(ret, "Error reading E2P_DATA");
+
+		data[i] = val & 0xFF;
+		offset++;
+	}
+
+	return 0;
+}
+
+static int smsc75xx_write_eeprom(struct usbnet *dev, u32 offset, u32 length,
+				 u8 *data)
+{
+	u32 val;
+	int i, ret;
+
+	BUG_ON(!dev);
+	BUG_ON(!data);
+
+	ret = smsc75xx_eeprom_confirm_not_busy(dev);
+	if (ret)
+		return ret;
+
+	/* Issue write/erase enable command */
+	val = E2P_CMD_BUSY | E2P_CMD_EWEN;
+	ret = smsc75xx_write_reg(dev, E2P_CMD, val);
+	check_warn_return(ret, "Error writing E2P_CMD");
+
+	ret = smsc75xx_wait_eeprom(dev);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < length; i++) {
+
+		/* Fill data register */
+		val = data[i];
+		ret = smsc75xx_write_reg(dev, E2P_DATA, val);
+		check_warn_return(ret, "Error writing E2P_DATA");
+
+		/* Send "write" command */
+		val = E2P_CMD_BUSY | E2P_CMD_WRITE | (offset & E2P_CMD_ADDR);
+		ret = smsc75xx_write_reg(dev, E2P_CMD, val);
+		check_warn_return(ret, "Error writing E2P_CMD");
+
+		ret = smsc75xx_wait_eeprom(dev);
+		if (ret < 0)
+			return ret;
+
+		offset++;
+	}
+
+	return 0;
+}
+
+static int smsc75xx_dataport_wait_not_busy(struct usbnet *dev)
+{
+	int i, ret;
+
+	for (i = 0; i < 100; i++) {
+		u32 dp_sel;
+		ret = smsc75xx_read_reg(dev, DP_SEL, &dp_sel);
+		check_warn_return(ret, "Error reading DP_SEL");
+
+		if (dp_sel & DP_SEL_DPRDY)
+			return 0;
+
+		udelay(40);
+	}
+
+	netdev_warn(dev->net, "smsc75xx_dataport_wait_not_busy timed out");
+
+	return -EIO;
+}
+
+static int smsc75xx_dataport_write(struct usbnet *dev, u32 ram_select, u32 addr,
+				   u32 length, u32 *buf)
+{
+	struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]);
+	u32 dp_sel;
+	int i, ret;
+
+	mutex_lock(&pdata->dataport_mutex);
+
+	ret = smsc75xx_dataport_wait_not_busy(dev);
+	check_warn_goto_done(ret, "smsc75xx_dataport_write busy on entry");
+
+	ret = smsc75xx_read_reg(dev, DP_SEL, &dp_sel);
+	check_warn_goto_done(ret, "Error reading DP_SEL");
+
+	dp_sel &= ~DP_SEL_RSEL;
+	dp_sel |= ram_select;
+	ret = smsc75xx_write_reg(dev, DP_SEL, dp_sel);
+	check_warn_goto_done(ret, "Error writing DP_SEL");
+
+	for (i = 0; i < length; i++) {
+		ret = smsc75xx_write_reg(dev, DP_ADDR, addr + i);
+		check_warn_goto_done(ret, "Error writing DP_ADDR");
+
+		ret = smsc75xx_write_reg(dev, DP_DATA, buf[i]);
+		check_warn_goto_done(ret, "Error writing DP_DATA");
+
+		ret = smsc75xx_write_reg(dev, DP_CMD, DP_CMD_WRITE);
+		check_warn_goto_done(ret, "Error writing DP_CMD");
+
+		ret = smsc75xx_dataport_wait_not_busy(dev);
+		check_warn_goto_done(ret, "smsc75xx_dataport_write timeout");
+	}
+
+done:
+	mutex_unlock(&pdata->dataport_mutex);
+	return ret;
+}
+
+/* returns hash bit number for given MAC address */
+static u32 smsc75xx_hash(char addr[ETH_ALEN])
+{
+	return (ether_crc(ETH_ALEN, addr) >> 23) & 0x1ff;
+}
+
+static void smsc75xx_deferred_multicast_write(struct work_struct *param)
+{
+	struct smsc75xx_priv *pdata =
+		container_of(param, struct smsc75xx_priv, set_multicast);
+	struct usbnet *dev = pdata->dev;
+	int ret;
+
+	netif_dbg(dev, drv, dev->net, "deferred multicast write 0x%08x",
+		pdata->rfe_ctl);
+
+	smsc75xx_dataport_write(dev, DP_SEL_VHF, DP_SEL_VHF_VLAN_LEN,
+		DP_SEL_VHF_HASH_LEN, pdata->multicast_hash_table);
+
+	ret = smsc75xx_write_reg(dev, RFE_CTL, pdata->rfe_ctl);
+	check_warn(ret, "Error writing RFE_CRL");
+}
+
+static void smsc75xx_set_multicast(struct net_device *netdev)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]);
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&pdata->rfe_ctl_lock, flags);
+
+	pdata->rfe_ctl &=
+		~(RFE_CTL_AU | RFE_CTL_AM | RFE_CTL_DPF | RFE_CTL_MHF);
+	pdata->rfe_ctl |= RFE_CTL_AB;
+
+	for (i = 0; i < DP_SEL_VHF_HASH_LEN; i++)
+		pdata->multicast_hash_table[i] = 0;
+
+	if (dev->net->flags & IFF_PROMISC) {
+		netif_dbg(dev, drv, dev->net, "promiscuous mode enabled");
+		pdata->rfe_ctl |= RFE_CTL_AM | RFE_CTL_AU;
+	} else if (dev->net->flags & IFF_ALLMULTI) {
+		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 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(ha, netdev) {
+			u32 bitnum = smsc75xx_hash(ha->addr);
+			pdata->multicast_hash_table[bitnum / 32] |=
+				(1 << (bitnum % 32));
+		}
+	} else {
+		netif_dbg(dev, drv, dev->net, "receive own packets only");
+		pdata->rfe_ctl |= RFE_CTL_DPF;
+	}
+
+	spin_unlock_irqrestore(&pdata->rfe_ctl_lock, flags);
+
+	/* defer register writes to a sleepable context */
+	schedule_work(&pdata->set_multicast);
+}
+
+static int smsc75xx_update_flowcontrol(struct usbnet *dev, u8 duplex,
+					    u16 lcladv, u16 rmtadv)
+{
+	u32 flow = 0, fct_flow = 0;
+	int ret;
+
+	if (duplex == DUPLEX_FULL) {
+		u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv);
+
+		if (cap & FLOW_CTRL_TX) {
+			flow = (FLOW_TX_FCEN | 0xFFFF);
+			/* set fct_flow thresholds to 20% and 80% */
+			fct_flow = (8 << 8) | 32;
+		}
+
+		if (cap & FLOW_CTRL_RX)
+			flow |= FLOW_RX_FCEN;
+
+		netif_dbg(dev, link, dev->net, "rx pause %s, tx pause %s",
+			(cap & FLOW_CTRL_RX ? "enabled" : "disabled"),
+			(cap & FLOW_CTRL_TX ? "enabled" : "disabled"));
+	} else {
+		netif_dbg(dev, link, dev->net, "half duplex");
+	}
+
+	ret = smsc75xx_write_reg(dev, FLOW, flow);
+	check_warn_return(ret, "Error writing FLOW");
+
+	ret = smsc75xx_write_reg(dev, FCT_FLOW, fct_flow);
+	check_warn_return(ret, "Error writing FCT_FLOW");
+
+	return 0;
+}
+
+static int smsc75xx_link_reset(struct usbnet *dev)
+{
+	struct mii_if_info *mii = &dev->mii;
+	struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+	u16 lcladv, rmtadv;
+	int ret;
+
+	/* read and write to clear phy interrupt status */
+	ret = smsc75xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
+	check_warn_return(ret, "Error reading PHY_INT_SRC");
+	smsc75xx_mdio_write(dev->net, mii->phy_id, PHY_INT_SRC, 0xffff);
+
+	ret = smsc75xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL);
+	check_warn_return(ret, "Error writing INT_STS");
+
+	mii_check_media(mii, 1, 1);
+	mii_ethtool_gset(&dev->mii, &ecmd);
+	lcladv = smsc75xx_mdio_read(dev->net, mii->phy_id, MII_ADVERTISE);
+	rmtadv = smsc75xx_mdio_read(dev->net, mii->phy_id, MII_LPA);
+
+	netif_dbg(dev, link, dev->net, "speed: %u duplex: %d lcladv: %04x"
+		  " rmtadv: %04x", ethtool_cmd_speed(&ecmd),
+		  ecmd.duplex, lcladv, rmtadv);
+
+	return smsc75xx_update_flowcontrol(dev, ecmd.duplex, lcladv, rmtadv);
+}
+
+static void smsc75xx_status(struct usbnet *dev, struct urb *urb)
+{
+	u32 intdata;
+
+	if (urb->actual_length != 4) {
+		netdev_warn(dev->net,
+			"unexpected urb length %d", urb->actual_length);
+		return;
+	}
+
+	memcpy(&intdata, urb->transfer_buffer, 4);
+	le32_to_cpus(&intdata);
+
+	netif_dbg(dev, link, dev->net, "intdata: 0x%08X", intdata);
+
+	if (intdata & INT_ENP_PHY_INT)
+		usbnet_defer_kevent(dev, EVENT_LINK_RESET);
+	else
+		netdev_warn(dev->net,
+			"unexpected interrupt, intdata=0x%08X", intdata);
+}
+
+static int smsc75xx_ethtool_get_eeprom_len(struct net_device *net)
+{
+	return MAX_EEPROM_SIZE;
+}
+
+static int smsc75xx_ethtool_get_eeprom(struct net_device *netdev,
+				       struct ethtool_eeprom *ee, u8 *data)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	ee->magic = LAN75XX_EEPROM_MAGIC;
+
+	return smsc75xx_read_eeprom(dev, ee->offset, ee->len, data);
+}
+
+static int smsc75xx_ethtool_set_eeprom(struct net_device *netdev,
+				       struct ethtool_eeprom *ee, u8 *data)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	if (ee->magic != LAN75XX_EEPROM_MAGIC) {
+		netdev_warn(dev->net,
+			"EEPROM: magic value mismatch: 0x%x", ee->magic);
+		return -EINVAL;
+	}
+
+	return smsc75xx_write_eeprom(dev, ee->offset, ee->len, data);
+}
+
+static const struct ethtool_ops smsc75xx_ethtool_ops = {
+	.get_link	= usbnet_get_link,
+	.nway_reset	= usbnet_nway_reset,
+	.get_drvinfo	= usbnet_get_drvinfo,
+	.get_msglevel	= usbnet_get_msglevel,
+	.set_msglevel	= usbnet_set_msglevel,
+	.get_settings	= usbnet_get_settings,
+	.set_settings	= usbnet_set_settings,
+	.get_eeprom_len	= smsc75xx_ethtool_get_eeprom_len,
+	.get_eeprom	= smsc75xx_ethtool_get_eeprom,
+	.set_eeprom	= smsc75xx_ethtool_set_eeprom,
+};
+
+static int smsc75xx_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	if (!netif_running(netdev))
+		return -EINVAL;
+
+	return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
+}
+
+static void smsc75xx_init_mac_address(struct usbnet *dev)
+{
+	/* try reading mac address from EEPROM */
+	if (smsc75xx_read_eeprom(dev, EEPROM_MAC_OFFSET, ETH_ALEN,
+			dev->net->dev_addr) == 0) {
+		if (is_valid_ether_addr(dev->net->dev_addr)) {
+			/* eeprom values are valid so use them */
+			netif_dbg(dev, ifup, dev->net,
+				"MAC address read from EEPROM");
+			return;
+		}
+	}
+
+	/* no eeprom, or eeprom values are invalid. generate random MAC */
+	eth_hw_addr_random(dev->net);
+	netif_dbg(dev, ifup, dev->net, "MAC address set to random_ether_addr");
+}
+
+static int smsc75xx_set_mac_address(struct usbnet *dev)
+{
+	u32 addr_lo = dev->net->dev_addr[0] | dev->net->dev_addr[1] << 8 |
+		dev->net->dev_addr[2] << 16 | dev->net->dev_addr[3] << 24;
+	u32 addr_hi = dev->net->dev_addr[4] | dev->net->dev_addr[5] << 8;
+
+	int ret = smsc75xx_write_reg(dev, RX_ADDRH, addr_hi);
+	check_warn_return(ret, "Failed to write RX_ADDRH: %d", ret);
+
+	ret = smsc75xx_write_reg(dev, RX_ADDRL, addr_lo);
+	check_warn_return(ret, "Failed to write RX_ADDRL: %d", ret);
+
+	addr_hi |= ADDR_FILTX_FB_VALID;
+	ret = smsc75xx_write_reg(dev, ADDR_FILTX, addr_hi);
+	check_warn_return(ret, "Failed to write ADDR_FILTX: %d", ret);
+
+	ret = smsc75xx_write_reg(dev, ADDR_FILTX + 4, addr_lo);
+	check_warn_return(ret, "Failed to write ADDR_FILTX+4: %d", ret);
+
+	return 0;
+}
+
+static int smsc75xx_phy_initialize(struct usbnet *dev)
+{
+	int bmcr, ret, timeout = 0;
+
+	/* Initialize MII structure */
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = smsc75xx_mdio_read;
+	dev->mii.mdio_write = smsc75xx_mdio_write;
+	dev->mii.phy_id_mask = 0x1f;
+	dev->mii.reg_num_mask = 0x1f;
+	dev->mii.supports_gmii = 1;
+	dev->mii.phy_id = SMSC75XX_INTERNAL_PHY_ID;
+
+	/* reset phy and wait for reset to complete */
+	smsc75xx_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
+
+	do {
+		msleep(10);
+		bmcr = smsc75xx_mdio_read(dev->net, dev->mii.phy_id, MII_BMCR);
+		check_warn_return(bmcr, "Error reading MII_BMCR");
+		timeout++;
+	} while ((bmcr & BMCR_RESET) && (timeout < 100));
+
+	if (timeout >= 100) {
+		netdev_warn(dev->net, "timeout on PHY Reset");
+		return -EIO;
+	}
+
+	smsc75xx_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+		ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP |
+		ADVERTISE_PAUSE_ASYM);
+	smsc75xx_mdio_write(dev->net, dev->mii.phy_id, MII_CTRL1000,
+		ADVERTISE_1000FULL);
+
+	/* read and write to clear phy interrupt status */
+	ret = smsc75xx_mdio_read(dev->net, dev->mii.phy_id, PHY_INT_SRC);
+	check_warn_return(ret, "Error reading PHY_INT_SRC");
+	smsc75xx_mdio_write(dev->net, dev->mii.phy_id, PHY_INT_SRC, 0xffff);
+
+	smsc75xx_mdio_write(dev->net, dev->mii.phy_id, PHY_INT_MASK,
+		PHY_INT_MASK_DEFAULT);
+	mii_nway_restart(&dev->mii);
+
+	netif_dbg(dev, ifup, dev->net, "phy initialised successfully");
+	return 0;
+}
+
+static int smsc75xx_set_rx_max_frame_length(struct usbnet *dev, int size)
+{
+	int ret = 0;
+	u32 buf;
+	bool rxenabled;
+
+	ret = smsc75xx_read_reg(dev, MAC_RX, &buf);
+	check_warn_return(ret, "Failed to read MAC_RX: %d", ret);
+
+	rxenabled = ((buf & MAC_RX_RXEN) != 0);
+
+	if (rxenabled) {
+		buf &= ~MAC_RX_RXEN;
+		ret = smsc75xx_write_reg(dev, MAC_RX, buf);
+		check_warn_return(ret, "Failed to write MAC_RX: %d", ret);
+	}
+
+	/* add 4 to size for FCS */
+	buf &= ~MAC_RX_MAX_SIZE;
+	buf |= (((size + 4) << MAC_RX_MAX_SIZE_SHIFT) & MAC_RX_MAX_SIZE);
+
+	ret = smsc75xx_write_reg(dev, MAC_RX, buf);
+	check_warn_return(ret, "Failed to write MAC_RX: %d", ret);
+
+	if (rxenabled) {
+		buf |= MAC_RX_RXEN;
+		ret = smsc75xx_write_reg(dev, MAC_RX, buf);
+		check_warn_return(ret, "Failed to write MAC_RX: %d", ret);
+	}
+
+	return 0;
+}
+
+static int smsc75xx_change_mtu(struct net_device *netdev, int new_mtu)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	int ret;
+
+	if (new_mtu > MAX_SINGLE_PACKET_SIZE)
+		return -EINVAL;
+
+	ret = smsc75xx_set_rx_max_frame_length(dev, new_mtu + ETH_HLEN);
+	check_warn_return(ret, "Failed to set mac rx frame length");
+
+	return usbnet_change_mtu(netdev, new_mtu);
+}
+
+/* Enable or disable Rx checksum offload engine */
+static int smsc75xx_set_features(struct net_device *netdev,
+	netdev_features_t features)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]);
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&pdata->rfe_ctl_lock, flags);
+
+	if (features & NETIF_F_RXCSUM)
+		pdata->rfe_ctl |= RFE_CTL_TCPUDP_CKM | RFE_CTL_IP_CKM;
+	else
+		pdata->rfe_ctl &= ~(RFE_CTL_TCPUDP_CKM | RFE_CTL_IP_CKM);
+
+	spin_unlock_irqrestore(&pdata->rfe_ctl_lock, flags);
+	/* it's racing here! */
+
+	ret = smsc75xx_write_reg(dev, RFE_CTL, pdata->rfe_ctl);
+	check_warn_return(ret, "Error writing RFE_CTL");
+
+	return 0;
+}
+
+static int smsc75xx_reset(struct usbnet *dev)
+{
+	struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]);
+	u32 buf;
+	int ret = 0, timeout;
+
+	netif_dbg(dev, ifup, dev->net, "entering smsc75xx_reset");
+
+	ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
+	check_warn_return(ret, "Failed to read HW_CFG: %d", ret);
+
+	buf |= HW_CFG_LRST;
+
+	ret = smsc75xx_write_reg(dev, HW_CFG, buf);
+	check_warn_return(ret, "Failed to write HW_CFG: %d", ret);
+
+	timeout = 0;
+	do {
+		msleep(10);
+		ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
+		check_warn_return(ret, "Failed to read HW_CFG: %d", ret);
+		timeout++;
+	} while ((buf & HW_CFG_LRST) && (timeout < 100));
+
+	if (timeout >= 100) {
+		netdev_warn(dev->net, "timeout on completion of Lite Reset");
+		return -EIO;
+	}
+
+	netif_dbg(dev, ifup, dev->net, "Lite reset complete, resetting PHY");
+
+	ret = smsc75xx_read_reg(dev, PMT_CTL, &buf);
+	check_warn_return(ret, "Failed to read PMT_CTL: %d", ret);
+
+	buf |= PMT_CTL_PHY_RST;
+
+	ret = smsc75xx_write_reg(dev, PMT_CTL, buf);
+	check_warn_return(ret, "Failed to write PMT_CTL: %d", ret);
+
+	timeout = 0;
+	do {
+		msleep(10);
+		ret = smsc75xx_read_reg(dev, PMT_CTL, &buf);
+		check_warn_return(ret, "Failed to read PMT_CTL: %d", ret);
+		timeout++;
+	} while ((buf & PMT_CTL_PHY_RST) && (timeout < 100));
+
+	if (timeout >= 100) {
+		netdev_warn(dev->net, "timeout waiting for PHY Reset");
+		return -EIO;
+	}
+
+	netif_dbg(dev, ifup, dev->net, "PHY reset complete");
+
+	smsc75xx_init_mac_address(dev);
+
+	ret = smsc75xx_set_mac_address(dev);
+	check_warn_return(ret, "Failed to set mac address");
+
+	netif_dbg(dev, ifup, dev->net, "MAC Address: %pM", dev->net->dev_addr);
+
+	ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
+	check_warn_return(ret, "Failed to read HW_CFG: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG : 0x%08x", buf);
+
+	buf |= HW_CFG_BIR;
+
+	ret = smsc75xx_write_reg(dev, HW_CFG, buf);
+	check_warn_return(ret, "Failed to write HW_CFG: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
+	check_warn_return(ret, "Failed to read HW_CFG: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG after "
+			"writing HW_CFG_BIR: 0x%08x", buf);
+
+	if (!turbo_mode) {
+		buf = 0;
+		dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE;
+	} else if (dev->udev->speed == USB_SPEED_HIGH) {
+		buf = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE;
+		dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE;
+	} else {
+		buf = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE;
+		dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE;
+	}
+
+	netif_dbg(dev, ifup, dev->net, "rx_urb_size=%ld",
+		(ulong)dev->rx_urb_size);
+
+	ret = smsc75xx_write_reg(dev, BURST_CAP, buf);
+	check_warn_return(ret, "Failed to write BURST_CAP: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, BURST_CAP, &buf);
+	check_warn_return(ret, "Failed to read BURST_CAP: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net,
+		"Read Value from BURST_CAP after writing: 0x%08x", buf);
+
+	ret = smsc75xx_write_reg(dev, BULK_IN_DLY, DEFAULT_BULK_IN_DELAY);
+	check_warn_return(ret, "Failed to write BULK_IN_DLY: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, BULK_IN_DLY, &buf);
+	check_warn_return(ret, "Failed to read BULK_IN_DLY: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net,
+		"Read Value from BULK_IN_DLY after writing: 0x%08x", buf);
+
+	if (turbo_mode) {
+		ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
+		check_warn_return(ret, "Failed to read HW_CFG: %d", ret);
+
+		netif_dbg(dev, ifup, dev->net, "HW_CFG: 0x%08x", buf);
+
+		buf |= (HW_CFG_MEF | HW_CFG_BCE);
+
+		ret = smsc75xx_write_reg(dev, HW_CFG, buf);
+		check_warn_return(ret, "Failed to write HW_CFG: %d", ret);
+
+		ret = smsc75xx_read_reg(dev, HW_CFG, &buf);
+		check_warn_return(ret, "Failed to read HW_CFG: %d", ret);
+
+		netif_dbg(dev, ifup, dev->net, "HW_CFG: 0x%08x", buf);
+	}
+
+	/* set FIFO sizes */
+	buf = (MAX_RX_FIFO_SIZE - 512) / 512;
+	ret = smsc75xx_write_reg(dev, FCT_RX_FIFO_END, buf);
+	check_warn_return(ret, "Failed to write FCT_RX_FIFO_END: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "FCT_RX_FIFO_END set to 0x%08x", buf);
+
+	buf = (MAX_TX_FIFO_SIZE - 512) / 512;
+	ret = smsc75xx_write_reg(dev, FCT_TX_FIFO_END, buf);
+	check_warn_return(ret, "Failed to write FCT_TX_FIFO_END: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "FCT_TX_FIFO_END set to 0x%08x", buf);
+
+	ret = smsc75xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL);
+	check_warn_return(ret, "Failed to write INT_STS: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, ID_REV, &buf);
+	check_warn_return(ret, "Failed to read ID_REV: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "ID_REV = 0x%08x", buf);
+
+	/* Configure GPIO pins as LED outputs */
+	ret = smsc75xx_read_reg(dev, LED_GPIO_CFG, &buf);
+	check_warn_return(ret, "Failed to read LED_GPIO_CFG: %d", ret);
+
+	buf &= ~(LED_GPIO_CFG_LED2_FUN_SEL | LED_GPIO_CFG_LED10_FUN_SEL);
+	buf |= LED_GPIO_CFG_LEDGPIO_EN | LED_GPIO_CFG_LED2_FUN_SEL;
+
+	ret = smsc75xx_write_reg(dev, LED_GPIO_CFG, buf);
+	check_warn_return(ret, "Failed to write LED_GPIO_CFG: %d", ret);
+
+	ret = smsc75xx_write_reg(dev, FLOW, 0);
+	check_warn_return(ret, "Failed to write FLOW: %d", ret);
+
+	ret = smsc75xx_write_reg(dev, FCT_FLOW, 0);
+	check_warn_return(ret, "Failed to write FCT_FLOW: %d", ret);
+
+	/* Don't need rfe_ctl_lock during initialisation */
+	ret = smsc75xx_read_reg(dev, RFE_CTL, &pdata->rfe_ctl);
+	check_warn_return(ret, "Failed to read RFE_CTL: %d", ret);
+
+	pdata->rfe_ctl |= RFE_CTL_AB | RFE_CTL_DPF;
+
+	ret = smsc75xx_write_reg(dev, RFE_CTL, pdata->rfe_ctl);
+	check_warn_return(ret, "Failed to write RFE_CTL: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, RFE_CTL, &pdata->rfe_ctl);
+	check_warn_return(ret, "Failed to read RFE_CTL: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "RFE_CTL set to 0x%08x", pdata->rfe_ctl);
+
+	/* Enable or disable checksum offload engines */
+	smsc75xx_set_features(dev->net, dev->net->features);
+
+	smsc75xx_set_multicast(dev->net);
+
+	ret = smsc75xx_phy_initialize(dev);
+	check_warn_return(ret, "Failed to initialize PHY: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, INT_EP_CTL, &buf);
+	check_warn_return(ret, "Failed to read INT_EP_CTL: %d", ret);
+
+	/* enable PHY interrupts */
+	buf |= INT_ENP_PHY_INT;
+
+	ret = smsc75xx_write_reg(dev, INT_EP_CTL, buf);
+	check_warn_return(ret, "Failed to write INT_EP_CTL: %d", ret);
+
+	/* allow mac to detect speed and duplex from phy */
+	ret = smsc75xx_read_reg(dev, MAC_CR, &buf);
+	check_warn_return(ret, "Failed to read MAC_CR: %d", ret);
+
+	buf |= (MAC_CR_ADD | MAC_CR_ASD);
+	ret = smsc75xx_write_reg(dev, MAC_CR, buf);
+	check_warn_return(ret, "Failed to write MAC_CR: %d", ret);
+
+	ret = smsc75xx_read_reg(dev, MAC_TX, &buf);
+	check_warn_return(ret, "Failed to read MAC_TX: %d", ret);
+
+	buf |= MAC_TX_TXEN;
+
+	ret = smsc75xx_write_reg(dev, MAC_TX, buf);
+	check_warn_return(ret, "Failed to write MAC_TX: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "MAC_TX set to 0x%08x", buf);
+
+	ret = smsc75xx_read_reg(dev, FCT_TX_CTL, &buf);
+	check_warn_return(ret, "Failed to read FCT_TX_CTL: %d", ret);
+
+	buf |= FCT_TX_CTL_EN;
+
+	ret = smsc75xx_write_reg(dev, FCT_TX_CTL, buf);
+	check_warn_return(ret, "Failed to write FCT_TX_CTL: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "FCT_TX_CTL set to 0x%08x", buf);
+
+	ret = smsc75xx_set_rx_max_frame_length(dev, dev->net->mtu + ETH_HLEN);
+	check_warn_return(ret, "Failed to set max rx frame length");
+
+	ret = smsc75xx_read_reg(dev, MAC_RX, &buf);
+	check_warn_return(ret, "Failed to read MAC_RX: %d", ret);
+
+	buf |= MAC_RX_RXEN;
+
+	ret = smsc75xx_write_reg(dev, MAC_RX, buf);
+	check_warn_return(ret, "Failed to write MAC_RX: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "MAC_RX set to 0x%08x", buf);
+
+	ret = smsc75xx_read_reg(dev, FCT_RX_CTL, &buf);
+	check_warn_return(ret, "Failed to read FCT_RX_CTL: %d", ret);
+
+	buf |= FCT_RX_CTL_EN;
+
+	ret = smsc75xx_write_reg(dev, FCT_RX_CTL, buf);
+	check_warn_return(ret, "Failed to write FCT_RX_CTL: %d", ret);
+
+	netif_dbg(dev, ifup, dev->net, "FCT_RX_CTL set to 0x%08x", buf);
+
+	netif_dbg(dev, ifup, dev->net, "smsc75xx_reset, return 0");
+	return 0;
+}
+
+static const struct net_device_ops smsc75xx_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= smsc75xx_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl 		= smsc75xx_ioctl,
+	.ndo_set_rx_mode	= smsc75xx_set_multicast,
+	.ndo_set_features	= smsc75xx_set_features,
+};
+
+static int smsc75xx_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct smsc75xx_priv *pdata = NULL;
+	int ret;
+
+	printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n");
+
+	ret = usbnet_get_endpoints(dev, intf);
+	check_warn_return(ret, "usbnet_get_endpoints failed: %d", ret);
+
+	dev->data[0] = (unsigned long)kzalloc(sizeof(struct smsc75xx_priv),
+		GFP_KERNEL);
+
+	pdata = (struct smsc75xx_priv *)(dev->data[0]);
+	if (!pdata) {
+		netdev_warn(dev->net, "Unable to allocate smsc75xx_priv");
+		return -ENOMEM;
+	}
+
+	pdata->dev = dev;
+
+	spin_lock_init(&pdata->rfe_ctl_lock);
+	mutex_init(&pdata->dataport_mutex);
+
+	INIT_WORK(&pdata->set_multicast, smsc75xx_deferred_multicast_write);
+
+	if (DEFAULT_TX_CSUM_ENABLE)
+		dev->net->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
+
+	if (DEFAULT_RX_CSUM_ENABLE)
+		dev->net->features |= NETIF_F_RXCSUM;
+
+	dev->net->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
+				NETIF_F_RXCSUM;
+
+	/* Init all registers */
+	ret = smsc75xx_reset(dev);
+
+	dev->net->netdev_ops = &smsc75xx_netdev_ops;
+	dev->net->ethtool_ops = &smsc75xx_ethtool_ops;
+	dev->net->flags |= IFF_MULTICAST;
+	dev->net->hard_header_len += SMSC75XX_TX_OVERHEAD;
+	dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
+	return 0;
+}
+
+static void smsc75xx_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]);
+	if (pdata) {
+		netif_dbg(dev, ifdown, dev->net, "free pdata");
+		kfree(pdata);
+		pdata = NULL;
+		dev->data[0] = 0;
+	}
+}
+
+static void smsc75xx_rx_csum_offload(struct usbnet *dev, struct sk_buff *skb,
+				     u32 rx_cmd_a, u32 rx_cmd_b)
+{
+	if (!(dev->net->features & NETIF_F_RXCSUM) ||
+	    unlikely(rx_cmd_a & RX_CMD_A_LCSM)) {
+		skb->ip_summed = CHECKSUM_NONE;
+	} else {
+		skb->csum = ntohs((u16)(rx_cmd_b >> RX_CMD_B_CSUM_SHIFT));
+		skb->ip_summed = CHECKSUM_COMPLETE;
+	}
+}
+
+static int smsc75xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len)
+		return 0;
+
+	while (skb->len > 0) {
+		u32 rx_cmd_a, rx_cmd_b, align_count, size;
+		struct sk_buff *ax_skb;
+		unsigned char *packet;
+
+		memcpy(&rx_cmd_a, skb->data, sizeof(rx_cmd_a));
+		le32_to_cpus(&rx_cmd_a);
+		skb_pull(skb, 4);
+
+		memcpy(&rx_cmd_b, skb->data, sizeof(rx_cmd_b));
+		le32_to_cpus(&rx_cmd_b);
+		skb_pull(skb, 4 + RXW_PADDING);
+
+		packet = skb->data;
+
+		/* get the packet length */
+		size = (rx_cmd_a & RX_CMD_A_LEN) - RXW_PADDING;
+		align_count = (4 - ((size + RXW_PADDING) % 4)) % 4;
+
+		if (unlikely(rx_cmd_a & RX_CMD_A_RED)) {
+			netif_dbg(dev, rx_err, dev->net,
+				"Error rx_cmd_a=0x%08x", rx_cmd_a);
+			dev->net->stats.rx_errors++;
+			dev->net->stats.rx_dropped++;
+
+			if (rx_cmd_a & RX_CMD_A_FCS)
+				dev->net->stats.rx_crc_errors++;
+			else if (rx_cmd_a & (RX_CMD_A_LONG | RX_CMD_A_RUNT))
+				dev->net->stats.rx_frame_errors++;
+		} else {
+			/* MAX_SINGLE_PACKET_SIZE + 4(CRC) + 2(COE) + 4(Vlan) */
+			if (unlikely(size > (MAX_SINGLE_PACKET_SIZE + ETH_HLEN + 12))) {
+				netif_dbg(dev, rx_err, dev->net,
+					"size err rx_cmd_a=0x%08x", rx_cmd_a);
+				return 0;
+			}
+
+			/* last frame in this batch */
+			if (skb->len == size) {
+				smsc75xx_rx_csum_offload(dev, skb, rx_cmd_a,
+					rx_cmd_b);
+
+				skb_trim(skb, skb->len - 4); /* remove fcs */
+				skb->truesize = size + sizeof(struct sk_buff);
+
+				return 1;
+			}
+
+			ax_skb = skb_clone(skb, GFP_ATOMIC);
+			if (unlikely(!ax_skb)) {
+				netdev_warn(dev->net, "Error allocating skb");
+				return 0;
+			}
+
+			ax_skb->len = size;
+			ax_skb->data = packet;
+			skb_set_tail_pointer(ax_skb, size);
+
+			smsc75xx_rx_csum_offload(dev, ax_skb, rx_cmd_a,
+				rx_cmd_b);
+
+			skb_trim(ax_skb, ax_skb->len - 4); /* remove fcs */
+			ax_skb->truesize = size + sizeof(struct sk_buff);
+
+			usbnet_skb_return(dev, ax_skb);
+		}
+
+		skb_pull(skb, size);
+
+		/* padding bytes before the next frame starts */
+		if (skb->len)
+			skb_pull(skb, align_count);
+	}
+
+	if (unlikely(skb->len < 0)) {
+		netdev_warn(dev->net, "invalid rx length<0 %d", skb->len);
+		return 0;
+	}
+
+	return 1;
+}
+
+static struct sk_buff *smsc75xx_tx_fixup(struct usbnet *dev,
+					 struct sk_buff *skb, gfp_t flags)
+{
+	u32 tx_cmd_a, tx_cmd_b;
+
+	if (skb_headroom(skb) < SMSC75XX_TX_OVERHEAD) {
+		struct sk_buff *skb2 =
+			skb_copy_expand(skb, SMSC75XX_TX_OVERHEAD, 0, flags);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+		if (!skb)
+			return NULL;
+	}
+
+	tx_cmd_a = (u32)(skb->len & TX_CMD_A_LEN) | TX_CMD_A_FCS;
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL)
+		tx_cmd_a |= TX_CMD_A_IPE | TX_CMD_A_TPE;
+
+	if (skb_is_gso(skb)) {
+		u16 mss = max(skb_shinfo(skb)->gso_size, TX_MSS_MIN);
+		tx_cmd_b = (mss << TX_CMD_B_MSS_SHIFT) & TX_CMD_B_MSS;
+
+		tx_cmd_a |= TX_CMD_A_LSO;
+	} else {
+		tx_cmd_b = 0;
+	}
+
+	skb_push(skb, 4);
+	cpu_to_le32s(&tx_cmd_b);
+	memcpy(skb->data, &tx_cmd_b, 4);
+
+	skb_push(skb, 4);
+	cpu_to_le32s(&tx_cmd_a);
+	memcpy(skb->data, &tx_cmd_a, 4);
+
+	return skb;
+}
+
+static const struct driver_info smsc75xx_info = {
+	.description	= "smsc75xx USB 2.0 Gigabit Ethernet",
+	.bind		= smsc75xx_bind,
+	.unbind		= smsc75xx_unbind,
+	.link_reset	= smsc75xx_link_reset,
+	.reset		= smsc75xx_reset,
+	.rx_fixup	= smsc75xx_rx_fixup,
+	.tx_fixup	= smsc75xx_tx_fixup,
+	.status		= smsc75xx_status,
+	.flags		= FLAG_ETHER | FLAG_SEND_ZLP | FLAG_LINK_INTR,
+};
+
+static const struct usb_device_id products[] = {
+	{
+		/* SMSC7500 USB Gigabit Ethernet Device */
+		USB_DEVICE(USB_VENDOR_ID_SMSC, USB_PRODUCT_ID_LAN7500),
+		.driver_info = (unsigned long) &smsc75xx_info,
+	},
+	{
+		/* SMSC7500 USB Gigabit Ethernet Device */
+		USB_DEVICE(USB_VENDOR_ID_SMSC, USB_PRODUCT_ID_LAN7505),
+		.driver_info = (unsigned long) &smsc75xx_info,
+	},
+	{ },		/* END */
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver smsc75xx_driver = {
+	.name		= SMSC_CHIPNAME,
+	.id_table	= products,
+	.probe		= usbnet_probe,
+	.suspend	= usbnet_suspend,
+	.resume		= usbnet_resume,
+	.disconnect	= usbnet_disconnect,
+};
+
+module_usb_driver(smsc75xx_driver);
+
+MODULE_AUTHOR("Nancy Lin");
+MODULE_AUTHOR("Steve Glendinning <steve.glendinning@smsc.com>");
+MODULE_DESCRIPTION("SMSC75XX USB 2.0 Gigabit Ethernet Devices");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc75xx.h b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc75xx.h
new file mode 100644
index 0000000..16e98c7
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc75xx.h
@@ -0,0 +1,421 @@
+ /***************************************************************************
+ *
+ * Copyright (C) 2007-2010 SMSC
+ *
+ * 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.
+ *
+ *****************************************************************************/
+
+#ifndef _SMSC75XX_H
+#define _SMSC75XX_H
+
+/* Tx command words */
+#define TX_CMD_A_LSO			(0x08000000)
+#define TX_CMD_A_IPE			(0x04000000)
+#define TX_CMD_A_TPE			(0x02000000)
+#define TX_CMD_A_IVTG			(0x01000000)
+#define TX_CMD_A_RVTG			(0x00800000)
+#define TX_CMD_A_FCS			(0x00400000)
+#define TX_CMD_A_LEN			(0x000FFFFF)
+
+#define TX_CMD_B_MSS			(0x3FFF0000)
+#define TX_CMD_B_MSS_SHIFT		(16)
+#define TX_MSS_MIN			((u16)8)
+#define TX_CMD_B_VTAG			(0x0000FFFF)
+
+/* Rx command words */
+#define RX_CMD_A_ICE			(0x80000000)
+#define RX_CMD_A_TCE			(0x40000000)
+#define RX_CMD_A_IPV			(0x20000000)
+#define RX_CMD_A_PID			(0x18000000)
+#define RX_CMD_A_PID_NIP		(0x00000000)
+#define RX_CMD_A_PID_TCP		(0x08000000)
+#define RX_CMD_A_PID_UDP		(0x10000000)
+#define RX_CMD_A_PID_PP			(0x18000000)
+#define RX_CMD_A_PFF			(0x04000000)
+#define RX_CMD_A_BAM			(0x02000000)
+#define RX_CMD_A_MAM			(0x01000000)
+#define RX_CMD_A_FVTG			(0x00800000)
+#define RX_CMD_A_RED			(0x00400000)
+#define RX_CMD_A_RWT			(0x00200000)
+#define RX_CMD_A_RUNT			(0x00100000)
+#define RX_CMD_A_LONG			(0x00080000)
+#define RX_CMD_A_RXE			(0x00040000)
+#define RX_CMD_A_DRB			(0x00020000)
+#define RX_CMD_A_FCS			(0x00010000)
+#define RX_CMD_A_UAM			(0x00008000)
+#define RX_CMD_A_LCSM			(0x00004000)
+#define RX_CMD_A_LEN			(0x00003FFF)
+
+#define RX_CMD_B_CSUM			(0xFFFF0000)
+#define RX_CMD_B_CSUM_SHIFT		(16)
+#define RX_CMD_B_VTAG			(0x0000FFFF)
+
+/* SCSRs */
+#define ID_REV				(0x0000)
+
+#define FPGA_REV			(0x0004)
+
+#define BOND_CTL			(0x0008)
+
+#define INT_STS				(0x000C)
+#define INT_STS_RDFO_INT		(0x00400000)
+#define INT_STS_TXE_INT			(0x00200000)
+#define INT_STS_MACRTO_INT		(0x00100000)
+#define INT_STS_TX_DIS_INT		(0x00080000)
+#define INT_STS_RX_DIS_INT		(0x00040000)
+#define INT_STS_PHY_INT_		(0x00020000)
+#define INT_STS_MAC_ERR_INT		(0x00008000)
+#define INT_STS_TDFU			(0x00004000)
+#define INT_STS_TDFO			(0x00002000)
+#define INT_STS_GPIOS			(0x00000FFF)
+#define INT_STS_CLEAR_ALL		(0xFFFFFFFF)
+
+#define HW_CFG				(0x0010)
+#define HW_CFG_SMDET_STS		(0x00008000)
+#define HW_CFG_SMDET_EN			(0x00004000)
+#define HW_CFG_EEM			(0x00002000)
+#define HW_CFG_RST_PROTECT		(0x00001000)
+#define HW_CFG_PORT_SWAP		(0x00000800)
+#define HW_CFG_PHY_BOOST		(0x00000600)
+#define HW_CFG_PHY_BOOST_NORMAL		(0x00000000)
+#define HW_CFG_PHY_BOOST_4		(0x00002000)
+#define HW_CFG_PHY_BOOST_8		(0x00004000)
+#define HW_CFG_PHY_BOOST_12		(0x00006000)
+#define HW_CFG_LEDB			(0x00000100)
+#define HW_CFG_BIR			(0x00000080)
+#define HW_CFG_SBP			(0x00000040)
+#define HW_CFG_IME			(0x00000020)
+#define HW_CFG_MEF			(0x00000010)
+#define HW_CFG_ETC			(0x00000008)
+#define HW_CFG_BCE			(0x00000004)
+#define HW_CFG_LRST			(0x00000002)
+#define HW_CFG_SRST			(0x00000001)
+
+#define PMT_CTL				(0x0014)
+#define PMT_CTL_PHY_PWRUP		(0x00000400)
+#define PMT_CTL_RES_CLR_WKP_EN		(0x00000100)
+#define PMT_CTL_DEV_RDY			(0x00000080)
+#define PMT_CTL_SUS_MODE		(0x00000060)
+#define PMT_CTL_SUS_MODE_0		(0x00000000)
+#define PMT_CTL_SUS_MODE_1		(0x00000020)
+#define PMT_CTL_SUS_MODE_2		(0x00000040)
+#define PMT_CTL_SUS_MODE_3		(0x00000060)
+#define PMT_CTL_PHY_RST			(0x00000010)
+#define PMT_CTL_WOL_EN			(0x00000008)
+#define PMT_CTL_ED_EN			(0x00000004)
+#define PMT_CTL_WUPS			(0x00000003)
+#define PMT_CTL_WUPS_NO			(0x00000000)
+#define PMT_CTL_WUPS_ED			(0x00000001)
+#define PMT_CTL_WUPS_WOL		(0x00000002)
+#define PMT_CTL_WUPS_MULTI		(0x00000003)
+
+#define LED_GPIO_CFG			(0x0018)
+#define LED_GPIO_CFG_LED2_FUN_SEL	(0x80000000)
+#define LED_GPIO_CFG_LED10_FUN_SEL	(0x40000000)
+#define LED_GPIO_CFG_LEDGPIO_EN		(0x0000F000)
+#define LED_GPIO_CFG_LEDGPIO_EN_0	(0x00001000)
+#define LED_GPIO_CFG_LEDGPIO_EN_1	(0x00002000)
+#define LED_GPIO_CFG_LEDGPIO_EN_2	(0x00004000)
+#define LED_GPIO_CFG_LEDGPIO_EN_3	(0x00008000)
+#define LED_GPIO_CFG_GPBUF		(0x00000F00)
+#define LED_GPIO_CFG_GPBUF_0		(0x00000100)
+#define LED_GPIO_CFG_GPBUF_1		(0x00000200)
+#define LED_GPIO_CFG_GPBUF_2		(0x00000400)
+#define LED_GPIO_CFG_GPBUF_3		(0x00000800)
+#define LED_GPIO_CFG_GPDIR		(0x000000F0)
+#define LED_GPIO_CFG_GPDIR_0		(0x00000010)
+#define LED_GPIO_CFG_GPDIR_1		(0x00000020)
+#define LED_GPIO_CFG_GPDIR_2		(0x00000040)
+#define LED_GPIO_CFG_GPDIR_3		(0x00000080)
+#define LED_GPIO_CFG_GPDATA		(0x0000000F)
+#define LED_GPIO_CFG_GPDATA_0		(0x00000001)
+#define LED_GPIO_CFG_GPDATA_1		(0x00000002)
+#define LED_GPIO_CFG_GPDATA_2		(0x00000004)
+#define LED_GPIO_CFG_GPDATA_3		(0x00000008)
+
+#define GPIO_CFG			(0x001C)
+#define GPIO_CFG_SHIFT			(24)
+#define GPIO_CFG_GPEN			(0xFF000000)
+#define GPIO_CFG_GPBUF			(0x00FF0000)
+#define GPIO_CFG_GPDIR			(0x0000FF00)
+#define GPIO_CFG_GPDATA			(0x000000FF)
+
+#define GPIO_WAKE			(0x0020)
+#define GPIO_WAKE_PHY_LINKUP_EN		(0x80000000)
+#define GPIO_WAKE_POL			(0x0FFF0000)
+#define GPIO_WAKE_POL_SHIFT		(16)
+#define GPIO_WAKE_WK			(0x00000FFF)
+
+#define DP_SEL				(0x0024)
+#define DP_SEL_DPRDY			(0x80000000)
+#define DP_SEL_RSEL			(0x0000000F)
+#define DP_SEL_URX			(0x00000000)
+#define DP_SEL_VHF			(0x00000001)
+#define DP_SEL_VHF_HASH_LEN		(16)
+#define DP_SEL_VHF_VLAN_LEN		(128)
+#define DP_SEL_LSO_HEAD			(0x00000002)
+#define DP_SEL_FCT_RX			(0x00000003)
+#define DP_SEL_FCT_TX			(0x00000004)
+#define DP_SEL_DESCRIPTOR		(0x00000005)
+#define DP_SEL_WOL			(0x00000006)
+
+#define DP_CMD				(0x0028)
+#define DP_CMD_WRITE			(0x01)
+#define DP_CMD_READ			(0x00)
+
+#define DP_ADDR				(0x002C)
+
+#define DP_DATA				(0x0030)
+
+#define BURST_CAP			(0x0034)
+#define BURST_CAP_MASK			(0x0000000F)
+
+#define INT_EP_CTL			(0x0038)
+#define INT_EP_CTL_INTEP_ON		(0x80000000)
+#define INT_EP_CTL_RDFO_EN		(0x00400000)
+#define INT_EP_CTL_TXE_EN		(0x00200000)
+#define INT_EP_CTL_MACROTO_EN		(0x00100000)
+#define INT_EP_CTL_TX_DIS_EN		(0x00080000)
+#define INT_EP_CTL_RX_DIS_EN		(0x00040000)
+#define INT_EP_CTL_PHY_EN_		(0x00020000)
+#define INT_EP_CTL_MAC_ERR_EN		(0x00008000)
+#define INT_EP_CTL_TDFU_EN		(0x00004000)
+#define INT_EP_CTL_TDFO_EN		(0x00002000)
+#define INT_EP_CTL_RX_FIFO_EN		(0x00001000)
+#define INT_EP_CTL_GPIOX_EN		(0x00000FFF)
+
+#define BULK_IN_DLY			(0x003C)
+#define BULK_IN_DLY_MASK		(0xFFFF)
+
+#define E2P_CMD				(0x0040)
+#define E2P_CMD_BUSY			(0x80000000)
+#define E2P_CMD_MASK			(0x70000000)
+#define E2P_CMD_READ			(0x00000000)
+#define E2P_CMD_EWDS			(0x10000000)
+#define E2P_CMD_EWEN			(0x20000000)
+#define E2P_CMD_WRITE			(0x30000000)
+#define E2P_CMD_WRAL			(0x40000000)
+#define E2P_CMD_ERASE			(0x50000000)
+#define E2P_CMD_ERAL			(0x60000000)
+#define E2P_CMD_RELOAD			(0x70000000)
+#define E2P_CMD_TIMEOUT			(0x00000400)
+#define E2P_CMD_LOADED			(0x00000200)
+#define E2P_CMD_ADDR			(0x000001FF)
+
+#define MAX_EEPROM_SIZE			(512)
+
+#define E2P_DATA			(0x0044)
+#define E2P_DATA_MASK_			(0x000000FF)
+
+#define RFE_CTL				(0x0060)
+#define RFE_CTL_TCPUDP_CKM		(0x00001000)
+#define RFE_CTL_IP_CKM			(0x00000800)
+#define RFE_CTL_AB			(0x00000400)
+#define RFE_CTL_AM			(0x00000200)
+#define RFE_CTL_AU			(0x00000100)
+#define RFE_CTL_VS			(0x00000080)
+#define RFE_CTL_UF			(0x00000040)
+#define RFE_CTL_VF			(0x00000020)
+#define RFE_CTL_SPF			(0x00000010)
+#define RFE_CTL_MHF			(0x00000008)
+#define RFE_CTL_DHF			(0x00000004)
+#define RFE_CTL_DPF			(0x00000002)
+#define RFE_CTL_RST_RF			(0x00000001)
+
+#define VLAN_TYPE			(0x0064)
+#define VLAN_TYPE_MASK			(0x0000FFFF)
+
+#define FCT_RX_CTL			(0x0090)
+#define FCT_RX_CTL_EN			(0x80000000)
+#define FCT_RX_CTL_RST			(0x40000000)
+#define FCT_RX_CTL_SBF			(0x02000000)
+#define FCT_RX_CTL_OVERFLOW		(0x01000000)
+#define FCT_RX_CTL_FRM_DROP		(0x00800000)
+#define FCT_RX_CTL_RX_NOT_EMPTY		(0x00400000)
+#define FCT_RX_CTL_RX_EMPTY		(0x00200000)
+#define FCT_RX_CTL_RX_DISABLED		(0x00100000)
+#define FCT_RX_CTL_RXUSED		(0x0000FFFF)
+
+#define FCT_TX_CTL			(0x0094)
+#define FCT_TX_CTL_EN			(0x80000000)
+#define FCT_TX_CTL_RST			(0x40000000)
+#define FCT_TX_CTL_TX_NOT_EMPTY		(0x00400000)
+#define FCT_TX_CTL_TX_EMPTY		(0x00200000)
+#define FCT_TX_CTL_TX_DISABLED		(0x00100000)
+#define FCT_TX_CTL_TXUSED		(0x0000FFFF)
+
+#define FCT_RX_FIFO_END			(0x0098)
+#define FCT_RX_FIFO_END_MASK		(0x0000007F)
+
+#define FCT_TX_FIFO_END			(0x009C)
+#define FCT_TX_FIFO_END_MASK		(0x0000003F)
+
+#define FCT_FLOW			(0x00A0)
+#define FCT_FLOW_THRESHOLD_OFF		(0x00007F00)
+#define FCT_FLOW_THRESHOLD_OFF_SHIFT	(8)
+#define FCT_FLOW_THRESHOLD_ON		(0x0000007F)
+
+/* MAC CSRs */
+#define MAC_CR				(0x100)
+#define MAC_CR_ADP			(0x00002000)
+#define MAC_CR_ADD			(0x00001000)
+#define MAC_CR_ASD			(0x00000800)
+#define MAC_CR_INT_LOOP			(0x00000400)
+#define MAC_CR_BOLMT			(0x000000C0)
+#define MAC_CR_FDPX			(0x00000008)
+#define MAC_CR_CFG			(0x00000006)
+#define MAC_CR_CFG_10			(0x00000000)
+#define MAC_CR_CFG_100			(0x00000002)
+#define MAC_CR_CFG_1000			(0x00000004)
+#define MAC_CR_RST			(0x00000001)
+
+#define MAC_RX				(0x104)
+#define MAC_RX_MAX_SIZE			(0x3FFF0000)
+#define MAC_RX_MAX_SIZE_SHIFT		(16)
+#define MAC_RX_FCS_STRIP		(0x00000010)
+#define MAC_RX_FSE			(0x00000004)
+#define MAC_RX_RXD			(0x00000002)
+#define MAC_RX_RXEN			(0x00000001)
+
+#define MAC_TX				(0x108)
+#define MAC_TX_BFCS			(0x00000004)
+#define MAC_TX_TXD			(0x00000002)
+#define MAC_TX_TXEN			(0x00000001)
+
+#define FLOW				(0x10C)
+#define FLOW_FORCE_FC			(0x80000000)
+#define FLOW_TX_FCEN			(0x40000000)
+#define FLOW_RX_FCEN			(0x20000000)
+#define FLOW_FPF			(0x10000000)
+#define FLOW_PAUSE_TIME			(0x0000FFFF)
+
+#define RAND_SEED			(0x110)
+#define RAND_SEED_MASK			(0x0000FFFF)
+
+#define ERR_STS				(0x114)
+#define ERR_STS_FCS_ERR			(0x00000100)
+#define ERR_STS_LFRM_ERR		(0x00000080)
+#define ERR_STS_RUNT_ERR		(0x00000040)
+#define ERR_STS_COLLISION_ERR		(0x00000010)
+#define ERR_STS_ALIGN_ERR		(0x00000008)
+#define ERR_STS_URUN_ERR		(0x00000004)
+
+#define RX_ADDRH			(0x118)
+#define RX_ADDRH_MASK			(0x0000FFFF)
+
+#define RX_ADDRL			(0x11C)
+
+#define MII_ACCESS			(0x120)
+#define MII_ACCESS_PHY_ADDR		(0x0000F800)
+#define MII_ACCESS_PHY_ADDR_SHIFT	(11)
+#define MII_ACCESS_REG_ADDR		(0x000007C0)
+#define MII_ACCESS_REG_ADDR_SHIFT	(6)
+#define MII_ACCESS_READ			(0x00000000)
+#define MII_ACCESS_WRITE		(0x00000002)
+#define MII_ACCESS_BUSY			(0x00000001)
+
+#define MII_DATA			(0x124)
+#define MII_DATA_MASK			(0x0000FFFF)
+
+#define WUCSR				(0x140)
+#define WUCSR_PFDA_FR			(0x00000080)
+#define WUCSR_WUFR			(0x00000040)
+#define WUCSR_MPR			(0x00000020)
+#define WUCSR_BCAST_FR			(0x00000010)
+#define WUCSR_PFDA_EN			(0x00000008)
+#define WUCSR_WUEN			(0x00000004)
+#define WUCSR_MPEN			(0x00000002)
+#define WUCSR_BCST_EN			(0x00000001)
+
+#define WUF_CFGX			(0x144)
+#define WUF_CFGX_EN			(0x80000000)
+#define WUF_CFGX_ATYPE			(0x03000000)
+#define WUF_CFGX_ATYPE_UNICAST		(0x00000000)
+#define WUF_CFGX_ATYPE_MULTICAST	(0x02000000)
+#define WUF_CFGX_ATYPE_ALL		(0x03000000)
+#define WUF_CFGX_PATTERN_OFFSET		(0x007F0000)
+#define WUF_CFGX_PATTERN_OFFSET_SHIFT	(16)
+#define WUF_CFGX_CRC16			(0x0000FFFF)
+#define WUF_NUM				(8)
+
+#define WUF_MASKX			(0x170)
+#define WUF_MASKX_AVALID		(0x80000000)
+#define WUF_MASKX_ATYPE			(0x40000000)
+
+#define ADDR_FILTX			(0x300)
+#define ADDR_FILTX_FB_VALID		(0x80000000)
+#define ADDR_FILTX_FB_TYPE		(0x40000000)
+#define ADDR_FILTX_FB_ADDRHI		(0x0000FFFF)
+#define ADDR_FILTX_SB_ADDRLO		(0xFFFFFFFF)
+
+#define WUCSR2				(0x500)
+#define WUCSR2_NS_RCD			(0x00000040)
+#define WUCSR2_ARP_RCD			(0x00000020)
+#define WUCSR2_TCPSYN_RCD		(0x00000010)
+#define WUCSR2_NS_OFFLOAD		(0x00000004)
+#define WUCSR2_ARP_OFFLOAD		(0x00000002)
+#define WUCSR2_TCPSYN_OFFLOAD		(0x00000001)
+
+#define WOL_FIFO_STS			(0x504)
+
+#define IPV6_ADDRX			(0x510)
+
+#define IPV4_ADDRX			(0x590)
+
+
+/* Vendor-specific PHY Definitions */
+
+/* Mode Control/Status Register */
+#define PHY_MODE_CTRL_STS		(17)
+#define MODE_CTRL_STS_EDPWRDOWN		((u16)0x2000)
+#define MODE_CTRL_STS_ENERGYON		((u16)0x0002)
+
+#define PHY_INT_SRC			(29)
+#define PHY_INT_SRC_ENERGY_ON		((u16)0x0080)
+#define PHY_INT_SRC_ANEG_COMP		((u16)0x0040)
+#define PHY_INT_SRC_REMOTE_FAULT	((u16)0x0020)
+#define PHY_INT_SRC_LINK_DOWN		((u16)0x0010)
+
+#define PHY_INT_MASK			(30)
+#define PHY_INT_MASK_ENERGY_ON		((u16)0x0080)
+#define PHY_INT_MASK_ANEG_COMP		((u16)0x0040)
+#define PHY_INT_MASK_REMOTE_FAULT	((u16)0x0020)
+#define PHY_INT_MASK_LINK_DOWN		((u16)0x0010)
+#define PHY_INT_MASK_DEFAULT		(PHY_INT_MASK_ANEG_COMP | \
+					 PHY_INT_MASK_LINK_DOWN)
+
+#define PHY_SPECIAL			(31)
+#define PHY_SPECIAL_SPD			((u16)0x001C)
+#define PHY_SPECIAL_SPD_10HALF		((u16)0x0004)
+#define PHY_SPECIAL_SPD_10FULL		((u16)0x0014)
+#define PHY_SPECIAL_SPD_100HALF		((u16)0x0008)
+#define PHY_SPECIAL_SPD_100FULL		((u16)0x0018)
+
+/* USB Vendor Requests */
+#define USB_VENDOR_REQUEST_WRITE_REGISTER	0xA0
+#define USB_VENDOR_REQUEST_READ_REGISTER	0xA1
+#define USB_VENDOR_REQUEST_GET_STATS		0xA2
+
+/* Interrupt Endpoint status word bitfields */
+#define INT_ENP_RDFO_INT		((u32)BIT(22))
+#define INT_ENP_TXE_INT			((u32)BIT(21))
+#define INT_ENP_TX_DIS_INT		((u32)BIT(19))
+#define INT_ENP_RX_DIS_INT		((u32)BIT(18))
+#define INT_ENP_PHY_INT			((u32)BIT(17))
+#define INT_ENP_MAC_ERR_INT		((u32)BIT(15))
+#define INT_ENP_RX_FIFO_DATA_INT	((u32)BIT(12))
+
+#endif /* _SMSC75XX_H */
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc95xx.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc95xx.c
new file mode 100644
index 0000000..13f5071
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc95xx.c
@@ -0,0 +1,1311 @@
+ /***************************************************************************
+ *
+ * Copyright (C) 2007-2008 SMSC
+ *
+ * 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 <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+#include "smsc95xx.h"
+
+#define SMSC_CHIPNAME			"smsc95xx"
+#define SMSC_DRIVER_VERSION		"1.0.4"
+#define HS_USB_PKT_SIZE			(512)
+#define FS_USB_PKT_SIZE			(64)
+#define DEFAULT_HS_BURST_CAP_SIZE	(16 * 1024 + 5 * HS_USB_PKT_SIZE)
+#define DEFAULT_FS_BURST_CAP_SIZE	(6 * 1024 + 33 * FS_USB_PKT_SIZE)
+#define DEFAULT_BULK_IN_DELAY		(0x00002000)
+#define MAX_SINGLE_PACKET_SIZE		(2048)
+#define LAN95XX_EEPROM_MAGIC		(0x9500)
+#define EEPROM_MAC_OFFSET		(0x01)
+#define DEFAULT_TX_CSUM_ENABLE		(true)
+#define DEFAULT_RX_CSUM_ENABLE		(true)
+#define SMSC95XX_INTERNAL_PHY_ID	(1)
+#define SMSC95XX_TX_OVERHEAD		(8)
+#define SMSC95XX_TX_OVERHEAD_CSUM	(12)
+
+struct smsc95xx_priv {
+	u32 mac_cr;
+	u32 hash_hi;
+	u32 hash_lo;
+	spinlock_t mac_cr_lock;
+};
+
+struct usb_context {
+	struct usb_ctrlrequest req;
+	struct usbnet *dev;
+};
+
+static bool turbo_mode = true;
+module_param(turbo_mode, bool, 0644);
+MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction");
+
+static int smsc95xx_read_reg(struct usbnet *dev, u32 index, u32 *data)
+{
+	u32 *buf = kmalloc(4, GFP_KERNEL);
+	int ret;
+
+	BUG_ON(!dev);
+
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+		USB_VENDOR_REQUEST_READ_REGISTER,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		00, index, buf, 4, USB_CTRL_GET_TIMEOUT);
+
+	if (unlikely(ret < 0))
+		netdev_warn(dev->net, "Failed to read register index 0x%08x\n", index);
+
+	le32_to_cpus(buf);
+	*data = *buf;
+	kfree(buf);
+
+	return ret;
+}
+
+static int smsc95xx_write_reg(struct usbnet *dev, u32 index, u32 data)
+{
+	u32 *buf = kmalloc(4, GFP_KERNEL);
+	int ret;
+
+	BUG_ON(!dev);
+
+	if (!buf)
+		return -ENOMEM;
+
+	*buf = data;
+	cpu_to_le32s(buf);
+
+	ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+		USB_VENDOR_REQUEST_WRITE_REGISTER,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		00, index, buf, 4, USB_CTRL_SET_TIMEOUT);
+
+	if (unlikely(ret < 0))
+		netdev_warn(dev->net, "Failed to write register index 0x%08x\n", index);
+
+	kfree(buf);
+
+	return ret;
+}
+
+/* Loop until the read is completed with timeout
+ * called with phy_mutex held */
+static int smsc95xx_phy_wait_not_busy(struct usbnet *dev)
+{
+	unsigned long start_time = jiffies;
+	u32 val;
+
+	do {
+		smsc95xx_read_reg(dev, MII_ADDR, &val);
+		if (!(val & MII_BUSY_))
+			return 0;
+	} while (!time_after(jiffies, start_time + HZ));
+
+	return -EIO;
+}
+
+static int smsc95xx_mdio_read(struct net_device *netdev, int phy_id, int idx)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	u32 val, addr;
+
+	mutex_lock(&dev->phy_mutex);
+
+	/* confirm MII not busy */
+	if (smsc95xx_phy_wait_not_busy(dev)) {
+		netdev_warn(dev->net, "MII is busy in smsc95xx_mdio_read\n");
+		mutex_unlock(&dev->phy_mutex);
+		return -EIO;
+	}
+
+	/* set the address, index & direction (read from PHY) */
+	phy_id &= dev->mii.phy_id_mask;
+	idx &= dev->mii.reg_num_mask;
+	addr = (phy_id << 11) | (idx << 6) | MII_READ_;
+	smsc95xx_write_reg(dev, MII_ADDR, addr);
+
+	if (smsc95xx_phy_wait_not_busy(dev)) {
+		netdev_warn(dev->net, "Timed out reading MII reg %02X\n", idx);
+		mutex_unlock(&dev->phy_mutex);
+		return -EIO;
+	}
+
+	smsc95xx_read_reg(dev, MII_DATA, &val);
+
+	mutex_unlock(&dev->phy_mutex);
+
+	return (u16)(val & 0xFFFF);
+}
+
+static void smsc95xx_mdio_write(struct net_device *netdev, int phy_id, int idx,
+				int regval)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	u32 val, addr;
+
+	mutex_lock(&dev->phy_mutex);
+
+	/* confirm MII not busy */
+	if (smsc95xx_phy_wait_not_busy(dev)) {
+		netdev_warn(dev->net, "MII is busy in smsc95xx_mdio_write\n");
+		mutex_unlock(&dev->phy_mutex);
+		return;
+	}
+
+	val = regval;
+	smsc95xx_write_reg(dev, MII_DATA, val);
+
+	/* set the address, index & direction (write to PHY) */
+	phy_id &= dev->mii.phy_id_mask;
+	idx &= dev->mii.reg_num_mask;
+	addr = (phy_id << 11) | (idx << 6) | MII_WRITE_;
+	smsc95xx_write_reg(dev, MII_ADDR, addr);
+
+	if (smsc95xx_phy_wait_not_busy(dev))
+		netdev_warn(dev->net, "Timed out writing MII reg %02X\n", idx);
+
+	mutex_unlock(&dev->phy_mutex);
+}
+
+static int smsc95xx_wait_eeprom(struct usbnet *dev)
+{
+	unsigned long start_time = jiffies;
+	u32 val;
+
+	do {
+		smsc95xx_read_reg(dev, E2P_CMD, &val);
+		if (!(val & E2P_CMD_BUSY_) || (val & E2P_CMD_TIMEOUT_))
+			break;
+		udelay(40);
+	} while (!time_after(jiffies, start_time + HZ));
+
+	if (val & (E2P_CMD_TIMEOUT_ | E2P_CMD_BUSY_)) {
+		netdev_warn(dev->net, "EEPROM read operation timeout\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int smsc95xx_eeprom_confirm_not_busy(struct usbnet *dev)
+{
+	unsigned long start_time = jiffies;
+	u32 val;
+
+	do {
+		smsc95xx_read_reg(dev, E2P_CMD, &val);
+
+		if (!(val & E2P_CMD_BUSY_))
+			return 0;
+
+		udelay(40);
+	} while (!time_after(jiffies, start_time + HZ));
+
+	netdev_warn(dev->net, "EEPROM is busy\n");
+	return -EIO;
+}
+
+static int smsc95xx_read_eeprom(struct usbnet *dev, u32 offset, u32 length,
+				u8 *data)
+{
+	u32 val;
+	int i, ret;
+
+	BUG_ON(!dev);
+	BUG_ON(!data);
+
+	ret = smsc95xx_eeprom_confirm_not_busy(dev);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < length; i++) {
+		val = E2P_CMD_BUSY_ | E2P_CMD_READ_ | (offset & E2P_CMD_ADDR_);
+		smsc95xx_write_reg(dev, E2P_CMD, val);
+
+		ret = smsc95xx_wait_eeprom(dev);
+		if (ret < 0)
+			return ret;
+
+		smsc95xx_read_reg(dev, E2P_DATA, &val);
+
+		data[i] = val & 0xFF;
+		offset++;
+	}
+
+	return 0;
+}
+
+static int smsc95xx_write_eeprom(struct usbnet *dev, u32 offset, u32 length,
+				 u8 *data)
+{
+	u32 val;
+	int i, ret;
+
+	BUG_ON(!dev);
+	BUG_ON(!data);
+
+	ret = smsc95xx_eeprom_confirm_not_busy(dev);
+	if (ret)
+		return ret;
+
+	/* Issue write/erase enable command */
+	val = E2P_CMD_BUSY_ | E2P_CMD_EWEN_;
+	smsc95xx_write_reg(dev, E2P_CMD, val);
+
+	ret = smsc95xx_wait_eeprom(dev);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < length; i++) {
+
+		/* Fill data register */
+		val = data[i];
+		smsc95xx_write_reg(dev, E2P_DATA, val);
+
+		/* Send "write" command */
+		val = E2P_CMD_BUSY_ | E2P_CMD_WRITE_ | (offset & E2P_CMD_ADDR_);
+		smsc95xx_write_reg(dev, E2P_CMD, val);
+
+		ret = smsc95xx_wait_eeprom(dev);
+		if (ret < 0)
+			return ret;
+
+		offset++;
+	}
+
+	return 0;
+}
+
+static void smsc95xx_async_cmd_callback(struct urb *urb)
+{
+	struct usb_context *usb_context = urb->context;
+	struct usbnet *dev = usb_context->dev;
+	int status = urb->status;
+
+	if (status < 0)
+		netdev_warn(dev->net, "async callback failed with %d\n", status);
+
+	kfree(usb_context);
+	usb_free_urb(urb);
+}
+
+static int smsc95xx_write_reg_async(struct usbnet *dev, u16 index, u32 *data)
+{
+	struct usb_context *usb_context;
+	int status;
+	struct urb *urb;
+	const u16 size = 4;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb) {
+		netdev_warn(dev->net, "Error allocating URB\n");
+		return -ENOMEM;
+	}
+
+	usb_context = kmalloc(sizeof(struct usb_context), GFP_ATOMIC);
+	if (usb_context == NULL) {
+		netdev_warn(dev->net, "Error allocating control msg\n");
+		usb_free_urb(urb);
+		return -ENOMEM;
+	}
+
+	usb_context->req.bRequestType =
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+	usb_context->req.bRequest = USB_VENDOR_REQUEST_WRITE_REGISTER;
+	usb_context->req.wValue = 00;
+	usb_context->req.wIndex = cpu_to_le16(index);
+	usb_context->req.wLength = cpu_to_le16(size);
+
+	usb_fill_control_urb(urb, dev->udev, usb_sndctrlpipe(dev->udev, 0),
+		(void *)&usb_context->req, data, size,
+		smsc95xx_async_cmd_callback,
+		(void *)usb_context);
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0) {
+		netdev_warn(dev->net, "Error submitting control msg, sts=%d\n",
+			    status);
+		kfree(usb_context);
+		usb_free_urb(urb);
+	}
+
+	return status;
+}
+
+/* returns hash bit number for given MAC address
+ * example:
+ * 01 00 5E 00 00 01 -> returns bit number 31 */
+static unsigned int smsc95xx_hash(char addr[ETH_ALEN])
+{
+	return (ether_crc(ETH_ALEN, addr) >> 26) & 0x3f;
+}
+
+static void smsc95xx_set_multicast(struct net_device *netdev)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	unsigned long flags;
+
+	pdata->hash_hi = 0;
+	pdata->hash_lo = 0;
+
+	spin_lock_irqsave(&pdata->mac_cr_lock, flags);
+
+	if (dev->net->flags & IFF_PROMISC) {
+		netif_dbg(dev, drv, dev->net, "promiscuous mode enabled\n");
+		pdata->mac_cr |= MAC_CR_PRMS_;
+		pdata->mac_cr &= ~(MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+	} else if (dev->net->flags & IFF_ALLMULTI) {
+		netif_dbg(dev, drv, dev->net, "receive all multicast enabled\n");
+		pdata->mac_cr |= MAC_CR_MCPAS_;
+		pdata->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_HPFILT_);
+	} else if (!netdev_mc_empty(dev->net)) {
+		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(ha, netdev) {
+			u32 bitnum = smsc95xx_hash(ha->addr);
+			u32 mask = 0x01 << (bitnum & 0x1F);
+			if (bitnum & 0x20)
+				pdata->hash_hi |= mask;
+			else
+				pdata->hash_lo |= mask;
+		}
+
+		netif_dbg(dev, drv, dev->net, "HASHH=0x%08X, HASHL=0x%08X\n",
+				   pdata->hash_hi, pdata->hash_lo);
+	} else {
+		netif_dbg(dev, drv, dev->net, "receive own packets only\n");
+		pdata->mac_cr &=
+			~(MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+	}
+
+	spin_unlock_irqrestore(&pdata->mac_cr_lock, flags);
+
+	/* Initiate async writes, as we can't wait for completion here */
+	smsc95xx_write_reg_async(dev, HASHH, &pdata->hash_hi);
+	smsc95xx_write_reg_async(dev, HASHL, &pdata->hash_lo);
+	smsc95xx_write_reg_async(dev, MAC_CR, &pdata->mac_cr);
+}
+
+static void smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex,
+					    u16 lcladv, u16 rmtadv)
+{
+	u32 flow, afc_cfg = 0;
+
+	int ret = smsc95xx_read_reg(dev, AFC_CFG, &afc_cfg);
+	if (ret < 0) {
+		netdev_warn(dev->net, "error reading AFC_CFG\n");
+		return;
+	}
+
+	if (duplex == DUPLEX_FULL) {
+		u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv);
+
+		if (cap & FLOW_CTRL_RX)
+			flow = 0xFFFF0002;
+		else
+			flow = 0;
+
+		if (cap & FLOW_CTRL_TX)
+			afc_cfg |= 0xF;
+		else
+			afc_cfg &= ~0xF;
+
+		netif_dbg(dev, link, dev->net, "rx pause %s, tx pause %s\n",
+				   cap & FLOW_CTRL_RX ? "enabled" : "disabled",
+				   cap & FLOW_CTRL_TX ? "enabled" : "disabled");
+	} else {
+		netif_dbg(dev, link, dev->net, "half duplex\n");
+		flow = 0;
+		afc_cfg |= 0xF;
+	}
+
+	smsc95xx_write_reg(dev, FLOW, flow);
+	smsc95xx_write_reg(dev,	AFC_CFG, afc_cfg);
+}
+
+static int smsc95xx_link_reset(struct usbnet *dev)
+{
+	struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	struct mii_if_info *mii = &dev->mii;
+	struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET };
+	unsigned long flags;
+	u16 lcladv, rmtadv;
+	u32 intdata;
+
+	/* clear interrupt status */
+	smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC);
+	intdata = 0xFFFFFFFF;
+	smsc95xx_write_reg(dev, INT_STS, intdata);
+
+	mii_check_media(mii, 1, 1);
+	mii_ethtool_gset(&dev->mii, &ecmd);
+	lcladv = smsc95xx_mdio_read(dev->net, mii->phy_id, MII_ADVERTISE);
+	rmtadv = smsc95xx_mdio_read(dev->net, mii->phy_id, MII_LPA);
+
+	netif_dbg(dev, link, dev->net,
+		  "speed: %u duplex: %d lcladv: %04x rmtadv: %04x\n",
+		  ethtool_cmd_speed(&ecmd), ecmd.duplex, lcladv, rmtadv);
+
+	spin_lock_irqsave(&pdata->mac_cr_lock, flags);
+	if (ecmd.duplex != DUPLEX_FULL) {
+		pdata->mac_cr &= ~MAC_CR_FDPX_;
+		pdata->mac_cr |= MAC_CR_RCVOWN_;
+	} else {
+		pdata->mac_cr &= ~MAC_CR_RCVOWN_;
+		pdata->mac_cr |= MAC_CR_FDPX_;
+	}
+	spin_unlock_irqrestore(&pdata->mac_cr_lock, flags);
+
+	smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr);
+
+	smsc95xx_phy_update_flowcontrol(dev, ecmd.duplex, lcladv, rmtadv);
+
+	return 0;
+}
+
+static void smsc95xx_status(struct usbnet *dev, struct urb *urb)
+{
+	u32 intdata;
+
+	if (urb->actual_length != 4) {
+		netdev_warn(dev->net, "unexpected urb length %d\n",
+			    urb->actual_length);
+		return;
+	}
+
+	memcpy(&intdata, urb->transfer_buffer, 4);
+	le32_to_cpus(&intdata);
+
+	netif_dbg(dev, link, dev->net, "intdata: 0x%08X\n", intdata);
+
+	if (intdata & INT_ENP_PHY_INT_)
+		usbnet_defer_kevent(dev, EVENT_LINK_RESET);
+	else
+		netdev_warn(dev->net, "unexpected interrupt, intdata=0x%08X\n",
+			    intdata);
+}
+
+/* Enable or disable Tx & Rx checksum offload engines */
+static int smsc95xx_set_features(struct net_device *netdev,
+	netdev_features_t features)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+	u32 read_buf;
+	int ret;
+
+	ret = smsc95xx_read_reg(dev, COE_CR, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read COE_CR: %d\n", ret);
+		return ret;
+	}
+
+	if (features & NETIF_F_HW_CSUM)
+		read_buf |= Tx_COE_EN_;
+	else
+		read_buf &= ~Tx_COE_EN_;
+
+	if (features & NETIF_F_RXCSUM)
+		read_buf |= Rx_COE_EN_;
+	else
+		read_buf &= ~Rx_COE_EN_;
+
+	ret = smsc95xx_write_reg(dev, COE_CR, read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write COE_CR: %d\n", ret);
+		return ret;
+	}
+
+	netif_dbg(dev, hw, dev->net, "COE_CR = 0x%08x\n", read_buf);
+	return 0;
+}
+
+static int smsc95xx_ethtool_get_eeprom_len(struct net_device *net)
+{
+	return MAX_EEPROM_SIZE;
+}
+
+static int smsc95xx_ethtool_get_eeprom(struct net_device *netdev,
+				       struct ethtool_eeprom *ee, u8 *data)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	ee->magic = LAN95XX_EEPROM_MAGIC;
+
+	return smsc95xx_read_eeprom(dev, ee->offset, ee->len, data);
+}
+
+static int smsc95xx_ethtool_set_eeprom(struct net_device *netdev,
+				       struct ethtool_eeprom *ee, u8 *data)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	if (ee->magic != LAN95XX_EEPROM_MAGIC) {
+		netdev_warn(dev->net, "EEPROM: magic value mismatch, magic = 0x%x\n",
+			    ee->magic);
+		return -EINVAL;
+	}
+
+	return smsc95xx_write_eeprom(dev, ee->offset, ee->len, data);
+}
+
+static const struct ethtool_ops smsc95xx_ethtool_ops = {
+	.get_link	= usbnet_get_link,
+	.nway_reset	= usbnet_nway_reset,
+	.get_drvinfo	= usbnet_get_drvinfo,
+	.get_msglevel	= usbnet_get_msglevel,
+	.set_msglevel	= usbnet_set_msglevel,
+	.get_settings	= usbnet_get_settings,
+	.set_settings	= usbnet_set_settings,
+	.get_eeprom_len	= smsc95xx_ethtool_get_eeprom_len,
+	.get_eeprom	= smsc95xx_ethtool_get_eeprom,
+	.set_eeprom	= smsc95xx_ethtool_set_eeprom,
+};
+
+static int smsc95xx_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
+{
+	struct usbnet *dev = netdev_priv(netdev);
+
+	if (!netif_running(netdev))
+		return -EINVAL;
+
+	return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
+}
+
+static void smsc95xx_init_mac_address(struct usbnet *dev)
+{
+	/* try reading mac address from EEPROM */
+	if (smsc95xx_read_eeprom(dev, EEPROM_MAC_OFFSET, ETH_ALEN,
+			dev->net->dev_addr) == 0) {
+		if (is_valid_ether_addr(dev->net->dev_addr)) {
+			/* eeprom values are valid so use them */
+			netif_dbg(dev, ifup, dev->net, "MAC address read from EEPROM\n");
+			return;
+		}
+	}
+
+	/* no eeprom, or eeprom values are invalid. generate random MAC */
+	eth_hw_addr_random(dev->net);
+	netif_dbg(dev, ifup, dev->net, "MAC address set to random_ether_addr\n");
+}
+
+static int smsc95xx_set_mac_address(struct usbnet *dev)
+{
+	u32 addr_lo = dev->net->dev_addr[0] | dev->net->dev_addr[1] << 8 |
+		dev->net->dev_addr[2] << 16 | dev->net->dev_addr[3] << 24;
+	u32 addr_hi = dev->net->dev_addr[4] | dev->net->dev_addr[5] << 8;
+	int ret;
+
+	ret = smsc95xx_write_reg(dev, ADDRL, addr_lo);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write ADDRL: %d\n", ret);
+		return ret;
+	}
+
+	ret = smsc95xx_write_reg(dev, ADDRH, addr_hi);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write ADDRH: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* starts the TX path */
+static void smsc95xx_start_tx_path(struct usbnet *dev)
+{
+	struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	unsigned long flags;
+	u32 reg_val;
+
+	/* Enable Tx at MAC */
+	spin_lock_irqsave(&pdata->mac_cr_lock, flags);
+	pdata->mac_cr |= MAC_CR_TXEN_;
+	spin_unlock_irqrestore(&pdata->mac_cr_lock, flags);
+
+	smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr);
+
+	/* Enable Tx at SCSRs */
+	reg_val = TX_CFG_ON_;
+	smsc95xx_write_reg(dev, TX_CFG, reg_val);
+}
+
+/* Starts the Receive path */
+static void smsc95xx_start_rx_path(struct usbnet *dev)
+{
+	struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->mac_cr_lock, flags);
+	pdata->mac_cr |= MAC_CR_RXEN_;
+	spin_unlock_irqrestore(&pdata->mac_cr_lock, flags);
+
+	smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr);
+}
+
+static int smsc95xx_phy_initialize(struct usbnet *dev)
+{
+	int bmcr, timeout = 0;
+
+	/* Initialize MII structure */
+	dev->mii.dev = dev->net;
+	dev->mii.mdio_read = smsc95xx_mdio_read;
+	dev->mii.mdio_write = smsc95xx_mdio_write;
+	dev->mii.phy_id_mask = 0x1f;
+	dev->mii.reg_num_mask = 0x1f;
+	dev->mii.phy_id = SMSC95XX_INTERNAL_PHY_ID;
+
+	/* reset phy and wait for reset to complete */
+	smsc95xx_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
+
+	do {
+		msleep(10);
+		bmcr = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, MII_BMCR);
+		timeout++;
+	} while ((bmcr & BMCR_RESET) && (timeout < 100));
+
+	if (timeout >= 100) {
+		netdev_warn(dev->net, "timeout on PHY Reset");
+		return -EIO;
+	}
+
+	smsc95xx_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE,
+		ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP |
+		ADVERTISE_PAUSE_ASYM);
+
+	/* read to clear */
+	smsc95xx_mdio_read(dev->net, dev->mii.phy_id, PHY_INT_SRC);
+
+	smsc95xx_mdio_write(dev->net, dev->mii.phy_id, PHY_INT_MASK,
+		PHY_INT_MASK_DEFAULT_);
+	mii_nway_restart(&dev->mii);
+
+	netif_dbg(dev, ifup, dev->net, "phy initialised successfully\n");
+	return 0;
+}
+
+static int smsc95xx_reset(struct usbnet *dev)
+{
+	struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	u32 read_buf, write_buf, burst_cap;
+	int ret = 0, timeout;
+
+	netif_dbg(dev, ifup, dev->net, "entering smsc95xx_reset\n");
+
+	write_buf = HW_CFG_LRST_;
+	ret = smsc95xx_write_reg(dev, HW_CFG, write_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write HW_CFG_LRST_ bit in HW_CFG register, ret = %d\n",
+			    ret);
+		return ret;
+	}
+
+	timeout = 0;
+	do {
+		ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf);
+		if (ret < 0) {
+			netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret);
+			return ret;
+		}
+		msleep(10);
+		timeout++;
+	} while ((read_buf & HW_CFG_LRST_) && (timeout < 100));
+
+	if (timeout >= 100) {
+		netdev_warn(dev->net, "timeout waiting for completion of Lite Reset\n");
+		return ret;
+	}
+
+	write_buf = PM_CTL_PHY_RST_;
+	ret = smsc95xx_write_reg(dev, PM_CTRL, write_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write PM_CTRL: %d\n", ret);
+		return ret;
+	}
+
+	timeout = 0;
+	do {
+		ret = smsc95xx_read_reg(dev, PM_CTRL, &read_buf);
+		if (ret < 0) {
+			netdev_warn(dev->net, "Failed to read PM_CTRL: %d\n", ret);
+			return ret;
+		}
+		msleep(10);
+		timeout++;
+	} while ((read_buf & PM_CTL_PHY_RST_) && (timeout < 100));
+
+	if (timeout >= 100) {
+		netdev_warn(dev->net, "timeout waiting for PHY Reset\n");
+		return ret;
+	}
+
+	ret = smsc95xx_set_mac_address(dev);
+	if (ret < 0)
+		return ret;
+
+	netif_dbg(dev, ifup, dev->net,
+		  "MAC Address: %pM\n", dev->net->dev_addr);
+
+	ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret);
+		return ret;
+	}
+
+	netif_dbg(dev, ifup, dev->net,
+		  "Read Value from HW_CFG : 0x%08x\n", read_buf);
+
+	read_buf |= HW_CFG_BIR_;
+
+	ret = smsc95xx_write_reg(dev, HW_CFG, read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write HW_CFG_BIR_ bit in HW_CFG register, ret = %d\n",
+			    ret);
+		return ret;
+	}
+
+	ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret);
+		return ret;
+	}
+	netif_dbg(dev, ifup, dev->net,
+		  "Read Value from HW_CFG after writing HW_CFG_BIR_: 0x%08x\n",
+		  read_buf);
+
+	if (!turbo_mode) {
+		burst_cap = 0;
+		dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE;
+	} else if (dev->udev->speed == USB_SPEED_HIGH) {
+		burst_cap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE;
+		dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE;
+	} else {
+		burst_cap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE;
+		dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE;
+	}
+
+	netif_dbg(dev, ifup, dev->net,
+		  "rx_urb_size=%ld\n", (ulong)dev->rx_urb_size);
+
+	ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write BURST_CAP: %d\n", ret);
+		return ret;
+	}
+
+	ret = smsc95xx_read_reg(dev, BURST_CAP, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read BURST_CAP: %d\n", ret);
+		return ret;
+	}
+	netif_dbg(dev, ifup, dev->net,
+		  "Read Value from BURST_CAP after writing: 0x%08x\n",
+		  read_buf);
+
+	read_buf = DEFAULT_BULK_IN_DELAY;
+	ret = smsc95xx_write_reg(dev, BULK_IN_DLY, read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "ret = %d\n", ret);
+		return ret;
+	}
+
+	ret = smsc95xx_read_reg(dev, BULK_IN_DLY, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read BULK_IN_DLY: %d\n", ret);
+		return ret;
+	}
+	netif_dbg(dev, ifup, dev->net,
+		  "Read Value from BULK_IN_DLY after writing: 0x%08x\n",
+		  read_buf);
+
+	ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret);
+		return ret;
+	}
+	netif_dbg(dev, ifup, dev->net,
+		  "Read Value from HW_CFG: 0x%08x\n", read_buf);
+
+	if (turbo_mode)
+		read_buf |= (HW_CFG_MEF_ | HW_CFG_BCE_);
+
+	read_buf &= ~HW_CFG_RXDOFF_;
+
+	/* set Rx data offset=2, Make IP header aligns on word boundary. */
+	read_buf |= NET_IP_ALIGN << 9;
+
+	ret = smsc95xx_write_reg(dev, HW_CFG, read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write HW_CFG register, ret=%d\n",
+			    ret);
+		return ret;
+	}
+
+	ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret);
+		return ret;
+	}
+	netif_dbg(dev, ifup, dev->net,
+		  "Read Value from HW_CFG after writing: 0x%08x\n", read_buf);
+
+	write_buf = 0xFFFFFFFF;
+	ret = smsc95xx_write_reg(dev, INT_STS, write_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write INT_STS register, ret=%d\n",
+			    ret);
+		return ret;
+	}
+
+	ret = smsc95xx_read_reg(dev, ID_REV, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read ID_REV: %d\n", ret);
+		return ret;
+	}
+	netif_dbg(dev, ifup, dev->net, "ID_REV = 0x%08x\n", read_buf);
+
+	/* Configure GPIO pins as LED outputs */
+	write_buf = LED_GPIO_CFG_SPD_LED | LED_GPIO_CFG_LNK_LED |
+		LED_GPIO_CFG_FDX_LED;
+	ret = smsc95xx_write_reg(dev, LED_GPIO_CFG, write_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write LED_GPIO_CFG register, ret=%d\n",
+			    ret);
+		return ret;
+	}
+
+	/* Init Tx */
+	write_buf = 0;
+	ret = smsc95xx_write_reg(dev, FLOW, write_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write FLOW: %d\n", ret);
+		return ret;
+	}
+
+	read_buf = AFC_CFG_DEFAULT;
+	ret = smsc95xx_write_reg(dev, AFC_CFG, read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write AFC_CFG: %d\n", ret);
+		return ret;
+	}
+
+	/* Don't need mac_cr_lock during initialisation */
+	ret = smsc95xx_read_reg(dev, MAC_CR, &pdata->mac_cr);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read MAC_CR: %d\n", ret);
+		return ret;
+	}
+
+	/* Init Rx */
+	/* Set Vlan */
+	write_buf = (u32)ETH_P_8021Q;
+	ret = smsc95xx_write_reg(dev, VLAN1, write_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write VAN1: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable or disable checksum offload engines */
+	smsc95xx_set_features(dev->net, dev->net->features);
+
+	smsc95xx_set_multicast(dev->net);
+
+	if (smsc95xx_phy_initialize(dev) < 0)
+		return -EIO;
+
+	ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to read INT_EP_CTL: %d\n", ret);
+		return ret;
+	}
+
+	/* enable PHY interrupts */
+	read_buf |= INT_EP_CTL_PHY_INT_;
+
+	ret = smsc95xx_write_reg(dev, INT_EP_CTL, read_buf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "Failed to write INT_EP_CTL: %d\n", ret);
+		return ret;
+	}
+
+	smsc95xx_start_tx_path(dev);
+	smsc95xx_start_rx_path(dev);
+
+	netif_dbg(dev, ifup, dev->net, "smsc95xx_reset, return 0\n");
+	return 0;
+}
+
+static const struct net_device_ops smsc95xx_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_do_ioctl 		= smsc95xx_ioctl,
+	.ndo_set_rx_mode	= smsc95xx_set_multicast,
+	.ndo_set_features	= smsc95xx_set_features,
+};
+
+static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct smsc95xx_priv *pdata = NULL;
+	int ret;
+
+	printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n");
+
+	ret = usbnet_get_endpoints(dev, intf);
+	if (ret < 0) {
+		netdev_warn(dev->net, "usbnet_get_endpoints failed: %d\n", ret);
+		return ret;
+	}
+
+	dev->data[0] = (unsigned long)kzalloc(sizeof(struct smsc95xx_priv),
+		GFP_KERNEL);
+
+	pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	if (!pdata) {
+		netdev_warn(dev->net, "Unable to allocate struct smsc95xx_priv\n");
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&pdata->mac_cr_lock);
+
+	if (DEFAULT_TX_CSUM_ENABLE)
+		dev->net->features |= NETIF_F_HW_CSUM;
+	if (DEFAULT_RX_CSUM_ENABLE)
+		dev->net->features |= NETIF_F_RXCSUM;
+
+	dev->net->hw_features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM;
+
+	smsc95xx_init_mac_address(dev);
+
+	/* Init all registers */
+	ret = smsc95xx_reset(dev);
+
+	dev->net->netdev_ops = &smsc95xx_netdev_ops;
+	dev->net->ethtool_ops = &smsc95xx_ethtool_ops;
+	dev->net->flags |= IFF_MULTICAST;
+	dev->net->hard_header_len += SMSC95XX_TX_OVERHEAD_CSUM;
+	dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
+	return 0;
+}
+
+static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]);
+	if (pdata) {
+		netif_dbg(dev, ifdown, dev->net, "free pdata\n");
+		kfree(pdata);
+		pdata = NULL;
+		dev->data[0] = 0;
+	}
+}
+
+static void smsc95xx_rx_csum_offload(struct sk_buff *skb)
+{
+	skb->csum = *(u16 *)(skb_tail_pointer(skb) - 2);
+	skb->ip_summed = CHECKSUM_COMPLETE;
+	skb_trim(skb, skb->len - 2);
+}
+
+static int smsc95xx_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+	/* This check is no longer done by usbnet */
+	if (skb->len < dev->net->hard_header_len)
+		return 0;
+
+	while (skb->len > 0) {
+		u32 header, align_count;
+		struct sk_buff *ax_skb;
+		unsigned char *packet;
+		u16 size;
+
+		memcpy(&header, skb->data, sizeof(header));
+		le32_to_cpus(&header);
+		skb_pull(skb, 4 + NET_IP_ALIGN);
+		packet = skb->data;
+
+		/* get the packet length */
+		size = (u16)((header & RX_STS_FL_) >> 16);
+		align_count = (4 - ((size + NET_IP_ALIGN) % 4)) % 4;
+
+		if (unlikely(header & RX_STS_ES_)) {
+			netif_dbg(dev, rx_err, dev->net,
+				  "Error header=0x%08x\n", header);
+			dev->net->stats.rx_errors++;
+			dev->net->stats.rx_dropped++;
+
+			if (header & RX_STS_CRC_) {
+				dev->net->stats.rx_crc_errors++;
+			} else {
+				if (header & (RX_STS_TL_ | RX_STS_RF_))
+					dev->net->stats.rx_frame_errors++;
+
+				if ((header & RX_STS_LE_) &&
+					(!(header & RX_STS_FT_)))
+					dev->net->stats.rx_length_errors++;
+			}
+		} else {
+			/* ETH_FRAME_LEN + 4(CRC) + 2(COE) + 4(Vlan) */
+			if (unlikely(size > (ETH_FRAME_LEN + 12))) {
+				netif_dbg(dev, rx_err, dev->net,
+					  "size err header=0x%08x\n", header);
+				return 0;
+			}
+
+			/* last frame in this batch */
+			if (skb->len == size) {
+				if (dev->net->features & NETIF_F_RXCSUM)
+					smsc95xx_rx_csum_offload(skb);
+				skb_trim(skb, skb->len - 4); /* remove fcs */
+				skb->truesize = size + sizeof(struct sk_buff);
+
+				return 1;
+			}
+
+			ax_skb = skb_clone(skb, GFP_ATOMIC);
+			if (unlikely(!ax_skb)) {
+				netdev_warn(dev->net, "Error allocating skb\n");
+				return 0;
+			}
+
+			ax_skb->len = size;
+			ax_skb->data = packet;
+			skb_set_tail_pointer(ax_skb, size);
+
+			if (dev->net->features & NETIF_F_RXCSUM)
+				smsc95xx_rx_csum_offload(ax_skb);
+			skb_trim(ax_skb, ax_skb->len - 4); /* remove fcs */
+			ax_skb->truesize = size + sizeof(struct sk_buff);
+
+			usbnet_skb_return(dev, ax_skb);
+		}
+
+		skb_pull(skb, size);
+
+		/* padding bytes before the next frame starts */
+		if (skb->len)
+			skb_pull(skb, align_count);
+	}
+
+	if (unlikely(skb->len < 0)) {
+		netdev_warn(dev->net, "invalid rx length<0 %d\n", skb->len);
+		return 0;
+	}
+
+	return 1;
+}
+
+static u32 smsc95xx_calc_csum_preamble(struct sk_buff *skb)
+{
+	u16 low_16 = (u16)skb_checksum_start_offset(skb);
+	u16 high_16 = low_16 + skb->csum_offset;
+	return (high_16 << 16) | low_16;
+}
+
+static struct sk_buff *smsc95xx_tx_fixup(struct usbnet *dev,
+					 struct sk_buff *skb, gfp_t flags)
+{
+	bool csum = skb->ip_summed == CHECKSUM_PARTIAL;
+	int overhead = csum ? SMSC95XX_TX_OVERHEAD_CSUM : SMSC95XX_TX_OVERHEAD;
+	u32 tx_cmd_a, tx_cmd_b;
+
+	/* We do not advertise SG, so skbs should be already linearized */
+	BUG_ON(skb_shinfo(skb)->nr_frags);
+
+	if (skb_headroom(skb) < overhead) {
+		struct sk_buff *skb2 = skb_copy_expand(skb,
+			overhead, 0, flags);
+		dev_kfree_skb_any(skb);
+		skb = skb2;
+		if (!skb)
+			return NULL;
+	}
+
+	if (csum) {
+		if (skb->len <= 45) {
+			/* workaround - hardware tx checksum does not work
+			 * properly with extremely small packets */
+			long csstart = skb_checksum_start_offset(skb);
+			__wsum calc = csum_partial(skb->data + csstart,
+				skb->len - csstart, 0);
+			*((__sum16 *)(skb->data + csstart
+				+ skb->csum_offset)) = csum_fold(calc);
+
+			csum = false;
+		} else {
+			u32 csum_preamble = smsc95xx_calc_csum_preamble(skb);
+			skb_push(skb, 4);
+			memcpy(skb->data, &csum_preamble, 4);
+		}
+	}
+
+	skb_push(skb, 4);
+	tx_cmd_b = (u32)(skb->len - 4);
+	if (csum)
+		tx_cmd_b |= TX_CMD_B_CSUM_ENABLE;
+	cpu_to_le32s(&tx_cmd_b);
+	memcpy(skb->data, &tx_cmd_b, 4);
+
+	skb_push(skb, 4);
+	tx_cmd_a = (u32)(skb->len - 8) | TX_CMD_A_FIRST_SEG_ |
+		TX_CMD_A_LAST_SEG_;
+	cpu_to_le32s(&tx_cmd_a);
+	memcpy(skb->data, &tx_cmd_a, 4);
+
+	return skb;
+}
+
+static const struct driver_info smsc95xx_info = {
+	.description	= "smsc95xx USB 2.0 Ethernet",
+	.bind		= smsc95xx_bind,
+	.unbind		= smsc95xx_unbind,
+	.link_reset	= smsc95xx_link_reset,
+	.reset		= smsc95xx_reset,
+	.rx_fixup	= smsc95xx_rx_fixup,
+	.tx_fixup	= smsc95xx_tx_fixup,
+	.status		= smsc95xx_status,
+	.flags		= FLAG_ETHER | FLAG_SEND_ZLP | FLAG_LINK_INTR,
+};
+
+static const struct usb_device_id products[] = {
+	{
+		/* SMSC9500 USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9500),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9505 USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9505),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9500A USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9E00),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9505A USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9E01),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9512/9514 USB Hub & Ethernet Device */
+		USB_DEVICE(0x0424, 0xec00),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9500 USB Ethernet Device (SAL10) */
+		USB_DEVICE(0x0424, 0x9900),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9505 USB Ethernet Device (SAL10) */
+		USB_DEVICE(0x0424, 0x9901),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9500A USB Ethernet Device (SAL10) */
+		USB_DEVICE(0x0424, 0x9902),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9505A USB Ethernet Device (SAL10) */
+		USB_DEVICE(0x0424, 0x9903),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9512/9514 USB Hub & Ethernet Device (SAL10) */
+		USB_DEVICE(0x0424, 0x9904),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9500A USB Ethernet Device (HAL) */
+		USB_DEVICE(0x0424, 0x9905),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9505A USB Ethernet Device (HAL) */
+		USB_DEVICE(0x0424, 0x9906),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9500 USB Ethernet Device (Alternate ID) */
+		USB_DEVICE(0x0424, 0x9907),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9500A USB Ethernet Device (Alternate ID) */
+		USB_DEVICE(0x0424, 0x9908),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC9512/9514 USB Hub & Ethernet Device (Alternate ID) */
+		USB_DEVICE(0x0424, 0x9909),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC LAN9530 USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9530),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC LAN9730 USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9730),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{
+		/* SMSC LAN89530 USB Ethernet Device */
+		USB_DEVICE(0x0424, 0x9E08),
+		.driver_info = (unsigned long) &smsc95xx_info,
+	},
+	{ },		/* END */
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver smsc95xx_driver = {
+	.name		= "smsc95xx",
+	.id_table	= products,
+	.probe		= usbnet_probe,
+	.suspend	= usbnet_suspend,
+	.resume		= usbnet_resume,
+	.disconnect	= usbnet_disconnect,
+};
+
+module_usb_driver(smsc95xx_driver);
+
+MODULE_AUTHOR("Nancy Lin");
+MODULE_AUTHOR("Steve Glendinning <steve.glendinning@smsc.com>");
+MODULE_DESCRIPTION("SMSC95XX USB 2.0 Ethernet Devices");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc95xx.h b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc95xx.h
new file mode 100644
index 0000000..86bc449
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/smsc95xx.h
@@ -0,0 +1,256 @@
+ /***************************************************************************
+ *
+ * Copyright (C) 2007-2008 SMSC
+ *
+ * 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.
+ *
+ *****************************************************************************/
+
+#ifndef _SMSC95XX_H
+#define _SMSC95XX_H
+
+/* Tx command words */
+#define TX_CMD_A_DATA_OFFSET_		(0x001F0000)
+#define TX_CMD_A_FIRST_SEG_		(0x00002000)
+#define TX_CMD_A_LAST_SEG_		(0x00001000)
+#define TX_CMD_A_BUF_SIZE_		(0x000007FF)
+
+#define TX_CMD_B_CSUM_ENABLE		(0x00004000)
+#define TX_CMD_B_ADD_CRC_DISABLE_	(0x00002000)
+#define TX_CMD_B_DISABLE_PADDING_	(0x00001000)
+#define TX_CMD_B_PKT_BYTE_LENGTH_	(0x000007FF)
+
+/* Rx status word */
+#define RX_STS_FF_			(0x40000000)	/* Filter Fail */
+#define RX_STS_FL_			(0x3FFF0000)	/* Frame Length */
+#define RX_STS_ES_			(0x00008000)	/* Error Summary */
+#define RX_STS_BF_			(0x00002000)	/* Broadcast Frame */
+#define RX_STS_LE_			(0x00001000)	/* Length Error */
+#define RX_STS_RF_			(0x00000800)	/* Runt Frame */
+#define RX_STS_MF_			(0x00000400)	/* Multicast Frame */
+#define RX_STS_TL_			(0x00000080)	/* Frame too long */
+#define RX_STS_CS_			(0x00000040)	/* Collision Seen */
+#define RX_STS_FT_			(0x00000020)	/* Frame Type */
+#define RX_STS_RW_			(0x00000010)	/* Receive Watchdog */
+#define RX_STS_ME_			(0x00000008)	/* Mii Error */
+#define RX_STS_DB_			(0x00000004)	/* Dribbling */
+#define RX_STS_CRC_			(0x00000002)	/* CRC Error */
+
+/* SCSRs */
+#define ID_REV				(0x00)
+#define ID_REV_CHIP_ID_MASK_		(0xFFFF0000)
+#define ID_REV_CHIP_REV_MASK_		(0x0000FFFF)
+#define ID_REV_CHIP_ID_9500_		(0x9500)
+
+#define INT_STS				(0x08)
+#define INT_STS_TX_STOP_		(0x00020000)
+#define INT_STS_RX_STOP_		(0x00010000)
+#define INT_STS_PHY_INT_		(0x00008000)
+#define INT_STS_TXE_			(0x00004000)
+#define INT_STS_TDFU_			(0x00002000)
+#define INT_STS_TDFO_			(0x00001000)
+#define INT_STS_RXDF_			(0x00000800)
+#define INT_STS_GPIOS_			(0x000007FF)
+
+#define RX_CFG				(0x0C)
+#define RX_FIFO_FLUSH_			(0x00000001)
+
+#define TX_CFG				(0x10)
+#define TX_CFG_ON_			(0x00000004)
+#define TX_CFG_STOP_			(0x00000002)
+#define TX_CFG_FIFO_FLUSH_		(0x00000001)
+
+#define HW_CFG				(0x14)
+#define HW_CFG_BIR_			(0x00001000)
+#define HW_CFG_LEDB_			(0x00000800)
+#define HW_CFG_RXDOFF_			(0x00000600)
+#define HW_CFG_DRP_			(0x00000040)
+#define HW_CFG_MEF_			(0x00000020)
+#define HW_CFG_LRST_			(0x00000008)
+#define HW_CFG_PSEL_			(0x00000004)
+#define HW_CFG_BCE_			(0x00000002)
+#define HW_CFG_SRST_			(0x00000001)
+
+#define PM_CTRL				(0x20)
+#define PM_CTL_DEV_RDY_			(0x00000080)
+#define PM_CTL_SUS_MODE_		(0x00000060)
+#define PM_CTL_SUS_MODE_0		(0x00000000)
+#define PM_CTL_SUS_MODE_1		(0x00000020)
+#define PM_CTL_SUS_MODE_2		(0x00000060)
+#define PM_CTL_PHY_RST_			(0x00000010)
+#define PM_CTL_WOL_EN_			(0x00000008)
+#define PM_CTL_ED_EN_			(0x00000004)
+#define PM_CTL_WUPS_			(0x00000003)
+#define PM_CTL_WUPS_NO_			(0x00000000)
+#define PM_CTL_WUPS_ED_			(0x00000001)
+#define PM_CTL_WUPS_WOL_		(0x00000002)
+#define PM_CTL_WUPS_MULTI_		(0x00000003)
+
+#define LED_GPIO_CFG			(0x24)
+#define LED_GPIO_CFG_SPD_LED		(0x01000000)
+#define LED_GPIO_CFG_LNK_LED		(0x00100000)
+#define LED_GPIO_CFG_FDX_LED		(0x00010000)
+
+#define GPIO_CFG			(0x28)
+
+#define AFC_CFG				(0x2C)
+
+/* Hi watermark = 15.5Kb (~10 mtu pkts) */
+/* low watermark = 3k (~2 mtu pkts) */
+/* backpressure duration = ~ 350us */
+/* Apply FC on any frame. */
+#define AFC_CFG_DEFAULT			(0x00F830A1)
+
+#define E2P_CMD				(0x30)
+#define E2P_CMD_BUSY_			(0x80000000)
+#define E2P_CMD_MASK_			(0x70000000)
+#define E2P_CMD_READ_			(0x00000000)
+#define E2P_CMD_EWDS_			(0x10000000)
+#define E2P_CMD_EWEN_			(0x20000000)
+#define E2P_CMD_WRITE_			(0x30000000)
+#define E2P_CMD_WRAL_			(0x40000000)
+#define E2P_CMD_ERASE_			(0x50000000)
+#define E2P_CMD_ERAL_			(0x60000000)
+#define E2P_CMD_RELOAD_			(0x70000000)
+#define E2P_CMD_TIMEOUT_		(0x00000400)
+#define E2P_CMD_LOADED_			(0x00000200)
+#define E2P_CMD_ADDR_			(0x000001FF)
+
+#define MAX_EEPROM_SIZE			(512)
+
+#define E2P_DATA			(0x34)
+#define E2P_DATA_MASK_			(0x000000FF)
+
+#define BURST_CAP			(0x38)
+
+#define GPIO_WAKE			(0x64)
+
+#define INT_EP_CTL			(0x68)
+#define INT_EP_CTL_INTEP_		(0x80000000)
+#define INT_EP_CTL_MACRTO_		(0x00080000)
+#define INT_EP_CTL_TX_STOP_		(0x00020000)
+#define INT_EP_CTL_RX_STOP_		(0x00010000)
+#define INT_EP_CTL_PHY_INT_		(0x00008000)
+#define INT_EP_CTL_TXE_			(0x00004000)
+#define INT_EP_CTL_TDFU_		(0x00002000)
+#define INT_EP_CTL_TDFO_		(0x00001000)
+#define INT_EP_CTL_RXDF_		(0x00000800)
+#define INT_EP_CTL_GPIOS_		(0x000007FF)
+
+#define BULK_IN_DLY			(0x6C)
+
+/* MAC CSRs */
+#define MAC_CR				(0x100)
+#define MAC_CR_RXALL_			(0x80000000)
+#define MAC_CR_RCVOWN_			(0x00800000)
+#define MAC_CR_LOOPBK_			(0x00200000)
+#define MAC_CR_FDPX_			(0x00100000)
+#define MAC_CR_MCPAS_			(0x00080000)
+#define MAC_CR_PRMS_			(0x00040000)
+#define MAC_CR_INVFILT_			(0x00020000)
+#define MAC_CR_PASSBAD_			(0x00010000)
+#define MAC_CR_HFILT_			(0x00008000)
+#define MAC_CR_HPFILT_			(0x00002000)
+#define MAC_CR_LCOLL_			(0x00001000)
+#define MAC_CR_BCAST_			(0x00000800)
+#define MAC_CR_DISRTY_			(0x00000400)
+#define MAC_CR_PADSTR_			(0x00000100)
+#define MAC_CR_BOLMT_MASK		(0x000000C0)
+#define MAC_CR_DFCHK_			(0x00000020)
+#define MAC_CR_TXEN_			(0x00000008)
+#define MAC_CR_RXEN_			(0x00000004)
+
+#define ADDRH				(0x104)
+
+#define ADDRL				(0x108)
+
+#define HASHH				(0x10C)
+
+#define HASHL				(0x110)
+
+#define MII_ADDR			(0x114)
+#define MII_WRITE_			(0x02)
+#define MII_BUSY_			(0x01)
+#define MII_READ_			(0x00) /* ~of MII Write bit */
+
+#define MII_DATA			(0x118)
+
+#define FLOW				(0x11C)
+#define FLOW_FCPT_			(0xFFFF0000)
+#define FLOW_FCPASS_			(0x00000004)
+#define FLOW_FCEN_			(0x00000002)
+#define FLOW_FCBSY_			(0x00000001)
+
+#define VLAN1				(0x120)
+
+#define VLAN2				(0x124)
+
+#define WUFF				(0x128)
+
+#define WUCSR				(0x12C)
+
+#define COE_CR				(0x130)
+#define Tx_COE_EN_			(0x00010000)
+#define Rx_COE_MODE_			(0x00000002)
+#define Rx_COE_EN_			(0x00000001)
+
+/* Vendor-specific PHY Definitions */
+
+/* Mode Control/Status Register */
+#define PHY_MODE_CTRL_STS		(17)
+#define MODE_CTRL_STS_EDPWRDOWN_	((u16)0x2000)
+#define MODE_CTRL_STS_ENERGYON_		((u16)0x0002)
+
+#define SPECIAL_CTRL_STS		(27)
+#define SPECIAL_CTRL_STS_OVRRD_AMDIX_	((u16)0x8000)
+#define SPECIAL_CTRL_STS_AMDIX_ENABLE_	((u16)0x4000)
+#define SPECIAL_CTRL_STS_AMDIX_STATE_	((u16)0x2000)
+
+#define PHY_INT_SRC			(29)
+#define PHY_INT_SRC_ENERGY_ON_		((u16)0x0080)
+#define PHY_INT_SRC_ANEG_COMP_		((u16)0x0040)
+#define PHY_INT_SRC_REMOTE_FAULT_	((u16)0x0020)
+#define PHY_INT_SRC_LINK_DOWN_		((u16)0x0010)
+
+#define PHY_INT_MASK			(30)
+#define PHY_INT_MASK_ENERGY_ON_		((u16)0x0080)
+#define PHY_INT_MASK_ANEG_COMP_		((u16)0x0040)
+#define PHY_INT_MASK_REMOTE_FAULT_	((u16)0x0020)
+#define PHY_INT_MASK_LINK_DOWN_		((u16)0x0010)
+#define PHY_INT_MASK_DEFAULT_		(PHY_INT_MASK_ANEG_COMP_ | \
+					 PHY_INT_MASK_LINK_DOWN_)
+
+#define PHY_SPECIAL			(31)
+#define PHY_SPECIAL_SPD_		((u16)0x001C)
+#define PHY_SPECIAL_SPD_10HALF_		((u16)0x0004)
+#define PHY_SPECIAL_SPD_10FULL_		((u16)0x0014)
+#define PHY_SPECIAL_SPD_100HALF_	((u16)0x0008)
+#define PHY_SPECIAL_SPD_100FULL_	((u16)0x0018)
+
+/* USB Vendor Requests */
+#define USB_VENDOR_REQUEST_WRITE_REGISTER	0xA0
+#define USB_VENDOR_REQUEST_READ_REGISTER	0xA1
+#define USB_VENDOR_REQUEST_GET_STATS		0xA2
+
+/* Interrupt Endpoint status word bitfields */
+#define INT_ENP_TX_STOP_		((u32)BIT(17))
+#define INT_ENP_RX_STOP_		((u32)BIT(16))
+#define INT_ENP_PHY_INT_		((u32)BIT(15))
+#define INT_ENP_TXE_			((u32)BIT(14))
+#define INT_ENP_TDFU_			((u32)BIT(13))
+#define INT_ENP_TDFO_			((u32)BIT(12))
+#define INT_ENP_RXDF_			((u32)BIT(11))
+
+#endif /* _SMSC95XX_H */
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/usbnet.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/usbnet.c
new file mode 100644
index 0000000..1283de5
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/usbnet.c
@@ -0,0 +1,1599 @@
+/*
+ * USB Network driver infrastructure
+ * Copyright (C) 2000-2005 by David Brownell
+ * Copyright (C) 2003-2005 David Hollis <dhollis@davehollis.com>
+ *
+ * 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
+ */
+
+/*
+ * This is a generic "USB networking" framework that works with several
+ * kinds of full and high speed networking devices:  host-to-host cables,
+ * smart usb peripherals, and actual Ethernet adapters.
+ *
+ * These devices usually differ in terms of control protocols (if they
+ * even have one!) and sometimes they define new framing to wrap or batch
+ * Ethernet packets.  Otherwise, they talk to USB pretty much the same,
+ * so interface (un)binding, endpoint I/O queues, fault handling, and other
+ * issues can usefully be addressed by this framework.
+ */
+
+// #define	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ctype.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+
+#define DRIVER_VERSION		"22-Aug-2005"
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Nineteen USB 1.1 max size bulk transactions per frame (ms), max.
+ * Several dozen bytes of IPv4 data can fit in two such transactions.
+ * One maximum size Ethernet packet takes twenty four of them.
+ * For high speed, each frame comfortably fits almost 36 max size
+ * Ethernet packets (so queues should be bigger).
+ *
+ * REVISIT qlens should be members of 'struct usbnet'; the goal is to
+ * let the USB host controller be busy for 5msec or more before an irq
+ * is required, under load.  Jumbograms change the equation.
+ */
+#define RX_MAX_QUEUE_MEMORY (60 * 1518)
+#define	RX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \
+			(RX_MAX_QUEUE_MEMORY/(dev)->rx_urb_size) : 4)
+#define	TX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \
+			(RX_MAX_QUEUE_MEMORY/(dev)->hard_mtu) : 4)
+
+// reawaken network queue this soon after stopping; else watchdog barks
+#define TX_TIMEOUT_JIFFIES	(5*HZ)
+
+// throttle rx/tx briefly after some faults, so khubd might disconnect()
+// us (it polls at HZ/4 usually) before we report too many false errors.
+#define THROTTLE_JIFFIES	(HZ/8)
+
+// between wakeups
+#define UNLINK_TIMEOUT_MS	3
+
+/*-------------------------------------------------------------------------*/
+
+// randomly generated ethernet address
+static u8	node_id [ETH_ALEN];
+
+static const char driver_name [] = "usbnet";
+
+/* use ethtool to change the level for any given device */
+static int msg_level = -1;
+module_param (msg_level, int, 0);
+MODULE_PARM_DESC (msg_level, "Override default message level");
+
+/*-------------------------------------------------------------------------*/
+
+/* handles CDC Ethernet and many other network "bulk data" interfaces */
+int usbnet_get_endpoints(struct usbnet *dev, struct usb_interface *intf)
+{
+	int				tmp;
+	struct usb_host_interface	*alt = NULL;
+	struct usb_host_endpoint	*in = NULL, *out = NULL;
+	struct usb_host_endpoint	*status = NULL;
+
+	for (tmp = 0; tmp < intf->num_altsetting; tmp++) {
+		unsigned	ep;
+
+		in = out = status = NULL;
+		alt = intf->altsetting + tmp;
+
+		/* take the first altsetting with in-bulk + out-bulk;
+		 * remember any status endpoint, just in case;
+		 * ignore other endpoints and altsettings.
+		 */
+		for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) {
+			struct usb_host_endpoint	*e;
+			int				intr = 0;
+
+			e = alt->endpoint + ep;
+			switch (e->desc.bmAttributes) {
+			case USB_ENDPOINT_XFER_INT:
+				if (!usb_endpoint_dir_in(&e->desc))
+					continue;
+				intr = 1;
+				/* FALLTHROUGH */
+			case USB_ENDPOINT_XFER_BULK:
+				break;
+			default:
+				continue;
+			}
+			if (usb_endpoint_dir_in(&e->desc)) {
+				if (!intr && !in)
+					in = e;
+				else if (intr && !status)
+					status = e;
+			} else {
+				if (!out)
+					out = e;
+			}
+		}
+		if (in && out)
+			break;
+	}
+	if (!alt || !in || !out)
+		return -EINVAL;
+
+	if (alt->desc.bAlternateSetting != 0 ||
+	    !(dev->driver_info->flags & FLAG_NO_SETINT)) {
+		tmp = usb_set_interface (dev->udev, alt->desc.bInterfaceNumber,
+				alt->desc.bAlternateSetting);
+		if (tmp < 0)
+			return tmp;
+	}
+
+	dev->in = usb_rcvbulkpipe (dev->udev,
+			in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	dev->out = usb_sndbulkpipe (dev->udev,
+			out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+	dev->status = status;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_get_endpoints);
+
+int usbnet_get_ethernet_addr(struct usbnet *dev, int iMACAddress)
+{
+	int 		tmp, i;
+	unsigned char	buf [13];
+
+	tmp = usb_string(dev->udev, iMACAddress, buf, sizeof buf);
+	if (tmp != 12) {
+		dev_dbg(&dev->udev->dev,
+			"bad MAC string %d fetch, %d\n", iMACAddress, tmp);
+		if (tmp >= 0)
+			tmp = -EINVAL;
+		return tmp;
+	}
+	for (i = tmp = 0; i < 6; i++, tmp += 2)
+		dev->net->dev_addr [i] =
+			(hex_to_bin(buf[tmp]) << 4) + hex_to_bin(buf[tmp + 1]);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_get_ethernet_addr);
+
+static void intr_complete (struct urb *urb);
+
+static int init_status (struct usbnet *dev, struct usb_interface *intf)
+{
+	char		*buf = NULL;
+	unsigned	pipe = 0;
+	unsigned	maxp;
+	unsigned	period;
+
+	if (!dev->driver_info->status)
+		return 0;
+
+	pipe = usb_rcvintpipe (dev->udev,
+			dev->status->desc.bEndpointAddress
+				& USB_ENDPOINT_NUMBER_MASK);
+	maxp = usb_maxpacket (dev->udev, pipe, 0);
+
+	/* avoid 1 msec chatter:  min 8 msec poll rate */
+	period = max ((int) dev->status->desc.bInterval,
+		(dev->udev->speed == USB_SPEED_HIGH) ? 7 : 3);
+
+	buf = kmalloc (maxp, GFP_KERNEL);
+	if (buf) {
+		dev->interrupt = usb_alloc_urb (0, GFP_KERNEL);
+		if (!dev->interrupt) {
+			kfree (buf);
+			return -ENOMEM;
+		} else {
+			usb_fill_int_urb(dev->interrupt, dev->udev, pipe,
+				buf, maxp, intr_complete, dev, period);
+			dev->interrupt->transfer_flags |= URB_FREE_BUFFER;
+			dev_dbg(&intf->dev,
+				"status ep%din, %d bytes period %d\n",
+				usb_pipeendpoint(pipe), maxp, period);
+		}
+	}
+	return 0;
+}
+
+/* Passes this packet up the stack, updating its accounting.
+ * Some link protocols batch packets, so their rx_fixup paths
+ * can return clones as well as just modify the original skb.
+ */
+void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb)
+{
+	int	status;
+
+	if (test_bit(EVENT_RX_PAUSED, &dev->flags)) {
+		skb_queue_tail(&dev->rxq_pause, skb);
+		return;
+	}
+
+	skb->protocol = eth_type_trans (skb, dev->net);
+	dev->net->stats.rx_packets++;
+	dev->net->stats.rx_bytes += skb->len;
+
+	netif_dbg(dev, rx_status, dev->net, "< rx, len %zu, type 0x%x\n",
+		  skb->len + sizeof (struct ethhdr), skb->protocol);
+	memset (skb->cb, 0, sizeof (struct skb_data));
+
+	if (skb_defer_rx_timestamp(skb))
+		return;
+
+	status = netif_rx (skb);
+	if (status != NET_RX_SUCCESS)
+		netif_dbg(dev, rx_err, dev->net,
+			  "netif_rx status %d\n", status);
+}
+EXPORT_SYMBOL_GPL(usbnet_skb_return);
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Network Device Driver (peer link to "Host Device", from USB host)
+ *
+ *-------------------------------------------------------------------------*/
+
+int usbnet_change_mtu (struct net_device *net, int new_mtu)
+{
+	struct usbnet	*dev = netdev_priv(net);
+	int		ll_mtu = new_mtu + net->hard_header_len;
+	int		old_hard_mtu = dev->hard_mtu;
+	int		old_rx_urb_size = dev->rx_urb_size;
+
+	if (new_mtu <= 0)
+		return -EINVAL;
+	// no second zero-length packet read wanted after mtu-sized packets
+	if ((ll_mtu % dev->maxpacket) == 0)
+		return -EDOM;
+	net->mtu = new_mtu;
+
+	dev->hard_mtu = net->mtu + net->hard_header_len;
+	if (dev->rx_urb_size == old_hard_mtu) {
+		dev->rx_urb_size = dev->hard_mtu;
+		if (dev->rx_urb_size > old_rx_urb_size)
+			usbnet_unlink_rx_urbs(dev);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_change_mtu);
+
+/* The caller must hold list->lock */
+static void __usbnet_queue_skb(struct sk_buff_head *list,
+			struct sk_buff *newsk, enum skb_state state)
+{
+	struct skb_data *entry = (struct skb_data *) newsk->cb;
+
+	__skb_queue_tail(list, newsk);
+	entry->state = state;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks.  2.5 should have fixed those bugs...
+ */
+
+static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
+		struct sk_buff_head *list, enum skb_state state)
+{
+	unsigned long		flags;
+	enum skb_state 		old_state;
+	struct skb_data *entry = (struct skb_data *) skb->cb;
+
+	spin_lock_irqsave(&list->lock, flags);
+	old_state = entry->state;
+	entry->state = state;
+	__skb_unlink(skb, list);
+	spin_unlock(&list->lock);
+	spin_lock(&dev->done.lock);
+	__skb_queue_tail(&dev->done, skb);
+	if (dev->done.qlen == 1)
+		tasklet_schedule(&dev->bh);
+	spin_unlock_irqrestore(&dev->done.lock, flags);
+	return old_state;
+}
+
+/* some work can't be done in tasklets, so we use keventd
+ *
+ * NOTE:  annoying asymmetry:  if it's active, schedule_work() fails,
+ * but tasklet_schedule() doesn't.  hope the failure is rare.
+ */
+void usbnet_defer_kevent (struct usbnet *dev, int work)
+{
+	set_bit (work, &dev->flags);
+	if (!schedule_work (&dev->kevent))
+		netdev_err(dev->net, "kevent %d may have been dropped\n", work);
+	else
+		netdev_dbg(dev->net, "kevent %d scheduled\n", work);
+}
+EXPORT_SYMBOL_GPL(usbnet_defer_kevent);
+
+/*-------------------------------------------------------------------------*/
+
+static void rx_complete (struct urb *urb);
+
+static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
+{
+	struct sk_buff		*skb;
+	struct skb_data		*entry;
+	int			retval = 0;
+	unsigned long		lockflags;
+	size_t			size = dev->rx_urb_size;
+
+	skb = __netdev_alloc_skb_ip_align(dev->net, size, flags);
+	if (!skb) {
+		netif_dbg(dev, rx_err, dev->net, "no rx skb\n");
+		usbnet_defer_kevent (dev, EVENT_RX_MEMORY);
+		usb_free_urb (urb);
+		return -ENOMEM;
+	}
+
+	entry = (struct skb_data *) skb->cb;
+	entry->urb = urb;
+	entry->dev = dev;
+	entry->length = 0;
+
+	usb_fill_bulk_urb (urb, dev->udev, dev->in,
+		skb->data, size, rx_complete, skb);
+
+	spin_lock_irqsave (&dev->rxq.lock, lockflags);
+
+	if (netif_running (dev->net) &&
+	    netif_device_present (dev->net) &&
+	    !test_bit (EVENT_RX_HALT, &dev->flags) &&
+	    !test_bit (EVENT_DEV_ASLEEP, &dev->flags)) {
+		switch (retval = usb_submit_urb (urb, GFP_ATOMIC)) {
+		case -EPIPE:
+			usbnet_defer_kevent (dev, EVENT_RX_HALT);
+			break;
+		case -ENOMEM:
+			usbnet_defer_kevent (dev, EVENT_RX_MEMORY);
+			break;
+		case -ENODEV:
+			netif_dbg(dev, ifdown, dev->net, "device gone\n");
+			netif_device_detach (dev->net);
+			break;
+		case -EHOSTUNREACH:
+			retval = -ENOLINK;
+			break;
+		default:
+			netif_dbg(dev, rx_err, dev->net,
+				  "rx submit, %d\n", retval);
+			tasklet_schedule (&dev->bh);
+			break;
+		case 0:
+			__usbnet_queue_skb(&dev->rxq, skb, rx_start);
+		}
+	} else {
+		netif_dbg(dev, ifdown, dev->net, "rx: stopped\n");
+		retval = -ENOLINK;
+	}
+	spin_unlock_irqrestore (&dev->rxq.lock, lockflags);
+	if (retval) {
+		dev_kfree_skb_any (skb);
+		usb_free_urb (urb);
+	}
+	return retval;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static inline void rx_process (struct usbnet *dev, struct sk_buff *skb)
+{
+	if (dev->driver_info->rx_fixup &&
+	    !dev->driver_info->rx_fixup (dev, skb)) {
+		/* With RX_ASSEMBLE, rx_fixup() must update counters */
+		if (!(dev->driver_info->flags & FLAG_RX_ASSEMBLE))
+			dev->net->stats.rx_errors++;
+		goto done;
+	}
+	// else network stack removes extra byte if we forced a short packet
+
+	/* all data was already cloned from skb inside the driver */
+	if (dev->driver_info->flags & FLAG_MULTI_PACKET)
+		goto done;
+
+	if (skb->len < ETH_HLEN) {
+		dev->net->stats.rx_errors++;
+		dev->net->stats.rx_length_errors++;
+		netif_dbg(dev, rx_err, dev->net, "rx length %d\n", skb->len);
+	} else {
+		usbnet_skb_return(dev, skb);
+		return;
+	}
+
+done:
+	skb_queue_tail(&dev->done, skb);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void rx_complete (struct urb *urb)
+{
+	struct sk_buff		*skb = (struct sk_buff *) urb->context;
+	struct skb_data		*entry = (struct skb_data *) skb->cb;
+	struct usbnet		*dev = entry->dev;
+	int			urb_status = urb->status;
+	enum skb_state		state;
+
+	skb_put (skb, urb->actual_length);
+	state = rx_done;
+	entry->urb = NULL;
+
+	switch (urb_status) {
+	/* success */
+	case 0:
+		break;
+
+	/* stalls need manual reset. this is rare ... except that
+	 * when going through USB 2.0 TTs, unplug appears this way.
+	 * we avoid the highspeed version of the ETIMEDOUT/EILSEQ
+	 * storm, recovering as needed.
+	 */
+	case -EPIPE:
+		dev->net->stats.rx_errors++;
+		usbnet_defer_kevent (dev, EVENT_RX_HALT);
+		// FALLTHROUGH
+
+	/* software-driven interface shutdown */
+	case -ECONNRESET:		/* async unlink */
+	case -ESHUTDOWN:		/* hardware gone */
+		netif_dbg(dev, ifdown, dev->net,
+			  "rx shutdown, code %d\n", urb_status);
+		goto block;
+
+	/* we get controller i/o faults during khubd disconnect() delays.
+	 * throttle down resubmits, to avoid log floods; just temporarily,
+	 * so we still recover when the fault isn't a khubd delay.
+	 */
+	case -EPROTO:
+	case -ETIME:
+	case -EILSEQ:
+		dev->net->stats.rx_errors++;
+		if (!timer_pending (&dev->delay)) {
+			mod_timer (&dev->delay, jiffies + THROTTLE_JIFFIES);
+			netif_dbg(dev, link, dev->net,
+				  "rx throttle %d\n", urb_status);
+		}
+block:
+		state = rx_cleanup;
+		entry->urb = urb;
+		urb = NULL;
+		break;
+
+	/* data overrun ... flush fifo? */
+	case -EOVERFLOW:
+		dev->net->stats.rx_over_errors++;
+		// FALLTHROUGH
+
+	default:
+		state = rx_cleanup;
+		dev->net->stats.rx_errors++;
+		netif_dbg(dev, rx_err, dev->net, "rx status %d\n", urb_status);
+		break;
+	}
+
+	state = defer_bh(dev, skb, &dev->rxq, state);
+
+	if (urb) {
+		if (netif_running (dev->net) &&
+		    !test_bit (EVENT_RX_HALT, &dev->flags) &&
+		    state != unlink_start) {
+			rx_submit (dev, urb, GFP_ATOMIC);
+			usb_mark_last_busy(dev->udev);
+			return;
+		}
+		usb_free_urb (urb);
+	}
+	netif_dbg(dev, rx_err, dev->net, "no read resubmitted\n");
+}
+
+static void intr_complete (struct urb *urb)
+{
+	struct usbnet	*dev = urb->context;
+	int		status = urb->status;
+
+	switch (status) {
+	/* success */
+	case 0:
+		dev->driver_info->status(dev, urb);
+		break;
+
+	/* software-driven interface shutdown */
+	case -ENOENT:		/* urb killed */
+	case -ESHUTDOWN:	/* hardware gone */
+		netif_dbg(dev, ifdown, dev->net,
+			  "intr shutdown, code %d\n", status);
+		return;
+
+	/* NOTE:  not throttling like RX/TX, since this endpoint
+	 * already polls infrequently
+	 */
+	default:
+		netdev_dbg(dev->net, "intr status %d\n", status);
+		break;
+	}
+
+	if (!netif_running (dev->net))
+		return;
+
+	memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
+	status = usb_submit_urb (urb, GFP_ATOMIC);
+	if (status != 0)
+		netif_err(dev, timer, dev->net,
+			  "intr resubmit --> %d\n", status);
+}
+
+/*-------------------------------------------------------------------------*/
+void usbnet_pause_rx(struct usbnet *dev)
+{
+	set_bit(EVENT_RX_PAUSED, &dev->flags);
+
+	netif_dbg(dev, rx_status, dev->net, "paused rx queue enabled\n");
+}
+EXPORT_SYMBOL_GPL(usbnet_pause_rx);
+
+void usbnet_resume_rx(struct usbnet *dev)
+{
+	struct sk_buff *skb;
+	int num = 0;
+
+	clear_bit(EVENT_RX_PAUSED, &dev->flags);
+
+	while ((skb = skb_dequeue(&dev->rxq_pause)) != NULL) {
+		usbnet_skb_return(dev, skb);
+		num++;
+	}
+
+	tasklet_schedule(&dev->bh);
+
+	netif_dbg(dev, rx_status, dev->net,
+		  "paused rx queue disabled, %d skbs requeued\n", num);
+}
+EXPORT_SYMBOL_GPL(usbnet_resume_rx);
+
+void usbnet_purge_paused_rxq(struct usbnet *dev)
+{
+	skb_queue_purge(&dev->rxq_pause);
+}
+EXPORT_SYMBOL_GPL(usbnet_purge_paused_rxq);
+
+/*-------------------------------------------------------------------------*/
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+	unsigned long		flags;
+	struct sk_buff		*skb;
+	int			count = 0;
+
+	spin_lock_irqsave (&q->lock, flags);
+	while (!skb_queue_empty(q)) {
+		struct skb_data		*entry;
+		struct urb		*urb;
+		int			retval;
+
+		skb_queue_walk(q, skb) {
+			entry = (struct skb_data *) skb->cb;
+			if (entry->state != unlink_start)
+				goto found;
+		}
+		break;
+found:
+		entry->state = unlink_start;
+		urb = entry->urb;
+
+		/*
+		 * Get reference count of the URB to avoid it to be
+		 * freed during usb_unlink_urb, which may trigger
+		 * use-after-free problem inside usb_unlink_urb since
+		 * usb_unlink_urb is always racing with .complete
+		 * handler(include defer_bh).
+		 */
+		usb_get_urb(urb);
+		spin_unlock_irqrestore(&q->lock, flags);
+		// during some PM-driven resume scenarios,
+		// these (async) unlinks complete immediately
+		retval = usb_unlink_urb (urb);
+		if (retval != -EINPROGRESS && retval != 0)
+			netdev_dbg(dev->net, "unlink urb err, %d\n", retval);
+		else
+			count++;
+		usb_put_urb(urb);
+		spin_lock_irqsave(&q->lock, flags);
+	}
+	spin_unlock_irqrestore (&q->lock, flags);
+	return count;
+}
+
+// Flush all pending rx urbs
+// minidrivers may need to do this when the MTU changes
+
+void usbnet_unlink_rx_urbs(struct usbnet *dev)
+{
+	if (netif_running(dev->net)) {
+		(void) unlink_urbs (dev, &dev->rxq);
+		tasklet_schedule(&dev->bh);
+	}
+}
+EXPORT_SYMBOL_GPL(usbnet_unlink_rx_urbs);
+
+/*-------------------------------------------------------------------------*/
+
+// precondition: never called in_interrupt
+static void usbnet_terminate_urbs(struct usbnet *dev)
+{
+	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(unlink_wakeup);
+	DECLARE_WAITQUEUE(wait, current);
+	int temp;
+
+	/* ensure there are no more active urbs */
+	add_wait_queue(&unlink_wakeup, &wait);
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	dev->wait = &unlink_wakeup;
+	temp = unlink_urbs(dev, &dev->txq) +
+		unlink_urbs(dev, &dev->rxq);
+
+	/* maybe wait for deletions to finish. */
+	while (!skb_queue_empty(&dev->rxq)
+		&& !skb_queue_empty(&dev->txq)
+		&& !skb_queue_empty(&dev->done)) {
+			schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
+			set_current_state(TASK_UNINTERRUPTIBLE);
+			netif_dbg(dev, ifdown, dev->net,
+				  "waited for %d urb completions\n", temp);
+	}
+	set_current_state(TASK_RUNNING);
+	dev->wait = NULL;
+	remove_wait_queue(&unlink_wakeup, &wait);
+}
+
+int usbnet_stop (struct net_device *net)
+{
+	struct usbnet		*dev = netdev_priv(net);
+	struct driver_info	*info = dev->driver_info;
+	int			retval;
+
+	clear_bit(EVENT_DEV_OPEN, &dev->flags);
+	netif_stop_queue (net);
+
+	netif_info(dev, ifdown, dev->net,
+		   "stop stats: rx/tx %lu/%lu, errs %lu/%lu\n",
+		   net->stats.rx_packets, net->stats.tx_packets,
+		   net->stats.rx_errors, net->stats.tx_errors);
+
+	/* allow minidriver to stop correctly (wireless devices to turn off
+	 * radio etc) */
+	if (info->stop) {
+		retval = info->stop(dev);
+		if (retval < 0)
+			netif_info(dev, ifdown, dev->net,
+				   "stop fail (%d) usbnet usb-%s-%s, %s\n",
+				   retval,
+				   dev->udev->bus->bus_name, dev->udev->devpath,
+				   info->description);
+	}
+
+	if (!(info->flags & FLAG_AVOID_UNLINK_URBS))
+		usbnet_terminate_urbs(dev);
+
+	usb_kill_urb(dev->interrupt);
+
+	usbnet_purge_paused_rxq(dev);
+
+	/* deferred work (task, timer, softirq) must also stop.
+	 * can't flush_scheduled_work() until we drop rtnl (later),
+	 * else workers could deadlock; so make workers a NOP.
+	 */
+	dev->flags = 0;
+	del_timer_sync (&dev->delay);
+	tasklet_kill (&dev->bh);
+	if (info->manage_power)
+		info->manage_power(dev, 0);
+	else
+		usb_autopm_put_interface(dev->intf);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_stop);
+
+/*-------------------------------------------------------------------------*/
+
+// posts reads, and enables write queuing
+
+// precondition: never called in_interrupt
+
+int usbnet_open (struct net_device *net)
+{
+	struct usbnet		*dev = netdev_priv(net);
+	int			retval;
+	struct driver_info	*info = dev->driver_info;
+
+	if ((retval = usb_autopm_get_interface(dev->intf)) < 0) {
+		netif_info(dev, ifup, dev->net,
+			   "resumption fail (%d) usbnet usb-%s-%s, %s\n",
+			   retval,
+			   dev->udev->bus->bus_name,
+			   dev->udev->devpath,
+			   info->description);
+		goto done_nopm;
+	}
+
+	// put into "known safe" state
+	if (info->reset && (retval = info->reset (dev)) < 0) {
+		netif_info(dev, ifup, dev->net,
+			   "open reset fail (%d) usbnet usb-%s-%s, %s\n",
+			   retval,
+			   dev->udev->bus->bus_name,
+			   dev->udev->devpath,
+			   info->description);
+		goto done;
+	}
+
+	// insist peer be connected
+	if (info->check_connect && (retval = info->check_connect (dev)) < 0) {
+		netif_dbg(dev, ifup, dev->net, "can't open; %d\n", retval);
+		goto done;
+	}
+
+	/* start any status interrupt transfer */
+	if (dev->interrupt) {
+		retval = usb_submit_urb (dev->interrupt, GFP_KERNEL);
+		if (retval < 0) {
+			netif_err(dev, ifup, dev->net,
+				  "intr submit %d\n", retval);
+			goto done;
+		}
+	}
+
+	set_bit(EVENT_DEV_OPEN, &dev->flags);
+	netif_start_queue (net);
+	netif_info(dev, ifup, dev->net,
+		   "open: enable queueing (rx %d, tx %d) mtu %d %s framing\n",
+		   (int)RX_QLEN(dev), (int)TX_QLEN(dev),
+		   dev->net->mtu,
+		   (dev->driver_info->flags & FLAG_FRAMING_NC) ? "NetChip" :
+		   (dev->driver_info->flags & FLAG_FRAMING_GL) ? "GeneSys" :
+		   (dev->driver_info->flags & FLAG_FRAMING_Z) ? "Zaurus" :
+		   (dev->driver_info->flags & FLAG_FRAMING_RN) ? "RNDIS" :
+		   (dev->driver_info->flags & FLAG_FRAMING_AX) ? "ASIX" :
+		   "simple");
+
+	// delay posting reads until we're fully open
+	tasklet_schedule (&dev->bh);
+	if (info->manage_power) {
+		retval = info->manage_power(dev, 1);
+		if (retval < 0)
+			goto done;
+		usb_autopm_put_interface(dev->intf);
+	}
+	return retval;
+
+done:
+	usb_autopm_put_interface(dev->intf);
+done_nopm:
+	return retval;
+}
+EXPORT_SYMBOL_GPL(usbnet_open);
+
+/*-------------------------------------------------------------------------*/
+
+/* ethtool methods; minidrivers may need to add some more, but
+ * they'll probably want to use this base set.
+ */
+
+int usbnet_get_settings (struct net_device *net, struct ethtool_cmd *cmd)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	if (!dev->mii.mdio_read)
+		return -EOPNOTSUPP;
+
+	return mii_ethtool_gset(&dev->mii, cmd);
+}
+EXPORT_SYMBOL_GPL(usbnet_get_settings);
+
+int usbnet_set_settings (struct net_device *net, struct ethtool_cmd *cmd)
+{
+	struct usbnet *dev = netdev_priv(net);
+	int retval;
+
+	if (!dev->mii.mdio_write)
+		return -EOPNOTSUPP;
+
+	retval = mii_ethtool_sset(&dev->mii, cmd);
+
+	/* link speed/duplex might have changed */
+	if (dev->driver_info->link_reset)
+		dev->driver_info->link_reset(dev);
+
+	return retval;
+
+}
+EXPORT_SYMBOL_GPL(usbnet_set_settings);
+
+u32 usbnet_get_link (struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	/* If a check_connect is defined, return its result */
+	if (dev->driver_info->check_connect)
+		return dev->driver_info->check_connect (dev) == 0;
+
+	/* if the device has mii operations, use those */
+	if (dev->mii.mdio_read)
+		return mii_link_ok(&dev->mii);
+
+	/* Otherwise, dtrt for drivers calling netif_carrier_{on,off} */
+	return ethtool_op_get_link(net);
+}
+EXPORT_SYMBOL_GPL(usbnet_get_link);
+
+int usbnet_nway_reset(struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	if (!dev->mii.mdio_write)
+		return -EOPNOTSUPP;
+
+	return mii_nway_restart(&dev->mii);
+}
+EXPORT_SYMBOL_GPL(usbnet_nway_reset);
+
+void usbnet_get_drvinfo (struct net_device *net, struct ethtool_drvinfo *info)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	strncpy (info->driver, dev->driver_name, sizeof info->driver);
+	strncpy (info->version, DRIVER_VERSION, sizeof info->version);
+	strncpy (info->fw_version, dev->driver_info->description,
+		sizeof info->fw_version);
+	usb_make_path (dev->udev, info->bus_info, sizeof info->bus_info);
+}
+EXPORT_SYMBOL_GPL(usbnet_get_drvinfo);
+
+u32 usbnet_get_msglevel (struct net_device *net)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	return dev->msg_enable;
+}
+EXPORT_SYMBOL_GPL(usbnet_get_msglevel);
+
+void usbnet_set_msglevel (struct net_device *net, u32 level)
+{
+	struct usbnet *dev = netdev_priv(net);
+
+	dev->msg_enable = level;
+}
+EXPORT_SYMBOL_GPL(usbnet_set_msglevel);
+
+/* drivers may override default ethtool_ops in their bind() routine */
+static const struct ethtool_ops usbnet_ethtool_ops = {
+	.get_settings		= usbnet_get_settings,
+	.set_settings		= usbnet_set_settings,
+	.get_link		= usbnet_get_link,
+	.nway_reset		= usbnet_nway_reset,
+	.get_drvinfo		= usbnet_get_drvinfo,
+	.get_msglevel		= usbnet_get_msglevel,
+	.set_msglevel		= usbnet_set_msglevel,
+};
+
+/*-------------------------------------------------------------------------*/
+
+/* work that cannot be done in interrupt context uses keventd.
+ *
+ * NOTE:  with 2.5 we could do more of this using completion callbacks,
+ * especially now that control transfers can be queued.
+ */
+static void
+kevent (struct work_struct *work)
+{
+	struct usbnet		*dev =
+		container_of(work, struct usbnet, kevent);
+	int			status;
+
+	/* usb_clear_halt() needs a thread context */
+	if (test_bit (EVENT_TX_HALT, &dev->flags)) {
+		unlink_urbs (dev, &dev->txq);
+		status = usb_autopm_get_interface(dev->intf);
+		if (status < 0)
+			goto fail_pipe;
+		status = usb_clear_halt (dev->udev, dev->out);
+		usb_autopm_put_interface(dev->intf);
+		if (status < 0 &&
+		    status != -EPIPE &&
+		    status != -ESHUTDOWN) {
+			if (netif_msg_tx_err (dev))
+fail_pipe:
+				netdev_err(dev->net, "can't clear tx halt, status %d\n",
+					   status);
+		} else {
+			clear_bit (EVENT_TX_HALT, &dev->flags);
+			if (status != -ESHUTDOWN)
+				netif_wake_queue (dev->net);
+		}
+	}
+	if (test_bit (EVENT_RX_HALT, &dev->flags)) {
+		unlink_urbs (dev, &dev->rxq);
+		status = usb_autopm_get_interface(dev->intf);
+		if (status < 0)
+			goto fail_halt;
+		status = usb_clear_halt (dev->udev, dev->in);
+		usb_autopm_put_interface(dev->intf);
+		if (status < 0 &&
+		    status != -EPIPE &&
+		    status != -ESHUTDOWN) {
+			if (netif_msg_rx_err (dev))
+fail_halt:
+				netdev_err(dev->net, "can't clear rx halt, status %d\n",
+					   status);
+		} else {
+			clear_bit (EVENT_RX_HALT, &dev->flags);
+			tasklet_schedule (&dev->bh);
+		}
+	}
+
+	/* tasklet could resubmit itself forever if memory is tight */
+	if (test_bit (EVENT_RX_MEMORY, &dev->flags)) {
+		struct urb	*urb = NULL;
+		int resched = 1;
+
+		if (netif_running (dev->net))
+			urb = usb_alloc_urb (0, GFP_KERNEL);
+		else
+			clear_bit (EVENT_RX_MEMORY, &dev->flags);
+		if (urb != NULL) {
+			clear_bit (EVENT_RX_MEMORY, &dev->flags);
+			status = usb_autopm_get_interface(dev->intf);
+			if (status < 0) {
+				usb_free_urb(urb);
+				goto fail_lowmem;
+			}
+			if (rx_submit (dev, urb, GFP_KERNEL) == -ENOLINK)
+				resched = 0;
+			usb_autopm_put_interface(dev->intf);
+fail_lowmem:
+			if (resched)
+				tasklet_schedule (&dev->bh);
+		}
+	}
+
+	if (test_bit (EVENT_LINK_RESET, &dev->flags)) {
+		struct driver_info	*info = dev->driver_info;
+		int			retval = 0;
+
+		clear_bit (EVENT_LINK_RESET, &dev->flags);
+		status = usb_autopm_get_interface(dev->intf);
+		if (status < 0)
+			goto skip_reset;
+		if(info->link_reset && (retval = info->link_reset(dev)) < 0) {
+			usb_autopm_put_interface(dev->intf);
+skip_reset:
+			netdev_info(dev->net, "link reset failed (%d) usbnet usb-%s-%s, %s\n",
+				    retval,
+				    dev->udev->bus->bus_name,
+				    dev->udev->devpath,
+				    info->description);
+		} else {
+			usb_autopm_put_interface(dev->intf);
+		}
+	}
+
+	if (dev->flags)
+		netdev_dbg(dev->net, "kevent done, flags = 0x%lx\n", dev->flags);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void tx_complete (struct urb *urb)
+{
+	struct sk_buff		*skb = (struct sk_buff *) urb->context;
+	struct skb_data		*entry = (struct skb_data *) skb->cb;
+	struct usbnet		*dev = entry->dev;
+
+	if (urb->status == 0) {
+		if (!(dev->driver_info->flags & FLAG_MULTI_PACKET))
+			dev->net->stats.tx_packets++;
+		dev->net->stats.tx_bytes += entry->length;
+	} else {
+		dev->net->stats.tx_errors++;
+
+		switch (urb->status) {
+		case -EPIPE:
+			usbnet_defer_kevent (dev, EVENT_TX_HALT);
+			break;
+
+		/* software-driven interface shutdown */
+		case -ECONNRESET:		// async unlink
+		case -ESHUTDOWN:		// hardware gone
+			break;
+
+		// like rx, tx gets controller i/o faults during khubd delays
+		// and so it uses the same throttling mechanism.
+		case -EPROTO:
+		case -ETIME:
+		case -EILSEQ:
+			usb_mark_last_busy(dev->udev);
+			if (!timer_pending (&dev->delay)) {
+				mod_timer (&dev->delay,
+					jiffies + THROTTLE_JIFFIES);
+				netif_dbg(dev, link, dev->net,
+					  "tx throttle %d\n", urb->status);
+			}
+			netif_stop_queue (dev->net);
+			break;
+		default:
+			netif_dbg(dev, tx_err, dev->net,
+				  "tx err %d\n", entry->urb->status);
+			break;
+		}
+	}
+
+	usb_autopm_put_interface_async(dev->intf);
+	(void) defer_bh(dev, skb, &dev->txq, tx_done);
+}
+
+/*-------------------------------------------------------------------------*/
+
+void usbnet_tx_timeout (struct net_device *net)
+{
+	struct usbnet		*dev = netdev_priv(net);
+
+	unlink_urbs (dev, &dev->txq);
+	tasklet_schedule (&dev->bh);
+
+	// FIXME: device recovery -- reset?
+}
+EXPORT_SYMBOL_GPL(usbnet_tx_timeout);
+
+/*-------------------------------------------------------------------------*/
+
+netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
+				     struct net_device *net)
+{
+	struct usbnet		*dev = netdev_priv(net);
+	int			length;
+	struct urb		*urb = NULL;
+	struct skb_data		*entry;
+	struct driver_info	*info = dev->driver_info;
+	unsigned long		flags;
+	int retval;
+
+	if (skb)
+		skb_tx_timestamp(skb);
+
+	// some devices want funky USB-level framing, for
+	// win32 driver (usually) and/or hardware quirks
+	if (info->tx_fixup) {
+		skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+		if (!skb) {
+			if (netif_msg_tx_err(dev)) {
+				netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+				goto drop;
+			} else {
+				/* cdc_ncm collected packet; waits for more */
+				goto not_drop;
+			}
+		}
+	}
+	length = skb->len;
+
+	if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
+		netif_dbg(dev, tx_err, dev->net, "no urb\n");
+		goto drop;
+	}
+
+	entry = (struct skb_data *) skb->cb;
+	entry->urb = urb;
+	entry->dev = dev;
+	entry->length = length;
+
+	usb_fill_bulk_urb (urb, dev->udev, dev->out,
+			skb->data, skb->len, tx_complete, skb);
+
+	/* don't assume the hardware handles USB_ZERO_PACKET
+	 * NOTE:  strictly conforming cdc-ether devices should expect
+	 * the ZLP here, but ignore the one-byte packet.
+	 * NOTE2: CDC NCM specification is different from CDC ECM when
+	 * handling ZLP/short packets, so cdc_ncm driver will make short
+	 * packet itself if needed.
+	 */
+	if (length % dev->maxpacket == 0) {
+		if (!(info->flags & FLAG_SEND_ZLP)) {
+			if (!(info->flags & FLAG_MULTI_PACKET)) {
+				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);
+	retval = usb_autopm_get_interface_async(dev->intf);
+	if (retval < 0) {
+		spin_unlock_irqrestore(&dev->txq.lock, flags);
+		goto drop;
+	}
+
+#ifdef CONFIG_PM
+	/* if this triggers the device is still a sleep */
+	if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) {
+		/* transmission will be done in resume */
+		usb_anchor_urb(urb, &dev->deferred);
+		/* no use to process more packets */
+		netif_stop_queue(net);
+		usb_put_urb(urb);
+		spin_unlock_irqrestore(&dev->txq.lock, flags);
+		netdev_dbg(dev->net, "Delaying transmission for resumption\n");
+		goto deferred;
+	}
+#endif
+
+	switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) {
+	case -EPIPE:
+		netif_stop_queue (net);
+		usbnet_defer_kevent (dev, EVENT_TX_HALT);
+		usb_autopm_put_interface_async(dev->intf);
+		break;
+	default:
+		usb_autopm_put_interface_async(dev->intf);
+		netif_dbg(dev, tx_err, dev->net,
+			  "tx: submit urb err %d\n", retval);
+		break;
+	case 0:
+		net->trans_start = jiffies;
+		__usbnet_queue_skb(&dev->txq, skb, tx_start);
+		if (dev->txq.qlen >= TX_QLEN (dev))
+			netif_stop_queue (net);
+	}
+	spin_unlock_irqrestore (&dev->txq.lock, flags);
+
+	if (retval) {
+		netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval);
+drop:
+		dev->net->stats.tx_dropped++;
+not_drop:
+		if (skb)
+			dev_kfree_skb_any (skb);
+		usb_free_urb (urb);
+	} else
+		netif_dbg(dev, tx_queued, dev->net,
+			  "> tx, len %d, type 0x%x\n", length, skb->protocol);
+#ifdef CONFIG_PM
+deferred:
+#endif
+	return NETDEV_TX_OK;
+}
+EXPORT_SYMBOL_GPL(usbnet_start_xmit);
+
+/*-------------------------------------------------------------------------*/
+
+// tasklet (work deferred from completions, in_irq) or timer
+
+static void usbnet_bh (unsigned long param)
+{
+	struct usbnet		*dev = (struct usbnet *) param;
+	struct sk_buff		*skb;
+	struct skb_data		*entry;
+
+	while ((skb = skb_dequeue (&dev->done))) {
+		entry = (struct skb_data *) skb->cb;
+		switch (entry->state) {
+		case rx_done:
+			entry->state = rx_cleanup;
+			rx_process (dev, skb);
+			continue;
+		case tx_done:
+		case rx_cleanup:
+			usb_free_urb (entry->urb);
+			dev_kfree_skb (skb);
+			continue;
+		default:
+			netdev_dbg(dev->net, "bogus skb state %d\n", entry->state);
+		}
+	}
+
+	// waiting for all pending urbs to complete?
+	if (dev->wait) {
+		if ((dev->txq.qlen + dev->rxq.qlen + dev->done.qlen) == 0) {
+			wake_up (dev->wait);
+		}
+
+	// or are we maybe short a few urbs?
+	} else if (netif_running (dev->net) &&
+		   netif_device_present (dev->net) &&
+		   !timer_pending (&dev->delay) &&
+		   !test_bit (EVENT_RX_HALT, &dev->flags)) {
+		int	temp = dev->rxq.qlen;
+		int	qlen = RX_QLEN (dev);
+
+		if (temp < qlen) {
+			struct urb	*urb;
+			int		i;
+
+			// don't refill the queue all at once
+			for (i = 0; i < 10 && dev->rxq.qlen < qlen; i++) {
+				urb = usb_alloc_urb (0, GFP_ATOMIC);
+				if (urb != NULL) {
+					if (rx_submit (dev, urb, GFP_ATOMIC) ==
+					    -ENOLINK)
+						return;
+				}
+			}
+			if (temp != dev->rxq.qlen)
+				netif_dbg(dev, link, dev->net,
+					  "rxqlen %d --> %d\n",
+					  temp, dev->rxq.qlen);
+			if (dev->rxq.qlen < qlen)
+				tasklet_schedule (&dev->bh);
+		}
+		if (dev->txq.qlen < TX_QLEN (dev))
+			netif_wake_queue (dev->net);
+	}
+}
+
+
+/*-------------------------------------------------------------------------
+ *
+ * USB Device Driver support
+ *
+ *-------------------------------------------------------------------------*/
+
+// precondition: never called in_interrupt
+
+void usbnet_disconnect (struct usb_interface *intf)
+{
+	struct usbnet		*dev;
+	struct usb_device	*xdev;
+	struct net_device	*net;
+
+	dev = usb_get_intfdata(intf);
+	usb_set_intfdata(intf, NULL);
+	if (!dev)
+		return;
+
+	xdev = interface_to_usbdev (intf);
+
+	netif_info(dev, probe, dev->net, "unregister '%s' usb-%s-%s, %s\n",
+		   intf->dev.driver->name,
+		   xdev->bus->bus_name, xdev->devpath,
+		   dev->driver_info->description);
+
+	net = dev->net;
+	unregister_netdev (net);
+
+	cancel_work_sync(&dev->kevent);
+
+	usb_scuttle_anchored_urbs(&dev->deferred);
+
+	if (dev->driver_info->unbind)
+		dev->driver_info->unbind (dev, intf);
+
+	usb_kill_urb(dev->interrupt);
+	usb_free_urb(dev->interrupt);
+
+	free_netdev(net);
+	usb_put_dev (xdev);
+}
+EXPORT_SYMBOL_GPL(usbnet_disconnect);
+
+static const struct net_device_ops usbnet_netdev_ops = {
+	.ndo_open		= usbnet_open,
+	.ndo_stop		= usbnet_stop,
+	.ndo_start_xmit		= usbnet_start_xmit,
+	.ndo_tx_timeout		= usbnet_tx_timeout,
+	.ndo_change_mtu		= usbnet_change_mtu,
+	.ndo_set_mac_address 	= eth_mac_addr,
+	.ndo_validate_addr	= eth_validate_addr,
+};
+
+/*-------------------------------------------------------------------------*/
+
+// precondition: never called in_interrupt
+
+static struct device_type wlan_type = {
+	.name	= "wlan",
+};
+
+static struct device_type wwan_type = {
+	.name	= "wwan",
+};
+
+int
+usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
+{
+	struct usbnet			*dev;
+	struct net_device		*net;
+	struct usb_host_interface	*interface;
+	struct driver_info		*info;
+	struct usb_device		*xdev;
+	int				status;
+	const char			*name;
+	struct usb_driver 	*driver = to_usb_driver(udev->dev.driver);
+
+	/* usbnet already took usb runtime pm, so have to enable the feature
+	 * for usb interface, otherwise usb_autopm_get_interface may return
+	 * failure if USB_SUSPEND(RUNTIME_PM) is enabled.
+	 */
+	if (!driver->supports_autosuspend) {
+		driver->supports_autosuspend = 1;
+		pm_runtime_enable(&udev->dev);
+	}
+
+	name = udev->dev.driver->name;
+	info = (struct driver_info *) prod->driver_info;
+	if (!info) {
+		dev_dbg (&udev->dev, "blacklisted by %s\n", name);
+		return -ENODEV;
+	}
+	xdev = interface_to_usbdev (udev);
+	interface = udev->cur_altsetting;
+
+	usb_get_dev (xdev);
+
+	status = -ENOMEM;
+
+	// set up our own records
+	net = alloc_etherdev(sizeof(*dev));
+	if (!net)
+		goto out;
+
+	/* netdev_printk() needs this so do it as early as possible */
+	SET_NETDEV_DEV(net, &udev->dev);
+
+	dev = netdev_priv(net);
+	dev->udev = xdev;
+	dev->intf = udev;
+	dev->driver_info = info;
+	dev->driver_name = name;
+	dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV
+				| NETIF_MSG_PROBE | NETIF_MSG_LINK);
+	skb_queue_head_init (&dev->rxq);
+	skb_queue_head_init (&dev->txq);
+	skb_queue_head_init (&dev->done);
+	skb_queue_head_init(&dev->rxq_pause);
+	dev->bh.func = usbnet_bh;
+	dev->bh.data = (unsigned long) dev;
+	INIT_WORK (&dev->kevent, kevent);
+	init_usb_anchor(&dev->deferred);
+	dev->delay.function = usbnet_bh;
+	dev->delay.data = (unsigned long) dev;
+	init_timer (&dev->delay);
+	mutex_init (&dev->phy_mutex);
+
+	dev->net = net;
+	strcpy (net->name, "usb%d");
+	memcpy (net->dev_addr, node_id, sizeof node_id);
+
+	/* rx and tx sides can use different message sizes;
+	 * bind() should set rx_urb_size in that case.
+	 */
+	dev->hard_mtu = net->mtu + net->hard_header_len;
+#if 0
+// dma_supported() is deeply broken on almost all architectures
+	// possible with some EHCI controllers
+	if (dma_supported (&udev->dev, DMA_BIT_MASK(64)))
+		net->features |= NETIF_F_HIGHDMA;
+#endif
+
+	net->netdev_ops = &usbnet_netdev_ops;
+	net->watchdog_timeo = TX_TIMEOUT_JIFFIES;
+	net->ethtool_ops = &usbnet_ethtool_ops;
+
+	// allow device-specific bind/init procedures
+	// NOTE net->name still not usable ...
+	if (info->bind) {
+		status = info->bind (dev, udev);
+		if (status < 0)
+			goto out1;
+
+		// heuristic:  "usb%d" for links we know are two-host,
+		// else "eth%d" when there's reasonable doubt.  userspace
+		// can rename the link if it knows better.
+		if ((dev->driver_info->flags & FLAG_ETHER) != 0 &&
+		    ((dev->driver_info->flags & FLAG_POINTTOPOINT) == 0 ||
+		     (net->dev_addr [0] & 0x02) == 0))
+			strcpy (net->name, "eth%d");
+		/* WLAN devices should always be named "wlan%d" */
+		if ((dev->driver_info->flags & FLAG_WLAN) != 0)
+			strcpy(net->name, "wlan%d");
+		/* WWAN devices should always be named "wwan%d" */
+		if ((dev->driver_info->flags & FLAG_WWAN) != 0)
+			strcpy(net->name, "wwan%d");
+
+		/* maybe the remote can't receive an Ethernet MTU */
+		if (net->mtu > (dev->hard_mtu - net->hard_header_len))
+			net->mtu = dev->hard_mtu - net->hard_header_len;
+	} else if (!info->in || !info->out)
+		status = usbnet_get_endpoints (dev, udev);
+	else {
+		dev->in = usb_rcvbulkpipe (xdev, info->in);
+		dev->out = usb_sndbulkpipe (xdev, info->out);
+		if (!(info->flags & FLAG_NO_SETINT))
+			status = usb_set_interface (xdev,
+				interface->desc.bInterfaceNumber,
+				interface->desc.bAlternateSetting);
+		else
+			status = 0;
+
+	}
+	if (status >= 0 && dev->status)
+		status = init_status (dev, udev);
+	if (status < 0)
+		goto out3;
+
+	if (!dev->rx_urb_size)
+		dev->rx_urb_size = dev->hard_mtu;
+	dev->maxpacket = usb_maxpacket (dev->udev, dev->out, 1);
+
+	if ((dev->driver_info->flags & FLAG_WLAN) != 0)
+		SET_NETDEV_DEVTYPE(net, &wlan_type);
+	if ((dev->driver_info->flags & FLAG_WWAN) != 0)
+		SET_NETDEV_DEVTYPE(net, &wwan_type);
+
+	status = register_netdev (net);
+	if (status)
+		goto out4;
+	netif_info(dev, probe, dev->net,
+		   "register '%s' at usb-%s-%s, %s, %pM\n",
+		   udev->dev.driver->name,
+		   xdev->bus->bus_name, xdev->devpath,
+		   dev->driver_info->description,
+		   net->dev_addr);
+
+	// ok, it's ready to go.
+	usb_set_intfdata (udev, dev);
+
+	netif_device_attach (net);
+
+	if (dev->driver_info->flags & FLAG_LINK_INTR)
+		netif_carrier_off(net);
+
+	return 0;
+
+out4:
+	usb_free_urb(dev->interrupt);
+out3:
+	if (info->unbind)
+		info->unbind (dev, udev);
+out1:
+	free_netdev(net);
+out:
+	usb_put_dev(xdev);
+	return status;
+}
+EXPORT_SYMBOL_GPL(usbnet_probe);
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * suspend the whole driver as soon as the first interface is suspended
+ * resume only when the last interface is resumed
+ */
+
+int usbnet_suspend (struct usb_interface *intf, pm_message_t message)
+{
+	struct usbnet		*dev = usb_get_intfdata(intf);
+
+	if (!dev->suspend_count++) {
+		spin_lock_irq(&dev->txq.lock);
+		/* don't autosuspend while transmitting */
+		if (dev->txq.qlen && PMSG_IS_AUTO(message)) {
+			spin_unlock_irq(&dev->txq.lock);
+			return -EBUSY;
+		} else {
+			set_bit(EVENT_DEV_ASLEEP, &dev->flags);
+			spin_unlock_irq(&dev->txq.lock);
+		}
+		/*
+		 * accelerate emptying of the rx and queues, to avoid
+		 * having everything error out.
+		 */
+		netif_device_detach (dev->net);
+		usbnet_terminate_urbs(dev);
+		usb_kill_urb(dev->interrupt);
+
+		/*
+		 * reattach so runtime management can use and
+		 * wake the device
+		 */
+		netif_device_attach (dev->net);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_suspend);
+
+int usbnet_resume (struct usb_interface *intf)
+{
+	struct usbnet		*dev = usb_get_intfdata(intf);
+	struct sk_buff          *skb;
+	struct urb              *res;
+	int                     retval;
+
+	if (!--dev->suspend_count) {
+		/* resume interrupt URBs */
+		if (dev->interrupt && test_bit(EVENT_DEV_OPEN, &dev->flags))
+			usb_submit_urb(dev->interrupt, GFP_NOIO);
+
+		spin_lock_irq(&dev->txq.lock);
+		while ((res = usb_get_from_anchor(&dev->deferred))) {
+
+			skb = (struct sk_buff *)res->context;
+			retval = usb_submit_urb(res, GFP_ATOMIC);
+			if (retval < 0) {
+				dev_kfree_skb_any(skb);
+				usb_free_urb(res);
+				usb_autopm_put_interface_async(dev->intf);
+			} else {
+				dev->net->trans_start = jiffies;
+				__skb_queue_tail(&dev->txq, skb);
+			}
+		}
+
+		smp_mb();
+		clear_bit(EVENT_DEV_ASLEEP, &dev->flags);
+		spin_unlock_irq(&dev->txq.lock);
+
+		if (test_bit(EVENT_DEV_OPEN, &dev->flags)) {
+			if (!(dev->txq.qlen >= TX_QLEN(dev)))
+				netif_tx_wake_all_queues(dev->net);
+			tasklet_schedule (&dev->bh);
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(usbnet_resume);
+
+
+/*-------------------------------------------------------------------------*/
+
+static int __init usbnet_init(void)
+{
+	/* Compiler should optimize this out. */
+	BUILD_BUG_ON(
+		FIELD_SIZEOF(struct sk_buff, cb) < sizeof(struct skb_data));
+
+	random_ether_addr(node_id);
+	return 0;
+}
+module_init(usbnet_init);
+
+static void __exit usbnet_exit(void)
+{
+}
+module_exit(usbnet_exit);
+
+MODULE_AUTHOR("David Brownell");
+MODULE_DESCRIPTION("USB network driver framework");
+MODULE_LICENSE("GPL");
diff --git a/ap/os/linux/linux-3.4.x/drivers/net/usb/zaurus.c b/ap/os/linux/linux-3.4.x/drivers/net/usb/zaurus.c
new file mode 100644
index 0000000..34db195
--- /dev/null
+++ b/ap/os/linux/linux-3.4.x/drivers/net/usb/zaurus.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2002 Pavel Machek <pavel@ucw.cz>
+ * Copyright (C) 2002-2005 by David Brownell
+ *
+ * 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	DEBUG			// error path messages, extra info
+// #define	VERBOSE			// more; success messages
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+#include <linux/workqueue.h>
+#include <linux/mii.h>
+#include <linux/crc32.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb/usbnet.h>
+
+
+/*
+ * All known Zaurii lie about their standards conformance.  At least
+ * the earliest SA-1100 models lie by saying they support CDC Ethernet.
+ * Some later models (especially PXA-25x and PXA-27x based ones) lie
+ * and say they support CDC MDLM (for access to cell phone modems).
+ *
+ * There are non-Zaurus products that use these same protocols too.
+ *
+ * The annoying thing is that at the same time Sharp was developing
+ * that annoying standards-breaking software, the Linux community had
+ * a simple "CDC Subset" working reliably on the same SA-1100 hardware.
+ * That is, the same functionality but not violating standards.
+ *
+ * The CDC Ethernet nonconformance points are troublesome to hosts
+ * with a true CDC Ethernet implementation:
+ *   - Framing appends a CRC, which the spec says drivers "must not" do;
+ *   - Transfers data in altsetting zero, instead of altsetting 1;
+ *   - All these peripherals use the same ethernet address.
+ *
+ * The CDC MDLM nonconformance is less immediately troublesome, since all
+ * MDLM implementations are quasi-proprietary anyway.
+ */
+
+static struct sk_buff *
+zaurus_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+	int			padlen;
+	struct sk_buff		*skb2;
+
+	padlen = 2;
+	if (!skb_cloned(skb)) {
+		int	tailroom = skb_tailroom(skb);
+		if ((padlen + 4) <= tailroom)
+			goto done;
+	}
+	skb2 = skb_copy_expand(skb, 0, 4 + padlen, flags);
+	dev_kfree_skb_any(skb);
+	skb = skb2;
+	if (skb) {
+		u32		fcs;
+done:
+		fcs = crc32_le(~0, skb->data, skb->len);
+		fcs = ~fcs;
+
+		*skb_put (skb, 1) = fcs       & 0xff;
+		*skb_put (skb, 1) = (fcs>> 8) & 0xff;
+		*skb_put (skb, 1) = (fcs>>16) & 0xff;
+		*skb_put (skb, 1) = (fcs>>24) & 0xff;
+	}
+	return skb;
+}
+
+static int zaurus_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	/* Belcarra's funky framing has other options; mostly
+	 * TRAILERS (!) with 4 bytes CRC, and maybe 2 pad bytes.
+	 */
+	dev->net->hard_header_len += 6;
+	dev->rx_urb_size = dev->net->hard_header_len + dev->net->mtu;
+	return usbnet_generic_cdc_bind(dev, intf);
+}
+
+/* PDA style devices are always connected if present */
+static int always_connected (struct usbnet *dev)
+{
+	return 0;
+}
+
+static const struct driver_info	zaurus_sl5x00_info = {
+	.description =	"Sharp Zaurus SL-5x00",
+	.flags =	FLAG_POINTTOPOINT | FLAG_FRAMING_Z,
+	.check_connect = always_connected,
+	.bind =		zaurus_bind,
+	.unbind =	usbnet_cdc_unbind,
+	.tx_fixup =	zaurus_tx_fixup,
+};
+#define	ZAURUS_STRONGARM_INFO	((unsigned long)&zaurus_sl5x00_info)
+
+static const struct driver_info	zaurus_pxa_info = {
+	.description =	"Sharp Zaurus, PXA-2xx based",
+	.flags =	FLAG_POINTTOPOINT | FLAG_FRAMING_Z,
+	.check_connect = always_connected,
+	.bind =		zaurus_bind,
+	.unbind =	usbnet_cdc_unbind,
+	.tx_fixup =	zaurus_tx_fixup,
+};
+#define	ZAURUS_PXA_INFO		((unsigned long)&zaurus_pxa_info)
+
+static const struct driver_info	olympus_mxl_info = {
+	.description =	"Olympus R1000",
+	.flags =	FLAG_POINTTOPOINT | FLAG_FRAMING_Z,
+	.check_connect = always_connected,
+	.bind =		zaurus_bind,
+	.unbind =	usbnet_cdc_unbind,
+	.tx_fixup =	zaurus_tx_fixup,
+};
+#define	OLYMPUS_MXL_INFO	((unsigned long)&olympus_mxl_info)
+
+
+/* Some more recent products using Lineo/Belcarra code will wrongly claim
+ * CDC MDLM conformance.  They aren't conformant:  data endpoints live
+ * in the control interface, there's no data interface, and it's not used
+ * to talk to a cell phone radio.  But at least we can detect these two
+ * pseudo-classes, rather than growing this product list with entries for
+ * each new nonconformant product (sigh).
+ */
+static const u8 safe_guid[16] = {
+	0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6,
+	0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f,
+};
+static const u8 blan_guid[16] = {
+	0x74, 0xf0, 0x3d, 0xbd, 0x1e, 0xc1, 0x44, 0x70,
+	0xa3, 0x67, 0x71, 0x34, 0xc9, 0xf5, 0x54, 0x37,
+};
+
+static int blan_mdlm_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	u8				*buf = intf->cur_altsetting->extra;
+	int				len = intf->cur_altsetting->extralen;
+	struct usb_cdc_mdlm_desc	*desc = NULL;
+	struct usb_cdc_mdlm_detail_desc	*detail = NULL;
+
+	while (len > 3) {
+		if (buf [1] != USB_DT_CS_INTERFACE)
+			goto next_desc;
+
+		/* use bDescriptorSubType, and just verify that we get a
+		 * "BLAN" (or "SAFE") descriptor.
+		 */
+		switch (buf [2]) {
+		case USB_CDC_MDLM_TYPE:
+			if (desc) {
+				dev_dbg(&intf->dev, "extra MDLM\n");
+				goto bad_desc;
+			}
+			desc = (void *) buf;
+			if (desc->bLength != sizeof *desc) {
+				dev_dbg(&intf->dev, "MDLM len %u\n",
+					desc->bLength);
+				goto bad_desc;
+			}
+			/* expect bcdVersion 1.0, ignore */
+			if (memcmp(&desc->bGUID, blan_guid, 16) &&
+			    memcmp(&desc->bGUID, safe_guid, 16)) {
+				/* hey, this one might _really_ be MDLM! */
+				dev_dbg(&intf->dev, "MDLM guid\n");
+				goto bad_desc;
+			}
+			break;
+		case USB_CDC_MDLM_DETAIL_TYPE:
+			if (detail) {
+				dev_dbg(&intf->dev, "extra MDLM detail\n");
+				goto bad_desc;
+			}
+			detail = (void *) buf;
+			switch (detail->bGuidDescriptorType) {
+			case 0:			/* "SAFE" */
+				if (detail->bLength != (sizeof *detail + 2))
+					goto bad_detail;
+				break;
+			case 1:			/* "BLAN" */
+				if (detail->bLength != (sizeof *detail + 3))
+					goto bad_detail;
+				break;
+			default:
+				goto bad_detail;
+			}
+
+			/* assuming we either noticed BLAN already, or will
+			 * find it soon, there are some data bytes here:
+			 *  - bmNetworkCapabilities (unused)
+			 *  - bmDataCapabilities (bits, see below)
+			 *  - bPad (ignored, for PADAFTER -- BLAN-only)
+			 * bits are:
+			 *  - 0x01 -- Zaurus framing (add CRC)
+			 *  - 0x02 -- PADBEFORE (CRC includes some padding)
+			 *  - 0x04 -- PADAFTER (some padding after CRC)
+			 *  - 0x08 -- "fermat" packet mangling (for hw bugs)
+			 * the PADBEFORE appears not to matter; we interop
+			 * with devices that use it and those that don't.
+			 */
+			if ((detail->bDetailData[1] & ~0x02) != 0x01) {
+				/* bmDataCapabilities == 0 would be fine too,
+				 * but framing is minidriver-coupled for now.
+				 */
+bad_detail:
+				dev_dbg(&intf->dev,
+						"bad MDLM detail, %d %d %d\n",
+						detail->bLength,
+						detail->bDetailData[0],
+						detail->bDetailData[2]);
+				goto bad_desc;
+			}
+
+			/* same extra framing as for non-BLAN mode */
+			dev->net->hard_header_len += 6;
+			dev->rx_urb_size = dev->net->hard_header_len
+					+ dev->net->mtu;
+			break;
+		}
+next_desc:
+		len -= buf [0];	/* bLength */
+		buf += buf [0];
+	}
+
+	if (!desc || !detail) {
+		dev_dbg(&intf->dev, "missing cdc mdlm %s%sdescriptor\n",
+			desc ? "" : "func ",
+			detail ? "" : "detail ");
+		goto bad_desc;
+	}
+
+	/* There's probably a CDC Ethernet descriptor there, but we can't
+	 * rely on the Ethernet address it provides since not all vendors
+	 * bother to make it unique.  Likewise there's no point in tracking
+	 * of the CDC event notifications.
+	 */
+	return usbnet_get_endpoints(dev, intf);
+
+bad_desc:
+	dev_info(&dev->udev->dev, "unsupported MDLM descriptors\n");
+	return -ENODEV;
+}
+
+static const struct driver_info	bogus_mdlm_info = {
+	.description =	"pseudo-MDLM (BLAN) device",
+	.flags =	FLAG_POINTTOPOINT | FLAG_FRAMING_Z,
+	.check_connect = always_connected,
+	.tx_fixup =	zaurus_tx_fixup,
+	.bind =		blan_mdlm_bind,
+};
+
+static const struct usb_device_id	products [] = {
+#define	ZAURUS_MASTER_INTERFACE \
+	.bInterfaceClass	= USB_CLASS_COMM, \
+	.bInterfaceSubClass	= USB_CDC_SUBCLASS_ETHERNET, \
+	.bInterfaceProtocol	= USB_CDC_PROTO_NONE
+
+/* SA-1100 based Sharp Zaurus ("collie"), or compatible. */
+{
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8004,
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_STRONGARM_INFO,
+},
+
+/* PXA-2xx based models are also lying-about-cdc.  If you add any
+ * more devices that claim to be CDC Ethernet, make sure they get
+ * added to the blacklist in cdc_ether too.
+ *
+ * NOTE:  OpenZaurus versions with 2.6 kernels won't use these entries,
+ * unlike the older ones with 2.4 "embedix" kernels.
+ */
+{
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8005,	/* A-300 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_PXA_INFO,
+}, {
+	.match_flags	=   USB_DEVICE_ID_MATCH_INT_INFO
+			  | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8006,	/* B-500/SL-5600 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_PXA_INFO,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+	          | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor		= 0x04DD,
+	.idProduct		= 0x8007,	/* C-700 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_PXA_INFO,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	.idProduct              = 0x9031,	/* C-750 C-760 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_PXA_INFO,
+}, {
+	/* C-750/C-760/C-860/SL-C3000 PDA in MDLM mode */
+	USB_DEVICE_AND_INTERFACE_INFO(0x04DD, 0x9031, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &bogus_mdlm_info,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	.idProduct              = 0x9032,	/* SL-6000 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_PXA_INFO,
+}, {
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x04DD,
+	/* reported with some C860 units */
+	.idProduct              = 0x9050,	/* C-860 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = ZAURUS_PXA_INFO,
+},
+{
+	/* Motorola Rokr E6 */
+	USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x6027, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &bogus_mdlm_info,
+}, {
+	/* Motorola MOTOMAGX phones */
+	USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x6425, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &bogus_mdlm_info,
+},
+
+/* Olympus has some models with a Zaurus-compatible option.
+ * R-1000 uses a FreeScale i.MXL cpu (ARMv4T)
+ */
+{
+	.match_flags    =   USB_DEVICE_ID_MATCH_INT_INFO
+		 | USB_DEVICE_ID_MATCH_DEVICE,
+	.idVendor               = 0x07B4,
+	.idProduct              = 0x0F02,	/* R-1000 */
+	ZAURUS_MASTER_INTERFACE,
+	.driver_info = OLYMPUS_MXL_INFO,
+},
+
+/* Logitech Harmony 900 - uses the pseudo-MDLM (BLAN) driver */
+{
+	USB_DEVICE_AND_INTERFACE_INFO(0x046d, 0xc11f, USB_CLASS_COMM,
+			USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE),
+	.driver_info = (unsigned long) &bogus_mdlm_info,
+},
+	{ },		// END
+};
+MODULE_DEVICE_TABLE(usb, products);
+
+static struct usb_driver zaurus_driver = {
+	.name =		"zaurus",
+	.id_table =	products,
+	.probe =	usbnet_probe,
+	.disconnect =	usbnet_disconnect,
+	.suspend =	usbnet_suspend,
+	.resume =	usbnet_resume,
+};
+
+module_usb_driver(zaurus_driver);
+
+MODULE_AUTHOR("Pavel Machek, David Brownell");
+MODULE_DESCRIPTION("Sharp Zaurus PDA, and compatible products");
+MODULE_LICENSE("GPL");