diff options
Diffstat (limited to 'drivers/net/usb')
44 files changed, 37467 insertions, 0 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig new file mode 100644 index 000000000..7ba8d0885 --- /dev/null +++ b/drivers/net/usb/Kconfig @@ -0,0 +1,576 @@ +# +# USB Network devices configuration +# +comment "Host-side USB support is needed for USB Network Adapter support" + depends on !USB && NET + +menuconfig USB_NET_DRIVERS + tristate "USB Network Adapters" + default USB if USB + depends on USB && NET + +if USB_NET_DRIVERS + +config USB_CATC + tristate "USB CATC NetMate-based Ethernet device support" + 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 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" + 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_RTL8152 + tristate "Realtek RTL8152/RTL8153 Based USB Ethernet Adapters" + select MII + help + This option adds support for Realtek RTL8152 based USB 2.0 + 10/100 Ethernet adapters and RTL8153 based USB 3.0 10/100/1000 + Ethernet adapters. + + To compile this driver as a module, choose M here: the + module will be called r8152. + +config USB_USBNET + tristate "Multi-purpose USB Networking Framework" + 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 + select PHYLIB + 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 + * Sitecom LN-028 + * 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_AX88179_178A + tristate "ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet" + depends on USB_USBNET + select CRC32 + select PHYLIB + default y + help + This option adds support for ASIX AX88179 based USB 3.0/2.0 + to Gigabit Ethernet adapters. + + This driver should work with at least the following devices: + * ASIX AX88179 + * ASIX AX88178A + * Sitcomm LN-032 + + 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 + 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_HUAWEI_CDC_NCM + tristate "Huawei NCM embedded AT channel support" + depends on USB_USBNET + select USB_WDM + select USB_NET_CDC_NCM + help + This driver supports huawei-style NCM devices, that use NCM as a + transport for other protocols, usually an embedded AT channel. + Good examples are: + * Huawei E3131 + * Huawei E3251 + + To compile this driver as a module, choose M here: the module will be + called huawei_cdc_ncm.ko. + +config USB_NET_CDC_MBIM + tristate "CDC MBIM support" + depends on USB_USBNET + select USB_WDM + select USB_NET_CDC_NCM + help + This driver provides support for CDC MBIM (Mobile Broadband + Interface Model) devices. The CDC MBIM specification is + available from <http://www.usb.org/>. + + MBIM devices require configuration using the management + protocol defined by the MBIM specification. This driver + provides unfiltered access to the MBIM control channel + through the associated /dev/cdc-wdmx character device. + + To compile this driver as a module, choose M here: the + module will be called cdc_mbim. + +config USB_NET_DM9601 + tristate "Davicom DM96xx based USB 10/100 ethernet devices" + depends on USB_USBNET + select CRC32 + help + This option adds support for Davicom DM9601/DM9620/DM9621A + based USB 10/100 Ethernet adapters. + +config USB_NET_SR9700 + tristate "CoreChip-sz SR9700 based USB 1.1 10/100 ethernet devices" + depends on USB_USBNET + select CRC32 + help + This option adds support for CoreChip-sz SR9700 based USB 1.1 + 10/100 Ethernet adapters. + +config USB_NET_SR9800 + tristate "CoreChip-sz SR9800 based USB 2.0 10/100 ethernet devices" + depends on USB_USBNET + select CRC32 + ---help--- + Say Y if you want to use one of the following 100Mbps USB Ethernet + device based on the CoreChip-sz SR9800 chip. + + 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 sr9800. + +config USB_NET_SMSC75XX + tristate "SMSC LAN75XX based USB 2.0 gigabit ethernet devices" + depends on USB_USBNET + select BITREVERSE + select CRC16 + select CRC32 + help + This option adds support for SMSC LAN75XX 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 BITREVERSE + select CRC16 + 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 + 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" + depends on USB_USBNET + 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 + bool "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 + bool "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 + bool "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 + bool "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 + bool "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 + bool "KT Technology KC2190 based cables (InstaNet)" + depends on USB_NET_CDC_SUBSET + 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 && TTY + 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 && TTY + 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 + +endif # USB_NET_DRIVERS diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile new file mode 100644 index 000000000..e2797f1e1 --- /dev/null +++ b/drivers/net/usb/Makefile @@ -0,0 +1,40 @@ +# +# 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_RTL8152) += r8152.o +obj-$(CONFIG_USB_HSO) += hso.o +obj-$(CONFIG_USB_NET_AX8817X) += asix.o +asix-y := asix_devices.o asix_common.o ax88172a.o +obj-$(CONFIG_USB_NET_AX88179_178A) += ax88179_178a.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_SR9700) += sr9700.o +obj-$(CONFIG_USB_NET_SR9800) += sr9800.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_NET_HUAWEI_CDC_NCM) += huawei_cdc_ncm.o +obj-$(CONFIG_USB_VL600) += lg-vl600.o +obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o +obj-$(CONFIG_USB_NET_CDC_MBIM) += cdc_mbim.o + diff --git a/drivers/net/usb/asix.h b/drivers/net/usb/asix.h new file mode 100644 index 000000000..5d049d00c --- /dev/null +++ b/drivers/net/usb/asix.h @@ -0,0 +1,234 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _ASIX_H +#define _ASIX_H + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.h> +#include <linux/kmod.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_PHY_SELECT_MASK (BIT(3) | BIT(2)) +#define AX_PHY_SELECT_INTERNAL 0 +#define AX_PHY_SELECT_EXTERNAL BIT(2) + +#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 AX_EEPROM_LEN 0x200 + +/* 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 res; +}; + +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; +}; + +extern const struct driver_info ax88172a_info; + +/* ASIX specific flags */ +#define FLAG_EEPROM_MAC (1UL << 0) /* init device MAC from eeprom */ + +int asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data); + +int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data); + +void asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, + u16 index, u16 size, void *data); + +int asix_rx_fixup_internal(struct usbnet *dev, struct sk_buff *skb, + struct asix_rx_fixup_info *rx); +int asix_rx_fixup_common(struct usbnet *dev, struct sk_buff *skb); + +struct sk_buff *asix_tx_fixup(struct usbnet *dev, struct sk_buff *skb, + gfp_t flags); + +int asix_set_sw_mii(struct usbnet *dev); +int asix_set_hw_mii(struct usbnet *dev); + +int asix_read_phy_addr(struct usbnet *dev, int internal); +int asix_get_phy_addr(struct usbnet *dev); + +int asix_sw_reset(struct usbnet *dev, u8 flags); + +u16 asix_read_rx_ctl(struct usbnet *dev); +int asix_write_rx_ctl(struct usbnet *dev, u16 mode); + +u16 asix_read_medium_status(struct usbnet *dev); +int asix_write_medium_mode(struct usbnet *dev, u16 mode); + +int asix_write_gpio(struct usbnet *dev, u16 value, int sleep); + +void asix_set_multicast(struct net_device *net); + +int asix_mdio_read(struct net_device *netdev, int phy_id, int loc); +void asix_mdio_write(struct net_device *netdev, int phy_id, int loc, int val); + +void asix_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo); +int asix_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo); + +int asix_get_eeprom_len(struct net_device *net); +int asix_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, + u8 *data); +int asix_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, + u8 *data); + +void asix_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info); + +int asix_set_mac_address(struct net_device *net, void *p); + +#endif /* _ASIX_H */ diff --git a/drivers/net/usb/asix_common.c b/drivers/net/usb/asix_common.c new file mode 100644 index 000000000..75d6f2672 --- /dev/null +++ b/drivers/net/usb/asix_common.c @@ -0,0 +1,584 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "asix.h" + +int asix_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int ret; + ret = usbnet_read_cmd(dev, cmd, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, data, size); + + if (ret != size && ret >= 0) + return -EINVAL; + return ret; +} + +int asix_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + return usbnet_write_cmd(dev, cmd, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, data, size); +} + +void asix_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + usbnet_write_cmd_async(dev, cmd, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, data, size); +} + +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); + rx->ax_skb = NULL; + rx->size = 0U; + + 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; +} + +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); +} + +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; + + /* We need to push 4 bytes in front of frame (packet_len) + * and maybe add 4 bytes after the end (if padlen is 4) + * + * Avoid skb_copy_expand() expensive call, using following rules : + * - We are allowed to push 4 bytes in headroom if skb_header_cloned() + * is false (and if we have 4 bytes of headroom) + * - We are allowed to put 4 bytes at tail if skb_cloned() + * is false (and if we have 4 bytes of tailroom) + * + * TCP packets for example are cloned, but skb_header_release() + * was called in tcp stack, allowing us to use headroom for our needs. + */ + if (!skb_header_cloned(skb) && + !(padlen && skb_cloned(skb)) && + headroom + tailroom >= 4 + padlen) { + /* following should not happen, but better be safe */ + 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; + } + + packet_len = ((skb->len ^ 0x0000ffff) << 16) + skb->len; + skb_push(skb, 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)); + } + + usbnet_set_skb_tx_stats(skb, 1, 0); + return skb; +} + +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; +} + +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; +} + +int asix_read_phy_addr(struct usbnet *dev, int internal) +{ + int offset = (internal ? 1 : 0); + 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[offset]; + +out: + return ret; +} + +int asix_get_phy_addr(struct usbnet *dev) +{ + /* return the address of the internal phy */ + return asix_read_phy_addr(dev, 1); +} + + +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; +} + +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; +} + +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; +} + +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); + +} + +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; +} + +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 + */ +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); +} + +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); +} + +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); +} + +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; +} + +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; +} + +int asix_get_eeprom_len(struct net_device *net) +{ + return AX_EEPROM_LEN; +} + +int asix_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, + u8 *data) +{ + struct usbnet *dev = netdev_priv(net); + u16 *eeprom_buff; + int first_word, last_word; + int i; + + if (eeprom->len == 0) + return -EINVAL; + + eeprom->magic = AX_EEPROM_MAGIC; + + first_word = eeprom->offset >> 1; + last_word = (eeprom->offset + eeprom->len - 1) >> 1; + + eeprom_buff = kmalloc(sizeof(u16) * (last_word - first_word + 1), + GFP_KERNEL); + if (!eeprom_buff) + return -ENOMEM; + + /* ax8817x returns 2 bytes from eeprom on read */ + for (i = first_word; i <= last_word; i++) { + if (asix_read_cmd(dev, AX_CMD_READ_EEPROM, i, 0, 2, + &(eeprom_buff[i - first_word])) < 0) { + kfree(eeprom_buff); + return -EIO; + } + } + + memcpy(data, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len); + kfree(eeprom_buff); + return 0; +} + +int asix_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, + u8 *data) +{ + struct usbnet *dev = netdev_priv(net); + u16 *eeprom_buff; + int first_word, last_word; + int i; + int ret; + + netdev_dbg(net, "write EEPROM len %d, offset %d, magic 0x%x\n", + eeprom->len, eeprom->offset, eeprom->magic); + + if (eeprom->len == 0) + return -EINVAL; + + if (eeprom->magic != AX_EEPROM_MAGIC) + return -EINVAL; + + first_word = eeprom->offset >> 1; + last_word = (eeprom->offset + eeprom->len - 1) >> 1; + + eeprom_buff = kmalloc(sizeof(u16) * (last_word - first_word + 1), + GFP_KERNEL); + if (!eeprom_buff) + return -ENOMEM; + + /* align data to 16 bit boundaries, read the missing data from + the EEPROM */ + if (eeprom->offset & 1) { + ret = asix_read_cmd(dev, AX_CMD_READ_EEPROM, first_word, 0, 2, + &(eeprom_buff[0])); + if (ret < 0) { + netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", first_word); + goto free; + } + } + + if ((eeprom->offset + eeprom->len) & 1) { + ret = asix_read_cmd(dev, AX_CMD_READ_EEPROM, last_word, 0, 2, + &(eeprom_buff[last_word - first_word])); + if (ret < 0) { + netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", last_word); + goto free; + } + } + + memcpy((u8 *)eeprom_buff + (eeprom->offset & 1), data, eeprom->len); + + /* write data to EEPROM */ + ret = asix_write_cmd(dev, AX_CMD_WRITE_ENABLE, 0x0000, 0, 0, NULL); + if (ret < 0) { + netdev_err(net, "Failed to enable EEPROM write\n"); + goto free; + } + msleep(20); + + for (i = first_word; i <= last_word; i++) { + netdev_dbg(net, "write to EEPROM at offset 0x%02x, data 0x%04x\n", + i, eeprom_buff[i - first_word]); + ret = asix_write_cmd(dev, AX_CMD_WRITE_EEPROM, i, + eeprom_buff[i - first_word], 0, NULL); + if (ret < 0) { + netdev_err(net, "Failed to write EEPROM at offset 0x%02x.\n", + i); + goto free; + } + msleep(20); + } + + ret = asix_write_cmd(dev, AX_CMD_WRITE_DISABLE, 0x0000, 0, 0, NULL); + if (ret < 0) { + netdev_err(net, "Failed to disable EEPROM write\n"); + goto free; + } + + ret = 0; +free: + kfree(eeprom_buff); + return ret; +} + +void asix_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info) +{ + /* Inherit standard device info */ + usbnet_get_drvinfo(net, info); + strlcpy(info->driver, DRIVER_NAME, sizeof(info->driver)); + strlcpy(info->version, DRIVER_VERSION, sizeof(info->version)); + info->eedump_len = AX_EEPROM_LEN; +} + +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; +} diff --git a/drivers/net/usb/asix_devices.c b/drivers/net/usb/asix_devices.c new file mode 100644 index 000000000..1173a24fe --- /dev/null +++ b/drivers/net/usb/asix_devices.c @@ -0,0 +1,1107 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "asix.h" + +#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 + +struct ax88172_int_data { + __le16 res1; + u8 link; + __le16 res2; + u8 status; + __le16 res3; +} __packed; + +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) { + usbnet_link_change(dev, link, 1); + netdev_dbg(dev->net, "Link Status is: %d\n", link); + } +} + +static void asix_set_netdev_dev_addr(struct usbnet *dev, u8 *addr) +{ + if (is_valid_ether_addr(addr)) { + memcpy(dev->net->dev_addr, addr, ETH_ALEN); + } else { + netdev_info(dev->net, "invalid hw address, using random\n"); + eth_hw_addr_random(dev->net); + } +} + +/* 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 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); +} + +/* 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, + .set_eeprom = asix_set_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; + + 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) { + netdev_dbg(dev->net, "read AX_CMD_READ_NODE_ID failed: %d\n", + ret); + goto out; + } + + asix_set_netdev_dev_addr(dev, buf); + + /* 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; + dev->net->needed_headroom = 4; /* cf asix_tx_fixup() */ + dev->net->needed_tailroom = 4; /* cf asix_tx_fixup() */ + + 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, + .set_eeprom = asix_set_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) { + netdev_dbg(dev->net, "Select PHY #1 failed: %d\n", 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); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after software reset\n", rx_ctl); + ret = asix_write_rx_ctl(dev, 0x0000); + if (ret < 0) + goto out; + + rx_ctl = asix_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x setting to 0x0000\n", 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) { + netdev_dbg(dev->net, "Write IPG,IPG1,IPG2 failed: %d\n", 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); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after all initializations\n", + rx_ctl); + + rx_ctl = asix_read_medium_status(dev); + netdev_dbg(dev->net, + "Medium Status is 0x%04x after all initializations\n", + 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, i; + u8 buf[ETH_ALEN]; + u32 phyid; + + usbnet_get_endpoints(dev,intf); + + /* Get the MAC address */ + if (dev->driver_info->data & FLAG_EEPROM_MAC) { + for (i = 0; i < (ETH_ALEN >> 1); i++) { + ret = asix_read_cmd(dev, AX_CMD_READ_EEPROM, 0x04 + i, + 0, 2, buf + i * 2); + if (ret < 0) + break; + } + } else { + ret = asix_read_cmd(dev, AX_CMD_READ_NODE_ID, + 0, 0, ETH_ALEN, buf); + } + + if (ret < 0) { + netdev_dbg(dev->net, "Failed to read MAC address: %d\n", ret); + return ret; + } + + asix_set_netdev_dev_addr(dev, buf); + + /* 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; + dev->net->needed_headroom = 4; /* cf asix_tx_fixup() */ + dev->net->needed_tailroom = 4; /* cf asix_tx_fixup() */ + + 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) { + netdev_dbg(dev->net, "Select PHY #1 failed: %d\n", ret); + return ret; + } + + ax88772_reset(dev); + + /* Read PHYID register *AFTER* the PHY was reset properly */ + phyid = asix_get_phyid(dev); + netdev_dbg(dev->net, "PHYID=0x%08x\n", 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, + .set_eeprom = asix_set_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); + netdev_dbg(dev->net, "GPIO Status: 0x%04x\n", 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); + + netdev_dbg(dev->net, "EEPROM index 0x17 is 0x%04x\n", 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; + } + netdev_dbg(dev->net, "GPIO0: %d, PhyMode: %d\n", 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 { + netdev_dbg(dev->net, "gpio phymode == 1 path\n"); + 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); + netdev_dbg(dev->net, "PHYID=0x%08x\n", 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); + + /* max qlen depend on hard_mtu and rx_urb_size */ + usbnet_update_max_qlen(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]; + + 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) { + netdev_dbg(dev->net, "Failed to read MAC address: %d\n", ret); + return ret; + } + + asix_set_netdev_dev_addr(dev, buf); + + /* 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_link_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 ax88772b_info = { + .description = "ASIX AX88772B 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, + .data = FLAG_EEPROM_MAC, +}; + +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, +}; + +/* + * USBLINK 20F9 "USB 2.0 LAN" USB ethernet adapter, typically found in + * no-name packaging. + * USB device strings are: + * 1: Manufacturer: USBLINK + * 2: Product: HG20F9 USB2.0 + * 3: Serial: 000003 + * Appears to be compatible with Asix 88772B. + */ +static const struct driver_info hg20f9_info = { + .description = "HG20F9 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, + .data = FLAG_EEPROM_MAC, +}; + +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, +}, { + // Sitecom LN-028 "USB 2.0 10/100/1000 Ethernet adapter" + USB_DEVICE (0x0df6, 0x061c), + .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, +}, { + // Lenovo U2L100P 10/100 + USB_DEVICE (0x17ef, 0x7203), + .driver_info = (unsigned long) &ax88772_info, +}, { + // ASIX AX88772B 10/100 + USB_DEVICE (0x0b95, 0x772b), + .driver_info = (unsigned long) &ax88772b_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, +}, { + /* ASIX 88172a demo board */ + USB_DEVICE(0x0b95, 0x172a), + .driver_info = (unsigned long) &ax88172a_info, +}, { + /* + * USBLINK HG20F9 "USB 2.0 LAN" + * Appears to have gazumped Linksys's manufacturer ID but + * doesn't (yet) conflict with any known Linksys product. + */ + USB_DEVICE(0x066b, 0x20f9), + .driver_info = (unsigned long) &hg20f9_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, + .disable_hub_initiated_lpm = 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/drivers/net/usb/ax88172a.c b/drivers/net/usb/ax88172a.c new file mode 100644 index 000000000..5f18fcb8d --- /dev/null +++ b/drivers/net/usb/ax88172a.c @@ -0,0 +1,422 @@ +/* + * ASIX AX88172A based USB 2.0 Ethernet Devices + * Copyright (C) 2012 OMICRON electronics GmbH + * + * Supports external PHYs via phylib. Based on the driver for the + * AX88772. Original copyrights follow: + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include "asix.h" +#include <linux/phy.h> + +struct ax88172a_private { + struct mii_bus *mdio; + struct phy_device *phydev; + char phy_name[20]; + u16 phy_addr; + u16 oldmode; + int use_embdphy; + struct asix_rx_fixup_info rx_fixup_info; +}; + +/* MDIO read and write wrappers for phylib */ +static int asix_mdio_bus_read(struct mii_bus *bus, int phy_id, int regnum) +{ + return asix_mdio_read(((struct usbnet *)bus->priv)->net, phy_id, + regnum); +} + +static int asix_mdio_bus_write(struct mii_bus *bus, int phy_id, int regnum, + u16 val) +{ + asix_mdio_write(((struct usbnet *)bus->priv)->net, phy_id, regnum, val); + return 0; +} + +static int ax88172a_ioctl(struct net_device *net, struct ifreq *rq, int cmd) +{ + if (!netif_running(net)) + return -EINVAL; + + if (!net->phydev) + return -ENODEV; + + return phy_mii_ioctl(net->phydev, rq, cmd); +} + +/* set MAC link settings according to information from phylib */ +static void ax88172a_adjust_link(struct net_device *netdev) +{ + struct phy_device *phydev = netdev->phydev; + struct usbnet *dev = netdev_priv(netdev); + struct ax88172a_private *priv = dev->driver_priv; + u16 mode = 0; + + if (phydev->link) { + mode = AX88772_MEDIUM_DEFAULT; + + if (phydev->duplex == DUPLEX_HALF) + mode &= ~AX_MEDIUM_FD; + + if (phydev->speed != SPEED_100) + mode &= ~AX_MEDIUM_PS; + } + + if (mode != priv->oldmode) { + asix_write_medium_mode(dev, mode); + priv->oldmode = mode; + netdev_dbg(netdev, "speed %u duplex %d, setting mode to 0x%04x\n", + phydev->speed, phydev->duplex, mode); + phy_print_status(phydev); + } +} + +static void ax88172a_status(struct usbnet *dev, struct urb *urb) +{ + /* link changes are detected by polling the phy */ +} + +/* use phylib infrastructure */ +static int ax88172a_init_mdio(struct usbnet *dev) +{ + struct ax88172a_private *priv = dev->driver_priv; + int ret, i; + + priv->mdio = mdiobus_alloc(); + if (!priv->mdio) { + netdev_err(dev->net, "Could not allocate MDIO bus\n"); + return -ENOMEM; + } + + priv->mdio->priv = (void *)dev; + priv->mdio->read = &asix_mdio_bus_read; + priv->mdio->write = &asix_mdio_bus_write; + priv->mdio->name = "Asix MDIO Bus"; + /* mii bus name is usb-<usb bus number>-<usb device number> */ + snprintf(priv->mdio->id, MII_BUS_ID_SIZE, "usb-%03d:%03d", + dev->udev->bus->busnum, dev->udev->devnum); + + priv->mdio->irq = kzalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); + if (!priv->mdio->irq) { + ret = -ENOMEM; + goto mfree; + } + for (i = 0; i < PHY_MAX_ADDR; i++) + priv->mdio->irq[i] = PHY_POLL; + + ret = mdiobus_register(priv->mdio); + if (ret) { + netdev_err(dev->net, "Could not register MDIO bus\n"); + goto ifree; + } + + netdev_info(dev->net, "registered mdio bus %s\n", priv->mdio->id); + return 0; + +ifree: + kfree(priv->mdio->irq); +mfree: + mdiobus_free(priv->mdio); + return ret; +} + +static void ax88172a_remove_mdio(struct usbnet *dev) +{ + struct ax88172a_private *priv = dev->driver_priv; + + netdev_info(dev->net, "deregistering mdio bus %s\n", priv->mdio->id); + mdiobus_unregister(priv->mdio); + kfree(priv->mdio->irq); + mdiobus_free(priv->mdio); +} + +static const struct net_device_ops ax88172a_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 = ax88172a_ioctl, + .ndo_set_rx_mode = asix_set_multicast, +}; + +static int ax88172a_get_settings(struct net_device *net, + struct ethtool_cmd *cmd) +{ + if (!net->phydev) + return -ENODEV; + + return phy_ethtool_gset(net->phydev, cmd); +} + +static int ax88172a_set_settings(struct net_device *net, + struct ethtool_cmd *cmd) +{ + if (!net->phydev) + return -ENODEV; + + return phy_ethtool_sset(net->phydev, cmd); +} + +static int ax88172a_nway_reset(struct net_device *net) +{ + if (!net->phydev) + return -ENODEV; + + return phy_start_aneg(net->phydev); +} + +static const struct ethtool_ops ax88172a_ethtool_ops = { + .get_drvinfo = asix_get_drvinfo, + .get_link = usbnet_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, + .set_eeprom = asix_set_eeprom, + .get_settings = ax88172a_get_settings, + .set_settings = ax88172a_set_settings, + .nway_reset = ax88172a_nway_reset, +}; + +static int ax88172a_reset_phy(struct usbnet *dev, int embd_phy) +{ + int ret; + + ret = asix_sw_reset(dev, AX_SWRESET_IPPD); + if (ret < 0) + goto err; + + msleep(150); + ret = asix_sw_reset(dev, AX_SWRESET_CLEAR); + if (ret < 0) + goto err; + + msleep(150); + + ret = asix_sw_reset(dev, embd_phy ? AX_SWRESET_IPRL : AX_SWRESET_IPPD); + if (ret < 0) + goto err; + + return 0; + +err: + return ret; +} + + +static int ax88172a_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int ret; + u8 buf[ETH_ALEN]; + struct ax88172a_private *priv; + + usbnet_get_endpoints(dev, intf); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev->driver_priv = priv; + + /* Get the MAC address */ + ret = asix_read_cmd(dev, AX_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf); + if (ret < 0) { + netdev_err(dev->net, "Failed to read MAC address: %d\n", ret); + goto free; + } + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + + dev->net->netdev_ops = &ax88172a_netdev_ops; + dev->net->ethtool_ops = &ax88172a_ethtool_ops; + + /* are we using the internal or the external phy? */ + ret = asix_read_cmd(dev, AX_CMD_SW_PHY_STATUS, 0, 0, 1, buf); + if (ret < 0) { + netdev_err(dev->net, "Failed to read software interface selection register: %d\n", + ret); + goto free; + } + + netdev_dbg(dev->net, "AX_CMD_SW_PHY_STATUS = 0x%02x\n", buf[0]); + switch (buf[0] & AX_PHY_SELECT_MASK) { + case AX_PHY_SELECT_INTERNAL: + netdev_dbg(dev->net, "use internal phy\n"); + priv->use_embdphy = 1; + break; + case AX_PHY_SELECT_EXTERNAL: + netdev_dbg(dev->net, "use external phy\n"); + priv->use_embdphy = 0; + break; + default: + netdev_err(dev->net, "Interface mode not supported by driver\n"); + ret = -ENOTSUPP; + goto free; + } + + priv->phy_addr = asix_read_phy_addr(dev, priv->use_embdphy); + ax88172a_reset_phy(dev, priv->use_embdphy); + + /* 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; + } + + /* init MDIO bus */ + ret = ax88172a_init_mdio(dev); + if (ret) + goto free; + + return 0; + +free: + kfree(priv); + return ret; +} + +static int ax88172a_stop(struct usbnet *dev) +{ + struct ax88172a_private *priv = dev->driver_priv; + + netdev_dbg(dev->net, "Stopping interface\n"); + + if (priv->phydev) { + netdev_info(dev->net, "Disconnecting from phy %s\n", + priv->phy_name); + phy_stop(priv->phydev); + phy_disconnect(priv->phydev); + } + + return 0; +} + +static void ax88172a_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct ax88172a_private *priv = dev->driver_priv; + + ax88172a_remove_mdio(dev); + kfree(priv); +} + +static int ax88172a_reset(struct usbnet *dev) +{ + struct asix_data *data = (struct asix_data *)&dev->data; + struct ax88172a_private *priv = dev->driver_priv; + int ret; + u16 rx_ctl; + + ax88172a_reset_phy(dev, priv->use_embdphy); + + msleep(150); + rx_ctl = asix_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after software reset\n", rx_ctl); + ret = asix_write_rx_ctl(dev, 0x0000); + if (ret < 0) + goto out; + + rx_ctl = asix_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x setting to 0x0000\n", rx_ctl); + + msleep(150); + + ret = asix_write_cmd(dev, AX_CMD_WRITE_IPG0, + AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT, + AX88772_IPG2_DEFAULT, 0, NULL); + if (ret < 0) { + netdev_err(dev->net, "Write IPG,IPG1,IPG2 failed: %d\n", 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); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after all initializations\n", + rx_ctl); + + rx_ctl = asix_read_medium_status(dev); + netdev_dbg(dev->net, "Medium Status is 0x%04x after all initializations\n", + rx_ctl); + + /* Connect to PHY */ + snprintf(priv->phy_name, 20, PHY_ID_FMT, + priv->mdio->id, priv->phy_addr); + + priv->phydev = phy_connect(dev->net, priv->phy_name, + &ax88172a_adjust_link, + PHY_INTERFACE_MODE_MII); + if (IS_ERR(priv->phydev)) { + netdev_err(dev->net, "Could not connect to PHY device %s\n", + priv->phy_name); + ret = PTR_ERR(priv->phydev); + goto out; + } + + netdev_info(dev->net, "Connected to phy %s\n", priv->phy_name); + + /* During power-up, the AX88172A set the power down (BMCR_PDOWN) + * bit of the PHY. Bring the PHY up again. + */ + genphy_resume(priv->phydev); + phy_start(priv->phydev); + + return 0; + +out: + return ret; + +} + +static int ax88172a_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + struct ax88172a_private *dp = dev->driver_priv; + struct asix_rx_fixup_info *rx = &dp->rx_fixup_info; + + return asix_rx_fixup_internal(dev, skb, rx); +} + +const struct driver_info ax88172a_info = { + .description = "ASIX AX88172A USB 2.0 Ethernet", + .bind = ax88172a_bind, + .reset = ax88172a_reset, + .stop = ax88172a_stop, + .unbind = ax88172a_unbind, + .status = ax88172a_status, + .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR | + FLAG_MULTI_PACKET, + .rx_fixup = ax88172a_rx_fixup, + .tx_fixup = asix_tx_fixup, +}; diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c new file mode 100644 index 000000000..e6338c160 --- /dev/null +++ b/drivers/net/usb/ax88179_178a.c @@ -0,0 +1,1756 @@ +/* + * ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet Devices + * + * Copyright (C) 2011-2013 ASIX + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/etherdevice.h> +#include <linux/mii.h> +#include <linux/usb.h> +#include <linux/crc32.h> +#include <linux/usb/usbnet.h> +#include <uapi/linux/mdio.h> +#include <linux/mdio.h> + +#define AX88179_PHY_ID 0x03 +#define AX_EEPROM_LEN 0x100 +#define AX88179_EEPROM_MAGIC 0x17900b95 +#define AX_MCAST_FLTSIZE 8 +#define AX_MAX_MCAST 64 +#define AX_INT_PPLS_LINK ((u32)BIT(16)) +#define AX_RXHDR_L4_TYPE_MASK 0x1c +#define AX_RXHDR_L4_TYPE_UDP 4 +#define AX_RXHDR_L4_TYPE_TCP 16 +#define AX_RXHDR_L3CSUM_ERR 2 +#define AX_RXHDR_L4CSUM_ERR 1 +#define AX_RXHDR_CRC_ERR ((u32)BIT(29)) +#define AX_RXHDR_DROP_ERR ((u32)BIT(31)) +#define AX_ACCESS_MAC 0x01 +#define AX_ACCESS_PHY 0x02 +#define AX_ACCESS_EEPROM 0x04 +#define AX_ACCESS_EFUS 0x05 +#define AX_PAUSE_WATERLVL_HIGH 0x54 +#define AX_PAUSE_WATERLVL_LOW 0x55 + +#define PHYSICAL_LINK_STATUS 0x02 + #define AX_USB_SS 0x04 + #define AX_USB_HS 0x02 + +#define GENERAL_STATUS 0x03 +/* Check AX88179 version. UA1:Bit2 = 0, UA2:Bit2 = 1 */ + #define AX_SECLD 0x04 + +#define AX_SROM_ADDR 0x07 +#define AX_SROM_CMD 0x0a + #define EEP_RD 0x04 + #define EEP_BUSY 0x10 + +#define AX_SROM_DATA_LOW 0x08 +#define AX_SROM_DATA_HIGH 0x09 + +#define AX_RX_CTL 0x0b + #define AX_RX_CTL_DROPCRCERR 0x0100 + #define AX_RX_CTL_IPE 0x0200 + #define AX_RX_CTL_START 0x0080 + #define AX_RX_CTL_AP 0x0020 + #define AX_RX_CTL_AM 0x0010 + #define AX_RX_CTL_AB 0x0008 + #define AX_RX_CTL_AMALL 0x0002 + #define AX_RX_CTL_PRO 0x0001 + #define AX_RX_CTL_STOP 0x0000 + +#define AX_NODE_ID 0x10 +#define AX_MULFLTARY 0x16 + +#define AX_MEDIUM_STATUS_MODE 0x22 + #define AX_MEDIUM_GIGAMODE 0x01 + #define AX_MEDIUM_FULL_DUPLEX 0x02 + #define AX_MEDIUM_EN_125MHZ 0x08 + #define AX_MEDIUM_RXFLOW_CTRLEN 0x10 + #define AX_MEDIUM_TXFLOW_CTRLEN 0x20 + #define AX_MEDIUM_RECEIVE_EN 0x100 + #define AX_MEDIUM_PS 0x200 + #define AX_MEDIUM_JUMBO_EN 0x8040 + +#define AX_MONITOR_MOD 0x24 + #define AX_MONITOR_MODE_RWLC 0x02 + #define AX_MONITOR_MODE_RWMP 0x04 + #define AX_MONITOR_MODE_PMEPOL 0x20 + #define AX_MONITOR_MODE_PMETYPE 0x40 + +#define AX_GPIO_CTRL 0x25 + #define AX_GPIO_CTRL_GPIO3EN 0x80 + #define AX_GPIO_CTRL_GPIO2EN 0x40 + #define AX_GPIO_CTRL_GPIO1EN 0x20 + +#define AX_PHYPWR_RSTCTL 0x26 + #define AX_PHYPWR_RSTCTL_BZ 0x0010 + #define AX_PHYPWR_RSTCTL_IPRL 0x0020 + #define AX_PHYPWR_RSTCTL_AT 0x1000 + +#define AX_RX_BULKIN_QCTRL 0x2e +#define AX_CLK_SELECT 0x33 + #define AX_CLK_SELECT_BCS 0x01 + #define AX_CLK_SELECT_ACS 0x02 + #define AX_CLK_SELECT_ULR 0x08 + +#define AX_RXCOE_CTL 0x34 + #define AX_RXCOE_IP 0x01 + #define AX_RXCOE_TCP 0x02 + #define AX_RXCOE_UDP 0x04 + #define AX_RXCOE_TCPV6 0x20 + #define AX_RXCOE_UDPV6 0x40 + +#define AX_TXCOE_CTL 0x35 + #define AX_TXCOE_IP 0x01 + #define AX_TXCOE_TCP 0x02 + #define AX_TXCOE_UDP 0x04 + #define AX_TXCOE_TCPV6 0x20 + #define AX_TXCOE_UDPV6 0x40 + +#define AX_LEDCTRL 0x73 + +#define GMII_PHY_PHYSR 0x11 + #define GMII_PHY_PHYSR_SMASK 0xc000 + #define GMII_PHY_PHYSR_GIGA 0x8000 + #define GMII_PHY_PHYSR_100 0x4000 + #define GMII_PHY_PHYSR_FULL 0x2000 + #define GMII_PHY_PHYSR_LINK 0x400 + +#define GMII_LED_ACT 0x1a + #define GMII_LED_ACTIVE_MASK 0xff8f + #define GMII_LED0_ACTIVE BIT(4) + #define GMII_LED1_ACTIVE BIT(5) + #define GMII_LED2_ACTIVE BIT(6) + +#define GMII_LED_LINK 0x1c + #define GMII_LED_LINK_MASK 0xf888 + #define GMII_LED0_LINK_10 BIT(0) + #define GMII_LED0_LINK_100 BIT(1) + #define GMII_LED0_LINK_1000 BIT(2) + #define GMII_LED1_LINK_10 BIT(4) + #define GMII_LED1_LINK_100 BIT(5) + #define GMII_LED1_LINK_1000 BIT(6) + #define GMII_LED2_LINK_10 BIT(8) + #define GMII_LED2_LINK_100 BIT(9) + #define GMII_LED2_LINK_1000 BIT(10) + #define LED0_ACTIVE BIT(0) + #define LED0_LINK_10 BIT(1) + #define LED0_LINK_100 BIT(2) + #define LED0_LINK_1000 BIT(3) + #define LED0_FD BIT(4) + #define LED0_USB3_MASK 0x001f + #define LED1_ACTIVE BIT(5) + #define LED1_LINK_10 BIT(6) + #define LED1_LINK_100 BIT(7) + #define LED1_LINK_1000 BIT(8) + #define LED1_FD BIT(9) + #define LED1_USB3_MASK 0x03e0 + #define LED2_ACTIVE BIT(10) + #define LED2_LINK_1000 BIT(13) + #define LED2_LINK_100 BIT(12) + #define LED2_LINK_10 BIT(11) + #define LED2_FD BIT(14) + #define LED_VALID BIT(15) + #define LED2_USB3_MASK 0x7c00 + +#define GMII_PHYPAGE 0x1e +#define GMII_PHY_PAGE_SELECT 0x1f + #define GMII_PHY_PGSEL_EXT 0x0007 + #define GMII_PHY_PGSEL_PAGE0 0x0000 + #define GMII_PHY_PGSEL_PAGE3 0x0003 + #define GMII_PHY_PGSEL_PAGE5 0x0005 + +struct ax88179_data { + u8 eee_enabled; + u8 eee_active; + u16 rxctl; + u16 reserved; +}; + +struct ax88179_int_data { + __le32 intdata1; + __le32 intdata2; +}; + +static const struct { + unsigned char ctrl, timer_l, timer_h, size, ifg; +} AX88179_BULKIN_SIZE[] = { + {7, 0x4f, 0, 0x12, 0xff}, + {7, 0x20, 3, 0x16, 0xff}, + {7, 0xae, 7, 0x18, 0xff}, + {7, 0xcc, 0x4c, 0x18, 8}, +}; + +static int __ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data, int in_pm) +{ + int ret; + int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16); + + BUG_ON(!dev); + + if (!in_pm) + fn = usbnet_read_cmd; + else + fn = usbnet_read_cmd_nopm; + + ret = fn(dev, cmd, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, data, size); + + if (unlikely(ret < 0)) + netdev_warn(dev->net, "Failed to read reg index 0x%04x: %d\n", + index, ret); + + return ret; +} + +static int __ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data, int in_pm) +{ + int ret; + int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16); + + BUG_ON(!dev); + + if (!in_pm) + fn = usbnet_write_cmd; + else + fn = usbnet_write_cmd_nopm; + + ret = fn(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, data, size); + + if (unlikely(ret < 0)) + netdev_warn(dev->net, "Failed to write reg index 0x%04x: %d\n", + index, ret); + + return ret; +} + +static void ax88179_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, + u16 index, u16 size, void *data) +{ + u16 buf; + + if (2 == size) { + buf = *((u16 *)data); + cpu_to_le16s(&buf); + usbnet_write_cmd_async(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, &buf, + size); + } else { + usbnet_write_cmd_async(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, data, + size); + } +} + +static int ax88179_read_cmd_nopm(struct usbnet *dev, u8 cmd, u16 value, + u16 index, u16 size, void *data) +{ + int ret; + + if (2 == size) { + u16 buf; + ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf, 1); + le16_to_cpus(&buf); + *((u16 *)data) = buf; + } else if (4 == size) { + u32 buf; + ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf, 1); + le32_to_cpus(&buf); + *((u32 *)data) = buf; + } else { + ret = __ax88179_read_cmd(dev, cmd, value, index, size, data, 1); + } + + return ret; +} + +static int ax88179_write_cmd_nopm(struct usbnet *dev, u8 cmd, u16 value, + u16 index, u16 size, void *data) +{ + int ret; + + if (2 == size) { + u16 buf; + buf = *((u16 *)data); + cpu_to_le16s(&buf); + ret = __ax88179_write_cmd(dev, cmd, value, index, + size, &buf, 1); + } else { + ret = __ax88179_write_cmd(dev, cmd, value, index, + size, data, 1); + } + + return ret; +} + +static int ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int ret; + + if (2 == size) { + u16 buf; + ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf, 0); + le16_to_cpus(&buf); + *((u16 *)data) = buf; + } else if (4 == size) { + u32 buf; + ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf, 0); + le32_to_cpus(&buf); + *((u32 *)data) = buf; + } else { + ret = __ax88179_read_cmd(dev, cmd, value, index, size, data, 0); + } + + return ret; +} + +static int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int ret; + + if (2 == size) { + u16 buf; + buf = *((u16 *)data); + cpu_to_le16s(&buf); + ret = __ax88179_write_cmd(dev, cmd, value, index, + size, &buf, 0); + } else { + ret = __ax88179_write_cmd(dev, cmd, value, index, + size, data, 0); + } + + return ret; +} + +static void ax88179_status(struct usbnet *dev, struct urb *urb) +{ + struct ax88179_int_data *event; + u32 link; + + if (urb->actual_length < 8) + return; + + event = urb->transfer_buffer; + le32_to_cpus((void *)&event->intdata1); + + link = (((__force u32)event->intdata1) & AX_INT_PPLS_LINK) >> 16; + + if (netif_carrier_ok(dev->net) != link) { + usbnet_link_change(dev, link, 1); + netdev_info(dev->net, "ax88179 - Link status is: %d\n", link); + } +} + +static int ax88179_mdio_read(struct net_device *netdev, int phy_id, int loc) +{ + struct usbnet *dev = netdev_priv(netdev); + u16 res; + + ax88179_read_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)loc, 2, &res); + return res; +} + +static void ax88179_mdio_write(struct net_device *netdev, int phy_id, int loc, + int val) +{ + struct usbnet *dev = netdev_priv(netdev); + u16 res = (u16) val; + + ax88179_write_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)loc, 2, &res); +} + +static inline int ax88179_phy_mmd_indirect(struct usbnet *dev, u16 prtad, + u16 devad) +{ + u16 tmp16; + int ret; + + tmp16 = devad; + ret = ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_MMD_CTRL, 2, &tmp16); + + tmp16 = prtad; + ret = ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_MMD_DATA, 2, &tmp16); + + tmp16 = devad | MII_MMD_CTRL_NOINCR; + ret = ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_MMD_CTRL, 2, &tmp16); + + return ret; +} + +static int +ax88179_phy_read_mmd_indirect(struct usbnet *dev, u16 prtad, u16 devad) +{ + int ret; + u16 tmp16; + + ax88179_phy_mmd_indirect(dev, prtad, devad); + + ret = ax88179_read_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_MMD_DATA, 2, &tmp16); + if (ret < 0) + return ret; + + return tmp16; +} + +static int +ax88179_phy_write_mmd_indirect(struct usbnet *dev, u16 prtad, u16 devad, + u16 data) +{ + int ret; + + ax88179_phy_mmd_indirect(dev, prtad, devad); + + ret = ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_MMD_DATA, 2, &data); + + if (ret < 0) + return ret; + + return 0; +} + +static int ax88179_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + u16 tmp16; + u8 tmp8; + + usbnet_suspend(intf, message); + + /* Disable RX path */ + ax88179_read_cmd_nopm(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + tmp16 &= ~AX_MEDIUM_RECEIVE_EN; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + + /* Force bulk-in zero length */ + ax88179_read_cmd_nopm(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, + 2, 2, &tmp16); + + tmp16 |= AX_PHYPWR_RSTCTL_BZ | AX_PHYPWR_RSTCTL_IPRL; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, + 2, 2, &tmp16); + + /* change clock */ + tmp8 = 0; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, &tmp8); + + /* Configure RX control register => stop operation */ + tmp16 = AX_RX_CTL_STOP; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &tmp16); + + return 0; +} + +/* This function is used to enable the autodetach function. */ +/* This function is determined by offset 0x43 of EEPROM */ +static int ax88179_auto_detach(struct usbnet *dev, int in_pm) +{ + u16 tmp16; + u8 tmp8; + int (*fnr)(struct usbnet *, u8, u16, u16, u16, void *); + int (*fnw)(struct usbnet *, u8, u16, u16, u16, void *); + + if (!in_pm) { + fnr = ax88179_read_cmd; + fnw = ax88179_write_cmd; + } else { + fnr = ax88179_read_cmd_nopm; + fnw = ax88179_write_cmd_nopm; + } + + if (fnr(dev, AX_ACCESS_EEPROM, 0x43, 1, 2, &tmp16) < 0) + return 0; + + if ((tmp16 == 0xFFFF) || (!(tmp16 & 0x0100))) + return 0; + + /* Enable Auto Detach bit */ + tmp8 = 0; + fnr(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, &tmp8); + tmp8 |= AX_CLK_SELECT_ULR; + fnw(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, &tmp8); + + fnr(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, &tmp16); + tmp16 |= AX_PHYPWR_RSTCTL_AT; + fnw(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, &tmp16); + + return 0; +} + +static int ax88179_resume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + u16 tmp16; + u8 tmp8; + + usbnet_link_change(dev, 0, 0); + + /* Power up ethernet PHY */ + tmp16 = 0; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, + 2, 2, &tmp16); + udelay(1000); + + tmp16 = AX_PHYPWR_RSTCTL_IPRL; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, + 2, 2, &tmp16); + msleep(200); + + /* Ethernet PHY Auto Detach*/ + ax88179_auto_detach(dev, 1); + + /* Enable clock */ + ax88179_read_cmd_nopm(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, &tmp8); + tmp8 |= AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, &tmp8); + msleep(100); + + /* Configure RX control register => start operation */ + tmp16 = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_IPE | AX_RX_CTL_START | + AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB; + ax88179_write_cmd_nopm(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &tmp16); + + return usbnet_resume(intf); +} + +static void +ax88179_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + u8 opt; + + if (ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, + 1, 1, &opt) < 0) { + wolinfo->supported = 0; + wolinfo->wolopts = 0; + return; + } + + wolinfo->supported = WAKE_PHY | WAKE_MAGIC; + wolinfo->wolopts = 0; + if (opt & AX_MONITOR_MODE_RWLC) + wolinfo->wolopts |= WAKE_PHY; + if (opt & AX_MONITOR_MODE_RWMP) + wolinfo->wolopts |= WAKE_MAGIC; +} + +static int +ax88179_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_MODE_RWLC; + if (wolinfo->wolopts & WAKE_MAGIC) + opt |= AX_MONITOR_MODE_RWMP; + + if (ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, + 1, 1, &opt) < 0) + return -EINVAL; + + return 0; +} + +static int ax88179_get_eeprom_len(struct net_device *net) +{ + return AX_EEPROM_LEN; +} + +static int +ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, + u8 *data) +{ + struct usbnet *dev = netdev_priv(net); + u16 *eeprom_buff; + int first_word, last_word; + int i, ret; + + if (eeprom->len == 0) + return -EINVAL; + + eeprom->magic = AX88179_EEPROM_MAGIC; + + first_word = eeprom->offset >> 1; + last_word = (eeprom->offset + eeprom->len - 1) >> 1; + eeprom_buff = kmalloc(sizeof(u16) * (last_word - first_word + 1), + GFP_KERNEL); + if (!eeprom_buff) + return -ENOMEM; + + /* ax88179/178A returns 2 bytes from eeprom on read */ + for (i = first_word; i <= last_word; i++) { + ret = __ax88179_read_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2, + &eeprom_buff[i - first_word], + 0); + if (ret < 0) { + kfree(eeprom_buff); + return -EIO; + } + } + + memcpy(data, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len); + kfree(eeprom_buff); + return 0; +} + +static int ax88179_get_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + return mii_ethtool_gset(&dev->mii, cmd); +} + +static int ax88179_set_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + return mii_ethtool_sset(&dev->mii, cmd); +} + +static int +ax88179_ethtool_get_eee(struct usbnet *dev, struct ethtool_eee *data) +{ + int val; + + /* Get Supported EEE */ + val = ax88179_phy_read_mmd_indirect(dev, MDIO_PCS_EEE_ABLE, + MDIO_MMD_PCS); + if (val < 0) + return val; + data->supported = mmd_eee_cap_to_ethtool_sup_t(val); + + /* Get advertisement EEE */ + val = ax88179_phy_read_mmd_indirect(dev, MDIO_AN_EEE_ADV, + MDIO_MMD_AN); + if (val < 0) + return val; + data->advertised = mmd_eee_adv_to_ethtool_adv_t(val); + + /* Get LP advertisement EEE */ + val = ax88179_phy_read_mmd_indirect(dev, MDIO_AN_EEE_LPABLE, + MDIO_MMD_AN); + if (val < 0) + return val; + data->lp_advertised = mmd_eee_adv_to_ethtool_adv_t(val); + + return 0; +} + +static int +ax88179_ethtool_set_eee(struct usbnet *dev, struct ethtool_eee *data) +{ + u16 tmp16 = ethtool_adv_to_mmd_eee_adv_t(data->advertised); + + return ax88179_phy_write_mmd_indirect(dev, MDIO_AN_EEE_ADV, + MDIO_MMD_AN, tmp16); +} + +static int ax88179_chk_eee(struct usbnet *dev) +{ + struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET }; + struct ax88179_data *priv = (struct ax88179_data *)dev->data; + + mii_ethtool_gset(&dev->mii, &ecmd); + + if (ecmd.duplex & DUPLEX_FULL) { + int eee_lp, eee_cap, eee_adv; + u32 lp, cap, adv, supported = 0; + + eee_cap = ax88179_phy_read_mmd_indirect(dev, + MDIO_PCS_EEE_ABLE, + MDIO_MMD_PCS); + if (eee_cap < 0) { + priv->eee_active = 0; + return false; + } + + cap = mmd_eee_cap_to_ethtool_sup_t(eee_cap); + if (!cap) { + priv->eee_active = 0; + return false; + } + + eee_lp = ax88179_phy_read_mmd_indirect(dev, + MDIO_AN_EEE_LPABLE, + MDIO_MMD_AN); + if (eee_lp < 0) { + priv->eee_active = 0; + return false; + } + + eee_adv = ax88179_phy_read_mmd_indirect(dev, + MDIO_AN_EEE_ADV, + MDIO_MMD_AN); + + if (eee_adv < 0) { + priv->eee_active = 0; + return false; + } + + adv = mmd_eee_adv_to_ethtool_adv_t(eee_adv); + lp = mmd_eee_adv_to_ethtool_adv_t(eee_lp); + supported = (ecmd.speed == SPEED_1000) ? + SUPPORTED_1000baseT_Full : + SUPPORTED_100baseT_Full; + + if (!(lp & adv & supported)) { + priv->eee_active = 0; + return false; + } + + priv->eee_active = 1; + return true; + } + + priv->eee_active = 0; + return false; +} + +static void ax88179_disable_eee(struct usbnet *dev) +{ + u16 tmp16; + + tmp16 = GMII_PHY_PGSEL_PAGE3; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp16); + + tmp16 = 0x3246; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_PHYADDR, 2, &tmp16); + + tmp16 = GMII_PHY_PGSEL_PAGE0; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp16); +} + +static void ax88179_enable_eee(struct usbnet *dev) +{ + u16 tmp16; + + tmp16 = GMII_PHY_PGSEL_PAGE3; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp16); + + tmp16 = 0x3247; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_PHYADDR, 2, &tmp16); + + tmp16 = GMII_PHY_PGSEL_PAGE5; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp16); + + tmp16 = 0x0680; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + MII_BMSR, 2, &tmp16); + + tmp16 = GMII_PHY_PGSEL_PAGE0; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp16); +} + +static int ax88179_get_eee(struct net_device *net, struct ethtool_eee *edata) +{ + struct usbnet *dev = netdev_priv(net); + struct ax88179_data *priv = (struct ax88179_data *)dev->data; + + edata->eee_enabled = priv->eee_enabled; + edata->eee_active = priv->eee_active; + + return ax88179_ethtool_get_eee(dev, edata); +} + +static int ax88179_set_eee(struct net_device *net, struct ethtool_eee *edata) +{ + struct usbnet *dev = netdev_priv(net); + struct ax88179_data *priv = (struct ax88179_data *)dev->data; + int ret = -EOPNOTSUPP; + + priv->eee_enabled = edata->eee_enabled; + if (!priv->eee_enabled) { + ax88179_disable_eee(dev); + } else { + priv->eee_enabled = ax88179_chk_eee(dev); + if (!priv->eee_enabled) + return -EOPNOTSUPP; + + ax88179_enable_eee(dev); + } + + ret = ax88179_ethtool_set_eee(dev, edata); + if (ret) + return ret; + + mii_nway_restart(&dev->mii); + + usbnet_link_change(dev, 0, 0); + + return ret; +} + +static int ax88179_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 ax88179_ethtool_ops = { + .get_link = ethtool_op_get_link, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, + .get_wol = ax88179_get_wol, + .set_wol = ax88179_set_wol, + .get_eeprom_len = ax88179_get_eeprom_len, + .get_eeprom = ax88179_get_eeprom, + .get_settings = ax88179_get_settings, + .set_settings = ax88179_set_settings, + .get_eee = ax88179_get_eee, + .set_eee = ax88179_set_eee, + .nway_reset = usbnet_nway_reset, +}; + +static void ax88179_set_multicast(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct ax88179_data *data = (struct ax88179_data *)dev->data; + u8 *m_filter = ((u8 *)dev->data) + 12; + + data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_IPE); + + if (net->flags & IFF_PROMISC) { + data->rxctl |= AX_RX_CTL_PRO; + } else if (net->flags & IFF_ALLMULTI || + netdev_mc_count(net) > AX_MAX_MCAST) { + data->rxctl |= 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 + */ + u32 crc_bits; + struct netdev_hw_addr *ha; + + memset(m_filter, 0, AX_MCAST_FLTSIZE); + + netdev_for_each_mc_addr(ha, net) { + crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26; + *(m_filter + (crc_bits >> 3)) |= (1 << (crc_bits & 7)); + } + + ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_MULFLTARY, + AX_MCAST_FLTSIZE, AX_MCAST_FLTSIZE, + m_filter); + + data->rxctl |= AX_RX_CTL_AM; + } + + ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_RX_CTL, + 2, 2, &data->rxctl); +} + +static int +ax88179_set_features(struct net_device *net, netdev_features_t features) +{ + u8 tmp; + struct usbnet *dev = netdev_priv(net); + netdev_features_t changed = net->features ^ features; + + if (changed & NETIF_F_IP_CSUM) { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp); + tmp ^= AX_TXCOE_TCP | AX_TXCOE_UDP; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp); + } + + if (changed & NETIF_F_IPV6_CSUM) { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp); + tmp ^= AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp); + } + + if (changed & NETIF_F_RXCSUM) { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp); + tmp ^= AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP | + AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp); + } + + return 0; +} + +static int ax88179_change_mtu(struct net_device *net, int new_mtu) +{ + struct usbnet *dev = netdev_priv(net); + u16 tmp16; + + if (new_mtu <= 0 || new_mtu > 4088) + return -EINVAL; + + net->mtu = new_mtu; + dev->hard_mtu = net->mtu + net->hard_header_len; + + if (net->mtu > 1500) { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + tmp16 |= AX_MEDIUM_JUMBO_EN; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + } else { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + tmp16 &= ~AX_MEDIUM_JUMBO_EN; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + } + + /* max qlen depend on hard_mtu and rx_urb_size */ + usbnet_update_max_qlen(dev); + + return 0; +} + +static int ax88179_set_mac_addr(struct net_device *net, void *p) +{ + struct usbnet *dev = netdev_priv(net); + struct sockaddr *addr = p; + int ret; + + 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); + + /* Set the MAC address */ + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN, + ETH_ALEN, net->dev_addr); + if (ret < 0) + return ret; + + return 0; +} + +static const struct net_device_ops ax88179_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 = ax88179_change_mtu, + .ndo_set_mac_address = ax88179_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_do_ioctl = ax88179_ioctl, + .ndo_set_rx_mode = ax88179_set_multicast, + .ndo_set_features = ax88179_set_features, +}; + +static int ax88179_check_eeprom(struct usbnet *dev) +{ + u8 i, buf, eeprom[20]; + u16 csum, delay = HZ / 10; + unsigned long jtimeout; + + /* Read EEPROM content */ + for (i = 0; i < 6; i++) { + buf = i; + if (ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_SROM_ADDR, + 1, 1, &buf) < 0) + return -EINVAL; + + buf = EEP_RD; + if (ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_SROM_CMD, + 1, 1, &buf) < 0) + return -EINVAL; + + jtimeout = jiffies + delay; + do { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_SROM_CMD, + 1, 1, &buf); + + if (time_after(jiffies, jtimeout)) + return -EINVAL; + + } while (buf & EEP_BUSY); + + __ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_SROM_DATA_LOW, + 2, 2, &eeprom[i * 2], 0); + + if ((i == 0) && (eeprom[0] == 0xFF)) + return -EINVAL; + } + + csum = eeprom[6] + eeprom[7] + eeprom[8] + eeprom[9]; + csum = (csum >> 8) + (csum & 0xff); + if ((csum + eeprom[10]) != 0xff) + return -EINVAL; + + return 0; +} + +static int ax88179_check_efuse(struct usbnet *dev, u16 *ledmode) +{ + u8 i; + u8 efuse[64]; + u16 csum = 0; + + if (ax88179_read_cmd(dev, AX_ACCESS_EFUS, 0, 64, 64, efuse) < 0) + return -EINVAL; + + if (*efuse == 0xFF) + return -EINVAL; + + for (i = 0; i < 64; i++) + csum = csum + efuse[i]; + + while (csum > 255) + csum = (csum & 0x00FF) + ((csum >> 8) & 0x00FF); + + if (csum != 0xFF) + return -EINVAL; + + *ledmode = (efuse[51] << 8) | efuse[52]; + + return 0; +} + +static int ax88179_convert_old_led(struct usbnet *dev, u16 *ledvalue) +{ + u16 led; + + /* Loaded the old eFuse LED Mode */ + if (ax88179_read_cmd(dev, AX_ACCESS_EEPROM, 0x3C, 1, 2, &led) < 0) + return -EINVAL; + + led >>= 8; + switch (led) { + case 0xFF: + led = LED0_ACTIVE | LED1_LINK_10 | LED1_LINK_100 | + LED1_LINK_1000 | LED2_ACTIVE | LED2_LINK_10 | + LED2_LINK_100 | LED2_LINK_1000 | LED_VALID; + break; + case 0xFE: + led = LED0_ACTIVE | LED1_LINK_1000 | LED2_LINK_100 | LED_VALID; + break; + case 0xFD: + led = LED0_ACTIVE | LED1_LINK_1000 | LED2_LINK_100 | + LED2_LINK_10 | LED_VALID; + break; + case 0xFC: + led = LED0_ACTIVE | LED1_ACTIVE | LED1_LINK_1000 | LED2_ACTIVE | + LED2_LINK_100 | LED2_LINK_10 | LED_VALID; + break; + default: + led = LED0_ACTIVE | LED1_LINK_10 | LED1_LINK_100 | + LED1_LINK_1000 | LED2_ACTIVE | LED2_LINK_10 | + LED2_LINK_100 | LED2_LINK_1000 | LED_VALID; + break; + } + + *ledvalue = led; + + return 0; +} + +static int ax88179_led_setting(struct usbnet *dev) +{ + u8 ledfd, value = 0; + u16 tmp, ledact, ledlink, ledvalue = 0, delay = HZ / 10; + unsigned long jtimeout; + + /* Check AX88179 version. UA1 or UA2*/ + ax88179_read_cmd(dev, AX_ACCESS_MAC, GENERAL_STATUS, 1, 1, &value); + + if (!(value & AX_SECLD)) { /* UA1 */ + value = AX_GPIO_CTRL_GPIO3EN | AX_GPIO_CTRL_GPIO2EN | + AX_GPIO_CTRL_GPIO1EN; + if (ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_GPIO_CTRL, + 1, 1, &value) < 0) + return -EINVAL; + } + + /* Check EEPROM */ + if (!ax88179_check_eeprom(dev)) { + value = 0x42; + if (ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_SROM_ADDR, + 1, 1, &value) < 0) + return -EINVAL; + + value = EEP_RD; + if (ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_SROM_CMD, + 1, 1, &value) < 0) + return -EINVAL; + + jtimeout = jiffies + delay; + do { + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_SROM_CMD, + 1, 1, &value); + + if (time_after(jiffies, jtimeout)) + return -EINVAL; + + } while (value & EEP_BUSY); + + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_SROM_DATA_HIGH, + 1, 1, &value); + ledvalue = (value << 8); + + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_SROM_DATA_LOW, + 1, 1, &value); + ledvalue |= value; + + /* load internal ROM for defaule setting */ + if ((ledvalue == 0xFFFF) || ((ledvalue & LED_VALID) == 0)) + ax88179_convert_old_led(dev, &ledvalue); + + } else if (!ax88179_check_efuse(dev, &ledvalue)) { + if ((ledvalue == 0xFFFF) || ((ledvalue & LED_VALID) == 0)) + ax88179_convert_old_led(dev, &ledvalue); + } else { + ax88179_convert_old_led(dev, &ledvalue); + } + + tmp = GMII_PHY_PGSEL_EXT; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp); + + tmp = 0x2c; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHYPAGE, 2, &tmp); + + ax88179_read_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_LED_ACT, 2, &ledact); + + ax88179_read_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_LED_LINK, 2, &ledlink); + + ledact &= GMII_LED_ACTIVE_MASK; + ledlink &= GMII_LED_LINK_MASK; + + if (ledvalue & LED0_ACTIVE) + ledact |= GMII_LED0_ACTIVE; + + if (ledvalue & LED1_ACTIVE) + ledact |= GMII_LED1_ACTIVE; + + if (ledvalue & LED2_ACTIVE) + ledact |= GMII_LED2_ACTIVE; + + if (ledvalue & LED0_LINK_10) + ledlink |= GMII_LED0_LINK_10; + + if (ledvalue & LED1_LINK_10) + ledlink |= GMII_LED1_LINK_10; + + if (ledvalue & LED2_LINK_10) + ledlink |= GMII_LED2_LINK_10; + + if (ledvalue & LED0_LINK_100) + ledlink |= GMII_LED0_LINK_100; + + if (ledvalue & LED1_LINK_100) + ledlink |= GMII_LED1_LINK_100; + + if (ledvalue & LED2_LINK_100) + ledlink |= GMII_LED2_LINK_100; + + if (ledvalue & LED0_LINK_1000) + ledlink |= GMII_LED0_LINK_1000; + + if (ledvalue & LED1_LINK_1000) + ledlink |= GMII_LED1_LINK_1000; + + if (ledvalue & LED2_LINK_1000) + ledlink |= GMII_LED2_LINK_1000; + + tmp = ledact; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_LED_ACT, 2, &tmp); + + tmp = ledlink; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_LED_LINK, 2, &tmp); + + tmp = GMII_PHY_PGSEL_PAGE0; + ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PAGE_SELECT, 2, &tmp); + + /* LED full duplex setting */ + ledfd = 0; + if (ledvalue & LED0_FD) + ledfd |= 0x01; + else if ((ledvalue & LED0_USB3_MASK) == 0) + ledfd |= 0x02; + + if (ledvalue & LED1_FD) + ledfd |= 0x04; + else if ((ledvalue & LED1_USB3_MASK) == 0) + ledfd |= 0x08; + + if (ledvalue & LED2_FD) + ledfd |= 0x10; + else if ((ledvalue & LED2_USB3_MASK) == 0) + ledfd |= 0x20; + + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_LEDCTRL, 1, 1, &ledfd); + + return 0; +} + +static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf) +{ + u8 buf[5]; + u16 *tmp16; + u8 *tmp; + struct ax88179_data *ax179_data = (struct ax88179_data *)dev->data; + struct ethtool_eee eee_data; + + usbnet_get_endpoints(dev, intf); + + tmp16 = (u16 *)buf; + tmp = (u8 *)buf; + + memset(ax179_data, 0, sizeof(*ax179_data)); + + /* Power up ethernet PHY */ + *tmp16 = 0; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16); + *tmp16 = AX_PHYPWR_RSTCTL_IPRL; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16); + msleep(200); + + *tmp = AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, tmp); + msleep(100); + + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN, + ETH_ALEN, dev->net->dev_addr); + memcpy(dev->net->perm_addr, dev->net->dev_addr, ETH_ALEN); + + /* RX bulk configuration */ + memcpy(tmp, &AX88179_BULKIN_SIZE[0], 5); + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, tmp); + + dev->rx_urb_size = 1024 * 20; + + *tmp = 0x34; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp); + + *tmp = 0x52; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, + 1, 1, tmp); + + dev->net->netdev_ops = &ax88179_netdev_ops; + dev->net->ethtool_ops = &ax88179_ethtool_ops; + dev->net->needed_headroom = 8; + + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax88179_mdio_read; + dev->mii.mdio_write = ax88179_mdio_write; + dev->mii.phy_id_mask = 0xff; + dev->mii.reg_num_mask = 0xff; + dev->mii.phy_id = 0x03; + dev->mii.supports_gmii = 1; + + dev->net->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | + NETIF_F_RXCSUM; + + dev->net->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | + NETIF_F_RXCSUM; + + /* Enable checksum offload */ + *tmp = AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP | + AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, tmp); + + *tmp = AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP | + AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, tmp); + + /* Configure RX control register => start operation */ + *tmp16 = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_IPE | AX_RX_CTL_START | + AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, tmp16); + + *tmp = AX_MONITOR_MODE_PMETYPE | AX_MONITOR_MODE_PMEPOL | + AX_MONITOR_MODE_RWMP; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, tmp); + + /* Configure default medium type => giga */ + *tmp16 = AX_MEDIUM_RECEIVE_EN | AX_MEDIUM_TXFLOW_CTRLEN | + AX_MEDIUM_RXFLOW_CTRLEN | AX_MEDIUM_FULL_DUPLEX | + AX_MEDIUM_GIGAMODE; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, tmp16); + + ax88179_led_setting(dev); + + ax179_data->eee_enabled = 0; + ax179_data->eee_active = 0; + + ax88179_disable_eee(dev); + + ax88179_ethtool_get_eee(dev, &eee_data); + eee_data.advertised = 0; + ax88179_ethtool_set_eee(dev, &eee_data); + + /* Restart autoneg */ + mii_nway_restart(&dev->mii); + + usbnet_link_change(dev, 0, 0); + + return 0; +} + +static void ax88179_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + u16 tmp16; + + /* Configure RX control register => stop operation */ + tmp16 = AX_RX_CTL_STOP; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &tmp16); + + tmp16 = 0; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, &tmp16); + + /* Power down ethernet PHY */ + tmp16 = 0; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, &tmp16); +} + +static void +ax88179_rx_checksum(struct sk_buff *skb, u32 *pkt_hdr) +{ + skb->ip_summed = CHECKSUM_NONE; + + /* checksum error bit is set */ + if ((*pkt_hdr & AX_RXHDR_L3CSUM_ERR) || + (*pkt_hdr & AX_RXHDR_L4CSUM_ERR)) + return; + + /* It must be a TCP or UDP packet with a valid checksum */ + if (((*pkt_hdr & AX_RXHDR_L4_TYPE_MASK) == AX_RXHDR_L4_TYPE_TCP) || + ((*pkt_hdr & AX_RXHDR_L4_TYPE_MASK) == AX_RXHDR_L4_TYPE_UDP)) + skb->ip_summed = CHECKSUM_UNNECESSARY; +} + +static int ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + struct sk_buff *ax_skb; + int pkt_cnt; + u32 rx_hdr; + u16 hdr_off; + u32 *pkt_hdr; + + /* This check is no longer done by usbnet */ + if (skb->len < dev->net->hard_header_len) + return 0; + + skb_trim(skb, skb->len - 4); + memcpy(&rx_hdr, skb_tail_pointer(skb), 4); + le32_to_cpus(&rx_hdr); + + pkt_cnt = (u16)rx_hdr; + hdr_off = (u16)(rx_hdr >> 16); + pkt_hdr = (u32 *)(skb->data + hdr_off); + + while (pkt_cnt--) { + u16 pkt_len; + + le32_to_cpus(pkt_hdr); + pkt_len = (*pkt_hdr >> 16) & 0x1fff; + + /* Check CRC or runt packet */ + if ((*pkt_hdr & AX_RXHDR_CRC_ERR) || + (*pkt_hdr & AX_RXHDR_DROP_ERR)) { + skb_pull(skb, (pkt_len + 7) & 0xFFF8); + pkt_hdr++; + continue; + } + + if (pkt_cnt == 0) { + /* Skip IP alignment psudo header */ + skb_pull(skb, 2); + skb->len = pkt_len; + skb_set_tail_pointer(skb, pkt_len); + skb->truesize = pkt_len + sizeof(struct sk_buff); + ax88179_rx_checksum(skb, pkt_hdr); + return 1; + } + + ax_skb = skb_clone(skb, GFP_ATOMIC); + if (ax_skb) { + ax_skb->len = pkt_len; + ax_skb->data = skb->data + 2; + skb_set_tail_pointer(ax_skb, pkt_len); + ax_skb->truesize = pkt_len + sizeof(struct sk_buff); + ax88179_rx_checksum(ax_skb, pkt_hdr); + usbnet_skb_return(dev, ax_skb); + } else { + return 0; + } + + skb_pull(skb, (pkt_len + 7) & 0xFFF8); + pkt_hdr++; + } + return 1; +} + +static struct sk_buff * +ax88179_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + u32 tx_hdr1, tx_hdr2; + int frame_size = dev->maxpacket; + int mss = skb_shinfo(skb)->gso_size; + int headroom; + + tx_hdr1 = skb->len; + tx_hdr2 = mss; + if (((skb->len + 8) % frame_size) == 0) + tx_hdr2 |= 0x80008000; /* Enable padding */ + + headroom = skb_headroom(skb) - 8; + + if ((skb_header_cloned(skb) || headroom < 0) && + pskb_expand_head(skb, headroom < 0 ? 8 : 0, 0, GFP_ATOMIC)) { + dev_kfree_skb_any(skb); + return NULL; + } + + skb_push(skb, 4); + cpu_to_le32s(&tx_hdr2); + skb_copy_to_linear_data(skb, &tx_hdr2, 4); + + skb_push(skb, 4); + cpu_to_le32s(&tx_hdr1); + skb_copy_to_linear_data(skb, &tx_hdr1, 4); + + return skb; +} + +static int ax88179_link_reset(struct usbnet *dev) +{ + struct ax88179_data *ax179_data = (struct ax88179_data *)dev->data; + u8 tmp[5], link_sts; + u16 mode, tmp16, delay = HZ / 10; + u32 tmp32 = 0x40000000; + unsigned long jtimeout; + + jtimeout = jiffies + delay; + while (tmp32 & 0x40000000) { + mode = 0; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &mode); + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, + &ax179_data->rxctl); + + /*link up, check the usb device control TX FIFO full or empty*/ + ax88179_read_cmd(dev, 0x81, 0x8c, 0, 4, &tmp32); + + if (time_after(jiffies, jtimeout)) + return 0; + } + + mode = AX_MEDIUM_RECEIVE_EN | AX_MEDIUM_TXFLOW_CTRLEN | + AX_MEDIUM_RXFLOW_CTRLEN; + + ax88179_read_cmd(dev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS, + 1, 1, &link_sts); + + ax88179_read_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID, + GMII_PHY_PHYSR, 2, &tmp16); + + if (!(tmp16 & GMII_PHY_PHYSR_LINK)) { + return 0; + } else if (GMII_PHY_PHYSR_GIGA == (tmp16 & GMII_PHY_PHYSR_SMASK)) { + mode |= AX_MEDIUM_GIGAMODE | AX_MEDIUM_EN_125MHZ; + if (dev->net->mtu > 1500) + mode |= AX_MEDIUM_JUMBO_EN; + + if (link_sts & AX_USB_SS) + memcpy(tmp, &AX88179_BULKIN_SIZE[0], 5); + else if (link_sts & AX_USB_HS) + memcpy(tmp, &AX88179_BULKIN_SIZE[1], 5); + else + memcpy(tmp, &AX88179_BULKIN_SIZE[3], 5); + } else if (GMII_PHY_PHYSR_100 == (tmp16 & GMII_PHY_PHYSR_SMASK)) { + mode |= AX_MEDIUM_PS; + + if (link_sts & (AX_USB_SS | AX_USB_HS)) + memcpy(tmp, &AX88179_BULKIN_SIZE[2], 5); + else + memcpy(tmp, &AX88179_BULKIN_SIZE[3], 5); + } else { + memcpy(tmp, &AX88179_BULKIN_SIZE[3], 5); + } + + /* RX bulk configuration */ + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, tmp); + + dev->rx_urb_size = (1024 * (tmp[3] + 2)); + + if (tmp16 & GMII_PHY_PHYSR_FULL) + mode |= AX_MEDIUM_FULL_DUPLEX; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &mode); + + ax179_data->eee_enabled = ax88179_chk_eee(dev); + + netif_carrier_on(dev->net); + + return 0; +} + +static int ax88179_reset(struct usbnet *dev) +{ + u8 buf[5]; + u16 *tmp16; + u8 *tmp; + struct ax88179_data *ax179_data = (struct ax88179_data *)dev->data; + struct ethtool_eee eee_data; + + tmp16 = (u16 *)buf; + tmp = (u8 *)buf; + + /* Power up ethernet PHY */ + *tmp16 = 0; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16); + + *tmp16 = AX_PHYPWR_RSTCTL_IPRL; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16); + msleep(200); + + *tmp = AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, tmp); + msleep(100); + + /* Ethernet PHY Auto Detach*/ + ax88179_auto_detach(dev, 0); + + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN, ETH_ALEN, + dev->net->dev_addr); + memcpy(dev->net->perm_addr, dev->net->dev_addr, ETH_ALEN); + + /* RX bulk configuration */ + memcpy(tmp, &AX88179_BULKIN_SIZE[0], 5); + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, tmp); + + dev->rx_urb_size = 1024 * 20; + + *tmp = 0x34; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp); + + *tmp = 0x52; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, + 1, 1, tmp); + + dev->net->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | + NETIF_F_RXCSUM; + + dev->net->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | + NETIF_F_RXCSUM; + + /* Enable checksum offload */ + *tmp = AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP | + AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, tmp); + + *tmp = AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP | + AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, tmp); + + /* Configure RX control register => start operation */ + *tmp16 = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_IPE | AX_RX_CTL_START | + AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, tmp16); + + *tmp = AX_MONITOR_MODE_PMETYPE | AX_MONITOR_MODE_PMEPOL | + AX_MONITOR_MODE_RWMP; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, tmp); + + /* Configure default medium type => giga */ + *tmp16 = AX_MEDIUM_RECEIVE_EN | AX_MEDIUM_TXFLOW_CTRLEN | + AX_MEDIUM_RXFLOW_CTRLEN | AX_MEDIUM_FULL_DUPLEX | + AX_MEDIUM_GIGAMODE; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, tmp16); + + ax88179_led_setting(dev); + + ax179_data->eee_enabled = 0; + ax179_data->eee_active = 0; + + ax88179_disable_eee(dev); + + ax88179_ethtool_get_eee(dev, &eee_data); + eee_data.advertised = 0; + ax88179_ethtool_set_eee(dev, &eee_data); + + /* Restart autoneg */ + mii_nway_restart(&dev->mii); + + usbnet_link_change(dev, 0, 0); + + return 0; +} + +static int ax88179_stop(struct usbnet *dev) +{ + u16 tmp16; + + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + tmp16 &= ~AX_MEDIUM_RECEIVE_EN; + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, + 2, 2, &tmp16); + + return 0; +} + +static const struct driver_info ax88179_info = { + .description = "ASIX AX88179 USB 3.0 Gigabit Ethernet", + .bind = ax88179_bind, + .unbind = ax88179_unbind, + .status = ax88179_status, + .link_reset = ax88179_link_reset, + .reset = ax88179_reset, + .stop = ax88179_stop, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88179_rx_fixup, + .tx_fixup = ax88179_tx_fixup, +}; + +static const struct driver_info ax88178a_info = { + .description = "ASIX AX88178A USB 2.0 Gigabit Ethernet", + .bind = ax88179_bind, + .unbind = ax88179_unbind, + .status = ax88179_status, + .link_reset = ax88179_link_reset, + .reset = ax88179_reset, + .stop = ax88179_stop, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88179_rx_fixup, + .tx_fixup = ax88179_tx_fixup, +}; + +static const struct driver_info dlink_dub1312_info = { + .description = "D-Link DUB-1312 USB 3.0 to Gigabit Ethernet Adapter", + .bind = ax88179_bind, + .unbind = ax88179_unbind, + .status = ax88179_status, + .link_reset = ax88179_link_reset, + .reset = ax88179_reset, + .stop = ax88179_stop, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88179_rx_fixup, + .tx_fixup = ax88179_tx_fixup, +}; + +static const struct driver_info sitecom_info = { + .description = "Sitecom USB 3.0 to Gigabit Adapter", + .bind = ax88179_bind, + .unbind = ax88179_unbind, + .status = ax88179_status, + .link_reset = ax88179_link_reset, + .reset = ax88179_reset, + .stop = ax88179_stop, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88179_rx_fixup, + .tx_fixup = ax88179_tx_fixup, +}; + +static const struct driver_info samsung_info = { + .description = "Samsung USB Ethernet Adapter", + .bind = ax88179_bind, + .unbind = ax88179_unbind, + .status = ax88179_status, + .link_reset = ax88179_link_reset, + .reset = ax88179_reset, + .stop = ax88179_stop, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88179_rx_fixup, + .tx_fixup = ax88179_tx_fixup, +}; + +static const struct driver_info lenovo_info = { + .description = "Lenovo OneLinkDock Gigabit LAN", + .bind = ax88179_bind, + .unbind = ax88179_unbind, + .status = ax88179_status, + .link_reset = ax88179_link_reset, + .reset = ax88179_reset, + .stop = ax88179_stop, + .flags = FLAG_ETHER | FLAG_FRAMING_AX, + .rx_fixup = ax88179_rx_fixup, + .tx_fixup = ax88179_tx_fixup, +}; + +static const struct usb_device_id products[] = { +{ + /* ASIX AX88179 10/100/1000 */ + USB_DEVICE(0x0b95, 0x1790), + .driver_info = (unsigned long)&ax88179_info, +}, { + /* ASIX AX88178A 10/100/1000 */ + USB_DEVICE(0x0b95, 0x178a), + .driver_info = (unsigned long)&ax88178a_info, +}, { + /* D-Link DUB-1312 USB 3.0 to Gigabit Ethernet Adapter */ + USB_DEVICE(0x2001, 0x4a00), + .driver_info = (unsigned long)&dlink_dub1312_info, +}, { + /* Sitecom USB 3.0 to Gigabit Adapter */ + USB_DEVICE(0x0df6, 0x0072), + .driver_info = (unsigned long)&sitecom_info, +}, { + /* Samsung USB Ethernet Adapter */ + USB_DEVICE(0x04e8, 0xa100), + .driver_info = (unsigned long)&samsung_info, +}, { + /* Lenovo OneLinkDock Gigabit LAN */ + USB_DEVICE(0x17ef, 0x304b), + .driver_info = (unsigned long)&lenovo_info, +}, + { }, +}; +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver ax88179_178a_driver = { + .name = "ax88179_178a", + .id_table = products, + .probe = usbnet_probe, + .suspend = ax88179_suspend, + .resume = ax88179_resume, + .reset_resume = ax88179_resume, + .disconnect = usbnet_disconnect, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(ax88179_178a_driver); + +MODULE_DESCRIPTION("ASIX AX88179/178A based USB 3.0/2.0 Gigabit Ethernet Devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/catc.c b/drivers/net/usb/catc.c new file mode 100644 index 000000000..4e2b26a88 --- /dev/null +++ b/drivers/net/usb/catc.c @@ -0,0 +1,965 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + * + * 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/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) { + dev_dbg(&urb->dev->dev, "rx_done, status %d, length %d\n", + 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); + netdev_dbg(catc->netdev, "getting extra packet\n"); + urb->dev = catc->usbdev; + if ((state = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + netdev_dbg(catc->netdev, + "submit(rx_urb) status %d\n", 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 */ + dev_dbg(&urb->dev->dev, + "irq_done, status %d, data %02x %02x.\n", + status, data[0], data[1]); + goto resubmit; + } + + if (linksts == LinkGood) { + netif_carrier_on(catc->netdev); + netdev_dbg(catc->netdev, "link ok\n"); + } + + if (linksts == LinkBad) { + netif_carrier_off(catc->netdev); + netdev_dbg(catc->netdev, "link bad\n"); + } + + 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) { + dev_err(&catc->usbdev->dev, + "submit(rx_urb) status %d\n", res); + } + } + } +resubmit: + res = usb_submit_urb (urb, GFP_ATOMIC); + if (res) + dev_err(&catc->usbdev->dev, + "can't resubmit intr, %s-%s, status %d\n", + 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) + dev_err(&catc->usbdev->dev, "submit(tx_urb), status %d\n", + 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) { + dev_dbg(&urb->dev->dev, "Tx Reset.\n"); + 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) { + dev_dbg(&urb->dev->dev, "tx_done, status %d, length %d\n", + 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))) + dev_err(&catc->usbdev->dev, "submit(ctrl_urb) status %d\n", + 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) + dev_dbg(&urb->dev->dev, "ctrl_done, status %d, len %d.\n", + 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) { + dev_err(&catc->usbdev->dev, "ctrl queue full\n"); + 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[ETH_ALEN]; + u8 rx = RxEnable | RxPolarity | RxMultiCast; + + eth_broadcast_addr(broadcast); + 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; + netdev_dbg(catc->netdev, + "Setting RX mode to %2.2X %2.2X\n", + 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); + strlcpy(info->driver, driver_name, sizeof(info->driver)); + strlcpy(info->version, DRIVER_VERSION, sizeof(info->version)); + 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) { + dev_err(&catc->usbdev->dev, "submit(irq_urb) status %d\n", + 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 device *dev = &intf->dev; + struct usb_device *usbdev = interface_to_usbdev(intf); + struct net_device *netdev; + struct catc *catc; + u8 broadcast[ETH_ALEN]; + int i, pktsz; + + if (usb_set_interface(usbdev, + intf->altsetting->desc.bInterfaceNumber, 1)) { + dev_err(dev, "Can't set altsetting 1.\n"); + 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; + netdev->ethtool_ops = &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)) { + dev_err(&intf->dev, "No free urbs available.\n"); + 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) { + dev_dbg(dev, "Testing for f5u011\n"); + 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) { + dev_dbg(dev, "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); + dev_dbg(dev, "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); + dev_dbg(dev, "32k Memory\n"); + break; + } + + dev_dbg(dev, "Getting MAC from SEEROM.\n"); + + catc_get_mac(catc, netdev->dev_addr); + + dev_dbg(dev, "Setting MAC into registers.\n"); + + for (i = 0; i < 6; i++) + catc_set_reg(catc, StationAddr0 - i, netdev->dev_addr[i]); + + dev_dbg(dev, "Filling the multicast list.\n"); + + eth_broadcast_addr(broadcast); + catc_multicast(broadcast, catc->multicast); + catc_multicast(netdev->dev_addr, catc->multicast); + catc_write_mem(catc, 0xfa80, catc->multicast, 64); + + dev_dbg(dev, "Clearing error counters.\n"); + + for (i = 0; i < 8; i++) + catc_set_reg(catc, EthStats + i, 0); + catc->last_stats = jiffies; + + dev_dbg(dev, "Enabling.\n"); + + 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 { + dev_dbg(dev, "Performing reset\n"); + catc_reset(catc); + catc_get_mac(catc, netdev->dev_addr); + + dev_dbg(dev, "Setting RX Mode\n"); + catc->rxmode[0] = RxEnable | RxPolarity | RxMultiCast; + catc->rxmode[1] = 0; + f5u011_rxmode(catc, catc->rxmode); + } + dev_dbg(dev, "Init done.\n"); + 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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(catc_driver); diff --git a/drivers/net/usb/cdc-phonet.c b/drivers/net/usb/cdc-phonet.c new file mode 100644 index 000000000..415ce8b88 --- /dev/null +++ b/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 = __dev_alloc_page(gfp_flags | __GFP_NOMEMALLOC); + 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); +} + +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)) { + usb_free_urb(req); + 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; + +static 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, NET_NAME_UNKNOWN, usbpn_setup); + if (!dev) + return -ENOMEM; + + pnd = netdev_priv(dev); + SET_NETDEV_DEV(dev, &intf->dev); + + pnd->dev = dev; + pnd->usb = 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); + + 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); +} + +static struct usb_driver usbpn_driver = { + .name = "cdc_phonet", + .probe = usbpn_probe, + .disconnect = usbpn_disconnect, + .id_table = usbpn_ids, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(usbpn_driver); + +MODULE_AUTHOR("Remi Denis-Courmont"); +MODULE_DESCRIPTION("USB CDC Phonet host interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/cdc_eem.c b/drivers/net/usb/cdc_eem.c new file mode 100644 index 000000000..f7180f8db --- /dev/null +++ b/drivers/net/usb/cdc_eem.c @@ -0,0 +1,381 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.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> +#include <linux/if_vlan.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 + VLAN_HLEN; + 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 + * + * Suspend is reported and maybe heeded. + */ + case 2: /* Suspend hint */ + usbnet_device_suggests_idle(dev); + continue; + 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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(eem_driver); + +MODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>"); +MODULE_DESCRIPTION("USB CDC EEM"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c new file mode 100644 index 000000000..4545e7884 --- /dev/null +++ b/drivers/net/usb/cdc_ether.c @@ -0,0 +1,793 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.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 IS_ENABLED(CONFIG_USB_NET_RNDIS_HOST) + +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, +}; + +static void usbnet_cdc_update_filter(struct usbnet *dev) +{ + struct cdc_state *info = (void *) &dev->data; + struct usb_interface *intf = info->control; + struct net_device *net = dev->net; + + u16 cdc_filter = USB_CDC_PACKET_TYPE_DIRECTED + | USB_CDC_PACKET_TYPE_BROADCAST; + + /* filtering on the device is an optional feature and not worth + * the hassle so we just roughly care about snooping and if any + * multicast is requested, we take every multicast + */ + if (net->flags & IFF_PROMISC) + cdc_filter |= USB_CDC_PACKET_TYPE_PROMISCUOUS; + if (!netdev_mc_empty(net) || (net->flags & IFF_ALLMULTI)) + cdc_filter |= USB_CDC_PACKET_TYPE_ALL_MULTICAST; + + usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + USB_CDC_SET_ETHERNET_PACKET_FILTER, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + cdc_filter, + intf->cur_altsetting->desc.bInterfaceNumber, + NULL, + 0, + USB_CTRL_SET_TIMEOUT + ); +} + +/* 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; + } + + /* some devices merge these - skip class check */ + if (info->control == info->data) + goto next_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->bLength != sizeof(*info->ether)) { + dev_dbg(&intf->dev, "CDC ether len %u\n", + info->ether->bLength); + goto bad_desc; + } + 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. + */ + if (info->data != info->control) { + 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); + if (info->data != info->control) + usb_driver_release_interface(driver, info->data); + return status; + } + + /* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */ + if (info->data != info->control) + 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; + } + + /* Some devices don't initialise properly. In particular + * the packet filter is not reset. There are devices that + * don't do reset all the way. So the packet filter should + * be set to a sane initial value. + */ + usbnet_cdc_update_filter(dev); + + 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); + + /* combined interface - nothing to do */ + if (info->data == info->control) + return; + + /* 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"); + usbnet_link_change(dev, !!event->wValue, 0); + 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; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_cdc_bind); + +static const struct driver_info cdc_info = { + .description = "CDC Ethernet Device", + .flags = FLAG_ETHER | FLAG_POINTTOPOINT, + .bind = usbnet_cdc_bind, + .unbind = usbnet_cdc_unbind, + .status = usbnet_cdc_status, + .set_rx_mode = usbnet_cdc_update_filter, + .manage_power = usbnet_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, + .set_rx_mode = usbnet_cdc_update_filter, + .manage_power = usbnet_manage_power, +}; + +/*-------------------------------------------------------------------------*/ + +#define HUAWEI_VENDOR_ID 0x12D1 +#define NOVATEL_VENDOR_ID 0x1410 +#define ZTE_VENDOR_ID 0x19D2 +#define DELL_VENDOR_ID 0x413C +#define REALTEK_VENDOR_ID 0x0bda +#define SAMSUNG_VENDOR_ID 0x04e8 +#define LENOVO_VENDOR_ID 0x17ef + +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, +}, + +/* Novatel USB551L and MC551 - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0xB001, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Novatel E362 - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0x9010, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Dell Wireless 5800 (Novatel E362) - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x8195, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Dell Wireless 5800 (Novatel E362) - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x8196, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Dell Wireless 5804 (Novatel E371) - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x819b, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Novatel Expedite E371 - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0x9011, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* AnyDATA ADU960S - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(0x16d5, 0x650a, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Huawei E1820 - handled by qmi_wwan */ +{ + USB_DEVICE_INTERFACE_NUMBER(HUAWEI_VENDOR_ID, 0x14ac, 1), + .driver_info = 0, +}, + +/* Realtek RTL8152 Based USB 2.0 Ethernet Adapters */ +{ + USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8152, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Realtek RTL8153 Based USB 3.0 Ethernet Adapters */ +{ + USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8153, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Samsung USB Ethernet Adapters */ +{ + USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, 0xa101, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Lenovo Thinkpad USB 3.0 Ethernet Adapters (based on Realtek RTL8153) */ +{ + USB_DEVICE_AND_INTERFACE_INFO(LENOVO_VENDOR_ID, 0x7205, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, 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). + */ +{ + /* ZTE (Vodafone) K3805-Z */ + USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1003, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K3806-Z */ + USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1015, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K4510-Z */ + USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1173, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K3770-Z */ + USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1177, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K3772-Z */ + USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1181, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + 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 */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, 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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(cdc_driver); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("USB CDC Ethernet devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c new file mode 100644 index 000000000..e4b7a47a8 --- /dev/null +++ b/drivers/net/usb/cdc_mbim.c @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2012 Smith Micro Software, Inc. + * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> + * + * This driver is based on and reuse most of cdc_ncm, which is + * Copyright (C) ST-Ericsson 2010-2012 + * + * 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/if_vlan.h> +#include <linux/ip.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> +#include <linux/usb/cdc_ncm.h> +#include <net/ipv6.h> +#include <net/addrconf.h> + +/* alternative VLAN for IP session 0 if not untagged */ +#define MBIM_IPS0_VID 4094 + +/* driver specific data - must match cdc_ncm usage */ +struct cdc_mbim_state { + struct cdc_ncm_ctx *ctx; + atomic_t pmcount; + struct usb_driver *subdriver; + unsigned long _unused; + unsigned long flags; +}; + +/* flags for the cdc_mbim_state.flags field */ +enum cdc_mbim_flags { + FLAG_IPS0_VLAN = 1 << 0, /* IP session 0 is tagged */ +}; + +/* using a counter to merge subdriver requests with our own into a combined state */ +static int cdc_mbim_manage_power(struct usbnet *dev, int on) +{ + struct cdc_mbim_state *info = (void *)&dev->data; + int rv = 0; + + dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(&info->pmcount), on); + + if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { + /* need autopm_get/put here to ensure the usbcore sees the new value */ + rv = usb_autopm_get_interface(dev->intf); + dev->intf->needs_remote_wakeup = on; + if (!rv) + usb_autopm_put_interface(dev->intf); + } + return 0; +} + +static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + /* can be called while disconnecting */ + if (!dev) + return 0; + + return cdc_mbim_manage_power(dev, status); +} + +static int cdc_mbim_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid) +{ + struct usbnet *dev = netdev_priv(netdev); + struct cdc_mbim_state *info = (void *)&dev->data; + + /* creation of this VLAN is a request to tag IP session 0 */ + if (vid == MBIM_IPS0_VID) + info->flags |= FLAG_IPS0_VLAN; + else + if (vid >= 512) /* we don't map these to MBIM session */ + return -EINVAL; + return 0; +} + +static int cdc_mbim_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid) +{ + struct usbnet *dev = netdev_priv(netdev); + struct cdc_mbim_state *info = (void *)&dev->data; + + /* this is a request for an untagged IP session 0 */ + if (vid == MBIM_IPS0_VID) + info->flags &= ~FLAG_IPS0_VLAN; + return 0; +} + +static const struct net_device_ops cdc_mbim_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_vlan_rx_add_vid = cdc_mbim_rx_add_vid, + .ndo_vlan_rx_kill_vid = cdc_mbim_rx_kill_vid, +}; + +/* Change the control interface altsetting and update the .driver_info + * pointer if the matching entry after changing class codes points to + * a different struct + */ +static int cdc_mbim_set_ctrlalt(struct usbnet *dev, struct usb_interface *intf, u8 alt) +{ + struct usb_driver *driver = to_usb_driver(intf->dev.driver); + const struct usb_device_id *id; + struct driver_info *info; + int ret; + + ret = usb_set_interface(dev->udev, + intf->cur_altsetting->desc.bInterfaceNumber, + alt); + if (ret) + return ret; + + id = usb_match_id(intf, driver->id_table); + if (!id) + return -ENODEV; + + info = (struct driver_info *)id->driver_info; + if (info != dev->driver_info) { + dev_dbg(&intf->dev, "driver_info updated to '%s'\n", + info->description); + dev->driver_info = info; + } + return 0; +} + +static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf) +{ + struct cdc_ncm_ctx *ctx; + struct usb_driver *subdriver = ERR_PTR(-ENODEV); + int ret = -ENODEV; + u8 data_altsetting = 1; + struct cdc_mbim_state *info = (void *)&dev->data; + + /* should we change control altsetting on a NCM/MBIM function? */ + if (cdc_ncm_select_altsetting(intf) == CDC_NCM_COMM_ALTSETTING_MBIM) { + data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM; + ret = cdc_mbim_set_ctrlalt(dev, intf, CDC_NCM_COMM_ALTSETTING_MBIM); + if (ret) + goto err; + ret = -ENODEV; + } + + /* we will hit this for NCM/MBIM functions if prefer_mbim is false */ + if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) + goto err; + + ret = cdc_ncm_bind_common(dev, intf, data_altsetting); + if (ret) + goto err; + + ctx = info->ctx; + + /* The MBIM descriptor and the status endpoint are required */ + if (ctx->mbim_desc && dev->status) + subdriver = usb_cdc_wdm_register(ctx->control, + &dev->status->desc, + le16_to_cpu(ctx->mbim_desc->wMaxControlMessage), + cdc_mbim_wdm_manage_power); + if (IS_ERR(subdriver)) { + ret = PTR_ERR(subdriver); + cdc_ncm_unbind(dev, intf); + goto err; + } + + /* can't let usbnet use the interrupt endpoint */ + dev->status = NULL; + info->subdriver = subdriver; + + /* MBIM cannot do ARP */ + dev->net->flags |= IFF_NOARP; + + /* no need to put the VLAN tci in the packet headers */ + dev->net->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_FILTER; + + /* monitor VLAN additions and removals */ + dev->net->netdev_ops = &cdc_mbim_netdev_ops; +err: + return ret; +} + +static void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + + /* disconnect subdriver from control interface */ + if (info->subdriver && info->subdriver->disconnect) + info->subdriver->disconnect(ctx->control); + info->subdriver = NULL; + + /* let NCM unbind clean up both control and data interface */ + cdc_ncm_unbind(dev, intf); +} + +/* verify that the ethernet protocol is IPv4 or IPv6 */ +static bool is_ip_proto(__be16 proto) +{ + switch (proto) { + case htons(ETH_P_IP): + case htons(ETH_P_IPV6): + return true; + } + return false; +} + +static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + struct sk_buff *skb_out; + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + __le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN); + u16 tci = 0; + bool is_ip; + u8 *c; + + if (!ctx) + goto error; + + if (skb) { + if (skb->len <= ETH_HLEN) + goto error; + + /* Some applications using e.g. packet sockets will + * bypass the VLAN acceleration and create tagged + * ethernet frames directly. We primarily look for + * the accelerated out-of-band tag, but fall back if + * required + */ + skb_reset_mac_header(skb); + if (vlan_get_tag(skb, &tci) < 0 && skb->len > VLAN_ETH_HLEN && + __vlan_get_tag(skb, &tci) == 0) { + is_ip = is_ip_proto(vlan_eth_hdr(skb)->h_vlan_encapsulated_proto); + skb_pull(skb, VLAN_ETH_HLEN); + } else { + is_ip = is_ip_proto(eth_hdr(skb)->h_proto); + skb_pull(skb, ETH_HLEN); + } + + /* Is IP session <0> tagged too? */ + if (info->flags & FLAG_IPS0_VLAN) { + /* drop all untagged packets */ + if (!tci) + goto error; + /* map MBIM_IPS0_VID to IPS<0> */ + if (tci == MBIM_IPS0_VID) + tci = 0; + } + + /* mapping VLANs to MBIM sessions: + * no tag => IPS session <0> if !FLAG_IPS0_VLAN + * 1 - 255 => IPS session <vlanid> + * 256 - 511 => DSS session <vlanid - 256> + * 512 - 4093 => unsupported, drop + * 4094 => IPS session <0> if FLAG_IPS0_VLAN + */ + + switch (tci & 0x0f00) { + case 0x0000: /* VLAN ID 0 - 255 */ + if (!is_ip) + goto error; + c = (u8 *)&sign; + c[3] = tci; + break; + case 0x0100: /* VLAN ID 256 - 511 */ + if (is_ip) + goto error; + sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN); + c = (u8 *)&sign; + c[3] = tci; + break; + default: + netif_err(dev, tx_err, dev->net, + "unsupported tci=0x%04x\n", tci); + goto error; + } + } + + spin_lock_bh(&ctx->mtx); + skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign); + spin_unlock_bh(&ctx->mtx); + return skb_out; + +error: + if (skb) + dev_kfree_skb_any(skb); + + return NULL; +} + +/* Some devices are known to send Neigbor Solicitation messages and + * require Neigbor Advertisement replies. The IPv6 core will not + * respond since IFF_NOARP is set, so we must handle them ourselves. + */ +static void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci) +{ + struct ipv6hdr *iph = (void *)buf; + struct nd_msg *msg = (void *)(iph + 1); + struct net_device *netdev; + struct inet6_dev *in6_dev; + bool is_router; + + /* we'll only respond to requests from unicast addresses to + * our solicited node addresses. + */ + if (!ipv6_addr_is_solict_mult(&iph->daddr) || + !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST)) + return; + + /* need to send the NA on the VLAN dev, if any */ + rcu_read_lock(); + if (tci) { + netdev = __vlan_find_dev_deep_rcu(dev->net, htons(ETH_P_8021Q), + tci); + if (!netdev) { + rcu_read_unlock(); + return; + } + } else { + netdev = dev->net; + } + dev_hold(netdev); + rcu_read_unlock(); + + in6_dev = in6_dev_get(netdev); + if (!in6_dev) + goto out; + is_router = !!in6_dev->cnf.forwarding; + in6_dev_put(in6_dev); + + /* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */ + ipv6_stub->ndisc_send_na(netdev, NULL, &iph->saddr, &msg->target, + is_router /* router */, + true /* solicited */, + false /* override */, + true /* inc_opt */); +out: + dev_put(netdev); +} + +static bool is_neigh_solicit(u8 *buf, size_t len) +{ + struct ipv6hdr *iph = (void *)buf; + struct nd_msg *msg = (void *)(iph + 1); + + return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) && + iph->nexthdr == IPPROTO_ICMPV6 && + msg->icmph.icmp6_code == 0 && + msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION); +} + + +static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) +{ + __be16 proto = htons(ETH_P_802_3); + struct sk_buff *skb = NULL; + + if (tci < 256 || tci == MBIM_IPS0_VID) { /* IPS session? */ + if (len < sizeof(struct iphdr)) + goto err; + + switch (*buf & 0xf0) { + case 0x40: + proto = htons(ETH_P_IP); + break; + case 0x60: + if (is_neigh_solicit(buf, len)) + do_neigh_solicit(dev, buf, tci); + proto = htons(ETH_P_IPV6); + break; + default: + goto err; + } + } + + skb = netdev_alloc_skb_ip_align(dev->net, len + ETH_HLEN); + if (!skb) + goto err; + + /* add an ethernet header */ + skb_put(skb, ETH_HLEN); + skb_reset_mac_header(skb); + eth_hdr(skb)->h_proto = proto; + eth_zero_addr(eth_hdr(skb)->h_source); + memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); + + /* add datagram */ + memcpy(skb_put(skb, len), buf, len); + + /* map MBIM session to VLAN */ + if (tci) + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci); +err: + return skb; +} + +static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) +{ + struct sk_buff *skb; + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + int len; + int nframes; + int x; + int offset; + struct usb_cdc_ncm_ndp16 *ndp16; + struct usb_cdc_ncm_dpe16 *dpe16; + int ndpoffset; + int loopcount = 50; /* arbitrary max preventing infinite loop */ + u32 payload = 0; + u8 *c; + u16 tci; + + ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in); + if (ndpoffset < 0) + goto error; + +next_ndp: + nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset); + if (nframes < 0) + goto error; + + ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); + + switch (ndp16->dwSignature & cpu_to_le32(0x00ffffff)) { + case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN): + c = (u8 *)&ndp16->dwSignature; + tci = c[3]; + /* tag IPS<0> packets too if MBIM_IPS0_VID exists */ + if (!tci && info->flags & FLAG_IPS0_VLAN) + tci = MBIM_IPS0_VID; + break; + case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN): + c = (u8 *)&ndp16->dwSignature; + tci = c[3] + 256; + break; + default: + netif_dbg(dev, rx_err, dev->net, + "unsupported NDP signature <0x%08x>\n", + le32_to_cpu(ndp16->dwSignature)); + goto err_ndp; + + } + + dpe16 = ndp16->dpe16; + 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 err_ndp; /* empty NTB */ + break; + } + + /* sanity checking */ + if (((offset + len) > skb_in->len) || (len > ctx->rx_max)) { + netif_dbg(dev, rx_err, dev->net, + "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n", + x, offset, len, skb_in); + if (!x) + goto err_ndp; + break; + } else { + skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len, tci); + if (!skb) + goto error; + usbnet_skb_return(dev, skb); + payload += len; /* count payload bytes in this NTB */ + } + } +err_ndp: + /* are there more NDPs to process? */ + ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); + if (ndpoffset && loopcount--) + goto next_ndp; + + /* update stats */ + ctx->rx_overhead += skb_in->len - payload; + ctx->rx_ntbs++; + + return 1; +error: + return 0; +} + +static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) +{ + int ret = -ENODEV; + struct usbnet *dev = usb_get_intfdata(intf); + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + + if (!ctx) + goto error; + + /* + * Both usbnet_suspend() and subdriver->suspend() MUST return 0 + * in system sleep context, otherwise, the resume callback has + * to recover device from previous suspend failure. + */ + ret = usbnet_suspend(intf, message); + if (ret < 0) + goto error; + + if (intf == ctx->control && info->subdriver && info->subdriver->suspend) + ret = info->subdriver->suspend(intf, message); + if (ret < 0) + usbnet_resume(intf); + +error: + return ret; +} + +static int cdc_mbim_resume(struct usb_interface *intf) +{ + int ret = 0; + struct usbnet *dev = usb_get_intfdata(intf); + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + bool callsub = (intf == ctx->control && info->subdriver && info->subdriver->resume); + + if (callsub) + ret = info->subdriver->resume(intf); + if (ret < 0) + goto err; + ret = usbnet_resume(intf); + if (ret < 0 && callsub) + info->subdriver->suspend(intf, PMSG_SUSPEND); +err: + return ret; +} + +static const struct driver_info cdc_mbim_info = { + .description = "CDC MBIM", + .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, + .bind = cdc_mbim_bind, + .unbind = cdc_mbim_unbind, + .manage_power = cdc_mbim_manage_power, + .rx_fixup = cdc_mbim_rx_fixup, + .tx_fixup = cdc_mbim_tx_fixup, +}; + +/* MBIM and NCM devices should not need a ZLP after NTBs with + * dwNtbOutMaxSize length. Nevertheless, a number of devices from + * different vendor IDs will fail unless we send ZLPs, forcing us + * to make this the default. + * + * This default may cause a performance penalty for spec conforming + * devices wanting to take advantage of optimizations possible without + * ZLPs. A whitelist is added in an attempt to avoid this for devices + * known to conform to the MBIM specification. + * + * All known devices supporting NCM compatibility mode are also + * conforming to the NCM and MBIM specifications. For this reason, the + * NCM subclass entry is also in the ZLP whitelist. + */ +static const struct driver_info cdc_mbim_info_zlp = { + .description = "CDC MBIM", + .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN | FLAG_SEND_ZLP, + .bind = cdc_mbim_bind, + .unbind = cdc_mbim_unbind, + .manage_power = cdc_mbim_manage_power, + .rx_fixup = cdc_mbim_rx_fixup, + .tx_fixup = cdc_mbim_tx_fixup, +}; + +static const struct usb_device_id mbim_devs[] = { + /* This duplicate NCM entry is intentional. MBIM devices can + * be disguised as NCM by default, and this is necessary to + * allow us to bind the correct driver_info to such devices. + * + * bind() will sort out this for us, selecting the correct + * entry and reject the other + */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + /* ZLP conformance whitelist: All Ericsson MBIM devices */ + { USB_VENDOR_AND_INTERFACE_INFO(0x0bdb, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + /* default entry */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info_zlp, + }, + { + }, +}; +MODULE_DEVICE_TABLE(usb, mbim_devs); + +static struct usb_driver cdc_mbim_driver = { + .name = "cdc_mbim", + .id_table = mbim_devs, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = cdc_mbim_suspend, + .resume = cdc_mbim_resume, + .reset_resume = cdc_mbim_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; +module_usb_driver(cdc_mbim_driver); + +MODULE_AUTHOR("Greg Suarez <gsuarez@smithmicro.com>"); +MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>"); +MODULE_DESCRIPTION("USB CDC MBIM host driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c new file mode 100644 index 000000000..8067b8fbb --- /dev/null +++ b/drivers/net/usb/cdc_ncm.c @@ -0,0 +1,1616 @@ +/* + * 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/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> +#include <linux/usb/cdc_ncm.h> + +#if IS_ENABLED(CONFIG_USB_NET_CDC_MBIM) +static bool prefer_mbim = true; +#else +static bool prefer_mbim; +#endif +module_param(prefer_mbim, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(prefer_mbim, "Prefer MBIM setting on dual NCM/MBIM functions"); + +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 struct usb_driver cdc_ncm_driver; + +struct cdc_ncm_stats { + char stat_string[ETH_GSTRING_LEN]; + int sizeof_stat; + int stat_offset; +}; + +#define CDC_NCM_STAT(str, m) { \ + .stat_string = str, \ + .sizeof_stat = sizeof(((struct cdc_ncm_ctx *)0)->m), \ + .stat_offset = offsetof(struct cdc_ncm_ctx, m) } +#define CDC_NCM_SIMPLE_STAT(m) CDC_NCM_STAT(__stringify(m), m) + +static const struct cdc_ncm_stats cdc_ncm_gstrings_stats[] = { + CDC_NCM_SIMPLE_STAT(tx_reason_ntb_full), + CDC_NCM_SIMPLE_STAT(tx_reason_ndp_full), + CDC_NCM_SIMPLE_STAT(tx_reason_timeout), + CDC_NCM_SIMPLE_STAT(tx_reason_max_datagram), + CDC_NCM_SIMPLE_STAT(tx_overhead), + CDC_NCM_SIMPLE_STAT(tx_ntbs), + CDC_NCM_SIMPLE_STAT(rx_overhead), + CDC_NCM_SIMPLE_STAT(rx_ntbs), +}; + +static int cdc_ncm_get_sset_count(struct net_device __always_unused *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(cdc_ncm_gstrings_stats); + default: + return -EOPNOTSUPP; + } +} + +static void cdc_ncm_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats __always_unused *stats, + u64 *data) +{ + struct usbnet *dev = netdev_priv(netdev); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + int i; + char *p = NULL; + + for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) { + p = (char *)ctx + cdc_ncm_gstrings_stats[i].stat_offset; + data[i] = (cdc_ncm_gstrings_stats[i].sizeof_stat == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; + } +} + +static void cdc_ncm_get_strings(struct net_device __always_unused *netdev, u32 stringset, u8 *data) +{ + u8 *p = data; + int i; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) { + memcpy(p, cdc_ncm_gstrings_stats[i].stat_string, ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + } +} + +static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx); + +static const struct ethtool_ops cdc_ncm_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, + .get_ts_info = ethtool_op_get_ts_info, + .get_sset_count = cdc_ncm_get_sset_count, + .get_strings = cdc_ncm_get_strings, + .get_ethtool_stats = cdc_ncm_get_ethtool_stats, +}; + +static u32 cdc_ncm_check_rx_max(struct usbnet *dev, u32 new_rx) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u32 val, max, min; + + /* clamp new_rx to sane values */ + min = USB_CDC_NCM_NTB_MIN_IN_SIZE; + max = min_t(u32, CDC_NCM_NTB_MAX_SIZE_RX, le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)); + + /* dwNtbInMaxSize spec violation? Use MIN size for both limits */ + if (max < min) { + dev_warn(&dev->intf->dev, "dwNtbInMaxSize=%u is too small. Using %u\n", + le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize), min); + max = min; + } + + val = clamp_t(u32, new_rx, min, max); + if (val != new_rx) + dev_dbg(&dev->intf->dev, "rx_max must be in the [%u, %u] range\n", min, max); + + return val; +} + +static u32 cdc_ncm_check_tx_max(struct usbnet *dev, u32 new_tx) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u32 val, max, min; + + /* clamp new_tx to sane values */ + min = ctx->max_datagram_size + ctx->max_ndp_size + sizeof(struct usb_cdc_ncm_nth16); + max = min_t(u32, CDC_NCM_NTB_MAX_SIZE_TX, le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)); + + /* some devices set dwNtbOutMaxSize too low for the above default */ + min = min(min, max); + + val = clamp_t(u32, new_tx, min, max); + if (val != new_tx) + dev_dbg(&dev->intf->dev, "tx_max must be in the [%u, %u] range\n", min, max); + + return val; +} + +static ssize_t cdc_ncm_show_min_tx_pkt(struct device *d, struct device_attribute *attr, char *buf) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + return sprintf(buf, "%u\n", ctx->min_tx_pkt); +} + +static ssize_t cdc_ncm_show_rx_max(struct device *d, struct device_attribute *attr, char *buf) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + return sprintf(buf, "%u\n", ctx->rx_max); +} + +static ssize_t cdc_ncm_show_tx_max(struct device *d, struct device_attribute *attr, char *buf) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + return sprintf(buf, "%u\n", ctx->tx_max); +} + +static ssize_t cdc_ncm_show_tx_timer_usecs(struct device *d, struct device_attribute *attr, char *buf) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + return sprintf(buf, "%u\n", ctx->timer_interval / (u32)NSEC_PER_USEC); +} + +static ssize_t cdc_ncm_store_min_tx_pkt(struct device *d, struct device_attribute *attr, const char *buf, size_t len) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + unsigned long val; + + /* no need to restrict values - anything from 0 to infinity is OK */ + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + ctx->min_tx_pkt = val; + return len; +} + +static ssize_t cdc_ncm_store_rx_max(struct device *d, struct device_attribute *attr, const char *buf, size_t len) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + unsigned long val; + + if (kstrtoul(buf, 0, &val) || cdc_ncm_check_rx_max(dev, val) != val) + return -EINVAL; + + cdc_ncm_update_rxtx_max(dev, val, ctx->tx_max); + return len; +} + +static ssize_t cdc_ncm_store_tx_max(struct device *d, struct device_attribute *attr, const char *buf, size_t len) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + unsigned long val; + + if (kstrtoul(buf, 0, &val) || cdc_ncm_check_tx_max(dev, val) != val) + return -EINVAL; + + cdc_ncm_update_rxtx_max(dev, ctx->rx_max, val); + return len; +} + +static ssize_t cdc_ncm_store_tx_timer_usecs(struct device *d, struct device_attribute *attr, const char *buf, size_t len) +{ + struct usbnet *dev = netdev_priv(to_net_dev(d)); + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + ssize_t ret; + unsigned long val; + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + if (val && (val < CDC_NCM_TIMER_INTERVAL_MIN || val > CDC_NCM_TIMER_INTERVAL_MAX)) + return -EINVAL; + + spin_lock_bh(&ctx->mtx); + ctx->timer_interval = val * NSEC_PER_USEC; + if (!ctx->timer_interval) + ctx->tx_timer_pending = 0; + spin_unlock_bh(&ctx->mtx); + return len; +} + +static DEVICE_ATTR(min_tx_pkt, S_IRUGO | S_IWUSR, cdc_ncm_show_min_tx_pkt, cdc_ncm_store_min_tx_pkt); +static DEVICE_ATTR(rx_max, S_IRUGO | S_IWUSR, cdc_ncm_show_rx_max, cdc_ncm_store_rx_max); +static DEVICE_ATTR(tx_max, S_IRUGO | S_IWUSR, cdc_ncm_show_tx_max, cdc_ncm_store_tx_max); +static DEVICE_ATTR(tx_timer_usecs, S_IRUGO | S_IWUSR, cdc_ncm_show_tx_timer_usecs, cdc_ncm_store_tx_timer_usecs); + +#define NCM_PARM_ATTR(name, format, tocpu) \ +static ssize_t cdc_ncm_show_##name(struct device *d, struct device_attribute *attr, char *buf) \ +{ \ + struct usbnet *dev = netdev_priv(to_net_dev(d)); \ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; \ + return sprintf(buf, format "\n", tocpu(ctx->ncm_parm.name)); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, cdc_ncm_show_##name, NULL) + +NCM_PARM_ATTR(bmNtbFormatsSupported, "0x%04x", le16_to_cpu); +NCM_PARM_ATTR(dwNtbInMaxSize, "%u", le32_to_cpu); +NCM_PARM_ATTR(wNdpInDivisor, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpInPayloadRemainder, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpInAlignment, "%u", le16_to_cpu); +NCM_PARM_ATTR(dwNtbOutMaxSize, "%u", le32_to_cpu); +NCM_PARM_ATTR(wNdpOutDivisor, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpOutPayloadRemainder, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNdpOutAlignment, "%u", le16_to_cpu); +NCM_PARM_ATTR(wNtbOutMaxDatagrams, "%u", le16_to_cpu); + +static struct attribute *cdc_ncm_sysfs_attrs[] = { + &dev_attr_min_tx_pkt.attr, + &dev_attr_rx_max.attr, + &dev_attr_tx_max.attr, + &dev_attr_tx_timer_usecs.attr, + &dev_attr_bmNtbFormatsSupported.attr, + &dev_attr_dwNtbInMaxSize.attr, + &dev_attr_wNdpInDivisor.attr, + &dev_attr_wNdpInPayloadRemainder.attr, + &dev_attr_wNdpInAlignment.attr, + &dev_attr_dwNtbOutMaxSize.attr, + &dev_attr_wNdpOutDivisor.attr, + &dev_attr_wNdpOutPayloadRemainder.attr, + &dev_attr_wNdpOutAlignment.attr, + &dev_attr_wNtbOutMaxDatagrams.attr, + NULL, +}; + +static struct attribute_group cdc_ncm_sysfs_attr_group = { + .name = "cdc_ncm", + .attrs = cdc_ncm_sysfs_attrs, +}; + +/* handle rx_max and tx_max changes */ +static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; + u32 val; + + val = cdc_ncm_check_rx_max(dev, new_rx); + + /* inform device about NTB input size changes */ + if (val != ctx->rx_max) { + __le32 dwNtbInMaxSize = cpu_to_le32(val); + + dev_info(&dev->intf->dev, "setting rx_max = %u\n", val); + + /* tell device to use new size */ + if (usbnet_write_cmd(dev, USB_CDC_SET_NTB_INPUT_SIZE, + USB_TYPE_CLASS | USB_DIR_OUT + | USB_RECIP_INTERFACE, + 0, iface_no, &dwNtbInMaxSize, 4) < 0) + dev_dbg(&dev->intf->dev, "Setting NTB Input Size failed\n"); + else + ctx->rx_max = val; + } + + /* usbnet use these values for sizing rx queues */ + if (dev->rx_urb_size != ctx->rx_max) { + dev->rx_urb_size = ctx->rx_max; + if (netif_running(dev->net)) + usbnet_unlink_rx_urbs(dev); + } + + val = cdc_ncm_check_tx_max(dev, new_tx); + if (val != ctx->tx_max) + dev_info(&dev->intf->dev, "setting tx_max = %u\n", val); + + /* Adding a pad byte here if necessary simplifies the handling + * in cdc_ncm_fill_tx_frame, making tx_max always represent + * the real skb max size. + * + * We cannot use dev->maxpacket here because this is called from + * .bind which is called before usbnet sets up dev->maxpacket + */ + if (val != le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize) && + val % usb_maxpacket(dev->udev, dev->out, 1) == 0) + val++; + + /* we might need to flush any pending tx buffers if running */ + if (netif_running(dev->net) && val > ctx->tx_max) { + netif_tx_lock_bh(dev->net); + usbnet_start_xmit(NULL, dev->net); + /* make sure tx_curr_skb is reallocated if it was empty */ + if (ctx->tx_curr_skb) { + dev_kfree_skb_any(ctx->tx_curr_skb); + ctx->tx_curr_skb = NULL; + } + ctx->tx_max = val; + netif_tx_unlock_bh(dev->net); + } else { + ctx->tx_max = val; + } + + dev->hard_mtu = ctx->tx_max; + + /* max qlen depend on hard_mtu and rx_urb_size */ + usbnet_update_max_qlen(dev); + + /* never pad more than 3 full USB packets per transfer */ + ctx->min_tx_pkt = clamp_t(u16, ctx->tx_max - 3 * usb_maxpacket(dev->udev, dev->out, 1), + CDC_NCM_MIN_TX_PKT, ctx->tx_max); +} + +/* helpers for NCM and MBIM differences */ +static u8 cdc_ncm_flags(struct usbnet *dev) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting) && ctx->mbim_desc) + return ctx->mbim_desc->bmNetworkCapabilities; + if (ctx->func_desc) + return ctx->func_desc->bmNetworkCapabilities; + return 0; +} + +static int cdc_ncm_eth_hlen(struct usbnet *dev) +{ + if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting)) + return 0; + return ETH_HLEN; +} + +static u32 cdc_ncm_min_dgram_size(struct usbnet *dev) +{ + if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting)) + return CDC_MBIM_MIN_DATAGRAM_SIZE; + return CDC_NCM_MIN_DATAGRAM_SIZE; +} + +static u32 cdc_ncm_max_dgram_size(struct usbnet *dev) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + if (cdc_ncm_comm_intf_is_mbim(dev->intf->cur_altsetting) && ctx->mbim_desc) + return le16_to_cpu(ctx->mbim_desc->wMaxSegmentSize); + if (ctx->ether_desc) + return le16_to_cpu(ctx->ether_desc->wMaxSegmentSize); + return CDC_NCM_MAX_DATAGRAM_SIZE; +} + +/* initial one-time device setup. MUST be called with the data interface + * in altsetting 0 + */ +static int cdc_ncm_init(struct usbnet *dev) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; + int err; + + err = usbnet_read_cmd(dev, USB_CDC_GET_NTB_PARAMETERS, + USB_TYPE_CLASS | USB_DIR_IN + |USB_RECIP_INTERFACE, + 0, iface_no, &ctx->ncm_parm, + sizeof(ctx->ncm_parm)); + if (err < 0) { + dev_err(&dev->intf->dev, "failed GET_NTB_PARAMETERS\n"); + return err; /* GET_NTB_PARAMETERS is required */ + } + + /* set CRC Mode */ + if (cdc_ncm_flags(dev) & USB_CDC_NCM_NCAP_CRC_MODE) { + dev_dbg(&dev->intf->dev, "Setting CRC mode off\n"); + err = usbnet_write_cmd(dev, USB_CDC_SET_CRC_MODE, + USB_TYPE_CLASS | USB_DIR_OUT + | USB_RECIP_INTERFACE, + USB_CDC_NCM_CRC_NOT_APPENDED, + iface_no, NULL, 0); + if (err < 0) + dev_err(&dev->intf->dev, "SET_CRC_MODE failed\n"); + } + + /* set NTB format, if both formats are supported. + * + * "The host shall only send this command while the NCM Data + * Interface is in alternate setting 0." + */ + if (le16_to_cpu(ctx->ncm_parm.bmNtbFormatsSupported) & + USB_CDC_NCM_NTB32_SUPPORTED) { + dev_dbg(&dev->intf->dev, "Setting NTB format to 16-bit\n"); + err = usbnet_write_cmd(dev, USB_CDC_SET_NTB_FORMAT, + USB_TYPE_CLASS | USB_DIR_OUT + | USB_RECIP_INTERFACE, + USB_CDC_NCM_NTB16_FORMAT, + iface_no, NULL, 0); + if (err < 0) + dev_err(&dev->intf->dev, "SET_NTB_FORMAT failed\n"); + } + + /* set initial device values */ + 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); + + dev_dbg(&dev->intf->dev, + "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, cdc_ncm_flags(dev)); + + /* 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; + + /* set up maximum NDP size */ + ctx->max_ndp_size = sizeof(struct usb_cdc_ncm_ndp16) + (ctx->tx_max_datagrams + 1) * sizeof(struct usb_cdc_ncm_dpe16); + + /* initial coalescing timer interval */ + ctx->timer_interval = CDC_NCM_TIMER_INTERVAL_USEC * NSEC_PER_USEC; + + return 0; +} + +/* set a new max datagram size */ +static void cdc_ncm_set_dgram_size(struct usbnet *dev, int new_size) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u8 iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; + __le16 max_datagram_size; + u16 mbim_mtu; + int err; + + /* set default based on descriptors */ + ctx->max_datagram_size = clamp_t(u32, new_size, + cdc_ncm_min_dgram_size(dev), + CDC_NCM_MAX_DATAGRAM_SIZE); + + /* inform the device about the selected Max Datagram Size? */ + if (!(cdc_ncm_flags(dev) & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE)) + goto out; + + /* read current mtu value from device */ + err = usbnet_read_cmd(dev, USB_CDC_GET_MAX_DATAGRAM_SIZE, + USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE, + 0, iface_no, &max_datagram_size, 2); + if (err < 0) { + dev_dbg(&dev->intf->dev, "GET_MAX_DATAGRAM_SIZE failed\n"); + goto out; + } + + if (le16_to_cpu(max_datagram_size) == ctx->max_datagram_size) + goto out; + + max_datagram_size = cpu_to_le16(ctx->max_datagram_size); + err = usbnet_write_cmd(dev, USB_CDC_SET_MAX_DATAGRAM_SIZE, + USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE, + 0, iface_no, &max_datagram_size, 2); + if (err < 0) + dev_dbg(&dev->intf->dev, "SET_MAX_DATAGRAM_SIZE failed\n"); + +out: + /* set MTU to max supported by the device if necessary */ + dev->net->mtu = min_t(int, dev->net->mtu, ctx->max_datagram_size - cdc_ncm_eth_hlen(dev)); + + /* do not exceed operater preferred MTU */ + if (ctx->mbim_extended_desc) { + mbim_mtu = le16_to_cpu(ctx->mbim_extended_desc->wMTU); + if (mbim_mtu != 0 && mbim_mtu < dev->net->mtu) + dev->net->mtu = mbim_mtu; + } +} + +static void cdc_ncm_fix_modulus(struct usbnet *dev) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u32 val; + + /* + * 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)) { + dev_dbg(&dev->intf->dev, "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)) { + dev_dbg(&dev->intf->dev, "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) { + dev_dbg(&dev->intf->dev, "Using default transmit remainder: 0 bytes\n"); + ctx->tx_remainder = 0; + } + + /* adjust TX-remainder according to NCM specification. */ + ctx->tx_remainder = ((ctx->tx_remainder - cdc_ncm_eth_hlen(dev)) & + (ctx->tx_modulus - 1)); +} + +static int cdc_ncm_setup(struct usbnet *dev) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + u32 def_rx, def_tx; + + /* be conservative when selecting intial buffer size to + * increase the number of hosts this will work for + */ + def_rx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_RX, + le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)); + def_tx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_TX, + le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)); + + /* clamp rx_max and tx_max and inform device */ + cdc_ncm_update_rxtx_max(dev, def_rx, def_tx); + + /* sanitize the modulus and remainder values */ + cdc_ncm_fix_modulus(dev); + + /* set max datagram size */ + cdc_ncm_set_dgram_size(dev, cdc_ncm_max_dgram_size(dev)); + return 0; +} + +static void +cdc_ncm_find_endpoints(struct usbnet *dev, struct usb_interface *intf) +{ + struct usb_host_endpoint *e, *in = NULL, *out = NULL; + 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 (!dev->status) + dev->status = e; + } + break; + + case USB_ENDPOINT_XFER_BULK: + if (usb_endpoint_dir_in(&e->desc)) { + if (!in) + in = e; + } else { + if (!out) + out = e; + } + break; + + default: + break; + } + } + if (in && !dev->in) + dev->in = usb_rcvbulkpipe(dev->udev, + in->desc.bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK); + if (out && !dev->out) + dev->out = usb_sndbulkpipe(dev->udev, + out->desc.bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK); +} + +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); +} + +int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting) +{ + const struct usb_cdc_union_desc *union_desc = NULL; + 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) + return -ENOMEM; + + hrtimer_init(&ctx->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ctx->tx_timer.function = &cdc_ncm_tx_timer_cb; + ctx->bh.data = (unsigned long)dev; + ctx->bh.func = cdc_ncm_txpath_bh; + atomic_set(&ctx->stop, 0); + spin_lock_init(&ctx->mtx); + + /* store ctx pointer in device data field */ + dev->data[0] = (unsigned long)ctx; + + /* only the control interface can be successfully probed */ + ctx->control = intf; + + /* get some pointers */ + driver = driver_of(intf); + buf = intf->cur_altsetting->extra; + len = intf->cur_altsetting->extralen; + + /* 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(*union_desc)) + break; + + union_desc = (const struct usb_cdc_union_desc *)buf; + /* the master must be the interface we are probing */ + if (intf->cur_altsetting->desc.bInterfaceNumber != + union_desc->bMasterInterface0) { + dev_dbg(&intf->dev, "bogus CDC Union\n"); + goto error; + } + ctx->data = usb_ifnum_to_if(dev->udev, + 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; + 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; + + case USB_CDC_MBIM_TYPE: + if (buf[0] < sizeof(*(ctx->mbim_desc))) + break; + + ctx->mbim_desc = (const struct usb_cdc_mbim_desc *)buf; + break; + + case USB_CDC_MBIM_EXTENDED_TYPE: + if (buf[0] < sizeof(*(ctx->mbim_extended_desc))) + break; + + ctx->mbim_extended_desc = + (const struct usb_cdc_mbim_extended_desc *)buf; + break; + + default: + break; + } +advance: + /* advance to next descriptor */ + temp = buf[0]; + buf += temp; + len -= temp; + } + + /* some buggy devices have an IAD but no CDC Union */ + if (!union_desc && intf->intf_assoc && intf->intf_assoc->bInterfaceCount == 2) { + ctx->data = usb_ifnum_to_if(dev->udev, intf->cur_altsetting->desc.bInterfaceNumber + 1); + dev_dbg(&intf->dev, "CDC Union missing - got slave from IAD\n"); + } + + /* check if we got everything */ + if (!ctx->data) { + dev_dbg(&intf->dev, "CDC Union missing and no IAD found\n"); + goto error; + } + if (cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) { + if (!ctx->mbim_desc) { + dev_dbg(&intf->dev, "MBIM functional descriptor missing\n"); + goto error; + } + } else { + if (!ctx->ether_desc || !ctx->func_desc) { + dev_dbg(&intf->dev, "NCM or ECM functional descriptors missing\n"); + goto error; + } + } + + /* claim data interface, if different from control */ + if (ctx->data != ctx->control) { + temp = usb_driver_claim_interface(driver, ctx->data, dev); + if (temp) { + dev_dbg(&intf->dev, "failed to claim data intf\n"); + goto error; + } + } + + iface_no = ctx->data->cur_altsetting->desc.bInterfaceNumber; + + /* reset data interface */ + temp = usb_set_interface(dev->udev, iface_no, 0); + if (temp) { + dev_dbg(&intf->dev, "set interface failed\n"); + goto error2; + } + + /* initialize basic device settings */ + if (cdc_ncm_init(dev)) + goto error2; + + /* configure data interface */ + temp = usb_set_interface(dev->udev, iface_no, data_altsetting); + if (temp) { + dev_dbg(&intf->dev, "set interface failed\n"); + goto error2; + } + + cdc_ncm_find_endpoints(dev, ctx->data); + cdc_ncm_find_endpoints(dev, ctx->control); + if (!dev->in || !dev->out || !dev->status) { + dev_dbg(&intf->dev, "failed to collect endpoints\n"); + goto error2; + } + + usb_set_intfdata(ctx->data, dev); + usb_set_intfdata(ctx->control, dev); + + if (ctx->ether_desc) { + temp = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress); + if (temp) { + dev_dbg(&intf->dev, "failed to get mac address\n"); + goto error2; + } + dev_info(&intf->dev, "MAC-Address: %pM\n", dev->net->dev_addr); + } + + /* finish setting up the device specific data */ + cdc_ncm_setup(dev); + + /* override ethtool_ops */ + dev->net->ethtool_ops = &cdc_ncm_ethtool_ops; + + /* add our sysfs attrs */ + dev->net->sysfs_groups[0] = &cdc_ncm_sysfs_attr_group; + + return 0; + +error2: + usb_set_intfdata(ctx->control, NULL); + usb_set_intfdata(ctx->data, NULL); + if (ctx->data != ctx->control) + usb_driver_release_interface(driver, ctx->data); +error: + cdc_ncm_free((struct cdc_ncm_ctx *)dev->data[0]); + dev->data[0] = 0; + dev_info(&intf->dev, "bind() failure\n"); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(cdc_ncm_bind_common); + +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); + + /* handle devices with combined control and data interface */ + if (ctx->control == ctx->data) + ctx->data = NULL; + + /* 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(intf, NULL); + cdc_ncm_free(ctx); +} +EXPORT_SYMBOL_GPL(cdc_ncm_unbind); + +/* Return the number of the MBIM control interface altsetting iff it + * is preferred and available, + */ +u8 cdc_ncm_select_altsetting(struct usb_interface *intf) +{ + struct usb_host_interface *alt; + + /* The MBIM spec defines a NCM compatible default altsetting, + * which we may have matched: + * + * "Functions that implement both NCM 1.0 and MBIM (an + * “NCM/MBIM function”) according to this recommendation + * shall provide two alternate settings for the + * Communication Interface. Alternate setting 0, and the + * associated class and endpoint descriptors, shall be + * constructed according to the rules given for the + * Communication Interface in section 5 of [USBNCM10]. + * Alternate setting 1, and the associated class and + * endpoint descriptors, shall be constructed according to + * the rules given in section 6 (USB Device Model) of this + * specification." + */ + if (intf->num_altsetting < 2) + return intf->cur_altsetting->desc.bAlternateSetting; + + if (prefer_mbim) { + alt = usb_altnum_to_altsetting(intf, CDC_NCM_COMM_ALTSETTING_MBIM); + if (alt && cdc_ncm_comm_intf_is_mbim(alt)) + return CDC_NCM_COMM_ALTSETTING_MBIM; + } + return CDC_NCM_COMM_ALTSETTING_NCM; +} +EXPORT_SYMBOL_GPL(cdc_ncm_select_altsetting); + +static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int ret; + + /* MBIM backwards compatible function? */ + if (cdc_ncm_select_altsetting(intf) != CDC_NCM_COMM_ALTSETTING_NCM) + return -ENODEV; + + /* The NCM data altsetting is fixed */ + ret = cdc_ncm_bind_common(dev, intf, CDC_NCM_DATA_ALTSETTING_NCM); + + /* + * 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. + */ + usbnet_link_change(dev, 0, 0); + return ret; +} + +static void cdc_ncm_align_tail(struct sk_buff *skb, size_t modulus, size_t remainder, size_t max) +{ + size_t align = ALIGN(skb->len, modulus) - skb->len + remainder; + + if (skb->len + align > max) + align = max - skb->len; + if (align && skb_tailroom(skb) >= align) + memset(skb_put(skb, align), 0, align); +} + +/* return a pointer to a valid struct usb_cdc_ncm_ndp16 of type sign, possibly + * allocating a new one within skb + */ +static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_buff *skb, __le32 sign, size_t reserve) +{ + struct usb_cdc_ncm_ndp16 *ndp16 = NULL; + struct usb_cdc_ncm_nth16 *nth16 = (void *)skb->data; + size_t ndpoffset = le16_to_cpu(nth16->wNdpIndex); + + /* follow the chain of NDPs, looking for a match */ + while (ndpoffset) { + ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb->data + ndpoffset); + if (ndp16->dwSignature == sign) + return ndp16; + ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); + } + + /* align new NDP */ + cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max); + + /* verify that there is room for the NDP and the datagram (reserve) */ + if ((ctx->tx_max - skb->len - reserve) < ctx->max_ndp_size) + return NULL; + + /* link to it */ + if (ndp16) + ndp16->wNextNdpIndex = cpu_to_le16(skb->len); + else + nth16->wNdpIndex = cpu_to_le16(skb->len); + + /* push a new empty NDP */ + ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, ctx->max_ndp_size), 0, ctx->max_ndp_size); + ndp16->dwSignature = sign; + ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16)); + return ndp16; +} + +struct sk_buff * +cdc_ncm_fill_tx_frame(struct usbnet *dev, struct sk_buff *skb, __le32 sign) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + struct usb_cdc_ncm_nth16 *nth16; + struct usb_cdc_ncm_ndp16 *ndp16; + struct sk_buff *skb_out; + u16 n = 0, index, ndplen; + u8 ready2send = 0; + + /* if there is a remaining skb, it gets priority */ + if (skb != NULL) { + swap(skb, ctx->tx_rem_skb); + swap(sign, ctx->tx_rem_sign); + } else { + ready2send = 1; + } + + /* check if we are resuming an OUT skb */ + skb_out = ctx->tx_curr_skb; + + /* allocate a new OUT skb */ + if (!skb_out) { + skb_out = alloc_skb(ctx->tx_max, GFP_ATOMIC); + if (skb_out == NULL) { + if (skb != NULL) { + dev_kfree_skb_any(skb); + dev->net->stats.tx_dropped++; + } + goto exit_no_skb; + } + /* fill out the initial 16-bit NTB header */ + nth16 = (struct usb_cdc_ncm_nth16 *)memset(skb_put(skb_out, sizeof(struct usb_cdc_ncm_nth16)), 0, sizeof(struct usb_cdc_ncm_nth16)); + nth16->dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN); + nth16->wHeaderLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16)); + nth16->wSequence = cpu_to_le16(ctx->tx_seq++); + + /* count total number of frames in this NTB */ + ctx->tx_curr_frame_num = 0; + + /* recent payload counter for this skb_out */ + ctx->tx_curr_frame_payload = 0; + } + + for (n = ctx->tx_curr_frame_num; n < ctx->tx_max_datagrams; n++) { + /* send any remaining skb first */ + if (skb == NULL) { + skb = ctx->tx_rem_skb; + sign = ctx->tx_rem_sign; + ctx->tx_rem_skb = NULL; + + /* check for end of skb */ + if (skb == NULL) + break; + } + + /* get the appropriate NDP for this skb */ + ndp16 = cdc_ncm_ndp(ctx, skb_out, sign, skb->len + ctx->tx_modulus + ctx->tx_remainder); + + /* align beginning of next frame */ + cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max); + + /* check if we had enough room left for both NDP and frame */ + if (!ndp16 || skb_out->len + skb->len > ctx->tx_max) { + if (n == 0) { + /* won't fit, MTU problem? */ + dev_kfree_skb_any(skb); + skb = NULL; + dev->net->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); + dev->net->stats.tx_dropped++; + } + ctx->tx_rem_skb = skb; + ctx->tx_rem_sign = sign; + skb = NULL; + ready2send = 1; + ctx->tx_reason_ntb_full++; /* count reason for transmitting */ + } + break; + } + + /* calculate frame number withing this NDP */ + ndplen = le16_to_cpu(ndp16->wLength); + index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1; + + /* OK, add this skb */ + ndp16->dpe16[index].wDatagramLength = cpu_to_le16(skb->len); + ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(skb_out->len); + ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16)); + memcpy(skb_put(skb_out, skb->len), skb->data, skb->len); + ctx->tx_curr_frame_payload += skb->len; /* count real tx payload data */ + dev_kfree_skb_any(skb); + skb = NULL; + + /* send now if this NDP is full */ + if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) { + ready2send = 1; + ctx->tx_reason_ndp_full++; /* count reason for transmitting */ + break; + } + } + + /* free up any dangling skb */ + if (skb != NULL) { + dev_kfree_skb_any(skb); + skb = NULL; + dev->net->stats.tx_dropped++; + } + + ctx->tx_curr_frame_num = n; + + if (n == 0) { + /* wait for more frames */ + /* push variables */ + ctx->tx_curr_skb = skb_out; + goto exit_no_skb; + + } else if ((n < ctx->tx_max_datagrams) && (ready2send == 0) && (ctx->timer_interval > 0)) { + /* wait for more frames */ + /* push variables */ + ctx->tx_curr_skb = skb_out; + /* 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 { + if (n == ctx->tx_max_datagrams) + ctx->tx_reason_max_datagram++; /* count reason for transmitting */ + /* frame goes out */ + /* variables will be reset at next call */ + } + + /* If collected data size is less or equal ctx->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. + * + * This optimization support is pointless if we end up sending + * a ZLP after full sized NTBs. + */ + if (!(dev->driver_info->flags & FLAG_SEND_ZLP) && + skb_out->len > ctx->min_tx_pkt) + memset(skb_put(skb_out, ctx->tx_max - skb_out->len), 0, + ctx->tx_max - skb_out->len); + else if (skb_out->len < ctx->tx_max && (skb_out->len % dev->maxpacket) == 0) + *skb_put(skb_out, 1) = 0; /* force short packet */ + + /* set final frame length */ + nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data; + nth16->wBlockLength = cpu_to_le16(skb_out->len); + + /* return skb */ + ctx->tx_curr_skb = NULL; + + /* keep private stats: framing overhead and number of NTBs */ + ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload; + ctx->tx_ntbs++; + + /* usbnet will count all the framing overhead by default. + * Adjust the stats so that the tx_bytes counter show real + * payload data instead. + */ + usbnet_set_skb_tx_stats(skb_out, n, + (long)ctx->tx_curr_frame_payload - skb_out->len); + + return skb_out; + +exit_no_skb: + /* Start timer, if there is a remaining non-empty skb */ + if (ctx->tx_curr_skb != NULL && n > 0) + cdc_ncm_tx_timeout_start(ctx); + return NULL; +} +EXPORT_SYMBOL_GPL(cdc_ncm_fill_tx_frame); + +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, ctx->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 usbnet *dev = (struct usbnet *)param; + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + + 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 (dev->net != NULL) { + ctx->tx_reason_timeout++; /* count reason for transmitting */ + spin_unlock_bh(&ctx->mtx); + netif_tx_lock_bh(dev->net); + usbnet_start_xmit(NULL, dev->net); + netif_tx_unlock_bh(dev->net); + } else { + spin_unlock_bh(&ctx->mtx); + } +} + +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(dev, skb, cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN)); + spin_unlock_bh(&ctx->mtx); + return skb_out; + +error: + if (skb != NULL) + dev_kfree_skb_any(skb); + + return NULL; +} +EXPORT_SYMBOL_GPL(cdc_ncm_tx_fixup); + +/* verify NTB header and return offset of first NDP, or negative error */ +int cdc_ncm_rx_verify_nth16(struct cdc_ncm_ctx *ctx, struct sk_buff *skb_in) +{ + struct usbnet *dev = netdev_priv(skb_in->dev); + struct usb_cdc_ncm_nth16 *nth16; + int len; + int ret = -EINVAL; + + if (ctx == NULL) + goto error; + + if (skb_in->len < (sizeof(struct usb_cdc_ncm_nth16) + + sizeof(struct usb_cdc_ncm_ndp16))) { + netif_dbg(dev, rx_err, dev->net, "frame too short\n"); + goto error; + } + + nth16 = (struct usb_cdc_ncm_nth16 *)skb_in->data; + + if (nth16->dwSignature != cpu_to_le32(USB_CDC_NCM_NTH16_SIGN)) { + netif_dbg(dev, rx_err, dev->net, + "invalid NTH16 signature <%#010x>\n", + le32_to_cpu(nth16->dwSignature)); + goto error; + } + + len = le16_to_cpu(nth16->wBlockLength); + if (len > ctx->rx_max) { + netif_dbg(dev, rx_err, dev->net, + "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))) { + netif_dbg(dev, rx_err, dev->net, + "sequence number glitch prev=%d curr=%d\n", + ctx->rx_seq, le16_to_cpu(nth16->wSequence)); + } + ctx->rx_seq = le16_to_cpu(nth16->wSequence); + + ret = le16_to_cpu(nth16->wNdpIndex); +error: + return ret; +} +EXPORT_SYMBOL_GPL(cdc_ncm_rx_verify_nth16); + +/* verify NDP header and return number of datagrams, or negative error */ +int cdc_ncm_rx_verify_ndp16(struct sk_buff *skb_in, int ndpoffset) +{ + struct usbnet *dev = netdev_priv(skb_in->dev); + struct usb_cdc_ncm_ndp16 *ndp16; + int ret = -EINVAL; + + if ((ndpoffset + sizeof(struct usb_cdc_ncm_ndp16)) > skb_in->len) { + netif_dbg(dev, rx_err, dev->net, "invalid NDP offset <%u>\n", + ndpoffset); + goto error; + } + ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); + + if (le16_to_cpu(ndp16->wLength) < USB_CDC_NCM_NDP16_LENGTH_MIN) { + netif_dbg(dev, rx_err, dev->net, "invalid DPT16 length <%u>\n", + le16_to_cpu(ndp16->wLength)); + goto error; + } + + ret = ((le16_to_cpu(ndp16->wLength) - + sizeof(struct usb_cdc_ncm_ndp16)) / + sizeof(struct usb_cdc_ncm_dpe16)); + ret--; /* we process NDP entries except for the last one */ + + if ((sizeof(struct usb_cdc_ncm_ndp16) + + ret * (sizeof(struct usb_cdc_ncm_dpe16))) > skb_in->len) { + netif_dbg(dev, rx_err, dev->net, "Invalid nframes = %d\n", ret); + ret = -EINVAL; + } + +error: + return ret; +} +EXPORT_SYMBOL_GPL(cdc_ncm_rx_verify_ndp16); + +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_ndp16 *ndp16; + struct usb_cdc_ncm_dpe16 *dpe16; + int ndpoffset; + int loopcount = 50; /* arbitrary max preventing infinite loop */ + u32 payload = 0; + + ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in); + if (ndpoffset < 0) + goto error; + +next_ndp: + nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset); + if (nframes < 0) + goto error; + + ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset); + + if (ndp16->dwSignature != cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN)) { + netif_dbg(dev, rx_err, dev->net, + "invalid DPT16 signature <%#010x>\n", + le32_to_cpu(ndp16->dwSignature)); + goto err_ndp; + } + dpe16 = ndp16->dpe16; + + 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 err_ndp; /* empty NTB */ + break; + } + + /* sanity checking */ + if (((offset + len) > skb_in->len) || + (len > ctx->rx_max) || (len < ETH_HLEN)) { + netif_dbg(dev, rx_err, dev->net, + "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n", + x, offset, len, skb_in); + if (!x) + goto err_ndp; + break; + + } else { + /* create a fresh copy to reduce truesize */ + skb = netdev_alloc_skb_ip_align(dev->net, len); + if (!skb) + goto error; + memcpy(skb_put(skb, len), skb_in->data + offset, len); + usbnet_skb_return(dev, skb); + payload += len; /* count payload bytes in this NTB */ + } + } +err_ndp: + /* are there more NDPs to process? */ + ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); + if (ndpoffset && loopcount--) + goto next_ndp; + + /* update stats */ + ctx->rx_overhead += skb_in->len - payload; + ctx->rx_ntbs++; + + return 1; +error: + return 0; +} +EXPORT_SYMBOL_GPL(cdc_ncm_rx_fixup); + +static void +cdc_ncm_speed_change(struct usbnet *dev, + 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 > 1000000) && (rx_speed > 1000000)) { + netif_info(dev, link, dev->net, + "%u mbit/s downlink %u mbit/s uplink\n", + (unsigned int)(rx_speed / 1000000U), + (unsigned int)(tx_speed / 1000000U)); + } else { + netif_info(dev, link, dev->net, + "%u kbit/s downlink %u kbit/s uplink\n", + (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(dev, + (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. + */ + netif_info(dev, link, dev->net, + "network connection: %sconnected\n", + !!event->wValue ? "" : "dis"); + usbnet_link_change(dev, !!event->wValue, 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(dev, + (struct usb_cdc_speed_change *)&event[1]); + break; + + default: + dev_dbg(&dev->udev->dev, + "NCM: unexpected notification 0x%02x!\n", + event->bNotificationType); + break; + } +} + +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, + .manage_power = usbnet_manage_power, + .status = cdc_ncm_status, + .rx_fixup = cdc_ncm_rx_fixup, + .tx_fixup = cdc_ncm_tx_fixup, +}; + +/* Same as cdc_ncm_info, but with FLAG_WWAN */ +static const struct driver_info wwan_info = { + .description = "Mobile Broadband Network Device", + .flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET + | FLAG_WWAN, + .bind = cdc_ncm_bind, + .unbind = cdc_ncm_unbind, + .manage_power = usbnet_manage_power, + .status = cdc_ncm_status, + .rx_fixup = cdc_ncm_rx_fixup, + .tx_fixup = cdc_ncm_tx_fixup, +}; + +/* Same as wwan_info, but with FLAG_NOARP */ +static const struct driver_info wwan_noarp_info = { + .description = "Mobile Broadband Network Device (NO ARP)", + .flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET + | FLAG_WWAN | FLAG_NOARP, + .bind = cdc_ncm_bind, + .unbind = cdc_ncm_unbind, + .manage_power = usbnet_manage_power, + .status = cdc_ncm_status, + .rx_fixup = cdc_ncm_rx_fixup, + .tx_fixup = cdc_ncm_tx_fixup, +}; + +static const struct usb_device_id cdc_devs[] = { + /* Ericsson MBM devices like F5521gw */ + { .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_VENDOR, + .idVendor = 0x0bdb, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long) &wwan_info, + }, + + /* Dell branded MBM devices like DW5550 */ + { .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_VENDOR, + .idVendor = 0x413c, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long) &wwan_info, + }, + + /* Toshiba branded MBM devices */ + { .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_VENDOR, + .idVendor = 0x0930, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long) &wwan_info, + }, + + /* tag Huawei devices as wwan */ + { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_NCM, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_info, + }, + + /* Infineon(now Intel) HSPA Modem platform */ + { USB_DEVICE_AND_INTERFACE_INFO(0x1519, 0x0443, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_noarp_info, + }, + + /* Generic CDC-NCM devices */ + { 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 struct usb_driver cdc_ncm_driver = { + .name = "cdc_ncm", + .id_table = cdc_devs, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, + .reset_resume = usbnet_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/cdc_subset.c b/drivers/net/usb/cdc_subset.c new file mode 100644 index 000000000..6ea98cff2 --- /dev/null +++ b/drivers/net/usb/cdc_subset.c @@ -0,0 +1,369 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/kmod.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 void m5632_recover(struct usbnet *dev) +{ + struct usb_device *udev = dev->udev; + struct usb_interface *intf = dev->intf; + int r; + + r = usb_lock_device_for_reset(udev, intf); + if (r < 0) + return; + + usb_reset_device(udev); + usb_unlock_device(udev); +} + +static const struct driver_info ali_m5632_info = { + .description = "ALi M5632", + .flags = FLAG_POINTTOPOINT, + .recover = m5632_recover, +}; + +#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 int dummy_prereset(struct usb_interface *intf) +{ + return 0; +} + +static int dummy_postreset(struct usb_interface *intf) +{ + return 0; +} + +static struct usb_driver cdc_subset_driver = { + .name = "cdc_subset", + .probe = usbnet_probe, + .suspend = usbnet_suspend, + .resume = usbnet_resume, + .pre_reset = dummy_prereset, + .post_reset = dummy_postreset, + .disconnect = usbnet_disconnect, + .id_table = products, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(cdc_subset_driver); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("Simple 'CDC Subset' USB networking links"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/cx82310_eth.c b/drivers/net/usb/cx82310_eth.c new file mode 100644 index 000000000..e221bfcee --- /dev/null +++ b/drivers/net/usb/cx82310_eth.c @@ -0,0 +1,353 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.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 +#define CMD_TIMEOUT 100 +#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) { + if (cmd != CMD_GET_LINK_STATUS) + 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) { + if (cmd != CMD_GET_LINK_STATUS) + 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; + u8 link[3]; + int timeout = 50; + + /* 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; + + /* wait for firmware to become ready (indicated by the link being up) */ + while (--timeout) { + ret = cx82310_cmd(dev, CMD_GET_LINK_STATUS, true, NULL, 0, + link, sizeof(link)); + /* the command can time out during boot - it's not an error */ + if (!ret && link[0] == 1 && link[2] == 1) + break; + msleep(500); + } + if (!timeout) { + dev_err(&udev->dev, "firmware not ready in time\n"); + return -ETIMEDOUT; + } + + /* 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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/dm9601.c b/drivers/net/usb/dm9601.c new file mode 100644 index 000000000..6e9c344c7 --- /dev/null +++ b/drivers/net/usb/dm9601.c @@ -0,0 +1,647 @@ +/* + * Davicom DM96xx USB 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/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_CHIP_ID 0x2c +#define DM_MODE_CTRL 0x91 /* only on dm9620 */ + +/* chip id values */ +#define ID_DM9601 0 +#define ID_DM9620 1 + +#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) +{ + int err; + err = usbnet_read_cmd(dev, DM_READ_REGS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, reg, data, length); + if(err != length && err >= 0) + err = -EINVAL; + 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) +{ + int err; + err = usbnet_write_cmd(dev, DM_WRITE_REGS, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, reg, data, length); + + if (err >= 0 && err < length) + err = -EINVAL; + return err; +} + +static int dm_write_reg(struct usbnet *dev, u8 reg, u8 value) +{ + return usbnet_write_cmd(dev, DM_WRITE_REG, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, reg, NULL, 0); +} + +static void dm_write_async(struct usbnet *dev, u8 reg, u16 length, void *data) +{ + usbnet_write_cmd_async(dev, DM_WRITE_REGS, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, reg, data, length); +} + +static void dm_write_reg_async(struct usbnet *dev, u8 reg, u8 value) +{ + usbnet_write_cmd_async(dev, DM_WRITE_REG, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, reg, NULL, 0); +} + +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 = 0; + + 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 = 0; + + 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], id; + + 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); + } + + if (dm_read_reg(dev, DM_CHIP_ID, &id) < 0) { + netdev_err(dev->net, "Error reading chip ID\n"); + ret = -ENODEV; + goto out; + } + + /* put dm9620 devices in dm9601 mode */ + if (id == ID_DM9620) { + u8 mode; + + if (dm_read_reg(dev, DM_MODE_CTRL, &mode) < 0) { + netdev_err(dev->net, "Error reading MODE_CTRL\n"); + ret = -ENODEV; + goto out; + } + dm_write_reg(dev, DM_MODE_CTRL, mode & 0x7f); + } + + /* 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) { + usbnet_link_change(dev, link, 1); + 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 DM96xx USB 10/100 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, + }, + { + USB_DEVICE(0x0a46, 0x9620), /* DM9620 USB to Fast Ethernet Adapter */ + .driver_info = (unsigned long)&dm9601_info, + }, + { + USB_DEVICE(0x0a46, 0x9621), /* DM9621A USB to Fast Ethernet Adapter */ + .driver_info = (unsigned long)&dm9601_info, + }, + { + USB_DEVICE(0x0a46, 0x9622), /* DM9622 USB to Fast Ethernet Adapter */ + .driver_info = (unsigned long)&dm9601_info, + }, + { + USB_DEVICE(0x0a46, 0x0269), /* DM962OA USB to Fast Ethernet Adapter */ + .driver_info = (unsigned long)&dm9601_info, + }, + { + USB_DEVICE(0x0a46, 0x1269), /* DM9621A USB to Fast Ethernet Adapter */ + .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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(dm9601_driver); + +MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>"); +MODULE_DESCRIPTION("Davicom DM96xx USB 10/100 ethernet devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/gl620a.c b/drivers/net/usb/gl620a.c new file mode 100644 index 000000000..1cc24e6f2 --- /dev/null +++ b/drivers/net/usb/gl620a.c @@ -0,0 +1,242 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.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) { + netdev_dbg(dev->net, + "genelink: invalid received packet count %u\n", + 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) { + netdev_dbg(dev->net, "genelink: invalid rx length %d\n", + 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) { + netdev_dbg(dev->net, "genelink: invalid rx length %d\n", + 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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/hso.c b/drivers/net/usb/hso.c new file mode 100644 index 000000000..111d907e0 --- /dev/null +++ b/drivers/net/usb/hso.c @@ -0,0 +1,3323 @@ +/****************************************************************************** + * + * 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/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 + +/*****************************************************************************/ +/* 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; + char name[24]; + + 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_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; + + struct tty_port port; + /* from usb_serial_port */ + 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; + u8 rx_urb_filled[MAX_RX_URBS]; + struct tasklet_struct unthrottle_tasklet; +}; + +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 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); +/*****************************************************************************/ +/* 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, 0x9200)}, /* Option GTM671WFS */ + {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 struct attribute *hso_serial_dev_attrs[] = { + &dev_attr_hsotype.attr, + NULL +}; + +ATTRIBUTE_GROUPS(hso_serial_dev); + +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) + usb_queue_reset_device(hso_dev->interface); + 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; + continue; + } + + /* 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 = tty->driver_data; + + if (!serial) { + printk(KERN_ERR "%s: no tty structures", __func__); + return; + } + + D4("port %d", serial->minor); + + /* + * Fix up unsupported bits + */ + tty->termios.c_iflag &= ~IXON; /* disable enable XON/XOFF flow control */ + + tty->termios.c_cflag &= + ~(CSIZE /* no size */ + | PARENB /* disable parity bit */ + | CBAUD /* clear current baud rate */ + | CBAUDEX); /* clear current buad rate */ + + tty->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->port.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; + + D4("\n--- Got serial_read_bulk callback %02x ---", status); + + /* sanity check */ + if (!serial) { + D1("serial == NULL"); + return; + } + if (status) { + handle_usb_error(status, __func__, serial->parent); + return; + } + + D1("Actual length = %d\n", urb->actual_length); + DUMP1(urb->transfer_buffer, urb->actual_length); + + /* Anyone listening? */ + if (serial->port.count == 0) + return; + + 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); +} + +/* + * 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 = tty->driver_data; + + tasklet_hi_schedule(&serial->unthrottle_tasklet); +} + +/* 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); + + /* setup */ + tty->driver_data = serial; + tty_port_tty_set(&serial->port, tty); + + /* check for port already opened, if not set the termios */ + serial->port.count++; + if (serial->port.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); + result = hso_start_serial_device(serial->parent, GFP_KERNEL); + if (result) { + hso_stop_serial_device(serial->parent); + serial->port.count--; + } else { + kref_get(&serial->parent->ref); + } + } 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->port.count--; + + if (serial->port.count <= 0) { + serial->port.count = 0; + tty_port_tty_set(&serial->port, NULL); + if (!usb_gone) + hso_stop_serial_device(serial->parent); + tasklet_kill(&serial->unthrottle_tasklet); + } + + if (!usb_gone) + usb_autopm_put_interface(serial->parent->interface); + + mutex_unlock(&serial->parent->mutex); +} + +/* close the requested serial port */ +static int hso_serial_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct hso_serial *serial = tty->driver_data; + 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 = tty->driver_data; + 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; +} + +static void hso_serial_cleanup(struct tty_struct *tty) +{ + struct hso_serial *serial = tty->driver_data; + + if (!serial) + return; + + kref_put(&serial->parent->ref, hso_serial_ref_free); +} + +/* setup the term */ +static void hso_serial_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct hso_serial *serial = tty->driver_data; + 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->port.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 = tty->driver_data; + 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; + struct usb_interface *interface; + int if_num; + + /* Sanity checks */ + if (!serial) + return; + if (status) { + handle_usb_error(status, __func__, serial->parent); + return; + } + + /* tiocmget is only supported on HSO_PORT_MODEM */ + tiocmget = serial->tiocmget; + if (!tiocmget) + return; + BUG_ON((serial->parent->port_spec & HSO_PORT_MASK) != HSO_PORT_MODEM); + + usb = serial->parent->usb; + interface = serial->parent->interface; + + if_num = interface->cur_altsetting->desc.bInterfaceNumber; + + /* wIndex should be the USB interface number of the port to which the + * notification applies, which should always be the Modem port. + */ + 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) != if_num || + 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; + } + __set_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 = tty->driver_data; + 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 = tty->driver_data; + 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 = tty->driver_data; + struct usb_interface *interface; + + /* sanity check */ + if (!serial) { + D1("no tty structures"); + return -EINVAL; + } + + if ((serial->parent->port_spec & HSO_PORT_MASK) != HSO_PORT_MODEM) + return -EINVAL; + + interface = serial->parent->interface; + if_num = interface->cur_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 = tty->driver_data; + 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->port.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; + + /* sanity check */ + if (!serial) { + D1("serial == NULL"); + return; + } + + spin_lock(&serial->serial_lock); + serial->tx_urb_used = 0; + spin_unlock(&serial->serial_lock); + if (status) { + handle_usb_error(status, __func__, serial->parent); + return; + } + hso_put_activity(serial->parent); + tty_port_tty_wakeup(&serial->port); + 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; + + /* sanity check */ + if (!serial) + return; + + spin_lock(&serial->serial_lock); + serial->tx_urb_used = 0; + spin_unlock(&serial->serial_lock); + if (status) { + handle_usb_error(status, __func__, serial->parent); + 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); + tty_port_tty_wakeup(&serial->port); + /* response to a write command */ + hso_kick_transmit(serial); + } +} + +/* handle RX data for serial port */ +static int put_rxbuf_data(struct urb *urb, struct hso_serial *serial) +{ + struct tty_struct *tty; + int count; + + /* Sanity check */ + if (urb == NULL || serial == NULL) { + D1("serial = NULL"); + return -2; + } + + tty = tty_port_tty_get(&serial->port); + + if (tty && test_bit(TTY_THROTTLED, &tty->flags)) { + tty_kref_put(tty); + return -1; + } + + /* Push data to tty */ + D1("data to push to tty"); + count = tty_buffer_request_room(&serial->port, urb->actual_length); + if (count >= urb->actual_length) { + tty_insert_flip_string(&serial->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&serial->port); + } else { + dev_warn(&serial->parent->usb->dev, + "dropping data, %d bytes lost\n", urb->actual_length); + } + + tty_kref_put(tty); + + serial->rx_urb_filled[hso_urb_to_index(serial, urb)] = 0; + + return 0; +} + + +/* 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; + + 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_tty_unregister(struct hso_serial *serial) +{ + tty_unregister_device(tty_drv, serial->minor); +} + +static void hso_serial_common_free(struct hso_serial *serial) +{ + int i; + + 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_buffer); + kfree(serial->tx_data); + tty_port_destroy(&serial->port); +} + +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; + + tty_port_init(&serial->port); + + minor = get_free_serial_index(); + if (minor < 0) + goto exit; + + /* register our minor number */ + serial->parent->dev = tty_port_register_device_attr(&serial->port, + tty_drv, minor, &serial->parent->interface->dev, + serial->parent, hso_serial_dev_groups); + dev = serial->parent->dev; + + /* 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]) + 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) + goto exit; + + serial->tx_buffer = kzalloc(serial->tx_data_length, GFP_KERNEL); + if (!serial->tx_buffer) + goto exit; + + return 0; +exit: + hso_serial_tty_unregister(serial); + 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); + + 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; + net->ethtool_ops = &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; + static u32 rfkill_counter; + + snprintf(hso_net->name, sizeof(hso_net->name), "hso-%d", + rfkill_counter++); + + hso_net->rfkill = rfkill_alloc(hso_net->name, + &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__); + return; + } + if (rfkill_register(hso_net->rfkill) < 0) { + rfkill_destroy(hso_net->rfkill); + 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", NET_NAME_UNKNOWN, + 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]) + 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) + 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; + + 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_tty_unregister(serial); + 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); + } + 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) + 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 = kmalloc(17, GFP_KERNEL); + u32 if_num = interface->cur_altsetting->desc.bInterfaceNumber; + s32 result; + + if (!config_data) + return -ENOMEM; + if (usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + 0x86, 0xC0, 0, 0, config_data, 17, + USB_CTRL_SET_TIMEOUT) != 0x11) { + kfree(config_data); + 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; + + kfree(config_data); + 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 (interface->cur_altsetting->desc.bInterfaceClass != 0xFF) { + dev_err(&interface->dev, "Not our interface\n"); + return -ENODEV; + } + + if_num = interface->cur_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); + + /* 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])->port.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 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 *serial; + int i; + + for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) { + if (serial_table[i] && + (serial_table[i]->interface == interface)) { + serial = dev2ser(serial_table[i]); + tty_port_tty_hangup(&serial->port, false); + mutex_lock(&serial->parent->mutex); + serial->parent->usb_gone = 1; + mutex_unlock(&serial->parent->mutex); + cancel_work_sync(&serial_table[i]->async_put_intf); + cancel_work_sync(&serial_table[i]->async_get_intf); + hso_serial_tty_unregister(serial); + kref_put(&serial_table[i]->ref, hso_serial_ref_free); + set_serial_by_index(i, NULL); + } + } + + 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, + .cleanup = hso_serial_cleanup, + .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, + .disable_hub_initiated_lpm = 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->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); + goto err_free_tty; + } + + /* 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); + goto err_unreg_tty; + } + + /* done */ + return 0; +err_unreg_tty: + tty_unregister_driver(tty_drv); +err_free_tty: + put_tty_driver(tty_drv); + return result; +} + +static void __exit hso_exit(void) +{ + printk(KERN_INFO "hso: unloaded\n"); + + tty_unregister_driver(tty_drv); + put_tty_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/drivers/net/usb/huawei_cdc_ncm.c b/drivers/net/usb/huawei_cdc_ncm.c new file mode 100644 index 000000000..735f7dadb --- /dev/null +++ b/drivers/net/usb/huawei_cdc_ncm.c @@ -0,0 +1,221 @@ +/* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as + * transport layer. + * Copyright (C) 2013 Enrico Mioso <mrkiko.rs@gmail.com> + * + * + * ABSTRACT: + * This driver handles devices resembling the CDC NCM standard, but + * encapsulating another protocol inside it. An example are some Huawei 3G + * devices, exposing an embedded AT channel where you can set up the NCM + * connection. + * This code has been heavily inspired by the cdc_mbim.c driver, which is + * Copyright (c) 2012 Smith Micro Software, Inc. + * 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/if_vlan.h> +#include <linux/ip.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> +#include <linux/usb/cdc_ncm.h> + +/* Driver data */ +struct huawei_cdc_ncm_state { + struct cdc_ncm_ctx *ctx; + atomic_t pmcount; + struct usb_driver *subdriver; + struct usb_interface *control; + struct usb_interface *data; +}; + +static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on) +{ + struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; + int rv; + + if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || + (!on && atomic_dec_and_test(&drvstate->pmcount))) { + rv = usb_autopm_get_interface(usbnet_dev->intf); + usbnet_dev->intf->needs_remote_wakeup = on; + if (!rv) + usb_autopm_put_interface(usbnet_dev->intf); + } + return 0; +} + +static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, + int status) +{ + struct usbnet *usbnet_dev = usb_get_intfdata(intf); + + /* can be called while disconnecting */ + if (!usbnet_dev) + return 0; + + return huawei_cdc_ncm_manage_power(usbnet_dev, status); +} + + +static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, + struct usb_interface *intf) +{ + struct cdc_ncm_ctx *ctx; + struct usb_driver *subdriver = ERR_PTR(-ENODEV); + int ret = -ENODEV; + struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; + + /* altsetting should always be 1 for NCM devices - so we hard-coded + * it here + */ + ret = cdc_ncm_bind_common(usbnet_dev, intf, 1); + if (ret) + goto err; + + ctx = drvstate->ctx; + + if (usbnet_dev->status) + /* The wMaxCommand buffer must be big enough to hold + * any message from the modem. Experience has shown + * that some replies are more than 256 bytes long + */ + subdriver = usb_cdc_wdm_register(ctx->control, + &usbnet_dev->status->desc, + 1024, /* wMaxCommand */ + huawei_cdc_ncm_wdm_manage_power); + if (IS_ERR(subdriver)) { + ret = PTR_ERR(subdriver); + cdc_ncm_unbind(usbnet_dev, intf); + goto err; + } + + /* Prevent usbnet from using the status descriptor */ + usbnet_dev->status = NULL; + + drvstate->subdriver = subdriver; + +err: + return ret; +} + +static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, + struct usb_interface *intf) +{ + struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; + struct cdc_ncm_ctx *ctx = drvstate->ctx; + + if (drvstate->subdriver && drvstate->subdriver->disconnect) + drvstate->subdriver->disconnect(ctx->control); + drvstate->subdriver = NULL; + + cdc_ncm_unbind(usbnet_dev, intf); +} + +static int huawei_cdc_ncm_suspend(struct usb_interface *intf, + pm_message_t message) +{ + int ret = 0; + struct usbnet *usbnet_dev = usb_get_intfdata(intf); + struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; + struct cdc_ncm_ctx *ctx = drvstate->ctx; + + if (ctx == NULL) { + ret = -ENODEV; + goto error; + } + + ret = usbnet_suspend(intf, message); + if (ret < 0) + goto error; + + if (intf == ctx->control && + drvstate->subdriver && + drvstate->subdriver->suspend) + ret = drvstate->subdriver->suspend(intf, message); + if (ret < 0) + usbnet_resume(intf); + +error: + return ret; +} + +static int huawei_cdc_ncm_resume(struct usb_interface *intf) +{ + int ret = 0; + struct usbnet *usbnet_dev = usb_get_intfdata(intf); + struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; + bool callsub; + struct cdc_ncm_ctx *ctx = drvstate->ctx; + + /* should we call subdriver's resume function? */ + callsub = + (intf == ctx->control && + drvstate->subdriver && + drvstate->subdriver->resume); + + if (callsub) + ret = drvstate->subdriver->resume(intf); + if (ret < 0) + goto err; + ret = usbnet_resume(intf); + if (ret < 0 && callsub) + drvstate->subdriver->suspend(intf, PMSG_SUSPEND); +err: + return ret; +} + +static const struct driver_info huawei_cdc_ncm_info = { + .description = "Huawei CDC NCM device", + .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, + .bind = huawei_cdc_ncm_bind, + .unbind = huawei_cdc_ncm_unbind, + .manage_power = huawei_cdc_ncm_manage_power, + .rx_fixup = cdc_ncm_rx_fixup, + .tx_fixup = cdc_ncm_tx_fixup, +}; + +static const struct usb_device_id huawei_cdc_ncm_devs[] = { + /* Huawei NCM devices disguised as vendor specific */ + { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), + .driver_info = (unsigned long)&huawei_cdc_ncm_info, + }, + { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), + .driver_info = (unsigned long)&huawei_cdc_ncm_info, + }, + { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), + .driver_info = (unsigned long)&huawei_cdc_ncm_info, + }, + { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16), + .driver_info = (unsigned long)&huawei_cdc_ncm_info, + }, + + /* Terminating entry */ + { + }, +}; +MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); + +static struct usb_driver huawei_cdc_ncm_driver = { + .name = "huawei_cdc_ncm", + .id_table = huawei_cdc_ncm_devs, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = huawei_cdc_ncm_suspend, + .resume = huawei_cdc_ncm_resume, + .reset_resume = huawei_cdc_ncm_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; +module_usb_driver(huawei_cdc_ncm_driver); +MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>"); +MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/int51x1.c b/drivers/net/usb/int51x1.c new file mode 100644 index 000000000..4ff70b22c --- /dev/null +++ b/drivers/net/usb/int51x1.c @@ -0,0 +1,199 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#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_set_multicast(struct net_device *netdev) +{ + 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"); + } + + usbnet_write_cmd_async(dev, SET_ETHERNET_PACKET_FILTER, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + filter, 0, NULL, 0); +} + +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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(int51x1_driver); + +MODULE_AUTHOR("Peter Holik"); +MODULE_DESCRIPTION("Intellon usb powerline adapter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/ipheth.c b/drivers/net/usb/ipheth.c new file mode 100644 index 000000000..76465b117 --- /dev/null +++ b/drivers/net/usb/ipheth.c @@ -0,0 +1,588 @@ +/* + * 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/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_IPAD_2 0x12a2 +#define USB_PRODUCT_IPAD_3 0x12a6 +#define USB_PRODUCT_IPAD_MINI 0x12ab +#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_IPAD_2, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO) }, + { USB_DEVICE_AND_INTERFACE_INFO( + USB_VENDOR_APPLE, USB_PRODUCT_IPAD_3, + IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS, + IPHETH_USBINTF_PROTO) }, + { USB_DEVICE_AND_INTERFACE_INFO( + USB_VENDOR_APPLE, USB_PRODUCT_IPAD_MINI, + 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: + dev_err(&dev->intf->dev, "%s: urb status: %d\n", + __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) { + dev_err(&dev->intf->dev, "%s: dev_alloc_skb: -ENOMEM\n", + __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) + dev_err(&dev->intf->dev, "%s: urb status: %d\n", + __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) { + dev_err(&dev->intf->dev, "%s: usb_control_msg: %d\n", + __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) { + dev_err(&dev->intf->dev, "%s: usb_control_msg: %d\n", + __func__, retval); + } else if (retval < ETH_ALEN) { + dev_err(&dev->intf->dev, + "%s: usb_control_msg: short packet: %d bytes\n", + __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) + dev_err(&dev->intf->dev, "%s: usb_submit_urb: %d\n", + __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) { + dev_err(&dev->intf->dev, "%s: usb_submit_urb: %d\n", + __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); + + dev_err(&dev->intf->dev, "%s: TX timeout\n", __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; + dev_err(&intf->dev, "Unable to find alternate settings interface\n"); + 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; + dev_err(&intf->dev, "Unable to find endpoints\n"); + 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) { + dev_err(&intf->dev, "error allocating urbs: %d\n", retval); + goto err_alloc_urbs; + } + + usb_set_intfdata(intf, dev); + + SET_NETDEV_DEV(netdev, &intf->dev); + netdev->ethtool_ops = &ops; + + retval = register_netdev(netdev); + if (retval) { + dev_err(&intf->dev, "error registering netdev: %d\n", 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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/kalmia.c b/drivers/net/usb/kalmia.c new file mode 100644 index 000000000..5662babf0 --- /dev/null +++ b/drivers/net/usb/kalmia.c @@ -0,0 +1,366 @@ +/* + * 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/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); + + 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: %6phC.", + content_len, padlen, header_start); + + 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: %6phC. Package length: %i\n", + header_start, + skb->len - KALMIA_HEADER_LENGTH); + } + else { + netdev_err(dev->net, + "Received unknown frame header: %6phC. Package length: %i\n", + header_start, + skb->len - KALMIA_HEADER_LENGTH); + return 0; + } + } + else + netdev_dbg(dev->net, + "Received header: %6phC. Package length: %i\n", + header_start, 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: %6phC. Package length: %i\n", + header_start, + 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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/kaweth.c b/drivers/net/usb/kaweth.c new file mode 100644 index 000000000..2b231579d --- /dev/null +++ b/drivers/net/usb/kaweth.c @@ -0,0 +1,1328 @@ +/**************************************************************** + * + * 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, see <http://www.gnu.org/licenses/>. + * + ****************************************************************/ + +/* 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/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"); +/*(DEBLOBBED)*/ + +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, + .disable_hub_initiated_lpm = 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; + + netdev_dbg(kaweth->net, "kaweth_control()\n"); + + if(in_interrupt()) { + netdev_dbg(kaweth->net, "in_interrupt()\n"); + return -EBUSY; + } + + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC); + if (!dr) + 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; + + netdev_dbg(kaweth->net, "Reading kaweth configuration\n"); + + 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; + + netdev_dbg(kaweth->net, "Setting URB size to %d\n", (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; + + netdev_dbg(kaweth->net, "Set SOFS wait to %d\n", (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; + + netdev_dbg(kaweth->net, "Set receive filter to %d\n", + (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 = reject_firmware(&fw, fwname, &kaweth->dev->dev); + if (ret) { + dev_err(&kaweth->intf->dev, "Firmware request failed\n"); + return ret; + } + + if (fw->size > KAWETH_FIRMWARE_BUF_SIZE) { + dev_err(&kaweth->intf->dev, "Firmware too big: %zu\n", + 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; + + netdev_dbg(kaweth->net, "High: %i, Low:%i\n", kaweth->firmware_buf[3], + kaweth->firmware_buf[2]); + + netdev_dbg(kaweth->net, + "Downloading firmware at %p to kaweth device at %p\n", + kaweth->firmware_buf, kaweth); + netdev_dbg(kaweth->net, "Firmware length: %d\n", 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; + + netdev_dbg(kaweth->net, "Triggering firmware\n"); + + 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; + + netdev_dbg(kaweth->net, "kaweth_reset(%p)\n", kaweth); + result = usb_reset_configuration(kaweth->dev); + mdelay(10); + + netdev_dbg(kaweth->net, "kaweth_reset() returns %d.\n", 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) + dev_err(&kaweth->intf->dev, + "can't resubmit intr, %s-%s, status %d\n", + 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); + } + dev_err(&kaweth->intf->dev, "resubmitting rx_urb %d failed\n", + 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 device *dev = &urb->dev->dev; + 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); + dev_dbg(dev, "Status was -EPIPE.\n"); + 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); + dev_dbg(dev, "Status was -ECONNRESET or -ESHUTDOWN.\n"); + return; + } + if (unlikely(status == -EPROTO || status == -ETIME || + status == -EILSEQ)) { + kaweth->stats.rx_errors++; + dev_dbg(dev, "Status was -EPROTO, -ETIME, or -EILSEQ.\n"); + return; + } + if (unlikely(status == -EOVERFLOW)) { + kaweth->stats.rx_errors++; + dev_dbg(dev, "Status was -EOVERFLOW.\n"); + } + 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) { + dev_err(&kaweth->intf->dev, + "%s RX status: %d count: %d packet_len: %d\n", + 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)) { + dev_err(&kaweth->intf->dev, + "Packet length too long for USB frame (pkt_len: %x, count: %x)\n", + pkt_len, count); + dev_err(&kaweth->intf->dev, "Packet len & 2047: %x\n", + pkt_len & 2047); + dev_err(&kaweth->intf->dev, "Count 2: %x\n", 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; + + netdev_dbg(kaweth->net, "Opening network device.\n"); + + res = usb_autopm_get_interface(kaweth->intf); + if (res) { + dev_err(&kaweth->intf->dev, "Interface cannot be resumed.\n"); + 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) + dev_dbg(&urb->dev->dev, "%s: TX status %d.\n", + 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; + + netdev_dbg(net, "Setting Rx mode to %d\n", 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) { + dev_err(&kaweth->intf->dev, "Failed to set Rx mode: %d\n", + result); + } + else { + netdev_dbg(kaweth->net, "Set Rx mode to %d\n", + 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; + + dev_dbg(&intf->dev, "Suspending device\n"); + 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; + + dev_dbg(&intf->dev, "Resuming device\n"); + 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 device *dev = &intf->dev; + struct usb_device *udev = 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; + + dev_dbg(dev, + "Kawasaki Device Probe (Device number:%d): 0x%4.4x:0x%4.4x:0x%4.4x\n", + udev->devnum, le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + le16_to_cpu(udev->descriptor.bcdDevice)); + + dev_dbg(dev, "Device at %p\n", udev); + + dev_dbg(dev, "Descriptor length: %x type: %x\n", + (int)udev->descriptor.bLength, + (int)udev->descriptor.bDescriptorType); + + netdev = alloc_etherdev(sizeof(*kaweth)); + if (!netdev) + return -ENOMEM; + + kaweth = netdev_priv(netdev); + kaweth->dev = udev; + kaweth->net = netdev; + + spin_lock_init(&kaweth->device_lock); + init_waitqueue_head(&kaweth->term_wait); + + dev_dbg(dev, "Resetting.\n"); + + 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(udev->descriptor.bcdDevice) >> 8) { + dev_info(dev, "Firmware present in device.\n"); + } else { + /* Download the firmware */ + dev_info(dev, "Downloading firmware...\n"); + kaweth->firmware_buf = (__u8 *)__get_free_page(GFP_KERNEL); + if ((result = kaweth_download_firmware(kaweth, + "/*(DEBLOBBED)*/", + 100, + 2)) < 0) { + dev_err(dev, "Error downloading firmware (%d)\n", + result); + goto err_fw; + } + + if ((result = kaweth_download_firmware(kaweth, + "/*(DEBLOBBED)*/", + 100, + 3)) < 0) { + dev_err(dev, "Error downloading firmware fix (%d)\n", + result); + goto err_fw; + } + + if ((result = kaweth_download_firmware(kaweth, + "/*(DEBLOBBED)*/", + 126, + 2)) < 0) { + dev_err(dev, "Error downloading trigger code (%d)\n", + result); + goto err_fw; + + } + + if ((result = kaweth_download_firmware(kaweth, + "/*(DEBLOBBED)*/", + 126, + 3)) < 0) { + dev_err(dev, "Error downloading trigger code fix (%d)\n", result); + goto err_fw; + } + + + if ((result = kaweth_trigger_firmware(kaweth, 126)) < 0) { + dev_err(dev, "Error triggering firmware (%d)\n", result); + goto err_fw; + } + + /* Device will now disappear for a moment... */ + dev_info(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) { + dev_err(dev, "Error reading configuration (%d), no net device created\n", result); + goto err_free_netdev; + } + + dev_info(dev, "Statistics collection: %x\n", kaweth->configuration.statistics_mask); + dev_info(dev, "Multicast filter limit: %x\n", kaweth->configuration.max_multicast_filters & ((1 << 15) - 1)); + dev_info(dev, "MTU: %d\n", le16_to_cpu(kaweth->configuration.segment_size)); + dev_info(dev, "Read MAC address %pM\n", kaweth->configuration.hw_addr); + + if(!memcmp(&kaweth->configuration.hw_addr, + &bcast_addr, + sizeof(bcast_addr))) { + dev_err(dev, "Firmware not functioning properly, no net device created\n"); + goto err_free_netdev; + } + + if(kaweth_set_urb_size(kaweth, KAWETH_BUF_SIZE) < 0) { + dev_dbg(dev, "Error setting URB size\n"); + goto err_free_netdev; + } + + if(kaweth_set_sofs_wait(kaweth, KAWETH_SOFS_TO_WAIT) < 0) { + dev_err(dev, "Error setting SOFS wait\n"); + 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) { + dev_err(dev, "Error setting receive filter\n"); + goto err_free_netdev; + } + + dev_dbg(dev, "Initializing net device.\n"); + + 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); + netdev->ethtool_ops = &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 (dev, 0xffffffffffffffffULL)) + kaweth->net->features |= NETIF_F_HIGHDMA; +#endif + + SET_NETDEV_DEV(netdev, dev); + if (register_netdev(netdev) != 0) { + dev_err(dev, "Error registering netdev.\n"); + goto err_intfdata; + } + + dev_info(dev, "kaweth interface created at %s\n", + kaweth->net->name); + + dev_dbg(dev, "Kaweth probe returning.\n"); + + 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; + + netdev_dbg(kaweth->net, "Unregistering net device\n"); + 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_ATOMIC); + 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/drivers/net/usb/lg-vl600.c b/drivers/net/usb/lg-vl600.c new file mode 100644 index 000000000..571410753 --- /dev/null +++ b/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, see <http://www.gnu.org/licenses/>. + */ +#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(ðhdr->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 { + eth_zero_addr(ethhdr->h_source); + 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 = 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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(lg_vl600_driver); + +MODULE_AUTHOR("Anrzej Zaborowski"); +MODULE_DESCRIPTION("LG-VL600 modem's ethernet link"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/mcs7830.c b/drivers/net/usb/mcs7830.c new file mode 100644 index 000000000..82d844a8e --- /dev/null +++ b/drivers/net/usb/mcs7830.c @@ -0,0 +1,643 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.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) +{ + return usbnet_read_cmd(dev, MCS7830_RD_BREQ, MCS7830_RD_BMREQ, + 0x0000, index, data, size); +} + +static int mcs7830_set_reg(struct usbnet *dev, u16 index, u16 size, const void *data) +{ + return usbnet_write_cmd(dev, MCS7830_WR_BREQ, MCS7830_WR_BMREQ, + 0x0000, index, data, size); +} + +static void mcs7830_set_reg_async(struct usbnet *dev, u16 index, u16 size, void *data) +{ + usbnet_write_cmd_async(dev, MCS7830_WR_BREQ, MCS7830_WR_BMREQ, + 0x0000, index, data, size); +} + +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 void mcs7830_status(struct usbnet *dev, struct urb *urb) +{ + u8 *buf = urb->transfer_buffer; + bool link, link_changed; + + if (urb->actual_length < 16) + return; + + link = !(buf[1] == 0x20); + link_changed = netif_carrier_ok(dev->net) != link; + if (link_changed) { + usbnet_link_change(dev, link, 0); + netdev_dbg(dev->net, "Link Status is: %d\n", link); + } +} + +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 | FLAG_LINK_INTR, + .status = mcs7830_status, + .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 | FLAG_LINK_INTR, + .status = mcs7830_status, + .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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(mcs7830_driver); + +MODULE_DESCRIPTION("USB to network adapter MCS7830)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/net1080.c b/drivers/net/usb/net1080.c new file mode 100644 index 000000000..4cbdb1307 --- /dev/null +++ b/drivers/net/usb/net1080.c @@ -0,0 +1,544 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.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 = usbnet_read_cmd(dev, req, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + 0, regnum, retval_ptr, + sizeof *retval_ptr); + 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) +{ + usbnet_write_cmd(dev, req, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, regnum, NULL, 0); +} + +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) + return; + + netdev_dbg(dev->net, "registers:\n"); + 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) + netdev_dbg(dev->net, "reg [0x%x] ==> error %d\n", + reg, retval); + else + netdev_dbg(dev->net, "reg [0x%x] = 0x%x\n", 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; + int retval; + + // nc_dump_registers(dev); + + if ((retval = nc_register_read(dev, REG_STATUS, &vp)) < 0) { + netdev_dbg(dev->net, "can't read %s-%s status: %d\n", + 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) { + netdev_dbg(dev->net, "can't read USBCTL, %d\n", 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) { + netdev_dbg(dev->net, "can't read TTL, %d\n", 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)) ); + netdev_dbg(dev->net, "assigned TTL, %d ms\n", 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: + return retval; +} + +static int net1080_check_connect(struct usbnet *dev) +{ + int retval; + u16 status; + u16 vp; + + retval = nc_register_read(dev, REG_STATUS, &vp); + status = vp; + if (retval != 0) { + netdev_dbg(dev->net, "net1080_check_conn read - %d\n", retval); + return retval; + } + if ((status & STATUS_CONN_OTHER) != STATUS_CONN_OTHER) + return -ENOLINK; + return 0; +} + +static void nc_ensure_sync(struct usbnet *dev) +{ + if (++dev->frame_errors <= 5) + return; + + if (usbnet_write_cmd_async(dev, REQUEST_REGISTER, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + USBCTL_FLUSH_THIS | + USBCTL_FLUSH_OTHER, + REG_USBCTL, NULL, 0)) + 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)) { + netdev_dbg(dev->net, "rx framesize %d range %d..%d mtu %d\n", + skb->len, dev->net->hard_header_len, dev->hard_mtu, + dev->net->mtu); + 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++; + netdev_dbg(dev->net, "packet too big, %d\n", packet_len); + nc_ensure_sync(dev); + return 0; + } else if (hdr_len < MIN_HEADER) { + dev->net->stats.rx_frame_errors++; + netdev_dbg(dev->net, "header too short, %d\n", hdr_len); + nc_ensure_sync(dev); + return 0; + } else if (hdr_len > MIN_HEADER) { + // out of band data for us? + netdev_dbg(dev->net, "header OOB, %d bytes\n", 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++; + netdev_dbg(dev->net, "bad pad\n"); + return 0; + } + skb_trim(skb, skb->len - 1); + } + if (skb->len != packet_len) { + dev->net->stats.rx_frame_errors++; + netdev_dbg(dev->net, "bad packet len %d (expected %d)\n", + 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++; + netdev_dbg(dev->net, "(2+ dropped) rx packet_id mismatch 0x%x 0x%x\n", + 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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c new file mode 100644 index 000000000..f84080215 --- /dev/null +++ b/drivers/net/usb/pegasus.c @@ -0,0 +1,1335 @@ +/* + * Copyright (c) 1999-2013 Petko Manolov (petkan@nucleusys.com) + * + * 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. + * ... + * v0.9.3 simplified [get|set]_register(s), async update registers + * logic revisited, receive skb_pool removed. + */ + +#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.9.3 (2013/04/25)" +#define DRIVER_AUTHOR "Petko Manolov <petkan@nucleusys.com>" +#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 void async_ctrl_callback(struct urb *urb) +{ + struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context; + int status = urb->status; + + if (status < 0) + dev_dbg(&urb->dev->dev, "%s failed with %d", __func__, status); + kfree(req); + usb_free_urb(urb); +} + +static int get_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data) +{ + int ret; + + ret = usb_control_msg(pegasus->usb, usb_rcvctrlpipe(pegasus->usb, 0), + PEGASUS_REQ_GET_REGS, PEGASUS_REQT_READ, 0, + indx, data, size, 1000); + if (ret < 0) + netif_dbg(pegasus, drv, pegasus->net, + "%s returned %d\n", __func__, ret); + return ret; +} + +static int set_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data) +{ + int ret; + + ret = usb_control_msg(pegasus->usb, usb_sndctrlpipe(pegasus->usb, 0), + PEGASUS_REQ_SET_REGS, PEGASUS_REQT_WRITE, 0, + indx, data, size, 100); + if (ret < 0) + netif_dbg(pegasus, drv, pegasus->net, + "%s returned %d\n", __func__, ret); + return ret; +} + +static int set_register(pegasus_t *pegasus, __u16 indx, __u8 data) +{ + int ret; + + ret = usb_control_msg(pegasus->usb, usb_sndctrlpipe(pegasus->usb, 0), + PEGASUS_REQ_SET_REG, PEGASUS_REQT_WRITE, data, + indx, &data, 1, 1000); + if (ret < 0) + netif_dbg(pegasus, drv, pegasus->net, + "%s returned %d\n", __func__, ret); + return ret; +} + +static int update_eth_regs_async(pegasus_t *pegasus) +{ + int ret = -ENOMEM; + struct urb *async_urb; + struct usb_ctrlrequest *req; + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC); + if (req == NULL) + return ret; + + async_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (async_urb == NULL) { + kfree(req); + return ret; + } + req->bRequestType = PEGASUS_REQT_WRITE; + req->bRequest = PEGASUS_REQ_SET_REGS; + req->wValue = cpu_to_le16(0); + req->wIndex = cpu_to_le16(EthCtrl0); + req->wLength = cpu_to_le16(3); + + usb_fill_control_urb(async_urb, pegasus->usb, + usb_sndctrlpipe(pegasus->usb, 0), (void *)req, + pegasus->eth_regs, 3, async_ctrl_callback, req); + + ret = usb_submit_urb(async_urb, GFP_ATOMIC); + if (ret) { + if (ret == -ENODEV) + netif_device_detach(pegasus->net); + netif_err(pegasus, drv, pegasus->net, + "%s returned %d\n", __func__, ret); + } + return ret; +} + +static int __mii_op(pegasus_t *p, __u8 phy, __u8 indx, __u16 *regd, __u8 cmd) +{ + int i; + __u8 data[4] = { phy, 0, 0, indx }; + __le16 regdi; + int ret = -ETIMEDOUT; + + if (cmd & PHY_WRITE) { + __le16 *t = (__le16 *) & data[1]; + *t = cpu_to_le16(*regd); + } + set_register(p, PhyCtrl, 0); + set_registers(p, PhyAddr, sizeof(data), data); + set_register(p, PhyCtrl, (indx | cmd)); + for (i = 0; i < REG_TIMEOUT; i++) { + ret = get_registers(p, PhyCtrl, 1, data); + if (ret < 0) + goto fail; + if (data[0] & PHY_DONE) + break; + } + if (i >= REG_TIMEOUT) + goto fail; + if (cmd & PHY_READ) { + ret = get_registers(p, PhyData, 2, ®di); + *regd = le16_to_cpu(regdi); + return ret; + } + return 0; +fail: + netif_dbg(p, drv, p->net, "%s failed\n", __func__); + return ret; +} + +/* Returns non-negative int on success, error on failure */ +static int read_mii_word(pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd) +{ + return __mii_op(pegasus, phy, indx, regd, PHY_READ); +} + +/* Returns zero on success, error on failure */ +static int write_mii_word(pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd) +{ + return __mii_op(pegasus, phy, indx, regd, PHY_WRITE); +} + +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 void mdio_write(struct net_device *dev, int phy_id, int loc, int val) +{ + pegasus_t *pegasus = netdev_priv(dev); + u16 data = val; + + write_mii_word(pegasus, phy_id, loc, &data); +} + +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; + + 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; + + 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); + auxmode |= 4; + write_mii_word(pegasus, 3, 0x1b, &auxmode); + } + + 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); + auxmode |= 4; + write_mii_word(pegasus, 0, 0x1b, &auxmode); + } + + return ret; +} + +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; + + pegasus->rx_skb = __netdev_alloc_skb_ip_align(pegasus->net, PEGASUS_MTU, + GFP_ATOMIC); + + 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; + int status; + + pegasus = (pegasus_t *) data; + if (pegasus->flags & PEGASUS_UNPLUG) + return; + + if (pegasus->flags & PEGASUS_RX_URB_FAIL) + if (pegasus->rx_skb) + goto try_again; + if (pegasus->rx_skb == NULL) + pegasus->rx_skb = __netdev_alloc_skb_ip_align(pegasus->net, + PEGASUS_MTU, + GFP_ATOMIC); + if (pegasus->rx_skb == NULL) { + netif_warn(pegasus, rx_err, pegasus->net, "low on memory\n"); + tasklet_schedule(&pegasus->rx_tl); + return; + } + 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; + } +} + +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); +} + +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); +} + +static int alloc_urbs(pegasus_t *pegasus) +{ + int res = -ENOMEM; + + pegasus->rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pegasus->rx_urb) { + return res; + } + pegasus->tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pegasus->tx_urb) { + usb_free_urb(pegasus->rx_urb); + return res; + } + 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); + return res; + } + + return 0; +} + +static int pegasus_open(struct net_device *net) +{ + pegasus_t *pegasus = netdev_priv(net); + int res=-ENOMEM; + + if (pegasus->rx_skb == NULL) + pegasus->rx_skb = __netdev_alloc_skb_ip_align(pegasus->net, + PEGASUS_MTU, + GFP_KERNEL); + if (!pegasus->rx_skb) + goto exit; + + 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; + } + res = enable_net_traffic(net, pegasus->usb); + if (res < 0) { + 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); + 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); + + strlcpy(info->driver, driver_name, sizeof(info->driver)); + strlcpy(info->version, DRIVER_VERSION, sizeof(info->version)); + 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; + int ret; + + 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; + + ret = set_register(pegasus, WakeupControl, reg78); + if (!ret) + ret = device_set_wakeup_enable(&pegasus->usb->dev, + wol->wolopts); + return ret; +} + +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; + } + update_eth_regs_async(pegasus); +} + +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++; + + net = alloc_etherdev(sizeof(struct pegasus)); + if (!net) + goto out; + + pegasus = netdev_priv(net); + pegasus->dev_index = dev_index; + + res = alloc_urbs(pegasus); + if (res < 0) { + 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; + net->ethtool_ops = &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; + 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); + 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); +out2: + free_all_urbs(pegasus); +out1: + free_netdev(net); +out: + 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); + unlink_all_urbs(pegasus); + free_all_urbs(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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/pegasus.h b/drivers/net/usb/pegasus.h new file mode 100644 index 000000000..d15646244 --- /dev/null +++ b/drivers/net/usb/pegasus.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 1999-2013 Petko Manolov (petkan@nucleusys.com) + * + * 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 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 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 *rx_urb, *tx_urb, *intr_urb; + struct sk_buff *rx_skb; + 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/drivers/net/usb/plusb.c b/drivers/net/usb/plusb.c new file mode 100644 index 000000000..1bfe0fcac --- /dev/null +++ b/drivers/net/usb/plusb.c @@ -0,0 +1,162 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.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 usbnet_read_cmd(dev, req, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + val, index, NULL, 0); +} + +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, +}, { + USB_DEVICE(0x3923, 0x7825), /* National Instruments USB + * Host-to-Host Cable + */ + .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, + .disable_hub_initiated_lpm = 1, +}; + +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/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c new file mode 100644 index 000000000..f603f3625 --- /dev/null +++ b/drivers/net/usb/qmi_wwan.c @@ -0,0 +1,883 @@ +/* + * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> + * + * The probing code is heavily inspired by cdc_ether, which is: + * 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 + * 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> + +/* 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 + * + * Alternatively, control and data functions can be combined in a + * single USB interface. + * + * Handling a protocol like QMI is out of the scope for any driver. + * It is exported as a character device using the cdc-wdm driver as + * a subdriver, enabling userspace applications ("modem managers") to + * handle it. + * + * These devices may alternatively/additionally be configured using AT + * commands on a serial interface + */ + +/* driver specific data */ +struct qmi_wwan_state { + struct usb_driver *subdriver; + atomic_t pmcount; + unsigned long unused; + struct usb_interface *control; + struct usb_interface *data; +}; + +/* default ethernet address used by the modem */ +static const u8 default_modem_addr[ETH_ALEN] = {0x02, 0x50, 0xf3}; + +static const u8 buggy_fw_addr[ETH_ALEN] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00}; + +/* 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; + eth_zero_addr(eth_hdr(skb)->h_source); +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) +{ + int ret; + struct sockaddr *addr = p; + + ret = eth_prepare_mac_addr_change(dev, p); + if (ret < 0) + return ret; + if (possibly_iphdr(addr->sa_data)) + return -EADDRNOTAVAIL; + eth_commit_mac_addr_change(dev, p); + 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) +{ + struct qmi_wwan_state *info = (void *)&dev->data; + int rv; + + dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, + atomic_read(&info->pmcount), on); + + if ((on && atomic_add_return(1, &info->pmcount) == 1) || + (!on && atomic_dec_and_test(&info->pmcount))) { + /* need autopm_get/put here to ensure the usbcore sees + * the new value + */ + rv = usb_autopm_get_interface(dev->intf); + dev->intf->needs_remote_wakeup = on; + if (!rv) + usb_autopm_put_interface(dev->intf); + } + return 0; +} + +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); +} + +/* collect all three endpoints and register subdriver */ +static int qmi_wwan_register_subdriver(struct usbnet *dev) +{ + int rv; + struct usb_driver *subdriver = NULL; + struct qmi_wwan_state *info = (void *)&dev->data; + + /* collect bulk endpoints */ + rv = usbnet_get_endpoints(dev, info->data); + if (rv < 0) + goto err; + + /* update status endpoint if separate control interface */ + if (info->control != info->data) + dev->status = &info->control->cur_altsetting->endpoint[0]; + + /* require interrupt endpoint for subdriver */ + if (!dev->status) { + rv = -EINVAL; + goto err; + } + + /* for subdriver power management */ + atomic_set(&info->pmcount, 0); + + /* register subdriver */ + subdriver = usb_cdc_wdm_register(info->control, &dev->status->desc, + 4096, &qmi_wwan_cdc_wdm_manage_power); + if (IS_ERR(subdriver)) { + dev_err(&info->control->dev, "subdriver registration failed\n"); + rv = PTR_ERR(subdriver); + goto err; + } + + /* prevent usbnet from using status endpoint */ + dev->status = NULL; + + /* save subdriver struct for suspend/resume wrappers */ + info->subdriver = subdriver; + +err: + return rv; +} + +static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int status = -1; + 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 found = 0; + struct usb_driver *driver = driver_of(intf); + struct qmi_wwan_state *info = (void *)&dev->data; + + BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) < + sizeof(struct qmi_wwan_state))); + + /* set up initial state */ + info->control = intf; + info->data = intf; + + /* and a number of CDC descriptors */ + 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; + } + + /* Use separate control and data interfaces if we found a CDC Union */ + if (cdc_union) { + info->data = usb_ifnum_to_if(dev->udev, + cdc_union->bSlaveInterface0); + if (desc->bInterfaceNumber != cdc_union->bMasterInterface0 || + !info->data) { + dev_err(&intf->dev, + "bogus CDC Union: master=%u, slave=%u\n", + cdc_union->bMasterInterface0, + cdc_union->bSlaveInterface0); + goto err; + } + } + + /* errors aren't fatal - we can live with the dynamic address */ + if (cdc_ether) { + dev->hard_mtu = le16_to_cpu(cdc_ether->wMaxSegmentSize); + usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress); + } + + /* claim data interface and set it up */ + if (info->control != info->data) { + status = usb_driver_claim_interface(driver, info->data, dev); + if (status < 0) + goto err; + } + + status = qmi_wwan_register_subdriver(dev); + if (status < 0 && info->control != info->data) { + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver, info->data); + } + + /* Never use the same address on both ends of the link, even if the + * buggy firmware told us to. Or, if device is assigned the well-known + * buggy firmware MAC address, replace it with a random address, + */ + if (ether_addr_equal(dev->net->dev_addr, default_modem_addr) || + ether_addr_equal(dev->net->dev_addr, buggy_fw_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 status; +} + +static void qmi_wwan_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct qmi_wwan_state *info = (void *)&dev->data; + struct usb_driver *driver = driver_of(intf); + struct usb_interface *other; + + if (info->subdriver && info->subdriver->disconnect) + info->subdriver->disconnect(info->control); + + /* allow user to unbind using either control or data */ + if (intf == info->control) + other = info->data; + else + other = info->control; + + /* only if not shared */ + if (other && intf != other) { + usb_set_intfdata(other, NULL); + usb_driver_release_interface(driver, other); + } + + info->subdriver = NULL; + info->data = NULL; + info->control = 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 qmi_wwan_state *info = (void *)&dev->data; + int ret; + + /* Both usbnet_suspend() and subdriver->suspend() MUST return 0 + * in system sleep context, otherwise, the resume callback has + * to recover device from previous suspend failure. + */ + ret = usbnet_suspend(intf, message); + if (ret < 0) + goto err; + + if (intf == info->control && info->subdriver && + info->subdriver->suspend) + ret = info->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 qmi_wwan_state *info = (void *)&dev->data; + int ret = 0; + bool callsub = (intf == info->control && info->subdriver && + info->subdriver->resume); + + if (callsub) + ret = info->subdriver->resume(intf); + if (ret < 0) + goto err; + ret = usbnet_resume(intf); + if (ret < 0 && callsub) + info->subdriver->suspend(intf, PMSG_SUSPEND); +err: + return ret; +} + +static const struct driver_info qmi_wwan_info = { + .description = "WWAN/QMI device", + .flags = FLAG_WWAN, + .bind = qmi_wwan_bind, + .unbind = qmi_wwan_unbind, + .manage_power = qmi_wwan_manage_power, + .rx_fixup = qmi_wwan_rx_fixup, +}; + +#define HUAWEI_VENDOR_ID 0x12D1 + +/* map QMI/wwan function by a fixed interface number */ +#define QMI_FIXED_INTF(vend, prod, num) \ + USB_DEVICE_INTERFACE_NUMBER(vend, prod, num), \ + .driver_info = (unsigned long)&qmi_wwan_info + +/* Gobi 1000 QMI/wwan interface number is 3 according to qcserial */ +#define QMI_GOBI1K_DEVICE(vend, prod) \ + QMI_FIXED_INTF(vend, prod, 3) + +/* Gobi 2000/3000 QMI/wwan interface number is 0 according to qcserial */ +#define QMI_GOBI_DEVICE(vend, prod) \ + QMI_FIXED_INTF(vend, prod, 0) + +static const struct usb_device_id products[] = { + /* 1. CDC ECM like devices match on the control interface */ + { /* Huawei E392, E398 and possibly others sharing both device id and more... */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 9), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Vodafone/Huawei K5005 (12d1:14c8) and similar modems */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 57), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* HUAWEI_INTERFACE_NDIS_CONTROL_QUALCOMM */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 0x01, 0x69), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + + /* 2. Combined interface devices matching on class+protocol */ + { /* Huawei E367 and possibly others in "Windows mode" */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 7), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Huawei E392, E398 and possibly others in "Windows mode" */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 17), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* HUAWEI_NDIS_SINGLE_INTERFACE_VDF */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 0x01, 0x37), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* HUAWEI_INTERFACE_NDIS_HW_QUALCOMM */ + USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 0x01, 0x67), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Pantech UML290, P4200 and more */ + USB_VENDOR_AND_INTERFACE_INFO(0x106c, USB_CLASS_VENDOR_SPEC, 0xf0, 0xff), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Pantech UML290 - newer firmware */ + USB_VENDOR_AND_INTERFACE_INFO(0x106c, USB_CLASS_VENDOR_SPEC, 0xf1, 0xff), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Novatel USB551L and MC551 */ + USB_DEVICE_AND_INTERFACE_INFO(0x1410, 0xb001, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Novatel E362 */ + USB_DEVICE_AND_INTERFACE_INFO(0x1410, 0x9010, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Novatel Expedite E371 */ + USB_DEVICE_AND_INTERFACE_INFO(0x1410, 0x9011, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Dell Wireless 5800 (Novatel E362) */ + USB_DEVICE_AND_INTERFACE_INFO(0x413C, 0x8195, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Dell Wireless 5800 V2 (Novatel E362) */ + USB_DEVICE_AND_INTERFACE_INFO(0x413C, 0x8196, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* Dell Wireless 5804 (Novatel E371) */ + USB_DEVICE_AND_INTERFACE_INFO(0x413C, 0x819b, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + { /* ADU960S */ + USB_DEVICE_AND_INTERFACE_INFO(0x16d5, 0x650a, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&qmi_wwan_info, + }, + + /* 3. Combined interface devices matching on interface number */ + {QMI_FIXED_INTF(0x0408, 0xea42, 4)}, /* Yota / Megafon M100-1 */ + {QMI_FIXED_INTF(0x05c6, 0x7000, 0)}, + {QMI_FIXED_INTF(0x05c6, 0x7001, 1)}, + {QMI_FIXED_INTF(0x05c6, 0x7002, 1)}, + {QMI_FIXED_INTF(0x05c6, 0x7101, 1)}, + {QMI_FIXED_INTF(0x05c6, 0x7101, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x7101, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x7102, 1)}, + {QMI_FIXED_INTF(0x05c6, 0x7102, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x7102, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x8000, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x8001, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9000, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9003, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9005, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x900a, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x900b, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x900c, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x900c, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x900c, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x900d, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x900f, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x900f, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x900f, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9010, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9010, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9011, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9011, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9021, 1)}, + {QMI_FIXED_INTF(0x05c6, 0x9022, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x9025, 4)}, /* Alcatel-sbell ASB TL131 TDD LTE (China Mobile) */ + {QMI_FIXED_INTF(0x05c6, 0x9026, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x902e, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9031, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9032, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9033, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9033, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9033, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9033, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9034, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9034, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9034, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9034, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9034, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9035, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9036, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9037, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9038, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x903b, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x903c, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x903d, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x903e, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9043, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9046, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9046, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9046, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9047, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x9047, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9047, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9048, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9048, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9048, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9048, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9048, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x904c, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x904c, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x904c, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x904c, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x9050, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9052, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9053, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9053, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9054, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9054, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9055, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9055, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9055, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9055, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9055, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9056, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x9062, 9)}, + {QMI_FIXED_INTF(0x05c6, 0x9064, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9065, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9065, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9066, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9066, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9067, 1)}, + {QMI_FIXED_INTF(0x05c6, 0x9068, 2)}, + {QMI_FIXED_INTF(0x05c6, 0x9068, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9068, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9068, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9068, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9068, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9069, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9069, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9069, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9069, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x9070, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9070, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9075, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9076, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9076, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9076, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9076, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9076, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x9077, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9077, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9077, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9077, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9078, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9079, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x9079, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9079, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9079, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9079, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x9080, 5)}, + {QMI_FIXED_INTF(0x05c6, 0x9080, 6)}, + {QMI_FIXED_INTF(0x05c6, 0x9080, 7)}, + {QMI_FIXED_INTF(0x05c6, 0x9080, 8)}, + {QMI_FIXED_INTF(0x05c6, 0x9083, 3)}, + {QMI_FIXED_INTF(0x05c6, 0x9084, 4)}, + {QMI_FIXED_INTF(0x05c6, 0x920d, 0)}, + {QMI_FIXED_INTF(0x05c6, 0x920d, 5)}, + {QMI_FIXED_INTF(0x0846, 0x68a2, 8)}, + {QMI_FIXED_INTF(0x12d1, 0x140c, 1)}, /* Huawei E173 */ + {QMI_FIXED_INTF(0x12d1, 0x14ac, 1)}, /* Huawei E1820 */ + {QMI_FIXED_INTF(0x16d8, 0x6003, 0)}, /* CMOTech 6003 */ + {QMI_FIXED_INTF(0x16d8, 0x6007, 0)}, /* CMOTech CHE-628S */ + {QMI_FIXED_INTF(0x16d8, 0x6008, 0)}, /* CMOTech CMU-301 */ + {QMI_FIXED_INTF(0x16d8, 0x6280, 0)}, /* CMOTech CHU-628 */ + {QMI_FIXED_INTF(0x16d8, 0x7001, 0)}, /* CMOTech CHU-720S */ + {QMI_FIXED_INTF(0x16d8, 0x7002, 0)}, /* CMOTech 7002 */ + {QMI_FIXED_INTF(0x16d8, 0x7003, 4)}, /* CMOTech CHU-629K */ + {QMI_FIXED_INTF(0x16d8, 0x7004, 3)}, /* CMOTech 7004 */ + {QMI_FIXED_INTF(0x16d8, 0x7006, 5)}, /* CMOTech CGU-629 */ + {QMI_FIXED_INTF(0x16d8, 0x700a, 4)}, /* CMOTech CHU-629S */ + {QMI_FIXED_INTF(0x16d8, 0x7211, 0)}, /* CMOTech CHU-720I */ + {QMI_FIXED_INTF(0x16d8, 0x7212, 0)}, /* CMOTech 7212 */ + {QMI_FIXED_INTF(0x16d8, 0x7213, 0)}, /* CMOTech 7213 */ + {QMI_FIXED_INTF(0x16d8, 0x7251, 1)}, /* CMOTech 7251 */ + {QMI_FIXED_INTF(0x16d8, 0x7252, 1)}, /* CMOTech 7252 */ + {QMI_FIXED_INTF(0x16d8, 0x7253, 1)}, /* CMOTech 7253 */ + {QMI_FIXED_INTF(0x19d2, 0x0002, 1)}, + {QMI_FIXED_INTF(0x19d2, 0x0012, 1)}, + {QMI_FIXED_INTF(0x19d2, 0x0017, 3)}, + {QMI_FIXED_INTF(0x19d2, 0x0019, 3)}, /* ONDA MT689DC */ + {QMI_FIXED_INTF(0x19d2, 0x0021, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0025, 1)}, + {QMI_FIXED_INTF(0x19d2, 0x0031, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0042, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0049, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0052, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0055, 1)}, /* ZTE (Vodafone) K3520-Z */ + {QMI_FIXED_INTF(0x19d2, 0x0058, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0063, 4)}, /* ZTE (Vodafone) K3565-Z */ + {QMI_FIXED_INTF(0x19d2, 0x0104, 4)}, /* ZTE (Vodafone) K4505-Z */ + {QMI_FIXED_INTF(0x19d2, 0x0113, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0118, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0121, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0123, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0124, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0125, 6)}, + {QMI_FIXED_INTF(0x19d2, 0x0126, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0130, 1)}, + {QMI_FIXED_INTF(0x19d2, 0x0133, 3)}, + {QMI_FIXED_INTF(0x19d2, 0x0141, 5)}, + {QMI_FIXED_INTF(0x19d2, 0x0157, 5)}, /* ZTE MF683 */ + {QMI_FIXED_INTF(0x19d2, 0x0158, 3)}, + {QMI_FIXED_INTF(0x19d2, 0x0167, 4)}, /* ZTE MF820D */ + {QMI_FIXED_INTF(0x19d2, 0x0168, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x0176, 3)}, + {QMI_FIXED_INTF(0x19d2, 0x0178, 3)}, + {QMI_FIXED_INTF(0x19d2, 0x0191, 4)}, /* ZTE EuFi890 */ + {QMI_FIXED_INTF(0x19d2, 0x0199, 1)}, /* ZTE MF820S */ + {QMI_FIXED_INTF(0x19d2, 0x0200, 1)}, + {QMI_FIXED_INTF(0x19d2, 0x0257, 3)}, /* ZTE MF821 */ + {QMI_FIXED_INTF(0x19d2, 0x0265, 4)}, /* ONDA MT8205 4G LTE */ + {QMI_FIXED_INTF(0x19d2, 0x0284, 4)}, /* ZTE MF880 */ + {QMI_FIXED_INTF(0x19d2, 0x0326, 4)}, /* ZTE MF821D */ + {QMI_FIXED_INTF(0x19d2, 0x0412, 4)}, /* Telewell TW-LTE 4G */ + {QMI_FIXED_INTF(0x19d2, 0x1008, 4)}, /* ZTE (Vodafone) K3570-Z */ + {QMI_FIXED_INTF(0x19d2, 0x1010, 4)}, /* ZTE (Vodafone) K3571-Z */ + {QMI_FIXED_INTF(0x19d2, 0x1012, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1018, 3)}, /* ZTE (Vodafone) K5006-Z */ + {QMI_FIXED_INTF(0x19d2, 0x1021, 2)}, + {QMI_FIXED_INTF(0x19d2, 0x1245, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1247, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1252, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1254, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1255, 3)}, + {QMI_FIXED_INTF(0x19d2, 0x1255, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1256, 4)}, + {QMI_FIXED_INTF(0x19d2, 0x1270, 5)}, /* ZTE MF667 */ + {QMI_FIXED_INTF(0x19d2, 0x1401, 2)}, + {QMI_FIXED_INTF(0x19d2, 0x1402, 2)}, /* ZTE MF60 */ + {QMI_FIXED_INTF(0x19d2, 0x1424, 2)}, + {QMI_FIXED_INTF(0x19d2, 0x1425, 2)}, + {QMI_FIXED_INTF(0x19d2, 0x1426, 2)}, /* ZTE MF91 */ + {QMI_FIXED_INTF(0x19d2, 0x1428, 2)}, /* Telewell TW-LTE 4G v2 */ + {QMI_FIXED_INTF(0x19d2, 0x2002, 4)}, /* ZTE (Vodafone) K3765-Z */ + {QMI_FIXED_INTF(0x0f3d, 0x68a2, 8)}, /* Sierra Wireless MC7700 */ + {QMI_FIXED_INTF(0x114f, 0x68a2, 8)}, /* Sierra Wireless MC7750 */ + {QMI_FIXED_INTF(0x1199, 0x68a2, 8)}, /* Sierra Wireless MC7710 in QMI mode */ + {QMI_FIXED_INTF(0x1199, 0x68a2, 19)}, /* Sierra Wireless MC7710 in QMI mode */ + {QMI_FIXED_INTF(0x1199, 0x68c0, 8)}, /* Sierra Wireless MC73xx */ + {QMI_FIXED_INTF(0x1199, 0x68c0, 10)}, /* Sierra Wireless MC73xx */ + {QMI_FIXED_INTF(0x1199, 0x901c, 8)}, /* Sierra Wireless EM7700 */ + {QMI_FIXED_INTF(0x1199, 0x901f, 8)}, /* Sierra Wireless EM7355 */ + {QMI_FIXED_INTF(0x1199, 0x9041, 8)}, /* Sierra Wireless MC7305/MC7355 */ + {QMI_FIXED_INTF(0x1199, 0x9051, 8)}, /* Netgear AirCard 340U */ + {QMI_FIXED_INTF(0x1199, 0x9053, 8)}, /* Sierra Wireless Modem */ + {QMI_FIXED_INTF(0x1199, 0x9054, 8)}, /* Sierra Wireless Modem */ + {QMI_FIXED_INTF(0x1199, 0x9055, 8)}, /* Netgear AirCard 341U */ + {QMI_FIXED_INTF(0x1199, 0x9056, 8)}, /* Sierra Wireless Modem */ + {QMI_FIXED_INTF(0x1199, 0x9057, 8)}, + {QMI_FIXED_INTF(0x1199, 0x9061, 8)}, /* Sierra Wireless Modem */ + {QMI_FIXED_INTF(0x1bbb, 0x011e, 4)}, /* Telekom Speedstick LTE II (Alcatel One Touch L100V LTE) */ + {QMI_FIXED_INTF(0x1bbb, 0x0203, 2)}, /* Alcatel L800MA */ + {QMI_FIXED_INTF(0x2357, 0x0201, 4)}, /* TP-LINK HSUPA Modem MA180 */ + {QMI_FIXED_INTF(0x2357, 0x9000, 4)}, /* TP-LINK MA260 */ + {QMI_FIXED_INTF(0x1bc7, 0x1200, 5)}, /* Telit LE920 */ + {QMI_FIXED_INTF(0x1bc7, 0x1201, 2)}, /* Telit LE920 */ + {QMI_FIXED_INTF(0x0b3c, 0xc000, 4)}, /* Olivetti Olicard 100 */ + {QMI_FIXED_INTF(0x0b3c, 0xc001, 4)}, /* Olivetti Olicard 120 */ + {QMI_FIXED_INTF(0x0b3c, 0xc002, 4)}, /* Olivetti Olicard 140 */ + {QMI_FIXED_INTF(0x0b3c, 0xc004, 6)}, /* Olivetti Olicard 155 */ + {QMI_FIXED_INTF(0x0b3c, 0xc005, 6)}, /* Olivetti Olicard 200 */ + {QMI_FIXED_INTF(0x0b3c, 0xc00a, 6)}, /* Olivetti Olicard 160 */ + {QMI_FIXED_INTF(0x0b3c, 0xc00b, 4)}, /* Olivetti Olicard 500 */ + {QMI_FIXED_INTF(0x1e2d, 0x0060, 4)}, /* Cinterion PLxx */ + {QMI_FIXED_INTF(0x1e2d, 0x0053, 4)}, /* Cinterion PHxx,PXxx */ + {QMI_FIXED_INTF(0x413c, 0x81a2, 8)}, /* Dell Wireless 5806 Gobi(TM) 4G LTE Mobile Broadband Card */ + {QMI_FIXED_INTF(0x413c, 0x81a3, 8)}, /* Dell Wireless 5570 HSPA+ (42Mbps) Mobile Broadband Card */ + {QMI_FIXED_INTF(0x413c, 0x81a4, 8)}, /* Dell Wireless 5570e HSPA+ (42Mbps) Mobile Broadband Card */ + {QMI_FIXED_INTF(0x413c, 0x81a8, 8)}, /* Dell Wireless 5808 Gobi(TM) 4G LTE Mobile Broadband Card */ + {QMI_FIXED_INTF(0x413c, 0x81a9, 8)}, /* Dell Wireless 5808e Gobi(TM) 4G LTE Mobile Broadband Card */ + {QMI_FIXED_INTF(0x03f0, 0x581d, 4)}, /* HP lt4112 LTE/HSPA+ Gobi 4G Module (Huawei me906e) */ + + /* 4. 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(0x04da, 0x250d)}, /* Panasonic Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x413c, 0x8172)}, /* Dell Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa001)}, /* Novatel/Verizon USB-1000 */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa002)}, /* Novatel Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa003)}, /* Novatel Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa004)}, /* Novatel Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa005)}, /* Novatel Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa006)}, /* Novatel Gobi Modem device */ + {QMI_GOBI1K_DEVICE(0x1410, 0xa007)}, /* 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 */ + + /* 5. Gobi 2000 and 3000 devices */ + {QMI_GOBI_DEVICE(0x413c, 0x8186)}, /* Dell Gobi 2000 Modem device (N0218, VU936) */ + {QMI_GOBI_DEVICE(0x413c, 0x8194)}, /* Dell Gobi 3000 Composite */ + {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(0x0af0, 0x8120)}, /* Option GTM681W */ + {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(0x03f0, 0x371d)}, /* HP un2430 Mobile Broadband Module */ + {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 */ + {QMI_GOBI_DEVICE(0x12d1, 0x14f1)}, /* Sony Gobi 3000 Composite */ + {QMI_GOBI_DEVICE(0x1410, 0xa021)}, /* Foxconn Gobi 3000 Modem device (Novatel E396) */ + + { } /* END */ +}; +MODULE_DEVICE_TABLE(usb, products); + +static int qmi_wwan_probe(struct usb_interface *intf, + const struct usb_device_id *prod) +{ + struct usb_device_id *id = (struct usb_device_id *)prod; + + /* Workaround to enable dynamic IDs. This disables usbnet + * blacklisting functionality. Which, if required, can be + * reimplemented here by using a magic "blacklist" value + * instead of 0 in the static device id table + */ + if (!id->driver_info) { + dev_dbg(&intf->dev, "setting defaults for dynamic device id\n"); + id->driver_info = (unsigned long)&qmi_wwan_info; + } + + return usbnet_probe(intf, id); +} + +static struct usb_driver qmi_wwan_driver = { + .name = "qmi_wwan", + .id_table = products, + .probe = qmi_wwan_probe, + .disconnect = usbnet_disconnect, + .suspend = qmi_wwan_suspend, + .resume = qmi_wwan_resume, + .reset_resume = qmi_wwan_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(qmi_wwan_driver); + +MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>"); +MODULE_DESCRIPTION("Qualcomm MSM Interface (QMI) WWAN driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c new file mode 100644 index 000000000..aafa1a189 --- /dev/null +++ b/drivers/net/usb/r8152.c @@ -0,0 +1,4141 @@ +/* + * Copyright (c) 2014 Realtek Semiconductor Corp. All rights reserved. + * + * 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/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 <linux/crc32.h> +#include <linux/if_vlan.h> +#include <linux/uaccess.h> +#include <linux/list.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/ip6_checksum.h> +#include <uapi/linux/mdio.h> +#include <linux/mdio.h> +#include <linux/usb/cdc.h> + +/* Version Information */ +#define DRIVER_VERSION "v1.08.0 (2015/01/13)" +#define DRIVER_AUTHOR "Realtek linux nic maintainers <nic_swsd@realtek.com>" +#define DRIVER_DESC "Realtek RTL8152/RTL8153 Based USB Ethernet Adapters" +#define MODULENAME "r8152" + +#define R8152_PHY_ID 32 + +#define PLA_IDR 0xc000 +#define PLA_RCR 0xc010 +#define PLA_RMS 0xc016 +#define PLA_RXFIFO_CTRL0 0xc0a0 +#define PLA_RXFIFO_CTRL1 0xc0a4 +#define PLA_RXFIFO_CTRL2 0xc0a8 +#define PLA_DMY_REG0 0xc0b0 +#define PLA_FMC 0xc0b4 +#define PLA_CFG_WOL 0xc0b6 +#define PLA_TEREDO_CFG 0xc0bc +#define PLA_MAR 0xcd00 +#define PLA_BACKUP 0xd000 +#define PAL_BDC_CR 0xd1a0 +#define PLA_TEREDO_TIMER 0xd2cc +#define PLA_REALWOW_TIMER 0xd2e8 +#define PLA_LEDSEL 0xdd90 +#define PLA_LED_FEATURE 0xdd92 +#define PLA_PHYAR 0xde00 +#define PLA_BOOT_CTRL 0xe004 +#define PLA_GPHY_INTR_IMR 0xe022 +#define PLA_EEE_CR 0xe040 +#define PLA_EEEP_CR 0xe080 +#define PLA_MAC_PWR_CTRL 0xe0c0 +#define PLA_MAC_PWR_CTRL2 0xe0ca +#define PLA_MAC_PWR_CTRL3 0xe0cc +#define PLA_MAC_PWR_CTRL4 0xe0ce +#define PLA_WDT6_CTRL 0xe428 +#define PLA_TCR0 0xe610 +#define PLA_TCR1 0xe612 +#define PLA_MTPS 0xe615 +#define PLA_TXFIFO_CTRL 0xe618 +#define PLA_RSTTALLY 0xe800 +#define PLA_CR 0xe813 +#define PLA_CRWECR 0xe81c +#define PLA_CONFIG12 0xe81e /* CONFIG1, CONFIG2 */ +#define PLA_CONFIG34 0xe820 /* CONFIG3, CONFIG4 */ +#define PLA_CONFIG5 0xe822 +#define PLA_PHY_PWR 0xe84c +#define PLA_OOB_CTRL 0xe84f +#define PLA_CPCR 0xe854 +#define PLA_MISC_0 0xe858 +#define PLA_MISC_1 0xe85a +#define PLA_OCP_GPHY_BASE 0xe86c +#define PLA_TALLYCNT 0xe890 +#define PLA_SFF_STS_7 0xe8de +#define PLA_PHYSTATUS 0xe908 +#define PLA_BP_BA 0xfc26 +#define PLA_BP_0 0xfc28 +#define PLA_BP_1 0xfc2a +#define PLA_BP_2 0xfc2c +#define PLA_BP_3 0xfc2e +#define PLA_BP_4 0xfc30 +#define PLA_BP_5 0xfc32 +#define PLA_BP_6 0xfc34 +#define PLA_BP_7 0xfc36 +#define PLA_BP_EN 0xfc38 + +#define USB_USB2PHY 0xb41e +#define USB_SSPHYLINK2 0xb428 +#define USB_U2P3_CTRL 0xb460 +#define USB_CSR_DUMMY1 0xb464 +#define USB_CSR_DUMMY2 0xb466 +#define USB_DEV_STAT 0xb808 +#define USB_CONNECT_TIMER 0xcbf8 +#define USB_BURST_SIZE 0xcfc0 +#define USB_USB_CTRL 0xd406 +#define USB_PHY_CTRL 0xd408 +#define USB_TX_AGG 0xd40a +#define USB_RX_BUF_TH 0xd40c +#define USB_USB_TIMER 0xd428 +#define USB_RX_EARLY_TIMEOUT 0xd42c +#define USB_RX_EARLY_SIZE 0xd42e +#define USB_PM_CTRL_STATUS 0xd432 +#define USB_TX_DMA 0xd434 +#define USB_TOLERANCE 0xd490 +#define USB_LPM_CTRL 0xd41a +#define USB_UPS_CTRL 0xd800 +#define USB_MISC_0 0xd81a +#define USB_POWER_CUT 0xd80a +#define USB_AFE_CTRL2 0xd824 +#define USB_WDT11_CTRL 0xe43c +#define USB_BP_BA 0xfc26 +#define USB_BP_0 0xfc28 +#define USB_BP_1 0xfc2a +#define USB_BP_2 0xfc2c +#define USB_BP_3 0xfc2e +#define USB_BP_4 0xfc30 +#define USB_BP_5 0xfc32 +#define USB_BP_6 0xfc34 +#define USB_BP_7 0xfc36 +#define USB_BP_EN 0xfc38 + +/* OCP Registers */ +#define OCP_ALDPS_CONFIG 0x2010 +#define OCP_EEE_CONFIG1 0x2080 +#define OCP_EEE_CONFIG2 0x2092 +#define OCP_EEE_CONFIG3 0x2094 +#define OCP_BASE_MII 0xa400 +#define OCP_EEE_AR 0xa41a +#define OCP_EEE_DATA 0xa41c +#define OCP_PHY_STATUS 0xa420 +#define OCP_POWER_CFG 0xa430 +#define OCP_EEE_CFG 0xa432 +#define OCP_SRAM_ADDR 0xa436 +#define OCP_SRAM_DATA 0xa438 +#define OCP_DOWN_SPEED 0xa442 +#define OCP_EEE_ABLE 0xa5c4 +#define OCP_EEE_ADV 0xa5d0 +#define OCP_EEE_LPABLE 0xa5d2 +#define OCP_ADC_CFG 0xbc06 + +/* SRAM Register */ +#define SRAM_LPF_CFG 0x8012 +#define SRAM_10M_AMP1 0x8080 +#define SRAM_10M_AMP2 0x8082 +#define SRAM_IMPEDANCE 0x8084 + +/* PLA_RCR */ +#define RCR_AAP 0x00000001 +#define RCR_APM 0x00000002 +#define RCR_AM 0x00000004 +#define RCR_AB 0x00000008 +#define RCR_ACPT_ALL (RCR_AAP | RCR_APM | RCR_AM | RCR_AB) + +/* PLA_RXFIFO_CTRL0 */ +#define RXFIFO_THR1_NORMAL 0x00080002 +#define RXFIFO_THR1_OOB 0x01800003 + +/* PLA_RXFIFO_CTRL1 */ +#define RXFIFO_THR2_FULL 0x00000060 +#define RXFIFO_THR2_HIGH 0x00000038 +#define RXFIFO_THR2_OOB 0x0000004a +#define RXFIFO_THR2_NORMAL 0x00a0 + +/* PLA_RXFIFO_CTRL2 */ +#define RXFIFO_THR3_FULL 0x00000078 +#define RXFIFO_THR3_HIGH 0x00000048 +#define RXFIFO_THR3_OOB 0x0000005a +#define RXFIFO_THR3_NORMAL 0x0110 + +/* PLA_TXFIFO_CTRL */ +#define TXFIFO_THR_NORMAL 0x00400008 +#define TXFIFO_THR_NORMAL2 0x01000008 + +/* PLA_DMY_REG0 */ +#define ECM_ALDPS 0x0002 + +/* PLA_FMC */ +#define FMC_FCR_MCU_EN 0x0001 + +/* PLA_EEEP_CR */ +#define EEEP_CR_EEEP_TX 0x0002 + +/* PLA_WDT6_CTRL */ +#define WDT6_SET_MODE 0x0010 + +/* PLA_TCR0 */ +#define TCR0_TX_EMPTY 0x0800 +#define TCR0_AUTO_FIFO 0x0080 + +/* PLA_TCR1 */ +#define VERSION_MASK 0x7cf0 + +/* PLA_MTPS */ +#define MTPS_JUMBO (12 * 1024 / 64) +#define MTPS_DEFAULT (6 * 1024 / 64) + +/* PLA_RSTTALLY */ +#define TALLY_RESET 0x0001 + +/* PLA_CR */ +#define CR_RST 0x10 +#define CR_RE 0x08 +#define CR_TE 0x04 + +/* PLA_CRWECR */ +#define CRWECR_NORAML 0x00 +#define CRWECR_CONFIG 0xc0 + +/* PLA_OOB_CTRL */ +#define NOW_IS_OOB 0x80 +#define TXFIFO_EMPTY 0x20 +#define RXFIFO_EMPTY 0x10 +#define LINK_LIST_READY 0x02 +#define DIS_MCU_CLROOB 0x01 +#define FIFO_EMPTY (TXFIFO_EMPTY | RXFIFO_EMPTY) + +/* PLA_MISC_1 */ +#define RXDY_GATED_EN 0x0008 + +/* PLA_SFF_STS_7 */ +#define RE_INIT_LL 0x8000 +#define MCU_BORW_EN 0x4000 + +/* PLA_CPCR */ +#define CPCR_RX_VLAN 0x0040 + +/* PLA_CFG_WOL */ +#define MAGIC_EN 0x0001 + +/* PLA_TEREDO_CFG */ +#define TEREDO_SEL 0x8000 +#define TEREDO_WAKE_MASK 0x7f00 +#define TEREDO_RS_EVENT_MASK 0x00fe +#define OOB_TEREDO_EN 0x0001 + +/* PAL_BDC_CR */ +#define ALDPS_PROXY_MODE 0x0001 + +/* PLA_CONFIG34 */ +#define LINK_ON_WAKE_EN 0x0010 +#define LINK_OFF_WAKE_EN 0x0008 + +/* PLA_CONFIG5 */ +#define BWF_EN 0x0040 +#define MWF_EN 0x0020 +#define UWF_EN 0x0010 +#define LAN_WAKE_EN 0x0002 + +/* PLA_LED_FEATURE */ +#define LED_MODE_MASK 0x0700 + +/* PLA_PHY_PWR */ +#define TX_10M_IDLE_EN 0x0080 +#define PFM_PWM_SWITCH 0x0040 + +/* PLA_MAC_PWR_CTRL */ +#define D3_CLK_GATED_EN 0x00004000 +#define MCU_CLK_RATIO 0x07010f07 +#define MCU_CLK_RATIO_MASK 0x0f0f0f0f +#define ALDPS_SPDWN_RATIO 0x0f87 + +/* PLA_MAC_PWR_CTRL2 */ +#define EEE_SPDWN_RATIO 0x8007 + +/* PLA_MAC_PWR_CTRL3 */ +#define PKT_AVAIL_SPDWN_EN 0x0100 +#define SUSPEND_SPDWN_EN 0x0004 +#define U1U2_SPDWN_EN 0x0002 +#define L1_SPDWN_EN 0x0001 + +/* PLA_MAC_PWR_CTRL4 */ +#define PWRSAVE_SPDWN_EN 0x1000 +#define RXDV_SPDWN_EN 0x0800 +#define TX10MIDLE_EN 0x0100 +#define TP100_SPDWN_EN 0x0020 +#define TP500_SPDWN_EN 0x0010 +#define TP1000_SPDWN_EN 0x0008 +#define EEE_SPDWN_EN 0x0001 + +/* PLA_GPHY_INTR_IMR */ +#define GPHY_STS_MSK 0x0001 +#define SPEED_DOWN_MSK 0x0002 +#define SPDWN_RXDV_MSK 0x0004 +#define SPDWN_LINKCHG_MSK 0x0008 + +/* PLA_PHYAR */ +#define PHYAR_FLAG 0x80000000 + +/* PLA_EEE_CR */ +#define EEE_RX_EN 0x0001 +#define EEE_TX_EN 0x0002 + +/* PLA_BOOT_CTRL */ +#define AUTOLOAD_DONE 0x0002 + +/* USB_USB2PHY */ +#define USB2PHY_SUSPEND 0x0001 +#define USB2PHY_L1 0x0002 + +/* USB_SSPHYLINK2 */ +#define pwd_dn_scale_mask 0x3ffe +#define pwd_dn_scale(x) ((x) << 1) + +/* USB_CSR_DUMMY1 */ +#define DYNAMIC_BURST 0x0001 + +/* USB_CSR_DUMMY2 */ +#define EP4_FULL_FC 0x0001 + +/* USB_DEV_STAT */ +#define STAT_SPEED_MASK 0x0006 +#define STAT_SPEED_HIGH 0x0000 +#define STAT_SPEED_FULL 0x0002 + +/* USB_TX_AGG */ +#define TX_AGG_MAX_THRESHOLD 0x03 + +/* USB_RX_BUF_TH */ +#define RX_THR_SUPPER 0x0c350180 +#define RX_THR_HIGH 0x7a120180 +#define RX_THR_SLOW 0xffff0180 + +/* USB_TX_DMA */ +#define TEST_MODE_DISABLE 0x00000001 +#define TX_SIZE_ADJUST1 0x00000100 + +/* USB_UPS_CTRL */ +#define POWER_CUT 0x0100 + +/* USB_PM_CTRL_STATUS */ +#define RESUME_INDICATE 0x0001 + +/* USB_USB_CTRL */ +#define RX_AGG_DISABLE 0x0010 + +/* USB_U2P3_CTRL */ +#define U2P3_ENABLE 0x0001 + +/* USB_POWER_CUT */ +#define PWR_EN 0x0001 +#define PHASE2_EN 0x0008 + +/* USB_MISC_0 */ +#define PCUT_STATUS 0x0001 + +/* USB_RX_EARLY_TIMEOUT */ +#define COALESCE_SUPER 85000U +#define COALESCE_HIGH 250000U +#define COALESCE_SLOW 524280U + +/* USB_WDT11_CTRL */ +#define TIMER11_EN 0x0001 + +/* USB_LPM_CTRL */ +/* bit 4 ~ 5: fifo empty boundary */ +#define FIFO_EMPTY_1FB 0x30 /* 0x1fb * 64 = 32448 bytes */ +/* bit 2 ~ 3: LMP timer */ +#define LPM_TIMER_MASK 0x0c +#define LPM_TIMER_500MS 0x04 /* 500 ms */ +#define LPM_TIMER_500US 0x0c /* 500 us */ +#define ROK_EXIT_LPM 0x02 + +/* USB_AFE_CTRL2 */ +#define SEN_VAL_MASK 0xf800 +#define SEN_VAL_NORMAL 0xa000 +#define SEL_RXIDLE 0x0100 + +/* OCP_ALDPS_CONFIG */ +#define ENPWRSAVE 0x8000 +#define ENPDNPS 0x0200 +#define LINKENA 0x0100 +#define DIS_SDSAVE 0x0010 + +/* OCP_PHY_STATUS */ +#define PHY_STAT_MASK 0x0007 +#define PHY_STAT_LAN_ON 3 +#define PHY_STAT_PWRDN 5 + +/* OCP_POWER_CFG */ +#define EEE_CLKDIV_EN 0x8000 +#define EN_ALDPS 0x0004 +#define EN_10M_PLLOFF 0x0001 + +/* OCP_EEE_CONFIG1 */ +#define RG_TXLPI_MSK_HFDUP 0x8000 +#define RG_MATCLR_EN 0x4000 +#define EEE_10_CAP 0x2000 +#define EEE_NWAY_EN 0x1000 +#define TX_QUIET_EN 0x0200 +#define RX_QUIET_EN 0x0100 +#define sd_rise_time_mask 0x0070 +#define sd_rise_time(x) (min(x, 7) << 4) /* bit 4 ~ 6 */ +#define RG_RXLPI_MSK_HFDUP 0x0008 +#define SDFALLTIME 0x0007 /* bit 0 ~ 2 */ + +/* OCP_EEE_CONFIG2 */ +#define RG_LPIHYS_NUM 0x7000 /* bit 12 ~ 15 */ +#define RG_DACQUIET_EN 0x0400 +#define RG_LDVQUIET_EN 0x0200 +#define RG_CKRSEL 0x0020 +#define RG_EEEPRG_EN 0x0010 + +/* OCP_EEE_CONFIG3 */ +#define fast_snr_mask 0xff80 +#define fast_snr(x) (min(x, 0x1ff) << 7) /* bit 7 ~ 15 */ +#define RG_LFS_SEL 0x0060 /* bit 6 ~ 5 */ +#define MSK_PH 0x0006 /* bit 0 ~ 3 */ + +/* OCP_EEE_AR */ +/* bit[15:14] function */ +#define FUN_ADDR 0x0000 +#define FUN_DATA 0x4000 +/* bit[4:0] device addr */ + +/* OCP_EEE_CFG */ +#define CTAP_SHORT_EN 0x0040 +#define EEE10_EN 0x0010 + +/* OCP_DOWN_SPEED */ +#define EN_10M_BGOFF 0x0080 + +/* OCP_ADC_CFG */ +#define CKADSEL_L 0x0100 +#define ADC_EN 0x0080 +#define EN_EMI_L 0x0040 + +/* SRAM_LPF_CFG */ +#define LPF_AUTO_TUNE 0x8000 + +/* SRAM_10M_AMP1 */ +#define GDAC_IB_UPALL 0x0008 + +/* SRAM_10M_AMP2 */ +#define AMP_DN 0x0200 + +/* SRAM_IMPEDANCE */ +#define RX_DRIVING_MASK 0x6000 + +enum rtl_register_content { + _1000bps = 0x10, + _100bps = 0x08, + _10bps = 0x04, + LINK_STATUS = 0x02, + FULL_DUP = 0x01, +}; + +#define RTL8152_MAX_TX 4 +#define RTL8152_MAX_RX 10 +#define INTBUFSIZE 2 +#define CRC_SIZE 4 +#define TX_ALIGN 4 +#define RX_ALIGN 8 + +#define INTR_LINK 0x0004 + +#define RTL8152_REQT_READ 0xc0 +#define RTL8152_REQT_WRITE 0x40 +#define RTL8152_REQ_GET_REGS 0x05 +#define RTL8152_REQ_SET_REGS 0x05 + +#define BYTE_EN_DWORD 0xff +#define BYTE_EN_WORD 0x33 +#define BYTE_EN_BYTE 0x11 +#define BYTE_EN_SIX_BYTES 0x3f +#define BYTE_EN_START_MASK 0x0f +#define BYTE_EN_END_MASK 0xf0 + +#define RTL8153_MAX_PACKET 9216 /* 9K */ +#define RTL8153_MAX_MTU (RTL8153_MAX_PACKET - VLAN_ETH_HLEN - VLAN_HLEN) +#define RTL8152_RMS (VLAN_ETH_FRAME_LEN + VLAN_HLEN) +#define RTL8153_RMS RTL8153_MAX_PACKET +#define RTL8152_TX_TIMEOUT (5 * HZ) +#define RTL8152_NAPI_WEIGHT 64 + +/* rtl8152 flags */ +enum rtl8152_flags { + RTL8152_UNPLUG = 0, + RTL8152_SET_RX_MODE, + WORK_ENABLE, + RTL8152_LINK_CHG, + SELECTIVE_SUSPEND, + PHY_RESET, + SCHEDULE_NAPI, +}; + +/* Define these values to match your device */ +#define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_SAMSUNG 0x04e8 +#define VENDOR_ID_LENOVO 0x17ef + +#define MCU_TYPE_PLA 0x0100 +#define MCU_TYPE_USB 0x0000 + +struct tally_counter { + __le64 tx_packets; + __le64 rx_packets; + __le64 tx_errors; + __le32 rx_errors; + __le16 rx_missed; + __le16 align_errors; + __le32 tx_one_collision; + __le32 tx_multi_collision; + __le64 rx_unicast; + __le64 rx_broadcast; + __le32 rx_multicast; + __le16 tx_aborted; + __le16 tx_underrun; +}; + +struct rx_desc { + __le32 opts1; +#define RX_LEN_MASK 0x7fff + + __le32 opts2; +#define RD_UDP_CS BIT(23) +#define RD_TCP_CS BIT(22) +#define RD_IPV6_CS BIT(20) +#define RD_IPV4_CS BIT(19) + + __le32 opts3; +#define IPF BIT(23) /* IP checksum fail */ +#define UDPF BIT(22) /* UDP checksum fail */ +#define TCPF BIT(21) /* TCP checksum fail */ +#define RX_VLAN_TAG BIT(16) + + __le32 opts4; + __le32 opts5; + __le32 opts6; +}; + +struct tx_desc { + __le32 opts1; +#define TX_FS BIT(31) /* First segment of a packet */ +#define TX_LS BIT(30) /* Final segment of a packet */ +#define GTSENDV4 BIT(28) +#define GTSENDV6 BIT(27) +#define GTTCPHO_SHIFT 18 +#define GTTCPHO_MAX 0x7fU +#define TX_LEN_MAX 0x3ffffU + + __le32 opts2; +#define UDP_CS BIT(31) /* Calculate UDP/IP checksum */ +#define TCP_CS BIT(30) /* Calculate TCP/IP checksum */ +#define IPV4_CS BIT(29) /* Calculate IPv4 checksum */ +#define IPV6_CS BIT(28) /* Calculate IPv6 checksum */ +#define MSS_SHIFT 17 +#define MSS_MAX 0x7ffU +#define TCPHO_SHIFT 17 +#define TCPHO_MAX 0x7ffU +#define TX_VLAN_TAG BIT(16) +}; + +struct r8152; + +struct rx_agg { + struct list_head list; + struct urb *urb; + struct r8152 *context; + void *buffer; + void *head; +}; + +struct tx_agg { + struct list_head list; + struct urb *urb; + struct r8152 *context; + void *buffer; + void *head; + u32 skb_num; + u32 skb_len; +}; + +struct r8152 { + unsigned long flags; + struct usb_device *udev; + struct napi_struct napi; + struct usb_interface *intf; + struct net_device *netdev; + struct urb *intr_urb; + struct tx_agg tx_info[RTL8152_MAX_TX]; + struct rx_agg rx_info[RTL8152_MAX_RX]; + struct list_head rx_done, tx_free; + struct sk_buff_head tx_queue, rx_queue; + spinlock_t rx_lock, tx_lock; + struct delayed_work schedule; + struct mii_if_info mii; + struct mutex control; /* use for hw setting */ + + struct rtl_ops { + void (*init)(struct r8152 *); + int (*enable)(struct r8152 *); + void (*disable)(struct r8152 *); + void (*up)(struct r8152 *); + void (*down)(struct r8152 *); + void (*unload)(struct r8152 *); + int (*eee_get)(struct r8152 *, struct ethtool_eee *); + int (*eee_set)(struct r8152 *, struct ethtool_eee *); + } rtl_ops; + + int intr_interval; + u32 saved_wolopts; + u32 msg_enable; + u32 tx_qlen; + u32 coalesce; + u16 ocp_base; + u8 *intr_buff; + u8 version; +}; + +enum rtl_version { + RTL_VER_UNKNOWN = 0, + RTL_VER_01, + RTL_VER_02, + RTL_VER_03, + RTL_VER_04, + RTL_VER_05, + RTL_VER_MAX +}; + +enum tx_csum_stat { + TX_CSUM_SUCCESS = 0, + TX_CSUM_TSO, + TX_CSUM_NONE +}; + +/* Maximum number of multicast addresses to filter (vs. Rx-all-multicast). + * The RTL chips use a 64 element hash table based on the Ethernet CRC. + */ +static const int multicast_filter_limit = 32; +static unsigned int agg_buf_sz = 16384; + +#define RTL_LIMITED_TSO_SIZE (agg_buf_sz - sizeof(struct tx_desc) - \ + VLAN_ETH_HLEN - VLAN_HLEN) + +static +int get_registers(struct r8152 *tp, u16 value, u16 index, u16 size, void *data) +{ + int ret; + void *tmp; + + tmp = kmalloc(size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + ret = usb_control_msg(tp->udev, usb_rcvctrlpipe(tp->udev, 0), + RTL8152_REQ_GET_REGS, RTL8152_REQT_READ, + value, index, tmp, size, 500); + + memcpy(data, tmp, size); + kfree(tmp); + + return ret; +} + +static +int set_registers(struct r8152 *tp, u16 value, u16 index, u16 size, void *data) +{ + int ret; + void *tmp; + + tmp = kmemdup(data, size, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + ret = usb_control_msg(tp->udev, usb_sndctrlpipe(tp->udev, 0), + RTL8152_REQ_SET_REGS, RTL8152_REQT_WRITE, + value, index, tmp, size, 500); + + kfree(tmp); + + return ret; +} + +static int generic_ocp_read(struct r8152 *tp, u16 index, u16 size, + void *data, u16 type) +{ + u16 limit = 64; + int ret = 0; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + /* both size and indix must be 4 bytes align */ + if ((size & 3) || !size || (index & 3) || !data) + return -EPERM; + + if ((u32)index + (u32)size > 0xffff) + return -EPERM; + + while (size) { + if (size > limit) { + ret = get_registers(tp, index, type, limit, data); + if (ret < 0) + break; + + index += limit; + data += limit; + size -= limit; + } else { + ret = get_registers(tp, index, type, size, data); + if (ret < 0) + break; + + index += size; + data += size; + size = 0; + break; + } + } + + if (ret == -ENODEV) + set_bit(RTL8152_UNPLUG, &tp->flags); + + return ret; +} + +static int generic_ocp_write(struct r8152 *tp, u16 index, u16 byteen, + u16 size, void *data, u16 type) +{ + int ret; + u16 byteen_start, byteen_end, byen; + u16 limit = 512; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + /* both size and indix must be 4 bytes align */ + if ((size & 3) || !size || (index & 3) || !data) + return -EPERM; + + if ((u32)index + (u32)size > 0xffff) + return -EPERM; + + byteen_start = byteen & BYTE_EN_START_MASK; + byteen_end = byteen & BYTE_EN_END_MASK; + + byen = byteen_start | (byteen_start << 4); + ret = set_registers(tp, index, type | byen, 4, data); + if (ret < 0) + goto error1; + + index += 4; + data += 4; + size -= 4; + + if (size) { + size -= 4; + + while (size) { + if (size > limit) { + ret = set_registers(tp, index, + type | BYTE_EN_DWORD, + limit, data); + if (ret < 0) + goto error1; + + index += limit; + data += limit; + size -= limit; + } else { + ret = set_registers(tp, index, + type | BYTE_EN_DWORD, + size, data); + if (ret < 0) + goto error1; + + index += size; + data += size; + size = 0; + break; + } + } + + byen = byteen_end | (byteen_end >> 4); + ret = set_registers(tp, index, type | byen, 4, data); + if (ret < 0) + goto error1; + } + +error1: + if (ret == -ENODEV) + set_bit(RTL8152_UNPLUG, &tp->flags); + + return ret; +} + +static inline +int pla_ocp_read(struct r8152 *tp, u16 index, u16 size, void *data) +{ + return generic_ocp_read(tp, index, size, data, MCU_TYPE_PLA); +} + +static inline +int pla_ocp_write(struct r8152 *tp, u16 index, u16 byteen, u16 size, void *data) +{ + return generic_ocp_write(tp, index, byteen, size, data, MCU_TYPE_PLA); +} + +static inline +int usb_ocp_read(struct r8152 *tp, u16 index, u16 size, void *data) +{ + return generic_ocp_read(tp, index, size, data, MCU_TYPE_USB); +} + +static inline +int usb_ocp_write(struct r8152 *tp, u16 index, u16 byteen, u16 size, void *data) +{ + return generic_ocp_write(tp, index, byteen, size, data, MCU_TYPE_USB); +} + +static u32 ocp_read_dword(struct r8152 *tp, u16 type, u16 index) +{ + __le32 data; + + generic_ocp_read(tp, index, sizeof(data), &data, type); + + return __le32_to_cpu(data); +} + +static void ocp_write_dword(struct r8152 *tp, u16 type, u16 index, u32 data) +{ + __le32 tmp = __cpu_to_le32(data); + + generic_ocp_write(tp, index, BYTE_EN_DWORD, sizeof(tmp), &tmp, type); +} + +static u16 ocp_read_word(struct r8152 *tp, u16 type, u16 index) +{ + u32 data; + __le32 tmp; + u8 shift = index & 2; + + index &= ~3; + + generic_ocp_read(tp, index, sizeof(tmp), &tmp, type); + + data = __le32_to_cpu(tmp); + data >>= (shift * 8); + data &= 0xffff; + + return (u16)data; +} + +static void ocp_write_word(struct r8152 *tp, u16 type, u16 index, u32 data) +{ + u32 mask = 0xffff; + __le32 tmp; + u16 byen = BYTE_EN_WORD; + u8 shift = index & 2; + + data &= mask; + + if (index & 2) { + byen <<= shift; + mask <<= (shift * 8); + data <<= (shift * 8); + index &= ~3; + } + + tmp = __cpu_to_le32(data); + + generic_ocp_write(tp, index, byen, sizeof(tmp), &tmp, type); +} + +static u8 ocp_read_byte(struct r8152 *tp, u16 type, u16 index) +{ + u32 data; + __le32 tmp; + u8 shift = index & 3; + + index &= ~3; + + generic_ocp_read(tp, index, sizeof(tmp), &tmp, type); + + data = __le32_to_cpu(tmp); + data >>= (shift * 8); + data &= 0xff; + + return (u8)data; +} + +static void ocp_write_byte(struct r8152 *tp, u16 type, u16 index, u32 data) +{ + u32 mask = 0xff; + __le32 tmp; + u16 byen = BYTE_EN_BYTE; + u8 shift = index & 3; + + data &= mask; + + if (index & 3) { + byen <<= shift; + mask <<= (shift * 8); + data <<= (shift * 8); + index &= ~3; + } + + tmp = __cpu_to_le32(data); + + generic_ocp_write(tp, index, byen, sizeof(tmp), &tmp, type); +} + +static u16 ocp_reg_read(struct r8152 *tp, u16 addr) +{ + u16 ocp_base, ocp_index; + + ocp_base = addr & 0xf000; + if (ocp_base != tp->ocp_base) { + ocp_write_word(tp, MCU_TYPE_PLA, PLA_OCP_GPHY_BASE, ocp_base); + tp->ocp_base = ocp_base; + } + + ocp_index = (addr & 0x0fff) | 0xb000; + return ocp_read_word(tp, MCU_TYPE_PLA, ocp_index); +} + +static void ocp_reg_write(struct r8152 *tp, u16 addr, u16 data) +{ + u16 ocp_base, ocp_index; + + ocp_base = addr & 0xf000; + if (ocp_base != tp->ocp_base) { + ocp_write_word(tp, MCU_TYPE_PLA, PLA_OCP_GPHY_BASE, ocp_base); + tp->ocp_base = ocp_base; + } + + ocp_index = (addr & 0x0fff) | 0xb000; + ocp_write_word(tp, MCU_TYPE_PLA, ocp_index, data); +} + +static inline void r8152_mdio_write(struct r8152 *tp, u32 reg_addr, u32 value) +{ + ocp_reg_write(tp, OCP_BASE_MII + reg_addr * 2, value); +} + +static inline int r8152_mdio_read(struct r8152 *tp, u32 reg_addr) +{ + return ocp_reg_read(tp, OCP_BASE_MII + reg_addr * 2); +} + +static void sram_write(struct r8152 *tp, u16 addr, u16 data) +{ + ocp_reg_write(tp, OCP_SRAM_ADDR, addr); + ocp_reg_write(tp, OCP_SRAM_DATA, data); +} + +static int read_mii_word(struct net_device *netdev, int phy_id, int reg) +{ + struct r8152 *tp = netdev_priv(netdev); + int ret; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + if (phy_id != R8152_PHY_ID) + return -EINVAL; + + ret = r8152_mdio_read(tp, reg); + + return ret; +} + +static +void write_mii_word(struct net_device *netdev, int phy_id, int reg, int val) +{ + struct r8152 *tp = netdev_priv(netdev); + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + if (phy_id != R8152_PHY_ID) + return; + + r8152_mdio_write(tp, reg, val); +} + +static int +r8152_submit_rx(struct r8152 *tp, struct rx_agg *agg, gfp_t mem_flags); + +static int rtl8152_set_mac_address(struct net_device *netdev, void *p) +{ + struct r8152 *tp = netdev_priv(netdev); + struct sockaddr *addr = p; + int ret = -EADDRNOTAVAIL; + + if (!is_valid_ether_addr(addr->sa_data)) + goto out1; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out1; + + mutex_lock(&tp->control); + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_CONFIG); + pla_ocp_write(tp, PLA_IDR, BYTE_EN_SIX_BYTES, 8, addr->sa_data); + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); +out1: + return ret; +} + +static int set_ethernet_addr(struct r8152 *tp) +{ + struct net_device *dev = tp->netdev; + struct sockaddr sa; + int ret; + + if (tp->version == RTL_VER_01) + ret = pla_ocp_read(tp, PLA_IDR, 8, sa.sa_data); + else + ret = pla_ocp_read(tp, PLA_BACKUP, 8, sa.sa_data); + + if (ret < 0) { + netif_err(tp, probe, dev, "Get ether addr fail\n"); + } else if (!is_valid_ether_addr(sa.sa_data)) { + netif_err(tp, probe, dev, "Invalid ether addr %pM\n", + sa.sa_data); + eth_hw_addr_random(dev); + ether_addr_copy(sa.sa_data, dev->dev_addr); + ret = rtl8152_set_mac_address(dev, &sa); + netif_info(tp, probe, dev, "Random ether addr %pM\n", + sa.sa_data); + } else { + if (tp->version == RTL_VER_01) + ether_addr_copy(dev->dev_addr, sa.sa_data); + else + ret = rtl8152_set_mac_address(dev, &sa); + } + + return ret; +} + +static void read_bulk_callback(struct urb *urb) +{ + struct net_device *netdev; + int status = urb->status; + struct rx_agg *agg; + struct r8152 *tp; + + agg = urb->context; + if (!agg) + return; + + tp = agg->context; + if (!tp) + return; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + if (!test_bit(WORK_ENABLE, &tp->flags)) + return; + + netdev = tp->netdev; + + /* When link down, the driver would cancel all bulks. */ + /* This avoid the re-submitting bulk */ + if (!netif_carrier_ok(netdev)) + return; + + usb_mark_last_busy(tp->udev); + + switch (status) { + case 0: + if (urb->actual_length < ETH_ZLEN) + break; + + spin_lock(&tp->rx_lock); + list_add_tail(&agg->list, &tp->rx_done); + spin_unlock(&tp->rx_lock); + napi_schedule(&tp->napi); + return; + case -ESHUTDOWN: + set_bit(RTL8152_UNPLUG, &tp->flags); + netif_device_detach(tp->netdev); + return; + case -ENOENT: + return; /* the urb is in unlink state */ + case -ETIME: + if (net_ratelimit()) + netdev_warn(netdev, "maybe reset is needed?\n"); + break; + default: + if (net_ratelimit()) + netdev_warn(netdev, "Rx status %d\n", status); + break; + } + + r8152_submit_rx(tp, agg, GFP_ATOMIC); +} + +static void write_bulk_callback(struct urb *urb) +{ + struct net_device_stats *stats; + struct net_device *netdev; + struct tx_agg *agg; + struct r8152 *tp; + int status = urb->status; + + agg = urb->context; + if (!agg) + return; + + tp = agg->context; + if (!tp) + return; + + netdev = tp->netdev; + stats = &netdev->stats; + if (status) { + if (net_ratelimit()) + netdev_warn(netdev, "Tx status %d\n", status); + stats->tx_errors += agg->skb_num; + } else { + stats->tx_packets += agg->skb_num; + stats->tx_bytes += agg->skb_len; + } + + spin_lock(&tp->tx_lock); + list_add_tail(&agg->list, &tp->tx_free); + spin_unlock(&tp->tx_lock); + + usb_autopm_put_interface_async(tp->intf); + + if (!netif_carrier_ok(netdev)) + return; + + if (!test_bit(WORK_ENABLE, &tp->flags)) + return; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + if (!skb_queue_empty(&tp->tx_queue)) + napi_schedule(&tp->napi); +} + +static void intr_callback(struct urb *urb) +{ + struct r8152 *tp; + __le16 *d; + int status = urb->status; + int res; + + tp = urb->context; + if (!tp) + return; + + if (!test_bit(WORK_ENABLE, &tp->flags)) + return; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + switch (status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: + netif_device_detach(tp->netdev); + case -ENOENT: + case -EPROTO: + netif_info(tp, intr, tp->netdev, + "Stop submitting intr, status %d\n", status); + return; + case -EOVERFLOW: + netif_info(tp, intr, tp->netdev, "intr status -EOVERFLOW\n"); + goto resubmit; + /* -EPIPE: should clear the halt */ + default: + netif_info(tp, intr, tp->netdev, "intr status %d\n", status); + goto resubmit; + } + + d = urb->transfer_buffer; + if (INTR_LINK & __le16_to_cpu(d[0])) { + if (!netif_carrier_ok(tp->netdev)) { + set_bit(RTL8152_LINK_CHG, &tp->flags); + schedule_delayed_work(&tp->schedule, 0); + } + } else { + if (netif_carrier_ok(tp->netdev)) { + set_bit(RTL8152_LINK_CHG, &tp->flags); + schedule_delayed_work(&tp->schedule, 0); + } + } + +resubmit: + res = usb_submit_urb(urb, GFP_ATOMIC); + if (res == -ENODEV) { + set_bit(RTL8152_UNPLUG, &tp->flags); + netif_device_detach(tp->netdev); + } else if (res) { + netif_err(tp, intr, tp->netdev, + "can't resubmit intr, status %d\n", res); + } +} + +static inline void *rx_agg_align(void *data) +{ + return (void *)ALIGN((uintptr_t)data, RX_ALIGN); +} + +static inline void *tx_agg_align(void *data) +{ + return (void *)ALIGN((uintptr_t)data, TX_ALIGN); +} + +static void free_all_mem(struct r8152 *tp) +{ + int i; + + for (i = 0; i < RTL8152_MAX_RX; i++) { + usb_free_urb(tp->rx_info[i].urb); + tp->rx_info[i].urb = NULL; + + kfree(tp->rx_info[i].buffer); + tp->rx_info[i].buffer = NULL; + tp->rx_info[i].head = NULL; + } + + for (i = 0; i < RTL8152_MAX_TX; i++) { + usb_free_urb(tp->tx_info[i].urb); + tp->tx_info[i].urb = NULL; + + kfree(tp->tx_info[i].buffer); + tp->tx_info[i].buffer = NULL; + tp->tx_info[i].head = NULL; + } + + usb_free_urb(tp->intr_urb); + tp->intr_urb = NULL; + + kfree(tp->intr_buff); + tp->intr_buff = NULL; +} + +static int alloc_all_mem(struct r8152 *tp) +{ + struct net_device *netdev = tp->netdev; + struct usb_interface *intf = tp->intf; + struct usb_host_interface *alt = intf->cur_altsetting; + struct usb_host_endpoint *ep_intr = alt->endpoint + 2; + struct urb *urb; + int node, i; + u8 *buf; + + node = netdev->dev.parent ? dev_to_node(netdev->dev.parent) : -1; + + spin_lock_init(&tp->rx_lock); + spin_lock_init(&tp->tx_lock); + INIT_LIST_HEAD(&tp->tx_free); + skb_queue_head_init(&tp->tx_queue); + skb_queue_head_init(&tp->rx_queue); + + for (i = 0; i < RTL8152_MAX_RX; i++) { + buf = kmalloc_node(agg_buf_sz, GFP_KERNEL, node); + if (!buf) + goto err1; + + if (buf != rx_agg_align(buf)) { + kfree(buf); + buf = kmalloc_node(agg_buf_sz + RX_ALIGN, GFP_KERNEL, + node); + if (!buf) + goto err1; + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(buf); + goto err1; + } + + INIT_LIST_HEAD(&tp->rx_info[i].list); + tp->rx_info[i].context = tp; + tp->rx_info[i].urb = urb; + tp->rx_info[i].buffer = buf; + tp->rx_info[i].head = rx_agg_align(buf); + } + + for (i = 0; i < RTL8152_MAX_TX; i++) { + buf = kmalloc_node(agg_buf_sz, GFP_KERNEL, node); + if (!buf) + goto err1; + + if (buf != tx_agg_align(buf)) { + kfree(buf); + buf = kmalloc_node(agg_buf_sz + TX_ALIGN, GFP_KERNEL, + node); + if (!buf) + goto err1; + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(buf); + goto err1; + } + + INIT_LIST_HEAD(&tp->tx_info[i].list); + tp->tx_info[i].context = tp; + tp->tx_info[i].urb = urb; + tp->tx_info[i].buffer = buf; + tp->tx_info[i].head = tx_agg_align(buf); + + list_add_tail(&tp->tx_info[i].list, &tp->tx_free); + } + + tp->intr_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tp->intr_urb) + goto err1; + + tp->intr_buff = kmalloc(INTBUFSIZE, GFP_KERNEL); + if (!tp->intr_buff) + goto err1; + + tp->intr_interval = (int)ep_intr->desc.bInterval; + usb_fill_int_urb(tp->intr_urb, tp->udev, usb_rcvintpipe(tp->udev, 3), + tp->intr_buff, INTBUFSIZE, intr_callback, + tp, tp->intr_interval); + + return 0; + +err1: + free_all_mem(tp); + return -ENOMEM; +} + +static struct tx_agg *r8152_get_tx_agg(struct r8152 *tp) +{ + struct tx_agg *agg = NULL; + unsigned long flags; + + if (list_empty(&tp->tx_free)) + return NULL; + + spin_lock_irqsave(&tp->tx_lock, flags); + if (!list_empty(&tp->tx_free)) { + struct list_head *cursor; + + cursor = tp->tx_free.next; + list_del_init(cursor); + agg = list_entry(cursor, struct tx_agg, list); + } + spin_unlock_irqrestore(&tp->tx_lock, flags); + + return agg; +} + +/* r8152_csum_workaround() + * The hw limites the value the transport offset. When the offset is out of the + * range, calculate the checksum by sw. + */ +static void r8152_csum_workaround(struct r8152 *tp, struct sk_buff *skb, + struct sk_buff_head *list) +{ + if (skb_shinfo(skb)->gso_size) { + netdev_features_t features = tp->netdev->features; + struct sk_buff_head seg_list; + struct sk_buff *segs, *nskb; + + features &= ~(NETIF_F_SG | NETIF_F_IPV6_CSUM | NETIF_F_TSO6); + segs = skb_gso_segment(skb, features); + if (IS_ERR(segs) || !segs) + goto drop; + + __skb_queue_head_init(&seg_list); + + do { + nskb = segs; + segs = segs->next; + nskb->next = NULL; + __skb_queue_tail(&seg_list, nskb); + } while (segs); + + skb_queue_splice(&seg_list, list); + dev_kfree_skb(skb); + } else if (skb->ip_summed == CHECKSUM_PARTIAL) { + if (skb_checksum_help(skb) < 0) + goto drop; + + __skb_queue_head(list, skb); + } else { + struct net_device_stats *stats; + +drop: + stats = &tp->netdev->stats; + stats->tx_dropped++; + dev_kfree_skb(skb); + } +} + +/* msdn_giant_send_check() + * According to the document of microsoft, the TCP Pseudo Header excludes the + * packet length for IPv6 TCP large packets. + */ +static int msdn_giant_send_check(struct sk_buff *skb) +{ + const struct ipv6hdr *ipv6h; + struct tcphdr *th; + int ret; + + ret = skb_cow_head(skb, 0); + if (ret) + return ret; + + ipv6h = ipv6_hdr(skb); + th = tcp_hdr(skb); + + th->check = 0; + th->check = ~tcp_v6_check(0, &ipv6h->saddr, &ipv6h->daddr, 0); + + return ret; +} + +static inline void rtl_tx_vlan_tag(struct tx_desc *desc, struct sk_buff *skb) +{ + if (skb_vlan_tag_present(skb)) { + u32 opts2; + + opts2 = TX_VLAN_TAG | swab16(skb_vlan_tag_get(skb)); + desc->opts2 |= cpu_to_le32(opts2); + } +} + +static inline void rtl_rx_vlan_tag(struct rx_desc *desc, struct sk_buff *skb) +{ + u32 opts2 = le32_to_cpu(desc->opts2); + + if (opts2 & RX_VLAN_TAG) + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), + swab16(opts2 & 0xffff)); +} + +static int r8152_tx_csum(struct r8152 *tp, struct tx_desc *desc, + struct sk_buff *skb, u32 len, u32 transport_offset) +{ + u32 mss = skb_shinfo(skb)->gso_size; + u32 opts1, opts2 = 0; + int ret = TX_CSUM_SUCCESS; + + WARN_ON_ONCE(len > TX_LEN_MAX); + + opts1 = len | TX_FS | TX_LS; + + if (mss) { + if (transport_offset > GTTCPHO_MAX) { + netif_warn(tp, tx_err, tp->netdev, + "Invalid transport offset 0x%x for TSO\n", + transport_offset); + ret = TX_CSUM_TSO; + goto unavailable; + } + + switch (vlan_get_protocol(skb)) { + case htons(ETH_P_IP): + opts1 |= GTSENDV4; + break; + + case htons(ETH_P_IPV6): + if (msdn_giant_send_check(skb)) { + ret = TX_CSUM_TSO; + goto unavailable; + } + opts1 |= GTSENDV6; + break; + + default: + WARN_ON_ONCE(1); + break; + } + + opts1 |= transport_offset << GTTCPHO_SHIFT; + opts2 |= min(mss, MSS_MAX) << MSS_SHIFT; + } else if (skb->ip_summed == CHECKSUM_PARTIAL) { + u8 ip_protocol; + + if (transport_offset > TCPHO_MAX) { + netif_warn(tp, tx_err, tp->netdev, + "Invalid transport offset 0x%x\n", + transport_offset); + ret = TX_CSUM_NONE; + goto unavailable; + } + + switch (vlan_get_protocol(skb)) { + case htons(ETH_P_IP): + opts2 |= IPV4_CS; + ip_protocol = ip_hdr(skb)->protocol; + break; + + case htons(ETH_P_IPV6): + opts2 |= IPV6_CS; + ip_protocol = ipv6_hdr(skb)->nexthdr; + break; + + default: + ip_protocol = IPPROTO_RAW; + break; + } + + if (ip_protocol == IPPROTO_TCP) + opts2 |= TCP_CS; + else if (ip_protocol == IPPROTO_UDP) + opts2 |= UDP_CS; + else + WARN_ON_ONCE(1); + + opts2 |= transport_offset << TCPHO_SHIFT; + } + + desc->opts2 = cpu_to_le32(opts2); + desc->opts1 = cpu_to_le32(opts1); + +unavailable: + return ret; +} + +static int r8152_tx_agg_fill(struct r8152 *tp, struct tx_agg *agg) +{ + struct sk_buff_head skb_head, *tx_queue = &tp->tx_queue; + int remain, ret; + u8 *tx_data; + + __skb_queue_head_init(&skb_head); + spin_lock(&tx_queue->lock); + skb_queue_splice_init(tx_queue, &skb_head); + spin_unlock(&tx_queue->lock); + + tx_data = agg->head; + agg->skb_num = 0; + agg->skb_len = 0; + remain = agg_buf_sz; + + while (remain >= ETH_ZLEN + sizeof(struct tx_desc)) { + struct tx_desc *tx_desc; + struct sk_buff *skb; + unsigned int len; + u32 offset; + + skb = __skb_dequeue(&skb_head); + if (!skb) + break; + + len = skb->len + sizeof(*tx_desc); + + if (len > remain) { + __skb_queue_head(&skb_head, skb); + break; + } + + tx_data = tx_agg_align(tx_data); + tx_desc = (struct tx_desc *)tx_data; + + offset = (u32)skb_transport_offset(skb); + + if (r8152_tx_csum(tp, tx_desc, skb, skb->len, offset)) { + r8152_csum_workaround(tp, skb, &skb_head); + continue; + } + + rtl_tx_vlan_tag(tx_desc, skb); + + tx_data += sizeof(*tx_desc); + + len = skb->len; + if (skb_copy_bits(skb, 0, tx_data, len) < 0) { + struct net_device_stats *stats = &tp->netdev->stats; + + stats->tx_dropped++; + dev_kfree_skb_any(skb); + tx_data -= sizeof(*tx_desc); + continue; + } + + tx_data += len; + agg->skb_len += len; + agg->skb_num++; + + dev_kfree_skb_any(skb); + + remain = agg_buf_sz - (int)(tx_agg_align(tx_data) - agg->head); + } + + if (!skb_queue_empty(&skb_head)) { + spin_lock(&tx_queue->lock); + skb_queue_splice(&skb_head, tx_queue); + spin_unlock(&tx_queue->lock); + } + + netif_tx_lock(tp->netdev); + + if (netif_queue_stopped(tp->netdev) && + skb_queue_len(&tp->tx_queue) < tp->tx_qlen) + netif_wake_queue(tp->netdev); + + netif_tx_unlock(tp->netdev); + + ret = usb_autopm_get_interface_async(tp->intf); + if (ret < 0) + goto out_tx_fill; + + usb_fill_bulk_urb(agg->urb, tp->udev, usb_sndbulkpipe(tp->udev, 2), + agg->head, (int)(tx_data - (u8 *)agg->head), + (usb_complete_t)write_bulk_callback, agg); + + ret = usb_submit_urb(agg->urb, GFP_ATOMIC); + if (ret < 0) + usb_autopm_put_interface_async(tp->intf); + +out_tx_fill: + return ret; +} + +static u8 r8152_rx_csum(struct r8152 *tp, struct rx_desc *rx_desc) +{ + u8 checksum = CHECKSUM_NONE; + u32 opts2, opts3; + + if (tp->version == RTL_VER_01) + goto return_result; + + opts2 = le32_to_cpu(rx_desc->opts2); + opts3 = le32_to_cpu(rx_desc->opts3); + + if (opts2 & RD_IPV4_CS) { + if (opts3 & IPF) + checksum = CHECKSUM_NONE; + else if ((opts2 & RD_UDP_CS) && (opts3 & UDPF)) + checksum = CHECKSUM_NONE; + else if ((opts2 & RD_TCP_CS) && (opts3 & TCPF)) + checksum = CHECKSUM_NONE; + else + checksum = CHECKSUM_UNNECESSARY; + } else if (RD_IPV6_CS) { + if ((opts2 & RD_UDP_CS) && !(opts3 & UDPF)) + checksum = CHECKSUM_UNNECESSARY; + else if ((opts2 & RD_TCP_CS) && !(opts3 & TCPF)) + checksum = CHECKSUM_UNNECESSARY; + } + +return_result: + return checksum; +} + +static int rx_bottom(struct r8152 *tp, int budget) +{ + unsigned long flags; + struct list_head *cursor, *next, rx_queue; + int ret = 0, work_done = 0; + + if (!skb_queue_empty(&tp->rx_queue)) { + while (work_done < budget) { + struct sk_buff *skb = __skb_dequeue(&tp->rx_queue); + struct net_device *netdev = tp->netdev; + struct net_device_stats *stats = &netdev->stats; + unsigned int pkt_len; + + if (!skb) + break; + + pkt_len = skb->len; + napi_gro_receive(&tp->napi, skb); + work_done++; + stats->rx_packets++; + stats->rx_bytes += pkt_len; + } + } + + if (list_empty(&tp->rx_done)) + goto out1; + + INIT_LIST_HEAD(&rx_queue); + spin_lock_irqsave(&tp->rx_lock, flags); + list_splice_init(&tp->rx_done, &rx_queue); + spin_unlock_irqrestore(&tp->rx_lock, flags); + + list_for_each_safe(cursor, next, &rx_queue) { + struct rx_desc *rx_desc; + struct rx_agg *agg; + int len_used = 0; + struct urb *urb; + u8 *rx_data; + + list_del_init(cursor); + + agg = list_entry(cursor, struct rx_agg, list); + urb = agg->urb; + if (urb->actual_length < ETH_ZLEN) + goto submit; + + rx_desc = agg->head; + rx_data = agg->head; + len_used += sizeof(struct rx_desc); + + while (urb->actual_length > len_used) { + struct net_device *netdev = tp->netdev; + struct net_device_stats *stats = &netdev->stats; + unsigned int pkt_len; + struct sk_buff *skb; + + pkt_len = le32_to_cpu(rx_desc->opts1) & RX_LEN_MASK; + if (pkt_len < ETH_ZLEN) + break; + + len_used += pkt_len; + if (urb->actual_length < len_used) + break; + + pkt_len -= CRC_SIZE; + rx_data += sizeof(struct rx_desc); + + skb = netdev_alloc_skb_ip_align(netdev, pkt_len); + if (!skb) { + stats->rx_dropped++; + goto find_next_rx; + } + + skb->ip_summed = r8152_rx_csum(tp, rx_desc); + memcpy(skb->data, rx_data, pkt_len); + skb_put(skb, pkt_len); + skb->protocol = eth_type_trans(skb, netdev); + rtl_rx_vlan_tag(rx_desc, skb); + if (work_done < budget) { + napi_gro_receive(&tp->napi, skb); + work_done++; + stats->rx_packets++; + stats->rx_bytes += pkt_len; + } else { + __skb_queue_tail(&tp->rx_queue, skb); + } + +find_next_rx: + rx_data = rx_agg_align(rx_data + pkt_len + CRC_SIZE); + rx_desc = (struct rx_desc *)rx_data; + len_used = (int)(rx_data - (u8 *)agg->head); + len_used += sizeof(struct rx_desc); + } + +submit: + if (!ret) { + ret = r8152_submit_rx(tp, agg, GFP_ATOMIC); + } else { + urb->actual_length = 0; + list_add_tail(&agg->list, next); + } + } + + if (!list_empty(&rx_queue)) { + spin_lock_irqsave(&tp->rx_lock, flags); + list_splice_tail(&rx_queue, &tp->rx_done); + spin_unlock_irqrestore(&tp->rx_lock, flags); + } + +out1: + return work_done; +} + +static void tx_bottom(struct r8152 *tp) +{ + int res; + + do { + struct tx_agg *agg; + + if (skb_queue_empty(&tp->tx_queue)) + break; + + agg = r8152_get_tx_agg(tp); + if (!agg) + break; + + res = r8152_tx_agg_fill(tp, agg); + if (res) { + struct net_device *netdev = tp->netdev; + + if (res == -ENODEV) { + set_bit(RTL8152_UNPLUG, &tp->flags); + netif_device_detach(netdev); + } else { + struct net_device_stats *stats = &netdev->stats; + unsigned long flags; + + netif_warn(tp, tx_err, netdev, + "failed tx_urb %d\n", res); + stats->tx_dropped += agg->skb_num; + + spin_lock_irqsave(&tp->tx_lock, flags); + list_add_tail(&agg->list, &tp->tx_free); + spin_unlock_irqrestore(&tp->tx_lock, flags); + } + } + } while (res == 0); +} + +static void bottom_half(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + if (!test_bit(WORK_ENABLE, &tp->flags)) + return; + + /* When link down, the driver would cancel all bulks. */ + /* This avoid the re-submitting bulk */ + if (!netif_carrier_ok(tp->netdev)) + return; + + clear_bit(SCHEDULE_NAPI, &tp->flags); + + tx_bottom(tp); +} + +static int r8152_poll(struct napi_struct *napi, int budget) +{ + struct r8152 *tp = container_of(napi, struct r8152, napi); + int work_done; + + work_done = rx_bottom(tp, budget); + bottom_half(tp); + + if (work_done < budget) { + napi_complete(napi); + if (!list_empty(&tp->rx_done)) + napi_schedule(napi); + } + + return work_done; +} + +static +int r8152_submit_rx(struct r8152 *tp, struct rx_agg *agg, gfp_t mem_flags) +{ + int ret; + + /* The rx would be stopped, so skip submitting */ + if (test_bit(RTL8152_UNPLUG, &tp->flags) || + !test_bit(WORK_ENABLE, &tp->flags) || !netif_carrier_ok(tp->netdev)) + return 0; + + usb_fill_bulk_urb(agg->urb, tp->udev, usb_rcvbulkpipe(tp->udev, 1), + agg->head, agg_buf_sz, + (usb_complete_t)read_bulk_callback, agg); + + ret = usb_submit_urb(agg->urb, mem_flags); + if (ret == -ENODEV) { + set_bit(RTL8152_UNPLUG, &tp->flags); + netif_device_detach(tp->netdev); + } else if (ret) { + struct urb *urb = agg->urb; + unsigned long flags; + + urb->actual_length = 0; + spin_lock_irqsave(&tp->rx_lock, flags); + list_add_tail(&agg->list, &tp->rx_done); + spin_unlock_irqrestore(&tp->rx_lock, flags); + + netif_err(tp, rx_err, tp->netdev, + "Couldn't submit rx[%p], ret = %d\n", agg, ret); + + napi_schedule(&tp->napi); + } + + return ret; +} + +static void rtl_drop_queued_tx(struct r8152 *tp) +{ + struct net_device_stats *stats = &tp->netdev->stats; + struct sk_buff_head skb_head, *tx_queue = &tp->tx_queue; + struct sk_buff *skb; + + if (skb_queue_empty(tx_queue)) + return; + + __skb_queue_head_init(&skb_head); + spin_lock_bh(&tx_queue->lock); + skb_queue_splice_init(tx_queue, &skb_head); + spin_unlock_bh(&tx_queue->lock); + + while ((skb = __skb_dequeue(&skb_head))) { + dev_kfree_skb(skb); + stats->tx_dropped++; + } +} + +static void rtl8152_tx_timeout(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + int i; + + netif_warn(tp, tx_err, netdev, "Tx timeout\n"); + for (i = 0; i < RTL8152_MAX_TX; i++) + usb_unlink_urb(tp->tx_info[i].urb); +} + +static void rtl8152_set_rx_mode(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + + if (netif_carrier_ok(netdev)) { + set_bit(RTL8152_SET_RX_MODE, &tp->flags); + schedule_delayed_work(&tp->schedule, 0); + } +} + +static void _rtl8152_set_rx_mode(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + u32 mc_filter[2]; /* Multicast hash filter */ + __le32 tmp[2]; + u32 ocp_data; + + clear_bit(RTL8152_SET_RX_MODE, &tp->flags); + netif_stop_queue(netdev); + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_data |= RCR_AB | RCR_APM; + + if (netdev->flags & IFF_PROMISC) { + /* Unconditionally log net taps. */ + netif_notice(tp, link, netdev, "Promiscuous mode enabled\n"); + ocp_data |= RCR_AM | RCR_AAP; + mc_filter[1] = 0xffffffff; + mc_filter[0] = 0xffffffff; + } else if ((netdev_mc_count(netdev) > multicast_filter_limit) || + (netdev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + ocp_data |= RCR_AM; + mc_filter[1] = 0xffffffff; + mc_filter[0] = 0xffffffff; + } else { + struct netdev_hw_addr *ha; + + mc_filter[1] = 0; + mc_filter[0] = 0; + netdev_for_each_mc_addr(ha, netdev) { + int bit_nr = ether_crc(ETH_ALEN, ha->addr) >> 26; + + mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31); + ocp_data |= RCR_AM; + } + } + + tmp[0] = __cpu_to_le32(swab32(mc_filter[1])); + tmp[1] = __cpu_to_le32(swab32(mc_filter[0])); + + pla_ocp_write(tp, PLA_MAR, BYTE_EN_DWORD, sizeof(tmp), tmp); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + netif_wake_queue(netdev); +} + +static netdev_features_t +rtl8152_features_check(struct sk_buff *skb, struct net_device *dev, + netdev_features_t features) +{ + u32 mss = skb_shinfo(skb)->gso_size; + int max_offset = mss ? GTTCPHO_MAX : TCPHO_MAX; + int offset = skb_transport_offset(skb); + + if ((mss || skb->ip_summed == CHECKSUM_PARTIAL) && offset > max_offset) + features &= ~(NETIF_F_ALL_CSUM | NETIF_F_GSO_MASK); + else if ((skb->len + sizeof(struct tx_desc)) > agg_buf_sz) + features &= ~NETIF_F_GSO_MASK; + + return features; +} + +static netdev_tx_t rtl8152_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + + skb_tx_timestamp(skb); + + skb_queue_tail(&tp->tx_queue, skb); + + if (!list_empty(&tp->tx_free)) { + if (test_bit(SELECTIVE_SUSPEND, &tp->flags)) { + set_bit(SCHEDULE_NAPI, &tp->flags); + schedule_delayed_work(&tp->schedule, 0); + } else { + usb_mark_last_busy(tp->udev); + napi_schedule(&tp->napi); + } + } else if (skb_queue_len(&tp->tx_queue) > tp->tx_qlen) { + netif_stop_queue(netdev); + } + + return NETDEV_TX_OK; +} + +static void r8152b_reset_packet_filter(struct r8152 *tp) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_FMC); + ocp_data &= ~FMC_FCR_MCU_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_FMC, ocp_data); + ocp_data |= FMC_FCR_MCU_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_FMC, ocp_data); +} + +static void rtl8152_nic_reset(struct r8152 *tp) +{ + int i; + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, CR_RST); + + for (i = 0; i < 1000; i++) { + if (!(ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR) & CR_RST)) + break; + usleep_range(100, 400); + } +} + +static void set_tx_qlen(struct r8152 *tp) +{ + struct net_device *netdev = tp->netdev; + + tp->tx_qlen = agg_buf_sz / (netdev->mtu + VLAN_ETH_HLEN + VLAN_HLEN + + sizeof(struct tx_desc)); +} + +static inline u8 rtl8152_get_speed(struct r8152 *tp) +{ + return ocp_read_byte(tp, MCU_TYPE_PLA, PLA_PHYSTATUS); +} + +static void rtl_set_eee_plus(struct r8152 *tp) +{ + u32 ocp_data; + u8 speed; + + speed = rtl8152_get_speed(tp); + if (speed & _10bps) { + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR); + ocp_data |= EEEP_CR_EEEP_TX; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR, ocp_data); + } else { + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR); + ocp_data &= ~EEEP_CR_EEEP_TX; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR, ocp_data); + } +} + +static void rxdy_gated_en(struct r8152 *tp, bool enable) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_MISC_1); + if (enable) + ocp_data |= RXDY_GATED_EN; + else + ocp_data &= ~RXDY_GATED_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MISC_1, ocp_data); +} + +static int rtl_start_rx(struct r8152 *tp) +{ + int i, ret = 0; + + napi_disable(&tp->napi); + INIT_LIST_HEAD(&tp->rx_done); + for (i = 0; i < RTL8152_MAX_RX; i++) { + INIT_LIST_HEAD(&tp->rx_info[i].list); + ret = r8152_submit_rx(tp, &tp->rx_info[i], GFP_KERNEL); + if (ret) + break; + } + napi_enable(&tp->napi); + + if (ret && ++i < RTL8152_MAX_RX) { + struct list_head rx_queue; + unsigned long flags; + + INIT_LIST_HEAD(&rx_queue); + + do { + struct rx_agg *agg = &tp->rx_info[i++]; + struct urb *urb = agg->urb; + + urb->actual_length = 0; + list_add_tail(&agg->list, &rx_queue); + } while (i < RTL8152_MAX_RX); + + spin_lock_irqsave(&tp->rx_lock, flags); + list_splice_tail(&rx_queue, &tp->rx_done); + spin_unlock_irqrestore(&tp->rx_lock, flags); + } + + return ret; +} + +static int rtl_stop_rx(struct r8152 *tp) +{ + int i; + + for (i = 0; i < RTL8152_MAX_RX; i++) + usb_kill_urb(tp->rx_info[i].urb); + + while (!skb_queue_empty(&tp->rx_queue)) + dev_kfree_skb(__skb_dequeue(&tp->rx_queue)); + + return 0; +} + +static int rtl_enable(struct r8152 *tp) +{ + u32 ocp_data; + + r8152b_reset_packet_filter(tp); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR); + ocp_data |= CR_RE | CR_TE; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, ocp_data); + + rxdy_gated_en(tp, false); + + return 0; +} + +static int rtl8152_enable(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + set_tx_qlen(tp); + rtl_set_eee_plus(tp); + + return rtl_enable(tp); +} + +static void r8153_set_rx_early_timeout(struct r8152 *tp) +{ + u32 ocp_data = tp->coalesce / 8; + + ocp_write_word(tp, MCU_TYPE_USB, USB_RX_EARLY_TIMEOUT, ocp_data); +} + +static void r8153_set_rx_early_size(struct r8152 *tp) +{ + u32 mtu = tp->netdev->mtu; + u32 ocp_data = (agg_buf_sz - mtu - VLAN_ETH_HLEN - VLAN_HLEN) / 4; + + ocp_write_word(tp, MCU_TYPE_USB, USB_RX_EARLY_SIZE, ocp_data); +} + +static int rtl8153_enable(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + set_tx_qlen(tp); + rtl_set_eee_plus(tp); + r8153_set_rx_early_timeout(tp); + r8153_set_rx_early_size(tp); + + return rtl_enable(tp); +} + +static void rtl_disable(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) { + rtl_drop_queued_tx(tp); + return; + } + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + + rtl_drop_queued_tx(tp); + + for (i = 0; i < RTL8152_MAX_TX; i++) + usb_kill_urb(tp->tx_info[i].urb); + + rxdy_gated_en(tp, true); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if ((ocp_data & FIFO_EMPTY) == FIFO_EMPTY) + break; + usleep_range(1000, 2000); + } + + for (i = 0; i < 1000; i++) { + if (ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR0) & TCR0_TX_EMPTY) + break; + usleep_range(1000, 2000); + } + + rtl_stop_rx(tp); + + rtl8152_nic_reset(tp); +} + +static void r8152_power_cut_en(struct r8152 *tp, bool enable) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_UPS_CTRL); + if (enable) + ocp_data |= POWER_CUT; + else + ocp_data &= ~POWER_CUT; + ocp_write_word(tp, MCU_TYPE_USB, USB_UPS_CTRL, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_PM_CTRL_STATUS); + ocp_data &= ~RESUME_INDICATE; + ocp_write_word(tp, MCU_TYPE_USB, USB_PM_CTRL_STATUS, ocp_data); +} + +static void rtl_rx_vlan_en(struct r8152 *tp, bool enable) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CPCR); + if (enable) + ocp_data |= CPCR_RX_VLAN; + else + ocp_data &= ~CPCR_RX_VLAN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_CPCR, ocp_data); +} + +static int rtl8152_set_features(struct net_device *dev, + netdev_features_t features) +{ + netdev_features_t changed = features ^ dev->features; + struct r8152 *tp = netdev_priv(dev); + int ret; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out; + + mutex_lock(&tp->control); + + if (changed & NETIF_F_HW_VLAN_CTAG_RX) { + if (features & NETIF_F_HW_VLAN_CTAG_RX) + rtl_rx_vlan_en(tp, true); + else + rtl_rx_vlan_en(tp, false); + } + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return ret; +} + +#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST) + +static u32 __rtl_get_wol(struct r8152 *tp) +{ + u32 ocp_data; + u32 wolopts = 0; + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CONFIG5); + if (!(ocp_data & LAN_WAKE_EN)) + return 0; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG34); + if (ocp_data & LINK_ON_WAKE_EN) + wolopts |= WAKE_PHY; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG5); + if (ocp_data & UWF_EN) + wolopts |= WAKE_UCAST; + if (ocp_data & BWF_EN) + wolopts |= WAKE_BCAST; + if (ocp_data & MWF_EN) + wolopts |= WAKE_MCAST; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CFG_WOL); + if (ocp_data & MAGIC_EN) + wolopts |= WAKE_MAGIC; + + return wolopts; +} + +static void __rtl_set_wol(struct r8152 *tp, u32 wolopts) +{ + u32 ocp_data; + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_CONFIG); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG34); + ocp_data &= ~LINK_ON_WAKE_EN; + if (wolopts & WAKE_PHY) + ocp_data |= LINK_ON_WAKE_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_CONFIG34, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG5); + ocp_data &= ~(UWF_EN | BWF_EN | MWF_EN | LAN_WAKE_EN); + if (wolopts & WAKE_UCAST) + ocp_data |= UWF_EN; + if (wolopts & WAKE_BCAST) + ocp_data |= BWF_EN; + if (wolopts & WAKE_MCAST) + ocp_data |= MWF_EN; + if (wolopts & WAKE_ANY) + ocp_data |= LAN_WAKE_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_CONFIG5, ocp_data); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CFG_WOL); + ocp_data &= ~MAGIC_EN; + if (wolopts & WAKE_MAGIC) + ocp_data |= MAGIC_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_CFG_WOL, ocp_data); + + if (wolopts & WAKE_ANY) + device_set_wakeup_enable(&tp->udev->dev, true); + else + device_set_wakeup_enable(&tp->udev->dev, false); +} + +static void rtl_runtime_suspend_enable(struct r8152 *tp, bool enable) +{ + if (enable) { + u32 ocp_data; + + __rtl_set_wol(tp, WAKE_ANY); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_CONFIG); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG34); + ocp_data |= LINK_OFF_WAKE_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_CONFIG34, ocp_data); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML); + } else { + __rtl_set_wol(tp, tp->saved_wolopts); + } +} + +static void rtl_phy_reset(struct r8152 *tp) +{ + u16 data; + int i; + + clear_bit(PHY_RESET, &tp->flags); + + data = r8152_mdio_read(tp, MII_BMCR); + + /* don't reset again before the previous one complete */ + if (data & BMCR_RESET) + return; + + data |= BMCR_RESET; + r8152_mdio_write(tp, MII_BMCR, data); + + for (i = 0; i < 50; i++) { + msleep(20); + if ((r8152_mdio_read(tp, MII_BMCR) & BMCR_RESET) == 0) + break; + } +} + +static void r8153_teredo_off(struct r8152 *tp) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG); + ocp_data &= ~(TEREDO_SEL | TEREDO_RS_EVENT_MASK | OOB_TEREDO_EN); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG, ocp_data); + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_WDT6_CTRL, WDT6_SET_MODE); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_REALWOW_TIMER, 0); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_TEREDO_TIMER, 0); +} + +static void r8152b_disable_aldps(struct r8152 *tp) +{ + ocp_reg_write(tp, OCP_ALDPS_CONFIG, ENPDNPS | LINKENA | DIS_SDSAVE); + msleep(20); +} + +static inline void r8152b_enable_aldps(struct r8152 *tp) +{ + ocp_reg_write(tp, OCP_ALDPS_CONFIG, ENPWRSAVE | ENPDNPS | + LINKENA | DIS_SDSAVE); +} + +static void rtl8152_disable(struct r8152 *tp) +{ + r8152b_disable_aldps(tp); + rtl_disable(tp); + r8152b_enable_aldps(tp); +} + +static void r8152b_hw_phy_cfg(struct r8152 *tp) +{ + u16 data; + + data = r8152_mdio_read(tp, MII_BMCR); + if (data & BMCR_PDOWN) { + data &= ~BMCR_PDOWN; + r8152_mdio_write(tp, MII_BMCR, data); + } + + set_bit(PHY_RESET, &tp->flags); +} + +static void r8152b_exit_oob(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + + rxdy_gated_en(tp, true); + r8153_teredo_off(tp); + r8152b_hw_phy_cfg(tp); + + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML); + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, 0x00); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data &= ~NOW_IS_OOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data &= ~MCU_BORW_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data |= RE_INIT_LL; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + rtl8152_nic_reset(tp); + + /* rx share fifo credit full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL0, RXFIFO_THR1_NORMAL); + + if (tp->udev->speed == USB_SPEED_FULL || + tp->udev->speed == USB_SPEED_LOW) { + /* rx share fifo credit near full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1, + RXFIFO_THR2_FULL); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2, + RXFIFO_THR3_FULL); + } else { + /* rx share fifo credit near full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1, + RXFIFO_THR2_HIGH); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2, + RXFIFO_THR3_HIGH); + } + + /* TX share fifo free credit full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_TXFIFO_CTRL, TXFIFO_THR_NORMAL); + + ocp_write_byte(tp, MCU_TYPE_USB, USB_TX_AGG, TX_AGG_MAX_THRESHOLD); + ocp_write_dword(tp, MCU_TYPE_USB, USB_RX_BUF_TH, RX_THR_HIGH); + ocp_write_dword(tp, MCU_TYPE_USB, USB_TX_DMA, + TEST_MODE_DISABLE | TX_SIZE_ADJUST1); + + rtl_rx_vlan_en(tp, tp->netdev->features & NETIF_F_HW_VLAN_CTAG_RX); + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RMS, RTL8152_RMS); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR0); + ocp_data |= TCR0_AUTO_FIFO; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_TCR0, ocp_data); +} + +static void r8152b_enter_oob(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data &= ~NOW_IS_OOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL0, RXFIFO_THR1_OOB); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1, RXFIFO_THR2_OOB); + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2, RXFIFO_THR3_OOB); + + rtl_disable(tp); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data |= RE_INIT_LL; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RMS, RTL8152_RMS); + + rtl_rx_vlan_en(tp, true); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PAL_BDC_CR); + ocp_data |= ALDPS_PROXY_MODE; + ocp_write_word(tp, MCU_TYPE_PLA, PAL_BDC_CR, ocp_data); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data |= NOW_IS_OOB | DIS_MCU_CLROOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + rxdy_gated_en(tp, false); + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data |= RCR_APM | RCR_AM | RCR_AB; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); +} + +static void r8153_hw_phy_cfg(struct r8152 *tp) +{ + u32 ocp_data; + u16 data; + + ocp_reg_write(tp, OCP_ADC_CFG, CKADSEL_L | ADC_EN | EN_EMI_L); + data = r8152_mdio_read(tp, MII_BMCR); + if (data & BMCR_PDOWN) { + data &= ~BMCR_PDOWN; + r8152_mdio_write(tp, MII_BMCR, data); + } + + if (tp->version == RTL_VER_03) { + data = ocp_reg_read(tp, OCP_EEE_CFG); + data &= ~CTAP_SHORT_EN; + ocp_reg_write(tp, OCP_EEE_CFG, data); + } + + data = ocp_reg_read(tp, OCP_POWER_CFG); + data |= EEE_CLKDIV_EN; + ocp_reg_write(tp, OCP_POWER_CFG, data); + + data = ocp_reg_read(tp, OCP_DOWN_SPEED); + data |= EN_10M_BGOFF; + ocp_reg_write(tp, OCP_DOWN_SPEED, data); + data = ocp_reg_read(tp, OCP_POWER_CFG); + data |= EN_10M_PLLOFF; + ocp_reg_write(tp, OCP_POWER_CFG, data); + sram_write(tp, SRAM_IMPEDANCE, 0x0b13); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_PHY_PWR); + ocp_data |= PFM_PWM_SWITCH; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_PHY_PWR, ocp_data); + + /* Enable LPF corner auto tune */ + sram_write(tp, SRAM_LPF_CFG, 0xf70f); + + /* Adjust 10M Amplitude */ + sram_write(tp, SRAM_10M_AMP1, 0x00af); + sram_write(tp, SRAM_10M_AMP2, 0x0208); + + set_bit(PHY_RESET, &tp->flags); +} + +static void r8153_u1u2en(struct r8152 *tp, bool enable) +{ + u8 u1u2[8]; + + if (enable) + memset(u1u2, 0xff, sizeof(u1u2)); + else + memset(u1u2, 0x00, sizeof(u1u2)); + + usb_ocp_write(tp, USB_TOLERANCE, BYTE_EN_SIX_BYTES, sizeof(u1u2), u1u2); +} + +static void r8153_u2p3en(struct r8152 *tp, bool enable) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_U2P3_CTRL); + if (enable) + ocp_data |= U2P3_ENABLE; + else + ocp_data &= ~U2P3_ENABLE; + ocp_write_word(tp, MCU_TYPE_USB, USB_U2P3_CTRL, ocp_data); +} + +static void r8153_power_cut_en(struct r8152 *tp, bool enable) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_POWER_CUT); + if (enable) + ocp_data |= PWR_EN | PHASE2_EN; + else + ocp_data &= ~(PWR_EN | PHASE2_EN); + ocp_write_word(tp, MCU_TYPE_USB, USB_POWER_CUT, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_MISC_0); + ocp_data &= ~PCUT_STATUS; + ocp_write_word(tp, MCU_TYPE_USB, USB_MISC_0, ocp_data); +} + +static void r8153_first_init(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + rxdy_gated_en(tp, true); + r8153_teredo_off(tp); + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data &= ~RCR_ACPT_ALL; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); + + r8153_hw_phy_cfg(tp); + + rtl8152_nic_reset(tp); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data &= ~NOW_IS_OOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data &= ~MCU_BORW_EN; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data |= RE_INIT_LL; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + rtl_rx_vlan_en(tp, tp->netdev->features & NETIF_F_HW_VLAN_CTAG_RX); + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RMS, RTL8153_RMS); + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_MTPS, MTPS_JUMBO); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR0); + ocp_data |= TCR0_AUTO_FIFO; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_TCR0, ocp_data); + + rtl8152_nic_reset(tp); + + /* rx share fifo credit full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL0, RXFIFO_THR1_NORMAL); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1, RXFIFO_THR2_NORMAL); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2, RXFIFO_THR3_NORMAL); + /* TX share fifo free credit full threshold */ + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_TXFIFO_CTRL, TXFIFO_THR_NORMAL2); + + /* rx aggregation */ + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_USB_CTRL); + ocp_data &= ~RX_AGG_DISABLE; + ocp_write_word(tp, MCU_TYPE_USB, USB_USB_CTRL, ocp_data); +} + +static void r8153_enter_oob(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data &= ~NOW_IS_OOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + rtl_disable(tp); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7); + ocp_data |= RE_INIT_LL; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data); + + for (i = 0; i < 1000; i++) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + if (ocp_data & LINK_LIST_READY) + break; + usleep_range(1000, 2000); + } + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RMS, RTL8153_RMS); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG); + ocp_data &= ~TEREDO_WAKE_MASK; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG, ocp_data); + + rtl_rx_vlan_en(tp, true); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PAL_BDC_CR); + ocp_data |= ALDPS_PROXY_MODE; + ocp_write_word(tp, MCU_TYPE_PLA, PAL_BDC_CR, ocp_data); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL); + ocp_data |= NOW_IS_OOB | DIS_MCU_CLROOB; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data); + + rxdy_gated_en(tp, false); + + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR); + ocp_data |= RCR_APM | RCR_AM | RCR_AB; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data); +} + +static void r8153_disable_aldps(struct r8152 *tp) +{ + u16 data; + + data = ocp_reg_read(tp, OCP_POWER_CFG); + data &= ~EN_ALDPS; + ocp_reg_write(tp, OCP_POWER_CFG, data); + msleep(20); +} + +static void r8153_enable_aldps(struct r8152 *tp) +{ + u16 data; + + data = ocp_reg_read(tp, OCP_POWER_CFG); + data |= EN_ALDPS; + ocp_reg_write(tp, OCP_POWER_CFG, data); +} + +static void rtl8153_disable(struct r8152 *tp) +{ + r8153_disable_aldps(tp); + rtl_disable(tp); + r8153_enable_aldps(tp); +} + +static int rtl8152_set_speed(struct r8152 *tp, u8 autoneg, u16 speed, u8 duplex) +{ + u16 bmcr, anar, gbcr; + int ret = 0; + + cancel_delayed_work_sync(&tp->schedule); + anar = r8152_mdio_read(tp, MII_ADVERTISE); + anar &= ~(ADVERTISE_10HALF | ADVERTISE_10FULL | + ADVERTISE_100HALF | ADVERTISE_100FULL); + if (tp->mii.supports_gmii) { + gbcr = r8152_mdio_read(tp, MII_CTRL1000); + gbcr &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF); + } else { + gbcr = 0; + } + + if (autoneg == AUTONEG_DISABLE) { + if (speed == SPEED_10) { + bmcr = 0; + anar |= ADVERTISE_10HALF | ADVERTISE_10FULL; + } else if (speed == SPEED_100) { + bmcr = BMCR_SPEED100; + anar |= ADVERTISE_100HALF | ADVERTISE_100FULL; + } else if (speed == SPEED_1000 && tp->mii.supports_gmii) { + bmcr = BMCR_SPEED1000; + gbcr |= ADVERTISE_1000FULL | ADVERTISE_1000HALF; + } else { + ret = -EINVAL; + goto out; + } + + if (duplex == DUPLEX_FULL) + bmcr |= BMCR_FULLDPLX; + } else { + if (speed == SPEED_10) { + if (duplex == DUPLEX_FULL) + anar |= ADVERTISE_10HALF | ADVERTISE_10FULL; + else + anar |= ADVERTISE_10HALF; + } else if (speed == SPEED_100) { + if (duplex == DUPLEX_FULL) { + anar |= ADVERTISE_10HALF | ADVERTISE_10FULL; + anar |= ADVERTISE_100HALF | ADVERTISE_100FULL; + } else { + anar |= ADVERTISE_10HALF; + anar |= ADVERTISE_100HALF; + } + } else if (speed == SPEED_1000 && tp->mii.supports_gmii) { + if (duplex == DUPLEX_FULL) { + anar |= ADVERTISE_10HALF | ADVERTISE_10FULL; + anar |= ADVERTISE_100HALF | ADVERTISE_100FULL; + gbcr |= ADVERTISE_1000FULL | ADVERTISE_1000HALF; + } else { + anar |= ADVERTISE_10HALF; + anar |= ADVERTISE_100HALF; + gbcr |= ADVERTISE_1000HALF; + } + } else { + ret = -EINVAL; + goto out; + } + + bmcr = BMCR_ANENABLE | BMCR_ANRESTART; + } + + if (test_bit(PHY_RESET, &tp->flags)) + bmcr |= BMCR_RESET; + + if (tp->mii.supports_gmii) + r8152_mdio_write(tp, MII_CTRL1000, gbcr); + + r8152_mdio_write(tp, MII_ADVERTISE, anar); + r8152_mdio_write(tp, MII_BMCR, bmcr); + + if (test_bit(PHY_RESET, &tp->flags)) { + int i; + + clear_bit(PHY_RESET, &tp->flags); + for (i = 0; i < 50; i++) { + msleep(20); + if ((r8152_mdio_read(tp, MII_BMCR) & BMCR_RESET) == 0) + break; + } + } + +out: + + return ret; +} + +static void rtl8152_up(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + r8152b_disable_aldps(tp); + r8152b_exit_oob(tp); + r8152b_enable_aldps(tp); +} + +static void rtl8152_down(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) { + rtl_drop_queued_tx(tp); + return; + } + + r8152_power_cut_en(tp, false); + r8152b_disable_aldps(tp); + r8152b_enter_oob(tp); + r8152b_enable_aldps(tp); +} + +static void rtl8153_up(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + r8153_disable_aldps(tp); + r8153_first_init(tp); + r8153_enable_aldps(tp); +} + +static void rtl8153_down(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) { + rtl_drop_queued_tx(tp); + return; + } + + r8153_u1u2en(tp, false); + r8153_power_cut_en(tp, false); + r8153_disable_aldps(tp); + r8153_enter_oob(tp); + r8153_enable_aldps(tp); +} + +static void set_carrier(struct r8152 *tp) +{ + struct net_device *netdev = tp->netdev; + u8 speed; + + clear_bit(RTL8152_LINK_CHG, &tp->flags); + speed = rtl8152_get_speed(tp); + + if (speed & LINK_STATUS) { + if (!netif_carrier_ok(netdev)) { + tp->rtl_ops.enable(tp); + set_bit(RTL8152_SET_RX_MODE, &tp->flags); + netif_carrier_on(netdev); + rtl_start_rx(tp); + } + } else { + if (netif_carrier_ok(netdev)) { + netif_carrier_off(netdev); + napi_disable(&tp->napi); + tp->rtl_ops.disable(tp); + napi_enable(&tp->napi); + } + } +} + +static void rtl_work_func_t(struct work_struct *work) +{ + struct r8152 *tp = container_of(work, struct r8152, schedule.work); + + /* If the device is unplugged or !netif_running(), the workqueue + * doesn't need to wake the device, and could return directly. + */ + if (test_bit(RTL8152_UNPLUG, &tp->flags) || !netif_running(tp->netdev)) + return; + + if (usb_autopm_get_interface(tp->intf) < 0) + return; + + if (!test_bit(WORK_ENABLE, &tp->flags)) + goto out1; + + if (!mutex_trylock(&tp->control)) { + schedule_delayed_work(&tp->schedule, 0); + goto out1; + } + + if (test_bit(RTL8152_LINK_CHG, &tp->flags)) + set_carrier(tp); + + if (test_bit(RTL8152_SET_RX_MODE, &tp->flags)) + _rtl8152_set_rx_mode(tp->netdev); + + /* don't schedule napi before linking */ + if (test_bit(SCHEDULE_NAPI, &tp->flags) && + netif_carrier_ok(tp->netdev)) { + clear_bit(SCHEDULE_NAPI, &tp->flags); + napi_schedule(&tp->napi); + } + + if (test_bit(PHY_RESET, &tp->flags)) + rtl_phy_reset(tp); + + mutex_unlock(&tp->control); + +out1: + usb_autopm_put_interface(tp->intf); +} + +static int rtl8152_open(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + int res = 0; + + res = alloc_all_mem(tp); + if (res) + goto out; + + netif_carrier_off(netdev); + + res = usb_autopm_get_interface(tp->intf); + if (res < 0) { + free_all_mem(tp); + goto out; + } + + mutex_lock(&tp->control); + + /* The WORK_ENABLE may be set when autoresume occurs */ + if (test_bit(WORK_ENABLE, &tp->flags)) { + clear_bit(WORK_ENABLE, &tp->flags); + usb_kill_urb(tp->intr_urb); + cancel_delayed_work_sync(&tp->schedule); + + /* disable the tx/rx, if the workqueue has enabled them. */ + if (netif_carrier_ok(netdev)) + tp->rtl_ops.disable(tp); + } + + tp->rtl_ops.up(tp); + + rtl8152_set_speed(tp, AUTONEG_ENABLE, + tp->mii.supports_gmii ? SPEED_1000 : SPEED_100, + DUPLEX_FULL); + netif_carrier_off(netdev); + netif_start_queue(netdev); + set_bit(WORK_ENABLE, &tp->flags); + + res = usb_submit_urb(tp->intr_urb, GFP_KERNEL); + if (res) { + if (res == -ENODEV) + netif_device_detach(tp->netdev); + netif_warn(tp, ifup, netdev, "intr_urb submit failed: %d\n", + res); + free_all_mem(tp); + } else { + napi_enable(&tp->napi); + } + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return res; +} + +static int rtl8152_close(struct net_device *netdev) +{ + struct r8152 *tp = netdev_priv(netdev); + int res = 0; + + napi_disable(&tp->napi); + clear_bit(WORK_ENABLE, &tp->flags); + usb_kill_urb(tp->intr_urb); + cancel_delayed_work_sync(&tp->schedule); + netif_stop_queue(netdev); + + res = usb_autopm_get_interface(tp->intf); + if (res < 0 || test_bit(RTL8152_UNPLUG, &tp->flags)) { + rtl_drop_queued_tx(tp); + rtl_stop_rx(tp); + } else { + mutex_lock(&tp->control); + + /* The autosuspend may have been enabled and wouldn't + * be disable when autoresume occurs, because the + * netif_running() would be false. + */ + rtl_runtime_suspend_enable(tp, false); + + tp->rtl_ops.down(tp); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + } + + free_all_mem(tp); + + return res; +} + +static inline void r8152_mmd_indirect(struct r8152 *tp, u16 dev, u16 reg) +{ + ocp_reg_write(tp, OCP_EEE_AR, FUN_ADDR | dev); + ocp_reg_write(tp, OCP_EEE_DATA, reg); + ocp_reg_write(tp, OCP_EEE_AR, FUN_DATA | dev); +} + +static u16 r8152_mmd_read(struct r8152 *tp, u16 dev, u16 reg) +{ + u16 data; + + r8152_mmd_indirect(tp, dev, reg); + data = ocp_reg_read(tp, OCP_EEE_DATA); + ocp_reg_write(tp, OCP_EEE_AR, 0x0000); + + return data; +} + +static void r8152_mmd_write(struct r8152 *tp, u16 dev, u16 reg, u16 data) +{ + r8152_mmd_indirect(tp, dev, reg); + ocp_reg_write(tp, OCP_EEE_DATA, data); + ocp_reg_write(tp, OCP_EEE_AR, 0x0000); +} + +static void r8152_eee_en(struct r8152 *tp, bool enable) +{ + u16 config1, config2, config3; + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEE_CR); + config1 = ocp_reg_read(tp, OCP_EEE_CONFIG1) & ~sd_rise_time_mask; + config2 = ocp_reg_read(tp, OCP_EEE_CONFIG2); + config3 = ocp_reg_read(tp, OCP_EEE_CONFIG3) & ~fast_snr_mask; + + if (enable) { + ocp_data |= EEE_RX_EN | EEE_TX_EN; + config1 |= EEE_10_CAP | EEE_NWAY_EN | TX_QUIET_EN | RX_QUIET_EN; + config1 |= sd_rise_time(1); + config2 |= RG_DACQUIET_EN | RG_LDVQUIET_EN; + config3 |= fast_snr(42); + } else { + ocp_data &= ~(EEE_RX_EN | EEE_TX_EN); + config1 &= ~(EEE_10_CAP | EEE_NWAY_EN | TX_QUIET_EN | + RX_QUIET_EN); + config1 |= sd_rise_time(7); + config2 &= ~(RG_DACQUIET_EN | RG_LDVQUIET_EN); + config3 |= fast_snr(511); + } + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEE_CR, ocp_data); + ocp_reg_write(tp, OCP_EEE_CONFIG1, config1); + ocp_reg_write(tp, OCP_EEE_CONFIG2, config2); + ocp_reg_write(tp, OCP_EEE_CONFIG3, config3); +} + +static void r8152b_enable_eee(struct r8152 *tp) +{ + r8152_eee_en(tp, true); + r8152_mmd_write(tp, MDIO_MMD_AN, MDIO_AN_EEE_ADV, MDIO_EEE_100TX); +} + +static void r8153_eee_en(struct r8152 *tp, bool enable) +{ + u32 ocp_data; + u16 config; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEE_CR); + config = ocp_reg_read(tp, OCP_EEE_CFG); + + if (enable) { + ocp_data |= EEE_RX_EN | EEE_TX_EN; + config |= EEE10_EN; + } else { + ocp_data &= ~(EEE_RX_EN | EEE_TX_EN); + config &= ~EEE10_EN; + } + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEE_CR, ocp_data); + ocp_reg_write(tp, OCP_EEE_CFG, config); +} + +static void r8153_enable_eee(struct r8152 *tp) +{ + r8153_eee_en(tp, true); + ocp_reg_write(tp, OCP_EEE_ADV, MDIO_EEE_1000T | MDIO_EEE_100TX); +} + +static void r8152b_enable_fc(struct r8152 *tp) +{ + u16 anar; + + anar = r8152_mdio_read(tp, MII_ADVERTISE); + anar |= ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM; + r8152_mdio_write(tp, MII_ADVERTISE, anar); +} + +static void rtl_tally_reset(struct r8152 *tp) +{ + u32 ocp_data; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_RSTTALLY); + ocp_data |= TALLY_RESET; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_RSTTALLY, ocp_data); +} + +static void r8152b_init(struct r8152 *tp) +{ + u32 ocp_data; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + r8152b_disable_aldps(tp); + + if (tp->version == RTL_VER_01) { + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_LED_FEATURE); + ocp_data &= ~LED_MODE_MASK; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_LED_FEATURE, ocp_data); + } + + r8152_power_cut_en(tp, false); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_PHY_PWR); + ocp_data |= TX_10M_IDLE_EN | PFM_PWM_SWITCH; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_PHY_PWR, ocp_data); + ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL); + ocp_data &= ~MCU_CLK_RATIO_MASK; + ocp_data |= MCU_CLK_RATIO | D3_CLK_GATED_EN; + ocp_write_dword(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL, ocp_data); + ocp_data = GPHY_STS_MSK | SPEED_DOWN_MSK | + SPDWN_RXDV_MSK | SPDWN_LINKCHG_MSK; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_GPHY_INTR_IMR, ocp_data); + + r8152b_enable_eee(tp); + r8152b_enable_aldps(tp); + r8152b_enable_fc(tp); + rtl_tally_reset(tp); + + /* enable rx aggregation */ + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_USB_CTRL); + ocp_data &= ~RX_AGG_DISABLE; + ocp_write_word(tp, MCU_TYPE_USB, USB_USB_CTRL, ocp_data); +} + +static void r8153_init(struct r8152 *tp) +{ + u32 ocp_data; + int i; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + r8153_disable_aldps(tp); + r8153_u1u2en(tp, false); + + for (i = 0; i < 500; i++) { + if (ocp_read_word(tp, MCU_TYPE_PLA, PLA_BOOT_CTRL) & + AUTOLOAD_DONE) + break; + msleep(20); + } + + for (i = 0; i < 500; i++) { + ocp_data = ocp_reg_read(tp, OCP_PHY_STATUS) & PHY_STAT_MASK; + if (ocp_data == PHY_STAT_LAN_ON || ocp_data == PHY_STAT_PWRDN) + break; + msleep(20); + } + + r8153_u2p3en(tp, false); + + if (tp->version == RTL_VER_04) { + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_SSPHYLINK2); + ocp_data &= ~pwd_dn_scale_mask; + ocp_data |= pwd_dn_scale(96); + ocp_write_word(tp, MCU_TYPE_USB, USB_SSPHYLINK2, ocp_data); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_USB, USB_USB2PHY); + ocp_data |= USB2PHY_L1 | USB2PHY_SUSPEND; + ocp_write_byte(tp, MCU_TYPE_USB, USB_USB2PHY, ocp_data); + } else if (tp->version == RTL_VER_05) { + ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_DMY_REG0); + ocp_data &= ~ECM_ALDPS; + ocp_write_byte(tp, MCU_TYPE_PLA, PLA_DMY_REG0, ocp_data); + + ocp_data = ocp_read_byte(tp, MCU_TYPE_USB, USB_CSR_DUMMY1); + if (ocp_read_word(tp, MCU_TYPE_USB, USB_BURST_SIZE) == 0) + ocp_data &= ~DYNAMIC_BURST; + else + ocp_data |= DYNAMIC_BURST; + ocp_write_byte(tp, MCU_TYPE_USB, USB_CSR_DUMMY1, ocp_data); + } + + ocp_data = ocp_read_byte(tp, MCU_TYPE_USB, USB_CSR_DUMMY2); + ocp_data |= EP4_FULL_FC; + ocp_write_byte(tp, MCU_TYPE_USB, USB_CSR_DUMMY2, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_WDT11_CTRL); + ocp_data &= ~TIMER11_EN; + ocp_write_word(tp, MCU_TYPE_USB, USB_WDT11_CTRL, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_LED_FEATURE); + ocp_data &= ~LED_MODE_MASK; + ocp_write_word(tp, MCU_TYPE_PLA, PLA_LED_FEATURE, ocp_data); + + ocp_data = FIFO_EMPTY_1FB | ROK_EXIT_LPM; + if (tp->version == RTL_VER_04 && tp->udev->speed != USB_SPEED_SUPER) + ocp_data |= LPM_TIMER_500MS; + else + ocp_data |= LPM_TIMER_500US; + ocp_write_byte(tp, MCU_TYPE_USB, USB_LPM_CTRL, ocp_data); + + ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_AFE_CTRL2); + ocp_data &= ~SEN_VAL_MASK; + ocp_data |= SEN_VAL_NORMAL | SEL_RXIDLE; + ocp_write_word(tp, MCU_TYPE_USB, USB_AFE_CTRL2, ocp_data); + + ocp_write_word(tp, MCU_TYPE_USB, USB_CONNECT_TIMER, 0x0001); + + r8153_power_cut_en(tp, false); + r8153_u1u2en(tp, true); + + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL, ALDPS_SPDWN_RATIO); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL2, EEE_SPDWN_RATIO); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL3, + PKT_AVAIL_SPDWN_EN | SUSPEND_SPDWN_EN | + U1U2_SPDWN_EN | L1_SPDWN_EN); + ocp_write_word(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL4, + PWRSAVE_SPDWN_EN | RXDV_SPDWN_EN | TX10MIDLE_EN | + TP100_SPDWN_EN | TP500_SPDWN_EN | TP1000_SPDWN_EN | + EEE_SPDWN_EN); + + r8153_enable_eee(tp); + r8153_enable_aldps(tp); + r8152b_enable_fc(tp); + rtl_tally_reset(tp); +} + +static int rtl8152_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct r8152 *tp = usb_get_intfdata(intf); + struct net_device *netdev = tp->netdev; + int ret = 0; + + mutex_lock(&tp->control); + + if (PMSG_IS_AUTO(message)) { + if (netif_running(netdev) && work_busy(&tp->schedule.work)) { + ret = -EBUSY; + goto out1; + } + + set_bit(SELECTIVE_SUSPEND, &tp->flags); + } else { + netif_device_detach(netdev); + } + + if (netif_running(netdev) && test_bit(WORK_ENABLE, &tp->flags)) { + clear_bit(WORK_ENABLE, &tp->flags); + usb_kill_urb(tp->intr_urb); + napi_disable(&tp->napi); + if (test_bit(SELECTIVE_SUSPEND, &tp->flags)) { + rtl_stop_rx(tp); + rtl_runtime_suspend_enable(tp, true); + } else { + cancel_delayed_work_sync(&tp->schedule); + tp->rtl_ops.down(tp); + } + napi_enable(&tp->napi); + } +out1: + mutex_unlock(&tp->control); + + return ret; +} + +static int rtl8152_resume(struct usb_interface *intf) +{ + struct r8152 *tp = usb_get_intfdata(intf); + + mutex_lock(&tp->control); + + if (!test_bit(SELECTIVE_SUSPEND, &tp->flags)) { + tp->rtl_ops.init(tp); + netif_device_attach(tp->netdev); + } + + if (netif_running(tp->netdev)) { + if (test_bit(SELECTIVE_SUSPEND, &tp->flags)) { + rtl_runtime_suspend_enable(tp, false); + clear_bit(SELECTIVE_SUSPEND, &tp->flags); + set_bit(WORK_ENABLE, &tp->flags); + if (netif_carrier_ok(tp->netdev)) + rtl_start_rx(tp); + } else { + tp->rtl_ops.up(tp); + rtl8152_set_speed(tp, AUTONEG_ENABLE, + tp->mii.supports_gmii ? + SPEED_1000 : SPEED_100, + DUPLEX_FULL); + netif_carrier_off(tp->netdev); + set_bit(WORK_ENABLE, &tp->flags); + } + usb_submit_urb(tp->intr_urb, GFP_KERNEL); + } else if (test_bit(SELECTIVE_SUSPEND, &tp->flags)) { + clear_bit(SELECTIVE_SUSPEND, &tp->flags); + } + + mutex_unlock(&tp->control); + + return 0; +} + +static void rtl8152_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + struct r8152 *tp = netdev_priv(dev); + + if (usb_autopm_get_interface(tp->intf) < 0) + return; + + mutex_lock(&tp->control); + + wol->supported = WAKE_ANY; + wol->wolopts = __rtl_get_wol(tp); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); +} + +static int rtl8152_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + struct r8152 *tp = netdev_priv(dev); + int ret; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out_set_wol; + + mutex_lock(&tp->control); + + __rtl_set_wol(tp, wol->wolopts); + tp->saved_wolopts = wol->wolopts & WAKE_ANY; + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out_set_wol: + return ret; +} + +static u32 rtl8152_get_msglevel(struct net_device *dev) +{ + struct r8152 *tp = netdev_priv(dev); + + return tp->msg_enable; +} + +static void rtl8152_set_msglevel(struct net_device *dev, u32 value) +{ + struct r8152 *tp = netdev_priv(dev); + + tp->msg_enable = value; +} + +static void rtl8152_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + struct r8152 *tp = netdev_priv(netdev); + + strlcpy(info->driver, MODULENAME, sizeof(info->driver)); + strlcpy(info->version, DRIVER_VERSION, sizeof(info->version)); + usb_make_path(tp->udev, info->bus_info, sizeof(info->bus_info)); +} + +static +int rtl8152_get_settings(struct net_device *netdev, struct ethtool_cmd *cmd) +{ + struct r8152 *tp = netdev_priv(netdev); + int ret; + + if (!tp->mii.mdio_read) + return -EOPNOTSUPP; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out; + + mutex_lock(&tp->control); + + ret = mii_ethtool_gset(&tp->mii, cmd); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return ret; +} + +static int rtl8152_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct r8152 *tp = netdev_priv(dev); + int ret; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out; + + mutex_lock(&tp->control); + + ret = rtl8152_set_speed(tp, cmd->autoneg, cmd->speed, cmd->duplex); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return ret; +} + +static const char rtl8152_gstrings[][ETH_GSTRING_LEN] = { + "tx_packets", + "rx_packets", + "tx_errors", + "rx_errors", + "rx_missed", + "align_errors", + "tx_single_collisions", + "tx_multi_collisions", + "rx_unicast", + "rx_broadcast", + "rx_multicast", + "tx_aborted", + "tx_underrun", +}; + +static int rtl8152_get_sset_count(struct net_device *dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(rtl8152_gstrings); + default: + return -EOPNOTSUPP; + } +} + +static void rtl8152_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data) +{ + struct r8152 *tp = netdev_priv(dev); + struct tally_counter tally; + + if (usb_autopm_get_interface(tp->intf) < 0) + return; + + generic_ocp_read(tp, PLA_TALLYCNT, sizeof(tally), &tally, MCU_TYPE_PLA); + + usb_autopm_put_interface(tp->intf); + + data[0] = le64_to_cpu(tally.tx_packets); + data[1] = le64_to_cpu(tally.rx_packets); + data[2] = le64_to_cpu(tally.tx_errors); + data[3] = le32_to_cpu(tally.rx_errors); + data[4] = le16_to_cpu(tally.rx_missed); + data[5] = le16_to_cpu(tally.align_errors); + data[6] = le32_to_cpu(tally.tx_one_collision); + data[7] = le32_to_cpu(tally.tx_multi_collision); + data[8] = le64_to_cpu(tally.rx_unicast); + data[9] = le64_to_cpu(tally.rx_broadcast); + data[10] = le32_to_cpu(tally.rx_multicast); + data[11] = le16_to_cpu(tally.tx_aborted); + data[12] = le16_to_cpu(tally.tx_underrun); +} + +static void rtl8152_get_strings(struct net_device *dev, u32 stringset, u8 *data) +{ + switch (stringset) { + case ETH_SS_STATS: + memcpy(data, *rtl8152_gstrings, sizeof(rtl8152_gstrings)); + break; + } +} + +static int r8152_get_eee(struct r8152 *tp, struct ethtool_eee *eee) +{ + u32 ocp_data, lp, adv, supported = 0; + u16 val; + + val = r8152_mmd_read(tp, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE); + supported = mmd_eee_cap_to_ethtool_sup_t(val); + + val = r8152_mmd_read(tp, MDIO_MMD_AN, MDIO_AN_EEE_ADV); + adv = mmd_eee_adv_to_ethtool_adv_t(val); + + val = r8152_mmd_read(tp, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE); + lp = mmd_eee_adv_to_ethtool_adv_t(val); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEE_CR); + ocp_data &= EEE_RX_EN | EEE_TX_EN; + + eee->eee_enabled = !!ocp_data; + eee->eee_active = !!(supported & adv & lp); + eee->supported = supported; + eee->advertised = adv; + eee->lp_advertised = lp; + + return 0; +} + +static int r8152_set_eee(struct r8152 *tp, struct ethtool_eee *eee) +{ + u16 val = ethtool_adv_to_mmd_eee_adv_t(eee->advertised); + + r8152_eee_en(tp, eee->eee_enabled); + + if (!eee->eee_enabled) + val = 0; + + r8152_mmd_write(tp, MDIO_MMD_AN, MDIO_AN_EEE_ADV, val); + + return 0; +} + +static int r8153_get_eee(struct r8152 *tp, struct ethtool_eee *eee) +{ + u32 ocp_data, lp, adv, supported = 0; + u16 val; + + val = ocp_reg_read(tp, OCP_EEE_ABLE); + supported = mmd_eee_cap_to_ethtool_sup_t(val); + + val = ocp_reg_read(tp, OCP_EEE_ADV); + adv = mmd_eee_adv_to_ethtool_adv_t(val); + + val = ocp_reg_read(tp, OCP_EEE_LPABLE); + lp = mmd_eee_adv_to_ethtool_adv_t(val); + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEE_CR); + ocp_data &= EEE_RX_EN | EEE_TX_EN; + + eee->eee_enabled = !!ocp_data; + eee->eee_active = !!(supported & adv & lp); + eee->supported = supported; + eee->advertised = adv; + eee->lp_advertised = lp; + + return 0; +} + +static int r8153_set_eee(struct r8152 *tp, struct ethtool_eee *eee) +{ + u16 val = ethtool_adv_to_mmd_eee_adv_t(eee->advertised); + + r8153_eee_en(tp, eee->eee_enabled); + + if (!eee->eee_enabled) + val = 0; + + ocp_reg_write(tp, OCP_EEE_ADV, val); + + return 0; +} + +static int +rtl_ethtool_get_eee(struct net_device *net, struct ethtool_eee *edata) +{ + struct r8152 *tp = netdev_priv(net); + int ret; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out; + + mutex_lock(&tp->control); + + ret = tp->rtl_ops.eee_get(tp, edata); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return ret; +} + +static int +rtl_ethtool_set_eee(struct net_device *net, struct ethtool_eee *edata) +{ + struct r8152 *tp = netdev_priv(net); + int ret; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out; + + mutex_lock(&tp->control); + + ret = tp->rtl_ops.eee_set(tp, edata); + if (!ret) + ret = mii_nway_restart(&tp->mii); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return ret; +} + +static int rtl8152_nway_reset(struct net_device *dev) +{ + struct r8152 *tp = netdev_priv(dev); + int ret; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + goto out; + + mutex_lock(&tp->control); + + ret = mii_nway_restart(&tp->mii); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + +out: + return ret; +} + +static int rtl8152_get_coalesce(struct net_device *netdev, + struct ethtool_coalesce *coalesce) +{ + struct r8152 *tp = netdev_priv(netdev); + + switch (tp->version) { + case RTL_VER_01: + case RTL_VER_02: + return -EOPNOTSUPP; + default: + break; + } + + coalesce->rx_coalesce_usecs = tp->coalesce; + + return 0; +} + +static int rtl8152_set_coalesce(struct net_device *netdev, + struct ethtool_coalesce *coalesce) +{ + struct r8152 *tp = netdev_priv(netdev); + int ret; + + switch (tp->version) { + case RTL_VER_01: + case RTL_VER_02: + return -EOPNOTSUPP; + default: + break; + } + + if (coalesce->rx_coalesce_usecs > COALESCE_SLOW) + return -EINVAL; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + return ret; + + mutex_lock(&tp->control); + + if (tp->coalesce != coalesce->rx_coalesce_usecs) { + tp->coalesce = coalesce->rx_coalesce_usecs; + + if (netif_running(tp->netdev) && netif_carrier_ok(netdev)) + r8153_set_rx_early_timeout(tp); + } + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + + return ret; +} + +static struct ethtool_ops ops = { + .get_drvinfo = rtl8152_get_drvinfo, + .get_settings = rtl8152_get_settings, + .set_settings = rtl8152_set_settings, + .get_link = ethtool_op_get_link, + .nway_reset = rtl8152_nway_reset, + .get_msglevel = rtl8152_get_msglevel, + .set_msglevel = rtl8152_set_msglevel, + .get_wol = rtl8152_get_wol, + .set_wol = rtl8152_set_wol, + .get_strings = rtl8152_get_strings, + .get_sset_count = rtl8152_get_sset_count, + .get_ethtool_stats = rtl8152_get_ethtool_stats, + .get_coalesce = rtl8152_get_coalesce, + .set_coalesce = rtl8152_set_coalesce, + .get_eee = rtl_ethtool_get_eee, + .set_eee = rtl_ethtool_set_eee, +}; + +static int rtl8152_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) +{ + struct r8152 *tp = netdev_priv(netdev); + struct mii_ioctl_data *data = if_mii(rq); + int res; + + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return -ENODEV; + + res = usb_autopm_get_interface(tp->intf); + if (res < 0) + goto out; + + switch (cmd) { + case SIOCGMIIPHY: + data->phy_id = R8152_PHY_ID; /* Internal PHY */ + break; + + case SIOCGMIIREG: + mutex_lock(&tp->control); + data->val_out = r8152_mdio_read(tp, data->reg_num); + mutex_unlock(&tp->control); + break; + + case SIOCSMIIREG: + if (!capable(CAP_NET_ADMIN)) { + res = -EPERM; + break; + } + mutex_lock(&tp->control); + r8152_mdio_write(tp, data->reg_num, data->val_in); + mutex_unlock(&tp->control); + break; + + default: + res = -EOPNOTSUPP; + } + + usb_autopm_put_interface(tp->intf); + +out: + return res; +} + +static int rtl8152_change_mtu(struct net_device *dev, int new_mtu) +{ + struct r8152 *tp = netdev_priv(dev); + int ret; + + switch (tp->version) { + case RTL_VER_01: + case RTL_VER_02: + return eth_change_mtu(dev, new_mtu); + default: + break; + } + + if (new_mtu < 68 || new_mtu > RTL8153_MAX_MTU) + return -EINVAL; + + ret = usb_autopm_get_interface(tp->intf); + if (ret < 0) + return ret; + + mutex_lock(&tp->control); + + dev->mtu = new_mtu; + + if (netif_running(dev) && netif_carrier_ok(dev)) + r8153_set_rx_early_size(tp); + + mutex_unlock(&tp->control); + + usb_autopm_put_interface(tp->intf); + + return ret; +} + +static const struct net_device_ops rtl8152_netdev_ops = { + .ndo_open = rtl8152_open, + .ndo_stop = rtl8152_close, + .ndo_do_ioctl = rtl8152_ioctl, + .ndo_start_xmit = rtl8152_start_xmit, + .ndo_tx_timeout = rtl8152_tx_timeout, + .ndo_set_features = rtl8152_set_features, + .ndo_set_rx_mode = rtl8152_set_rx_mode, + .ndo_set_mac_address = rtl8152_set_mac_address, + .ndo_change_mtu = rtl8152_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_features_check = rtl8152_features_check, +}; + +static void r8152b_get_version(struct r8152 *tp) +{ + u32 ocp_data; + u16 version; + + ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR1); + version = (u16)(ocp_data & VERSION_MASK); + + switch (version) { + case 0x4c00: + tp->version = RTL_VER_01; + break; + case 0x4c10: + tp->version = RTL_VER_02; + break; + case 0x5c00: + tp->version = RTL_VER_03; + tp->mii.supports_gmii = 1; + break; + case 0x5c10: + tp->version = RTL_VER_04; + tp->mii.supports_gmii = 1; + break; + case 0x5c20: + tp->version = RTL_VER_05; + tp->mii.supports_gmii = 1; + break; + default: + netif_info(tp, probe, tp->netdev, + "Unknown version 0x%04x\n", version); + break; + } +} + +static void rtl8152_unload(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + if (tp->version != RTL_VER_01) + r8152_power_cut_en(tp, true); +} + +static void rtl8153_unload(struct r8152 *tp) +{ + if (test_bit(RTL8152_UNPLUG, &tp->flags)) + return; + + r8153_power_cut_en(tp, false); +} + +static int rtl_ops_init(struct r8152 *tp) +{ + struct rtl_ops *ops = &tp->rtl_ops; + int ret = 0; + + switch (tp->version) { + case RTL_VER_01: + case RTL_VER_02: + ops->init = r8152b_init; + ops->enable = rtl8152_enable; + ops->disable = rtl8152_disable; + ops->up = rtl8152_up; + ops->down = rtl8152_down; + ops->unload = rtl8152_unload; + ops->eee_get = r8152_get_eee; + ops->eee_set = r8152_set_eee; + break; + + case RTL_VER_03: + case RTL_VER_04: + case RTL_VER_05: + ops->init = r8153_init; + ops->enable = rtl8153_enable; + ops->disable = rtl8153_disable; + ops->up = rtl8153_up; + ops->down = rtl8153_down; + ops->unload = rtl8153_unload; + ops->eee_get = r8153_get_eee; + ops->eee_set = r8153_set_eee; + break; + + default: + ret = -ENODEV; + netif_err(tp, probe, tp->netdev, "Unknown Device\n"); + break; + } + + return ret; +} + +static int rtl8152_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct r8152 *tp; + struct net_device *netdev; + int ret; + + if (udev->actconfig->desc.bConfigurationValue != 1) { + usb_driver_set_configuration(udev, 1); + return -ENODEV; + } + + usb_reset_device(udev); + netdev = alloc_etherdev(sizeof(struct r8152)); + if (!netdev) { + dev_err(&intf->dev, "Out of memory\n"); + return -ENOMEM; + } + + SET_NETDEV_DEV(netdev, &intf->dev); + tp = netdev_priv(netdev); + tp->msg_enable = 0x7FFF; + + tp->udev = udev; + tp->netdev = netdev; + tp->intf = intf; + + r8152b_get_version(tp); + ret = rtl_ops_init(tp); + if (ret) + goto out; + + mutex_init(&tp->control); + INIT_DELAYED_WORK(&tp->schedule, rtl_work_func_t); + + netdev->netdev_ops = &rtl8152_netdev_ops; + netdev->watchdog_timeo = RTL8152_TX_TIMEOUT; + + netdev->features |= NETIF_F_RXCSUM | NETIF_F_IP_CSUM | NETIF_F_SG | + NETIF_F_TSO | NETIF_F_FRAGLIST | NETIF_F_IPV6_CSUM | + NETIF_F_TSO6 | NETIF_F_HW_VLAN_CTAG_RX | + NETIF_F_HW_VLAN_CTAG_TX; + netdev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM | NETIF_F_SG | + NETIF_F_TSO | NETIF_F_FRAGLIST | + NETIF_F_IPV6_CSUM | NETIF_F_TSO6 | + NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX; + netdev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO | + NETIF_F_HIGHDMA | NETIF_F_FRAGLIST | + NETIF_F_IPV6_CSUM | NETIF_F_TSO6; + + netdev->ethtool_ops = &ops; + netif_set_gso_max_size(netdev, RTL_LIMITED_TSO_SIZE); + + tp->mii.dev = netdev; + tp->mii.mdio_read = read_mii_word; + tp->mii.mdio_write = write_mii_word; + tp->mii.phy_id_mask = 0x3f; + tp->mii.reg_num_mask = 0x1f; + tp->mii.phy_id = R8152_PHY_ID; + + switch (udev->speed) { + case USB_SPEED_SUPER: + tp->coalesce = COALESCE_SUPER; + break; + case USB_SPEED_HIGH: + tp->coalesce = COALESCE_HIGH; + break; + default: + tp->coalesce = COALESCE_SLOW; + break; + } + + intf->needs_remote_wakeup = 1; + + tp->rtl_ops.init(tp); + set_ethernet_addr(tp); + + usb_set_intfdata(intf, tp); + netif_napi_add(netdev, &tp->napi, r8152_poll, RTL8152_NAPI_WEIGHT); + + ret = register_netdev(netdev); + if (ret != 0) { + netif_err(tp, probe, netdev, "couldn't register the device\n"); + goto out1; + } + + tp->saved_wolopts = __rtl_get_wol(tp); + if (tp->saved_wolopts) + device_set_wakeup_enable(&udev->dev, true); + else + device_set_wakeup_enable(&udev->dev, false); + + netif_info(tp, probe, netdev, "%s\n", DRIVER_VERSION); + + return 0; + +out1: + netif_napi_del(&tp->napi); + usb_set_intfdata(intf, NULL); +out: + free_netdev(netdev); + return ret; +} + +static void rtl8152_disconnect(struct usb_interface *intf) +{ + struct r8152 *tp = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (tp) { + struct usb_device *udev = tp->udev; + + if (udev->state == USB_STATE_NOTATTACHED) + set_bit(RTL8152_UNPLUG, &tp->flags); + + netif_napi_del(&tp->napi); + unregister_netdev(tp->netdev); + tp->rtl_ops.unload(tp); + free_netdev(tp->netdev); + } +} + +#define REALTEK_USB_DEVICE(vend, prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC \ +}, \ +{ \ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | \ + USB_DEVICE_ID_MATCH_DEVICE, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .bInterfaceClass = USB_CLASS_COMM, \ + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, \ + .bInterfaceProtocol = USB_CDC_PROTO_NONE + +/* table of devices that work with this driver */ +static struct usb_device_id rtl8152_table[] = { + {REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8152)}, + {REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8153)}, + {REALTEK_USB_DEVICE(VENDOR_ID_SAMSUNG, 0xa101)}, + {REALTEK_USB_DEVICE(VENDOR_ID_LENOVO, 0x7205)}, + {REALTEK_USB_DEVICE(VENDOR_ID_LENOVO, 0x304f)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, rtl8152_table); + +static struct usb_driver rtl8152_driver = { + .name = MODULENAME, + .id_table = rtl8152_table, + .probe = rtl8152_probe, + .disconnect = rtl8152_disconnect, + .suspend = rtl8152_suspend, + .resume = rtl8152_resume, + .reset_resume = rtl8152_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(rtl8152_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c new file mode 100644 index 000000000..524a47a28 --- /dev/null +++ b/drivers/net/usb/rndis_host.c @@ -0,0 +1,649 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/module.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 { + u32 status = le32_to_cpu(msg->status); + + switch (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", 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; + u32 xid = 0, msg_len, request_id, msg_type, rsp, + status; + + /* REVISIT when this gets called from contexts other than probe() or + * disconnect(): either serialize, or dispatch responses on xid + */ + + msg_type = le32_to_cpu(buf->msg_type); + + /* Issue the request; xid is unique, don't bother byteswapping it */ + if (likely(msg_type != RNDIS_MSG_HALT && 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), + ¬ification, sizeof(notification), &partial, + RNDIS_CONTROL_TIMEOUT_MS); + if (unlikely(retval < 0)) + return retval; + } + + /* Poll the control channel; the request probably completed immediately */ + rsp = le32_to_cpu(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_type = le32_to_cpu(buf->msg_type); + msg_len = le32_to_cpu(buf->msg_len); + status = le32_to_cpu(buf->status); + request_id = (__force u32) buf->request_id; + if (likely(msg_type == rsp)) { + if (likely(request_id == xid)) { + if (unlikely(rsp == RNDIS_MSG_RESET_C)) + return 0; + if (likely(RNDIS_STATUS_SUCCESS == + status)) + return 0; + dev_dbg(&info->control->dev, + "rndis reply status %08x\n", + status); + return -EL3RST; + } + dev_dbg(&info->control->dev, + "rndis reply id %d expected %d\n", + request_id, xid); + /* then likely retry */ + } else switch (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 = cpu_to_le32(RNDIS_MSG_KEEPALIVE_C); + msg->msg_len = cpu_to_le32(sizeof *msg); + msg->status = cpu_to_le32(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, u32 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 = cpu_to_le32(RNDIS_MSG_QUERY); + u.get->msg_len = cpu_to_le32(sizeof *u.get + in_len); + u.get->oid = cpu_to_le32(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 = cpu_to_le32(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, + RNDIS_OID_GEN_PHYSICAL_MEDIUM, + 0, (void **) &phym, &reply_len); + if (retval != 0 || !phym) { + /* OID is optional so don't fail here. */ + phym_unspec = cpu_to_le32(RNDIS_PHYSICAL_MEDIUM_UNSPECIFIED); + phym = &phym_unspec; + } + if ((flags & FLAG_RNDIS_PHYM_WIRELESS) && + le32_to_cpup(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) && + le32_to_cpup(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, + RNDIS_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); + + /* set a nonzero filter to enable data transfers */ + memset(u.set, 0, sizeof *u.set); + u.set->msg_type = cpu_to_le32(RNDIS_MSG_SET); + u.set->msg_len = cpu_to_le32(4 + sizeof *u.set); + u.set->oid = cpu_to_le32(RNDIS_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) = cpu_to_le32(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 = cpu_to_le32(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 = cpu_to_le32(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_type, msg_len, data_offset, data_len; + + msg_type = le32_to_cpu(hdr->msg_type); + 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(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 = cpu_to_le32(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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(rndis_driver); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("USB Host side RNDIS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c new file mode 100644 index 000000000..d37b7dce2 --- /dev/null +++ b/drivers/net/usb/rtl8150.c @@ -0,0 +1,949 @@ +/* + * 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/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; + 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; + u8 *intr_buff; + u8 phy; +}; + +typedef struct rtl8150 rtl8150_t; + +struct async_req { + struct usb_ctrlrequest dr; + u16 rx_creg; +}; + +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 async_set_reg_cb(struct urb *urb) +{ + struct async_req *req = (struct async_req *)urb->context; + int status = urb->status; + + if (status < 0) + dev_dbg(&urb->dev->dev, "%s failed with %d", __func__, status); + kfree(req); + usb_free_urb(urb); +} + +static int async_set_registers(rtl8150_t *dev, u16 indx, u16 size, u16 reg) +{ + int res = -ENOMEM; + struct urb *async_urb; + struct async_req *req; + + req = kmalloc(sizeof(struct async_req), GFP_ATOMIC); + if (req == NULL) + return res; + async_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (async_urb == NULL) { + kfree(req); + return res; + } + req->rx_creg = cpu_to_le16(reg); + req->dr.bRequestType = RTL8150_REQT_WRITE; + req->dr.bRequest = RTL8150_REQ_SET_REGS; + req->dr.wIndex = 0; + req->dr.wValue = cpu_to_le16(indx); + req->dr.wLength = cpu_to_le16(size); + usb_fill_control_urb(async_urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), (void *)&req->dr, + &req->rx_creg, size, async_set_reg_cb, req); + res = usb_submit_urb(async_urb, GFP_ATOMIC); + if (res) { + if (res == -ENODEV) + netif_device_detach(dev->netdev); + dev_err(&dev->udev->dev, "%s failed with %d\n", __func__, res); + } + return res; +} + +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); + netdev_dbg(netdev, "Setting MAC address to %pM\n", 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; + } + + 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); +} + +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); +} + +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); + netdev_dbg(dev->netdev, "%s: LINK LOST\n", __func__); + } + } else { + if (!netif_carrier_ok(dev->netdev)) { + netif_carrier_on(dev->netdev); + netdev_dbg(dev->netdev, "%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) + dev_err(&dev->udev->dev, + "can't resubmit intr, %s-%s/input0, status %d\n", + 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; + 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); + u16 rx_creg = 0x9e; + + netif_stop_queue(netdev); + if (netdev->flags & IFF_PROMISC) { + rx_creg |= 0x0001; + dev_info(&netdev->dev, "%s: promiscuous mode\n", netdev->name); + } else if (!netdev_mc_empty(netdev) || + (netdev->flags & IFF_ALLMULTI)) { + rx_creg &= 0xfffe; + rx_creg |= 0x0002; + dev_info(&netdev->dev, "%s: allmulti set\n", netdev->name); + } else { + /* ~RX_MULTICAST, ~RX_PROMISCUOUS */ + rx_creg &= 0x00fc; + } + async_set_registers(dev, RCR, sizeof(rx_creg), rx_creg); + 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); + + netif_stop_queue(netdev); + if (!test_bit(RTL8150_UNPLUG, &dev->flags)) + disable_net_traffic(dev); + unlink_all_urbs(dev); + + return 0; +} + +static void rtl8150_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info) +{ + rtl8150_t *dev = netdev_priv(netdev); + + strlcpy(info->driver, driver_name, sizeof(info->driver)); + strlcpy(info->version, DRIVER_VERSION, sizeof(info->version)); + 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; + netdev->ethtool_ops = &ops; + dev->intr_interval = 100; /* 100ms */ + + if (!alloc_all_urbs(dev)) { + dev_err(&intf->dev, "out of memory\n"); + goto out; + } + if (!rtl8150_reset(dev)) { + dev_err(&intf->dev, "couldn't reset the device\n"); + 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) { + dev_err(&intf->dev, "couldn't register the device\n"); + 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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(rtl8150_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/sierra_net.c b/drivers/net/usb/sierra_net.c new file mode 100644 index 000000000..a25158876 --- /dev/null +++ b/drivers/net/usb/sierra_net.c @@ -0,0 +1,969 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +#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 + +/* Overriding the default usbnet rx_urb_size */ +#define SIERRA_NET_RX_URB_SIZE (8 * 1024) + +/* 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 = usbnet_write_cmd(dev, USB_CDC_SEND_ENCAPSULATED_COMMAND, + USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE, + 0, priv->ifnum, cmd, cmdlen); + + 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; + *((__be16 *)&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; + } else { + priv->link_up = 0; + } + usbnet_link_change(dev, link_up, 0); +} + +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__); + + /* The SIERRA_NET_HIP_MSYNC_ID command appears to request that the + * firmware restart itself. After restarting, the modem will respond + * with the SIERRA_NET_HIP_RESTART_ID indication. The driver continues + * sending MSYNC commands every few seconds until it receives the + * RESTART event from the firmware + */ + + /* 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) + 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 + */ +static 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); + strlcpy(info->driver, driver_name, sizeof(info->driver)); + strlcpy(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 sierra_net_get_fw_attr(struct usbnet *dev, u16 *datap) +{ + int result = 0; + __le16 attrdata; + + result = usbnet_read_cmd(dev, + /* _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 */ + ); + + if (result < 0) + return -EIO; + + *datap = le16_to_cpu(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}; + + dev_dbg(&dev->udev->dev, "%s", __func__); + + ifacenum = intf->cur_altsetting->desc.bInterfaceNumber; + 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) + return -ENOMEM; + + priv->usbnet = dev; + priv->ifnum = ifacenum; + dev->net->netdev_ops = &sierra_net_device_ops; + + /* change MAC addr to include, ifacenum, and to be unique */ + dev->net->dev_addr[ETH_ALEN-2] = atomic_inc_return(&iface_counter); + dev->net->dev_addr[ETH_ALEN-1] = ifacenum; + + /* we will have to manufacture ethernet headers, prepare template */ + eth = (struct ethhdr *)priv->ethr_hdr_tmpl; + memcpy(ð->h_dest, dev->net->dev_addr, ETH_ALEN); + eth->h_proto = cpu_to_be16(ETH_P_IP); + + /* prepare shutdown message template */ + memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg)); + /* set context index initially to 0 - prepares tx hdr template */ + sierra_net_set_ctx_index(priv, 0); + + /* prepare sync message template */ + memcpy(priv->sync_msg, sync_tmplate, sizeof(priv->sync_msg)); + + /* decrease the rx_urb_size and max_tx_size to 4k on USB 1.1 */ + dev->rx_urb_size = SIERRA_NET_RX_URB_SIZE; + if (dev->udev->speed != USB_SPEED_HIGH) + dev->rx_urb_size = min_t(size_t, 4096, SIERRA_NET_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; + } + + 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); + + usbnet_status_stop(dev); + + 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 ----------------------*/ +static 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 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, +}; + +static int +sierra_net_probe(struct usb_interface *udev, const struct usb_device_id *prod) +{ + int ret; + + ret = usbnet_probe(udev, prod); + if (ret == 0) { + struct usbnet *dev = usb_get_intfdata(udev); + + ret = usbnet_status_start(dev, GFP_KERNEL); + if (ret == 0) { + /* Interrupt URB now set up; initiate sync sequence */ + sierra_net_dosync(dev); + } + } + return ret; +} + +#define DIRECT_IP_DEVICE(vend, prod) \ + {USB_DEVICE_INTERFACE_NUMBER(vend, prod, 7), \ + .driver_info = (unsigned long)&sierra_net_info_direct_ip}, \ + {USB_DEVICE_INTERFACE_NUMBER(vend, prod, 10), \ + .driver_info = (unsigned long)&sierra_net_info_direct_ip}, \ + {USB_DEVICE_INTERFACE_NUMBER(vend, prod, 11), \ + .driver_info = (unsigned long)&sierra_net_info_direct_ip} + +static const struct usb_device_id products[] = { + DIRECT_IP_DEVICE(0x1199, 0x68A3), /* Sierra Wireless USB-to-WWAN modem */ + DIRECT_IP_DEVICE(0x0F3D, 0x68A3), /* AT&T Direct IP modem */ + DIRECT_IP_DEVICE(0x1199, 0x68AA), /* Sierra Wireless Direct IP LTE modem */ + DIRECT_IP_DEVICE(0x0F3D, 0x68AA), /* AT&T Direct IP LTE modem */ + + {}, /* 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 = sierra_net_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, + .no_dynamic_id = 1, + .disable_hub_initiated_lpm = 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/drivers/net/usb/smsc75xx.c b/drivers/net/usb/smsc75xx.c new file mode 100644 index 000000000..d9e789226 --- /dev/null +++ b/drivers/net/usb/smsc75xx.c @@ -0,0 +1,2286 @@ + /*************************************************************************** + * + * 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, see <http://www.gnu.org/licenses/>. + * + *****************************************************************************/ + +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/usb.h> +#include <linux/bitrev.h> +#include <linux/crc16.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 SUPPORTED_WAKE (WAKE_PHY | WAKE_UCAST | WAKE_BCAST | \ + WAKE_MCAST | WAKE_ARP | WAKE_MAGIC) + +#define SUSPEND_SUSPEND0 (0x01) +#define SUSPEND_SUSPEND1 (0x02) +#define SUSPEND_SUSPEND2 (0x04) +#define SUSPEND_SUSPEND3 (0x08) +#define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ + SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) + +struct smsc75xx_priv { + struct usbnet *dev; + u32 rfe_ctl; + u32 wolopts; + u32 multicast_hash_table[DP_SEL_VHF_HASH_LEN]; + struct mutex dataport_mutex; + spinlock_t rfe_ctl_lock; + struct work_struct set_multicast; + u8 suspend_flags; +}; + +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, int in_pm) +{ + u32 buf; + int ret; + int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16); + + BUG_ON(!dev); + + if (!in_pm) + fn = usbnet_read_cmd; + else + fn = usbnet_read_cmd_nopm; + + ret = fn(dev, USB_VENDOR_REQUEST_READ_REGISTER, USB_DIR_IN + | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, index, &buf, 4); + if (unlikely(ret < 0)) + netdev_warn(dev->net, "Failed to read reg index 0x%08x: %d\n", + index, ret); + + le32_to_cpus(&buf); + *data = buf; + + return ret; +} + +static int __must_check __smsc75xx_write_reg(struct usbnet *dev, u32 index, + u32 data, int in_pm) +{ + u32 buf; + int ret; + int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16); + + BUG_ON(!dev); + + if (!in_pm) + fn = usbnet_write_cmd; + else + fn = usbnet_write_cmd_nopm; + + buf = data; + cpu_to_le32s(&buf); + + ret = fn(dev, USB_VENDOR_REQUEST_WRITE_REGISTER, USB_DIR_OUT + | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, index, &buf, 4); + if (unlikely(ret < 0)) + netdev_warn(dev->net, "Failed to write reg index 0x%08x: %d\n", + index, ret); + + return ret; +} + +static int __must_check smsc75xx_read_reg_nopm(struct usbnet *dev, u32 index, + u32 *data) +{ + return __smsc75xx_read_reg(dev, index, data, 1); +} + +static int __must_check smsc75xx_write_reg_nopm(struct usbnet *dev, u32 index, + u32 data) +{ + return __smsc75xx_write_reg(dev, index, data, 1); +} + +static int __must_check smsc75xx_read_reg(struct usbnet *dev, u32 index, + u32 *data) +{ + return __smsc75xx_read_reg(dev, index, data, 0); +} + +static int __must_check smsc75xx_write_reg(struct usbnet *dev, u32 index, + u32 data) +{ + return __smsc75xx_write_reg(dev, index, data, 0); +} + +/* Loop until the read is completed with timeout + * called with phy_mutex held */ +static __must_check int __smsc75xx_phy_wait_not_busy(struct usbnet *dev, + int in_pm) +{ + unsigned long start_time = jiffies; + u32 val; + int ret; + + do { + ret = __smsc75xx_read_reg(dev, MII_ACCESS, &val, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error reading MII_ACCESS\n"); + return ret; + } + + 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, + int in_pm) +{ + 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, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "MII is busy in smsc75xx_mdio_read\n"); + goto done; + } + + /* 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, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error writing MII_ACCESS\n"); + goto done; + } + + ret = __smsc75xx_phy_wait_not_busy(dev, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Timed out reading MII reg %02X\n", idx); + goto done; + } + + ret = __smsc75xx_read_reg(dev, MII_DATA, &val, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error reading MII_DATA\n"); + goto done; + } + + 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, int in_pm) +{ + 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, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "MII is busy in smsc75xx_mdio_write\n"); + goto done; + } + + val = regval; + ret = __smsc75xx_write_reg(dev, MII_DATA, val, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error writing MII_DATA\n"); + goto done; + } + + /* 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, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error writing MII_ACCESS\n"); + goto done; + } + + ret = __smsc75xx_phy_wait_not_busy(dev, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Timed out writing MII reg %02X\n", idx); + goto done; + } + +done: + mutex_unlock(&dev->phy_mutex); +} + +static int smsc75xx_mdio_read_nopm(struct net_device *netdev, int phy_id, + int idx) +{ + return __smsc75xx_mdio_read(netdev, phy_id, idx, 1); +} + +static void smsc75xx_mdio_write_nopm(struct net_device *netdev, int phy_id, + int idx, int regval) +{ + __smsc75xx_mdio_write(netdev, phy_id, idx, regval, 1); +} + +static int smsc75xx_mdio_read(struct net_device *netdev, int phy_id, int idx) +{ + return __smsc75xx_mdio_read(netdev, phy_id, idx, 0); +} + +static void smsc75xx_mdio_write(struct net_device *netdev, int phy_id, int idx, + int regval) +{ + __smsc75xx_mdio_write(netdev, phy_id, idx, regval, 0); +} + +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); + if (ret < 0) { + netdev_warn(dev->net, "Error reading E2P_CMD\n"); + return ret; + } + + 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 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); + if (ret < 0) { + netdev_warn(dev->net, "Error reading E2P_CMD\n"); + return ret; + } + + 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 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); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_CMD\n"); + return ret; + } + + ret = smsc75xx_wait_eeprom(dev); + if (ret < 0) + return ret; + + ret = smsc75xx_read_reg(dev, E2P_DATA, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading E2P_DATA\n"); + return ret; + } + + 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); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_CMD\n"); + return ret; + } + + 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); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_DATA\n"); + return ret; + } + + /* Send "write" command */ + val = E2P_CMD_BUSY | E2P_CMD_WRITE | (offset & E2P_CMD_ADDR); + ret = smsc75xx_write_reg(dev, E2P_CMD, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_CMD\n"); + return ret; + } + + 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); + if (ret < 0) { + netdev_warn(dev->net, "Error reading DP_SEL\n"); + return ret; + } + + if (dp_sel & DP_SEL_DPRDY) + return 0; + + udelay(40); + } + + netdev_warn(dev->net, "smsc75xx_dataport_wait_not_busy timed out\n"); + + 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); + if (ret < 0) { + netdev_warn(dev->net, "smsc75xx_dataport_write busy on entry\n"); + goto done; + } + + ret = smsc75xx_read_reg(dev, DP_SEL, &dp_sel); + if (ret < 0) { + netdev_warn(dev->net, "Error reading DP_SEL\n"); + goto done; + } + + dp_sel &= ~DP_SEL_RSEL; + dp_sel |= ram_select; + ret = smsc75xx_write_reg(dev, DP_SEL, dp_sel); + if (ret < 0) { + netdev_warn(dev->net, "Error writing DP_SEL\n"); + goto done; + } + + for (i = 0; i < length; i++) { + ret = smsc75xx_write_reg(dev, DP_ADDR, addr + i); + if (ret < 0) { + netdev_warn(dev->net, "Error writing DP_ADDR\n"); + goto done; + } + + ret = smsc75xx_write_reg(dev, DP_DATA, buf[i]); + if (ret < 0) { + netdev_warn(dev->net, "Error writing DP_DATA\n"); + goto done; + } + + ret = smsc75xx_write_reg(dev, DP_CMD, DP_CMD_WRITE); + if (ret < 0) { + netdev_warn(dev->net, "Error writing DP_CMD\n"); + goto done; + } + + ret = smsc75xx_dataport_wait_not_busy(dev); + if (ret < 0) { + netdev_warn(dev->net, "smsc75xx_dataport_write timeout\n"); + goto done; + } + } + +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\n", + 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); + if (ret < 0) + netdev_warn(dev->net, "Error writing RFE_CRL\n"); +} + +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\n"); + 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\n"); + 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\n"); + + 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\n"); + 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\n", + (cap & FLOW_CTRL_RX ? "enabled" : "disabled"), + (cap & FLOW_CTRL_TX ? "enabled" : "disabled")); + } else { + netif_dbg(dev, link, dev->net, "half duplex\n"); + } + + ret = smsc75xx_write_reg(dev, FLOW, flow); + if (ret < 0) { + netdev_warn(dev->net, "Error writing FLOW\n"); + return ret; + } + + ret = smsc75xx_write_reg(dev, FCT_FLOW, fct_flow); + if (ret < 0) { + netdev_warn(dev->net, "Error writing FCT_FLOW\n"); + return ret; + } + + 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; + + /* write to clear phy interrupt status */ + smsc75xx_mdio_write(dev->net, mii->phy_id, PHY_INT_SRC, + PHY_INT_SRC_CLEAR_ALL); + + ret = smsc75xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL); + if (ret < 0) { + netdev_warn(dev->net, "Error writing INT_STS\n"); + return ret; + } + + 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\n", + 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\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); +} + +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\n", + ee->magic); + return -EINVAL; + } + + return smsc75xx_write_eeprom(dev, ee->offset, ee->len, data); +} + +static void smsc75xx_ethtool_get_wol(struct net_device *net, + struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + + wolinfo->supported = SUPPORTED_WAKE; + wolinfo->wolopts = pdata->wolopts; +} + +static int smsc75xx_ethtool_set_wol(struct net_device *net, + struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + int ret; + + pdata->wolopts = wolinfo->wolopts & SUPPORTED_WAKE; + + ret = device_set_wakeup_enable(&dev->udev->dev, pdata->wolopts); + if (ret < 0) + netdev_warn(dev->net, "device_set_wakeup_enable error %d\n", ret); + + return ret; +} + +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, + .get_wol = smsc75xx_ethtool_get_wol, + .set_wol = smsc75xx_ethtool_set_wol, +}; + +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\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 eth_random_addr\n"); +} + +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); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write RX_ADDRH: %d\n", ret); + return ret; + } + + ret = smsc75xx_write_reg(dev, RX_ADDRL, addr_lo); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write RX_ADDRL: %d\n", ret); + return ret; + } + + addr_hi |= ADDR_FILTX_FB_VALID; + ret = smsc75xx_write_reg(dev, ADDR_FILTX, addr_hi); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write ADDR_FILTX: %d\n", ret); + return ret; + } + + ret = smsc75xx_write_reg(dev, ADDR_FILTX + 4, addr_lo); + if (ret < 0) + netdev_warn(dev->net, "Failed to write ADDR_FILTX+4: %d\n", ret); + + return ret; +} + +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); + if (bmcr < 0) { + netdev_warn(dev->net, "Error reading MII_BMCR\n"); + return bmcr; + } + timeout++; + } while ((bmcr & BMCR_RESET) && (timeout < 100)); + + if (timeout >= 100) { + netdev_warn(dev->net, "timeout on PHY Reset\n"); + 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); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PHY_INT_SRC\n"); + return ret; + } + + 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\n"); + 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); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MAC_RX: %d\n", ret); + return ret; + } + + rxenabled = ((buf & MAC_RX_RXEN) != 0); + + if (rxenabled) { + buf &= ~MAC_RX_RXEN; + ret = smsc75xx_write_reg(dev, MAC_RX, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_RX: %d\n", ret); + return 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); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_RX: %d\n", ret); + return ret; + } + + if (rxenabled) { + buf |= MAC_RX_RXEN; + ret = smsc75xx_write_reg(dev, MAC_RX, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_RX: %d\n", ret); + return 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); + if (ret < 0) { + netdev_warn(dev->net, "Failed to set mac rx frame length\n"); + return ret; + } + + 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); + if (ret < 0) + netdev_warn(dev->net, "Error writing RFE_CTL\n"); + + return ret; +} + +static int smsc75xx_wait_ready(struct usbnet *dev, int in_pm) +{ + int timeout = 0; + + do { + u32 buf; + int ret; + + ret = __smsc75xx_read_reg(dev, PMT_CTL, &buf, in_pm); + + if (ret < 0) { + netdev_warn(dev->net, "Failed to read PMT_CTL: %d\n", ret); + return ret; + } + + if (buf & PMT_CTL_DEV_RDY) + return 0; + + msleep(10); + timeout++; + } while (timeout < 100); + + netdev_warn(dev->net, "timeout waiting for device ready\n"); + return -EIO; +} + +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\n"); + + ret = smsc75xx_wait_ready(dev, 0); + if (ret < 0) { + netdev_warn(dev->net, "device not ready in smsc75xx_reset\n"); + return ret; + } + + ret = smsc75xx_read_reg(dev, HW_CFG, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + + buf |= HW_CFG_LRST; + + ret = smsc75xx_write_reg(dev, HW_CFG, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write HW_CFG: %d\n", ret); + return ret; + } + + timeout = 0; + do { + msleep(10); + ret = smsc75xx_read_reg(dev, HW_CFG, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + timeout++; + } while ((buf & HW_CFG_LRST) && (timeout < 100)); + + if (timeout >= 100) { + netdev_warn(dev->net, "timeout on completion of Lite Reset\n"); + return -EIO; + } + + netif_dbg(dev, ifup, dev->net, "Lite reset complete, resetting PHY\n"); + + ret = smsc75xx_read_reg(dev, PMT_CTL, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read PMT_CTL: %d\n", ret); + return ret; + } + + buf |= PMT_CTL_PHY_RST; + + ret = smsc75xx_write_reg(dev, PMT_CTL, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write PMT_CTL: %d\n", ret); + return ret; + } + + timeout = 0; + do { + msleep(10); + ret = smsc75xx_read_reg(dev, PMT_CTL, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read PMT_CTL: %d\n", ret); + return ret; + } + timeout++; + } while ((buf & PMT_CTL_PHY_RST) && (timeout < 100)); + + if (timeout >= 100) { + netdev_warn(dev->net, "timeout waiting for PHY Reset\n"); + return -EIO; + } + + netif_dbg(dev, ifup, dev->net, "PHY reset complete\n"); + + ret = smsc75xx_set_mac_address(dev); + if (ret < 0) { + netdev_warn(dev->net, "Failed to set mac address\n"); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "MAC Address: %pM\n", + dev->net->dev_addr); + + ret = smsc75xx_read_reg(dev, HW_CFG, &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", + buf); + + buf |= HW_CFG_BIR; + + ret = smsc75xx_write_reg(dev, HW_CFG, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write HW_CFG: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, HW_CFG, &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", + 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\n", + (ulong)dev->rx_urb_size); + + ret = smsc75xx_write_reg(dev, BURST_CAP, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write BURST_CAP: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, BURST_CAP, &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", buf); + + ret = smsc75xx_write_reg(dev, BULK_IN_DLY, DEFAULT_BULK_IN_DELAY); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write BULK_IN_DLY: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, BULK_IN_DLY, &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", buf); + + if (turbo_mode) { + ret = smsc75xx_read_reg(dev, HW_CFG, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "HW_CFG: 0x%08x\n", buf); + + buf |= (HW_CFG_MEF | HW_CFG_BCE); + + ret = smsc75xx_write_reg(dev, HW_CFG, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write HW_CFG: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, HW_CFG, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read HW_CFG: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "HW_CFG: 0x%08x\n", buf); + } + + /* set FIFO sizes */ + buf = (MAX_RX_FIFO_SIZE - 512) / 512; + ret = smsc75xx_write_reg(dev, FCT_RX_FIFO_END, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FCT_RX_FIFO_END: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "FCT_RX_FIFO_END set to 0x%08x\n", buf); + + buf = (MAX_TX_FIFO_SIZE - 512) / 512; + ret = smsc75xx_write_reg(dev, FCT_TX_FIFO_END, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FCT_TX_FIFO_END: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "FCT_TX_FIFO_END set to 0x%08x\n", buf); + + ret = smsc75xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write INT_STS: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, ID_REV, &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", buf); + + ret = smsc75xx_read_reg(dev, E2P_CMD, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read E2P_CMD: %d\n", ret); + return ret; + } + + /* only set default GPIO/LED settings if no EEPROM is detected */ + if (!(buf & E2P_CMD_LOADED)) { + ret = smsc75xx_read_reg(dev, LED_GPIO_CFG, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read LED_GPIO_CFG: %d\n", ret); + return 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); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write LED_GPIO_CFG: %d\n", ret); + return ret; + } + } + + ret = smsc75xx_write_reg(dev, FLOW, 0); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FLOW: %d\n", ret); + return ret; + } + + ret = smsc75xx_write_reg(dev, FCT_FLOW, 0); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FCT_FLOW: %d\n", ret); + return ret; + } + + /* Don't need rfe_ctl_lock during initialisation */ + ret = smsc75xx_read_reg(dev, RFE_CTL, &pdata->rfe_ctl); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read RFE_CTL: %d\n", ret); + return ret; + } + + pdata->rfe_ctl |= RFE_CTL_AB | RFE_CTL_DPF; + + ret = smsc75xx_write_reg(dev, RFE_CTL, pdata->rfe_ctl); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write RFE_CTL: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, RFE_CTL, &pdata->rfe_ctl); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read RFE_CTL: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "RFE_CTL set to 0x%08x\n", + 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); + if (ret < 0) { + netdev_warn(dev->net, "Failed to initialize PHY: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, INT_EP_CTL, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read INT_EP_CTL: %d\n", ret); + return ret; + } + + /* enable PHY interrupts */ + buf |= INT_ENP_PHY_INT; + + ret = smsc75xx_write_reg(dev, INT_EP_CTL, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write INT_EP_CTL: %d\n", ret); + return ret; + } + + /* allow mac to detect speed and duplex from phy */ + ret = smsc75xx_read_reg(dev, MAC_CR, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MAC_CR: %d\n", ret); + return ret; + } + + buf |= (MAC_CR_ADD | MAC_CR_ASD); + ret = smsc75xx_write_reg(dev, MAC_CR, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_CR: %d\n", ret); + return ret; + } + + ret = smsc75xx_read_reg(dev, MAC_TX, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MAC_TX: %d\n", ret); + return ret; + } + + buf |= MAC_TX_TXEN; + + ret = smsc75xx_write_reg(dev, MAC_TX, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_TX: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "MAC_TX set to 0x%08x\n", buf); + + ret = smsc75xx_read_reg(dev, FCT_TX_CTL, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read FCT_TX_CTL: %d\n", ret); + return ret; + } + + buf |= FCT_TX_CTL_EN; + + ret = smsc75xx_write_reg(dev, FCT_TX_CTL, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FCT_TX_CTL: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "FCT_TX_CTL set to 0x%08x\n", buf); + + ret = smsc75xx_set_rx_max_frame_length(dev, dev->net->mtu + ETH_HLEN); + if (ret < 0) { + netdev_warn(dev->net, "Failed to set max rx frame length\n"); + return ret; + } + + ret = smsc75xx_read_reg(dev, MAC_RX, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MAC_RX: %d\n", ret); + return ret; + } + + buf |= MAC_RX_RXEN; + + ret = smsc75xx_write_reg(dev, MAC_RX, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_RX: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "MAC_RX set to 0x%08x\n", buf); + + ret = smsc75xx_read_reg(dev, FCT_RX_CTL, &buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read FCT_RX_CTL: %d\n", ret); + return ret; + } + + buf |= FCT_RX_CTL_EN; + + ret = smsc75xx_write_reg(dev, FCT_RX_CTL, buf); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write FCT_RX_CTL: %d\n", ret); + return ret; + } + + netif_dbg(dev, ifup, dev->net, "FCT_RX_CTL set to 0x%08x\n", buf); + + netif_dbg(dev, ifup, dev->net, "smsc75xx_reset, return 0\n"); + 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); + if (ret < 0) { + netdev_warn(dev->net, "usbnet_get_endpoints failed: %d\n", ret); + return ret; + } + + dev->data[0] = (unsigned long)kzalloc(sizeof(struct smsc75xx_priv), + GFP_KERNEL); + + pdata = (struct smsc75xx_priv *)(dev->data[0]); + if (!pdata) + 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; + + ret = smsc75xx_wait_ready(dev, 0); + if (ret < 0) { + netdev_warn(dev->net, "device not ready in smsc75xx_bind\n"); + return ret; + } + + smsc75xx_init_mac_address(dev); + + /* Init all registers */ + ret = smsc75xx_reset(dev); + if (ret < 0) { + netdev_warn(dev->net, "smsc75xx_reset error %d\n", ret); + return ret; + } + + 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\n"); + kfree(pdata); + pdata = NULL; + dev->data[0] = 0; + } +} + +static u16 smsc_crc(const u8 *buffer, size_t len) +{ + return bitrev16(crc16(0xFFFF, buffer, len)); +} + +static int smsc75xx_write_wuff(struct usbnet *dev, int filter, u32 wuf_cfg, + u32 wuf_mask1) +{ + int cfg_base = WUF_CFGX + filter * 4; + int mask_base = WUF_MASKX + filter * 16; + int ret; + + ret = smsc75xx_write_reg(dev, cfg_base, wuf_cfg); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUF_CFGX\n"); + return ret; + } + + ret = smsc75xx_write_reg(dev, mask_base, wuf_mask1); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUF_MASKX\n"); + return ret; + } + + ret = smsc75xx_write_reg(dev, mask_base + 4, 0); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUF_MASKX\n"); + return ret; + } + + ret = smsc75xx_write_reg(dev, mask_base + 8, 0); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUF_MASKX\n"); + return ret; + } + + ret = smsc75xx_write_reg(dev, mask_base + 12, 0); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUF_MASKX\n"); + return ret; + } + + return 0; +} + +static int smsc75xx_enter_suspend0(struct usbnet *dev) +{ + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + return ret; + } + + val &= (~(PMT_CTL_SUS_MODE | PMT_CTL_PHY_RST)); + val |= PMT_CTL_SUS_MODE_0 | PMT_CTL_WOL_EN | PMT_CTL_WUPS; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + + pdata->suspend_flags |= SUSPEND_SUSPEND0; + + return 0; +} + +static int smsc75xx_enter_suspend1(struct usbnet *dev) +{ + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + return ret; + } + + val &= ~(PMT_CTL_SUS_MODE | PMT_CTL_WUPS | PMT_CTL_PHY_RST); + val |= PMT_CTL_SUS_MODE_1; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + + /* clear wol status, enable energy detection */ + val &= ~PMT_CTL_WUPS; + val |= (PMT_CTL_WUPS_ED | PMT_CTL_ED_EN); + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + + pdata->suspend_flags |= SUSPEND_SUSPEND1; + + return 0; +} + +static int smsc75xx_enter_suspend2(struct usbnet *dev) +{ + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + return ret; + } + + val &= ~(PMT_CTL_SUS_MODE | PMT_CTL_WUPS | PMT_CTL_PHY_RST); + val |= PMT_CTL_SUS_MODE_2; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + + pdata->suspend_flags |= SUSPEND_SUSPEND2; + + return 0; +} + +static int smsc75xx_enter_suspend3(struct usbnet *dev) +{ + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc75xx_read_reg_nopm(dev, FCT_RX_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading FCT_RX_CTL\n"); + return ret; + } + + if (val & FCT_RX_CTL_RXUSED) { + netdev_dbg(dev->net, "rx fifo not empty in autosuspend\n"); + return -EBUSY; + } + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + return ret; + } + + val &= ~(PMT_CTL_SUS_MODE | PMT_CTL_WUPS | PMT_CTL_PHY_RST); + val |= PMT_CTL_SUS_MODE_3 | PMT_CTL_RES_CLR_WKP_EN; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + + /* clear wol status */ + val &= ~PMT_CTL_WUPS; + val |= PMT_CTL_WUPS_WOL; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + + pdata->suspend_flags |= SUSPEND_SUSPEND3; + + return 0; +} + +static int smsc75xx_enable_phy_wakeup_interrupts(struct usbnet *dev, u16 mask) +{ + struct mii_if_info *mii = &dev->mii; + int ret; + + netdev_dbg(dev->net, "enabling PHY wakeup interrupts\n"); + + /* read to clear */ + ret = smsc75xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_SRC); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PHY_INT_SRC\n"); + return ret; + } + + /* enable interrupt source */ + ret = smsc75xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_MASK); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PHY_INT_MASK\n"); + return ret; + } + + ret |= mask; + + smsc75xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_INT_MASK, ret); + + return 0; +} + +static int smsc75xx_link_ok_nopm(struct usbnet *dev) +{ + struct mii_if_info *mii = &dev->mii; + int ret; + + /* first, a dummy read, needed to latch some MII phys */ + ret = smsc75xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); + if (ret < 0) { + netdev_warn(dev->net, "Error reading MII_BMSR\n"); + return ret; + } + + ret = smsc75xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); + if (ret < 0) { + netdev_warn(dev->net, "Error reading MII_BMSR\n"); + return ret; + } + + return !!(ret & BMSR_LSTATUS); +} + +static int smsc75xx_autosuspend(struct usbnet *dev, u32 link_up) +{ + int ret; + + if (!netif_running(dev->net)) { + /* interface is ifconfig down so fully power down hw */ + netdev_dbg(dev->net, "autosuspend entering SUSPEND2\n"); + return smsc75xx_enter_suspend2(dev); + } + + if (!link_up) { + /* link is down so enter EDPD mode */ + netdev_dbg(dev->net, "autosuspend entering SUSPEND1\n"); + + /* enable PHY wakeup events for if cable is attached */ + ret = smsc75xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_ANEG_COMP); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + return ret; + } + + netdev_info(dev->net, "entering SUSPEND1 mode\n"); + return smsc75xx_enter_suspend1(dev); + } + + /* enable PHY wakeup events so we remote wakeup if cable is pulled */ + ret = smsc75xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_LINK_DOWN); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + return ret; + } + + netdev_dbg(dev->net, "autosuspend entering SUSPEND3\n"); + return smsc75xx_enter_suspend3(dev); +} + +static int smsc75xx_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u32 val, link_up; + int ret; + + ret = usbnet_suspend(intf, message); + if (ret < 0) { + netdev_warn(dev->net, "usbnet_suspend error\n"); + return ret; + } + + if (pdata->suspend_flags) { + netdev_warn(dev->net, "error during last resume\n"); + pdata->suspend_flags = 0; + } + + /* determine if link is up using only _nopm functions */ + link_up = smsc75xx_link_ok_nopm(dev); + + if (message.event == PM_EVENT_AUTO_SUSPEND) { + ret = smsc75xx_autosuspend(dev, link_up); + goto done; + } + + /* if we get this far we're not autosuspending */ + /* if no wol options set, or if link is down and we're not waking on + * PHY activity, enter lowest power SUSPEND2 mode + */ + if (!(pdata->wolopts & SUPPORTED_WAKE) || + !(link_up || (pdata->wolopts & WAKE_PHY))) { + netdev_info(dev->net, "entering SUSPEND2 mode\n"); + + /* disable energy detect (link up) & wake up events */ + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val &= ~(WUCSR_MPEN | WUCSR_WUEN); + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + goto done; + } + + val &= ~(PMT_CTL_ED_EN | PMT_CTL_WOL_EN); + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + goto done; + } + + ret = smsc75xx_enter_suspend2(dev); + goto done; + } + + if (pdata->wolopts & WAKE_PHY) { + ret = smsc75xx_enable_phy_wakeup_interrupts(dev, + (PHY_INT_MASK_ANEG_COMP | PHY_INT_MASK_LINK_DOWN)); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + goto done; + } + + /* if link is down then configure EDPD and enter SUSPEND1, + * otherwise enter SUSPEND0 below + */ + if (!link_up) { + struct mii_if_info *mii = &dev->mii; + netdev_info(dev->net, "entering SUSPEND1 mode\n"); + + /* enable energy detect power-down mode */ + ret = smsc75xx_mdio_read_nopm(dev->net, mii->phy_id, + PHY_MODE_CTRL_STS); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PHY_MODE_CTRL_STS\n"); + goto done; + } + + ret |= MODE_CTRL_STS_EDPWRDOWN; + + smsc75xx_mdio_write_nopm(dev->net, mii->phy_id, + PHY_MODE_CTRL_STS, ret); + + /* enter SUSPEND1 mode */ + ret = smsc75xx_enter_suspend1(dev); + goto done; + } + } + + if (pdata->wolopts & (WAKE_MCAST | WAKE_ARP)) { + int i, filter = 0; + + /* disable all filters */ + for (i = 0; i < WUF_NUM; i++) { + ret = smsc75xx_write_reg_nopm(dev, WUF_CFGX + i * 4, 0); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUF_CFGX\n"); + goto done; + } + } + + if (pdata->wolopts & WAKE_MCAST) { + const u8 mcast[] = {0x01, 0x00, 0x5E}; + netdev_info(dev->net, "enabling multicast detection\n"); + + val = WUF_CFGX_EN | WUF_CFGX_ATYPE_MULTICAST + | smsc_crc(mcast, 3); + ret = smsc75xx_write_wuff(dev, filter++, val, 0x0007); + if (ret < 0) { + netdev_warn(dev->net, "Error writing wakeup filter\n"); + goto done; + } + } + + if (pdata->wolopts & WAKE_ARP) { + const u8 arp[] = {0x08, 0x06}; + netdev_info(dev->net, "enabling ARP detection\n"); + + val = WUF_CFGX_EN | WUF_CFGX_ATYPE_ALL | (0x0C << 16) + | smsc_crc(arp, 2); + ret = smsc75xx_write_wuff(dev, filter++, val, 0x0003); + if (ret < 0) { + netdev_warn(dev->net, "Error writing wakeup filter\n"); + goto done; + } + } + + /* clear any pending pattern match packet status */ + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val |= WUCSR_WUFR; + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + + netdev_info(dev->net, "enabling packet match detection\n"); + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val |= WUCSR_WUEN; + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + } else { + netdev_info(dev->net, "disabling packet match detection\n"); + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val &= ~WUCSR_WUEN; + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + } + + /* disable magic, bcast & unicast wakeup sources */ + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val &= ~(WUCSR_MPEN | WUCSR_BCST_EN | WUCSR_PFDA_EN); + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + + if (pdata->wolopts & WAKE_PHY) { + netdev_info(dev->net, "enabling PHY wakeup\n"); + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + goto done; + } + + /* clear wol status, enable energy detection */ + val &= ~PMT_CTL_WUPS; + val |= (PMT_CTL_WUPS_ED | PMT_CTL_ED_EN); + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + goto done; + } + } + + if (pdata->wolopts & WAKE_MAGIC) { + netdev_info(dev->net, "enabling magic packet wakeup\n"); + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + /* clear any pending magic packet status */ + val |= WUCSR_MPR | WUCSR_MPEN; + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + } + + if (pdata->wolopts & WAKE_BCAST) { + netdev_info(dev->net, "enabling broadcast detection\n"); + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val |= WUCSR_BCAST_FR | WUCSR_BCST_EN; + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + } + + if (pdata->wolopts & WAKE_UCAST) { + netdev_info(dev->net, "enabling unicast detection\n"); + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + goto done; + } + + val |= WUCSR_WUFR | WUCSR_PFDA_EN; + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + goto done; + } + } + + /* enable receiver to enable frame reception */ + ret = smsc75xx_read_reg_nopm(dev, MAC_RX, &val); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read MAC_RX: %d\n", ret); + goto done; + } + + val |= MAC_RX_RXEN; + + ret = smsc75xx_write_reg_nopm(dev, MAC_RX, val); + if (ret < 0) { + netdev_warn(dev->net, "Failed to write MAC_RX: %d\n", ret); + goto done; + } + + /* some wol options are enabled, so enter SUSPEND0 */ + netdev_info(dev->net, "entering SUSPEND0 mode\n"); + ret = smsc75xx_enter_suspend0(dev); + +done: + /* + * TODO: resume() might need to handle the suspend failure + * in system sleep + */ + if (ret && PMSG_IS_AUTO(message)) + usbnet_resume(intf); + return ret; +} + +static int smsc75xx_resume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct smsc75xx_priv *pdata = (struct smsc75xx_priv *)(dev->data[0]); + u8 suspend_flags = pdata->suspend_flags; + int ret; + u32 val; + + netdev_dbg(dev->net, "resume suspend_flags=0x%02x\n", suspend_flags); + + /* do this first to ensure it's cleared even in error case */ + pdata->suspend_flags = 0; + + if (suspend_flags & SUSPEND_ALLMODES) { + /* Disable wakeup sources */ + ret = smsc75xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading WUCSR\n"); + return ret; + } + + val &= ~(WUCSR_WUEN | WUCSR_MPEN | WUCSR_PFDA_EN + | WUCSR_BCST_EN); + + ret = smsc75xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing WUCSR\n"); + return ret; + } + + /* clear wake-up status */ + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + return ret; + } + + val &= ~PMT_CTL_WOL_EN; + val |= PMT_CTL_WUPS; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + } + + if (suspend_flags & SUSPEND_SUSPEND2) { + netdev_info(dev->net, "resuming from SUSPEND2\n"); + + ret = smsc75xx_read_reg_nopm(dev, PMT_CTL, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading PMT_CTL\n"); + return ret; + } + + val |= PMT_CTL_PHY_PWRUP; + + ret = smsc75xx_write_reg_nopm(dev, PMT_CTL, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing PMT_CTL\n"); + return ret; + } + } + + ret = smsc75xx_wait_ready(dev, 1); + if (ret < 0) { + netdev_warn(dev->net, "device not ready in smsc75xx_resume\n"); + return ret; + } + + return usbnet_resume(intf); +} + +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\n", 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\n", + 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\n"); + 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\n", 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 int smsc75xx_manage_power(struct usbnet *dev, int on) +{ + dev->intf->needs_remote_wakeup = on; + return 0; +} + +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, + .manage_power = smsc75xx_manage_power, + .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 = smsc75xx_suspend, + .resume = smsc75xx_resume, + .reset_resume = smsc75xx_resume, + .disconnect = usbnet_disconnect, + .disable_hub_initiated_lpm = 1, + .supports_autosuspend = 1, +}; + +module_usb_driver(smsc75xx_driver); + +MODULE_AUTHOR("Nancy Lin"); +MODULE_AUTHOR("Steve Glendinning <steve.glendinning@shawell.net>"); +MODULE_DESCRIPTION("SMSC75XX USB 2.0 Gigabit Ethernet Devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/smsc75xx.h b/drivers/net/usb/smsc75xx.h new file mode 100644 index 000000000..2c7ea8fd1 --- /dev/null +++ b/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, see <http://www.gnu.org/licenses/>. + * + *****************************************************************************/ + +#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_SRC_CLEAR_ALL ((u16)0xffff) + +#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/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c new file mode 100644 index 000000000..26423adc3 --- /dev/null +++ b/drivers/net/usb/smsc95xx.c @@ -0,0 +1,2032 @@ + /*************************************************************************** + * + * 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, see <http://www.gnu.org/licenses/>. + * + *****************************************************************************/ + +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/usb.h> +#include <linux/bitrev.h> +#include <linux/crc16.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) +#define SUPPORTED_WAKE (WAKE_PHY | WAKE_UCAST | WAKE_BCAST | \ + WAKE_MCAST | WAKE_ARP | WAKE_MAGIC) + +#define FEATURE_8_WAKEUP_FILTERS (0x01) +#define FEATURE_PHY_NLP_CROSSOVER (0x02) +#define FEATURE_REMOTE_WAKEUP (0x04) + +#define SUSPEND_SUSPEND0 (0x01) +#define SUSPEND_SUSPEND1 (0x02) +#define SUSPEND_SUSPEND2 (0x04) +#define SUSPEND_SUSPEND3 (0x08) +#define SUSPEND_ALLMODES (SUSPEND_SUSPEND0 | SUSPEND_SUSPEND1 | \ + SUSPEND_SUSPEND2 | SUSPEND_SUSPEND3) + +struct smsc95xx_priv { + u32 mac_cr; + u32 hash_hi; + u32 hash_lo; + u32 wolopts; + spinlock_t mac_cr_lock; + u8 features; + u8 suspend_flags; +}; + +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 __smsc95xx_read_reg(struct usbnet *dev, u32 index, + u32 *data, int in_pm) +{ + u32 buf; + int ret; + int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16); + + BUG_ON(!dev); + + if (!in_pm) + fn = usbnet_read_cmd; + else + fn = usbnet_read_cmd_nopm; + + ret = fn(dev, USB_VENDOR_REQUEST_READ_REGISTER, USB_DIR_IN + | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, index, &buf, 4); + if (unlikely(ret < 0)) + netdev_warn(dev->net, "Failed to read reg index 0x%08x: %d\n", + index, ret); + + le32_to_cpus(&buf); + *data = buf; + + return ret; +} + +static int __must_check __smsc95xx_write_reg(struct usbnet *dev, u32 index, + u32 data, int in_pm) +{ + u32 buf; + int ret; + int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16); + + BUG_ON(!dev); + + if (!in_pm) + fn = usbnet_write_cmd; + else + fn = usbnet_write_cmd_nopm; + + buf = data; + cpu_to_le32s(&buf); + + ret = fn(dev, USB_VENDOR_REQUEST_WRITE_REGISTER, USB_DIR_OUT + | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, index, &buf, 4); + if (unlikely(ret < 0)) + netdev_warn(dev->net, "Failed to write reg index 0x%08x: %d\n", + index, ret); + + return ret; +} + +static int __must_check smsc95xx_read_reg_nopm(struct usbnet *dev, u32 index, + u32 *data) +{ + return __smsc95xx_read_reg(dev, index, data, 1); +} + +static int __must_check smsc95xx_write_reg_nopm(struct usbnet *dev, u32 index, + u32 data) +{ + return __smsc95xx_write_reg(dev, index, data, 1); +} + +static int __must_check smsc95xx_read_reg(struct usbnet *dev, u32 index, + u32 *data) +{ + return __smsc95xx_read_reg(dev, index, data, 0); +} + +static int __must_check smsc95xx_write_reg(struct usbnet *dev, u32 index, + u32 data) +{ + return __smsc95xx_write_reg(dev, index, data, 0); +} + +/* Loop until the read is completed with timeout + * called with phy_mutex held */ +static int __must_check __smsc95xx_phy_wait_not_busy(struct usbnet *dev, + int in_pm) +{ + unsigned long start_time = jiffies; + u32 val; + int ret; + + do { + ret = __smsc95xx_read_reg(dev, MII_ADDR, &val, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error reading MII_ACCESS\n"); + return ret; + } + + 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, + int in_pm) +{ + struct usbnet *dev = netdev_priv(netdev); + u32 val, addr; + int ret; + + mutex_lock(&dev->phy_mutex); + + /* confirm MII not busy */ + ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "MII is busy in smsc95xx_mdio_read\n"); + goto done; + } + + /* 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_ | MII_BUSY_; + ret = __smsc95xx_write_reg(dev, MII_ADDR, addr, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error writing MII_ADDR\n"); + goto done; + } + + ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Timed out reading MII reg %02X\n", idx); + goto done; + } + + ret = __smsc95xx_read_reg(dev, MII_DATA, &val, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error reading MII_DATA\n"); + goto done; + } + + ret = (u16)(val & 0xFFFF); + +done: + mutex_unlock(&dev->phy_mutex); + return ret; +} + +static void __smsc95xx_mdio_write(struct net_device *netdev, int phy_id, + int idx, int regval, int in_pm) +{ + struct usbnet *dev = netdev_priv(netdev); + u32 val, addr; + int ret; + + mutex_lock(&dev->phy_mutex); + + /* confirm MII not busy */ + ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "MII is busy in smsc95xx_mdio_write\n"); + goto done; + } + + val = regval; + ret = __smsc95xx_write_reg(dev, MII_DATA, val, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error writing MII_DATA\n"); + goto done; + } + + /* 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_ | MII_BUSY_; + ret = __smsc95xx_write_reg(dev, MII_ADDR, addr, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Error writing MII_ADDR\n"); + goto done; + } + + ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); + if (ret < 0) { + netdev_warn(dev->net, "Timed out writing MII reg %02X\n", idx); + goto done; + } + +done: + mutex_unlock(&dev->phy_mutex); +} + +static int smsc95xx_mdio_read_nopm(struct net_device *netdev, int phy_id, + int idx) +{ + return __smsc95xx_mdio_read(netdev, phy_id, idx, 1); +} + +static void smsc95xx_mdio_write_nopm(struct net_device *netdev, int phy_id, + int idx, int regval) +{ + __smsc95xx_mdio_write(netdev, phy_id, idx, regval, 1); +} + +static int smsc95xx_mdio_read(struct net_device *netdev, int phy_id, int idx) +{ + return __smsc95xx_mdio_read(netdev, phy_id, idx, 0); +} + +static void smsc95xx_mdio_write(struct net_device *netdev, int phy_id, int idx, + int regval) +{ + __smsc95xx_mdio_write(netdev, phy_id, idx, regval, 0); +} + +static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev) +{ + unsigned long start_time = jiffies; + u32 val; + int ret; + + do { + ret = smsc95xx_read_reg(dev, E2P_CMD, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading E2P_CMD\n"); + return ret; + } + + 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 __must_check smsc95xx_eeprom_confirm_not_busy(struct usbnet *dev) +{ + unsigned long start_time = jiffies; + u32 val; + int ret; + + do { + ret = smsc95xx_read_reg(dev, E2P_CMD, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading E2P_CMD\n"); + return ret; + } + + 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_); + ret = smsc95xx_write_reg(dev, E2P_CMD, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_CMD\n"); + return ret; + } + + ret = smsc95xx_wait_eeprom(dev); + if (ret < 0) + return ret; + + ret = smsc95xx_read_reg(dev, E2P_DATA, &val); + if (ret < 0) { + netdev_warn(dev->net, "Error reading E2P_DATA\n"); + return ret; + } + + 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_; + ret = smsc95xx_write_reg(dev, E2P_CMD, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_DATA\n"); + return ret; + } + + ret = smsc95xx_wait_eeprom(dev); + if (ret < 0) + return ret; + + for (i = 0; i < length; i++) { + + /* Fill data register */ + val = data[i]; + ret = smsc95xx_write_reg(dev, E2P_DATA, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_DATA\n"); + return ret; + } + + /* Send "write" command */ + val = E2P_CMD_BUSY_ | E2P_CMD_WRITE_ | (offset & E2P_CMD_ADDR_); + ret = smsc95xx_write_reg(dev, E2P_CMD, val); + if (ret < 0) { + netdev_warn(dev->net, "Error writing E2P_CMD\n"); + return ret; + } + + ret = smsc95xx_wait_eeprom(dev); + if (ret < 0) + return ret; + + offset++; + } + + return 0; +} + +static int __must_check smsc95xx_write_reg_async(struct usbnet *dev, u16 index, + u32 data) +{ + const u16 size = 4; + u32 buf; + int ret; + + buf = data; + cpu_to_le32s(&buf); + + ret = usbnet_write_cmd_async(dev, USB_VENDOR_REQUEST_WRITE_REGISTER, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + 0, index, &buf, size); + if (ret < 0) + netdev_warn(dev->net, "Error write async cmd, sts=%d\n", + ret); + return ret; +} + +/* 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; + int ret; + + 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 */ + ret = smsc95xx_write_reg_async(dev, HASHH, pdata->hash_hi); + if (ret < 0) + netdev_warn(dev->net, "failed to initiate async write to HASHH\n"); + + ret = smsc95xx_write_reg_async(dev, HASHL, pdata->hash_lo); + if (ret < 0) + netdev_warn(dev->net, "failed to initiate async write to HASHL\n"); + + ret = smsc95xx_write_reg_async(dev, MAC_CR, pdata->mac_cr); + if (ret < 0) + netdev_warn(dev->net, "failed to initiate async write to MAC_CR\n"); +} + +static int 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) + return ret; + + 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; + } + + ret = smsc95xx_write_reg(dev, FLOW, flow); + if (ret < 0) + return ret; + + return 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; + int ret; + + /* clear interrupt status */ + ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC); + if (ret < 0) + return ret; + + ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_); + if (ret < 0) + return ret; + + 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); + + ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); + if (ret < 0) + return ret; + + ret = smsc95xx_phy_update_flowcontrol(dev, ecmd.duplex, lcladv, rmtadv); + if (ret < 0) + netdev_warn(dev->net, "Error updating PHY flow control\n"); + + return ret; +} + +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) + 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) + 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 int smsc95xx_ethtool_getregslen(struct net_device *netdev) +{ + /* all smsc95xx registers */ + return COE_CR - ID_REV + sizeof(u32); +} + +static void +smsc95xx_ethtool_getregs(struct net_device *netdev, struct ethtool_regs *regs, + void *buf) +{ + struct usbnet *dev = netdev_priv(netdev); + unsigned int i, j; + int retval; + u32 *data = buf; + + retval = smsc95xx_read_reg(dev, ID_REV, ®s->version); + if (retval < 0) { + netdev_warn(netdev, "REGS: cannot read ID_REV\n"); + return; + } + + for (i = ID_REV, j = 0; i <= COE_CR; i += (sizeof(u32)), j++) { + retval = smsc95xx_read_reg(dev, i, &data[j]); + if (retval < 0) { + netdev_warn(netdev, "REGS: cannot read reg[%x]\n", i); + return; + } + } +} + +static void smsc95xx_ethtool_get_wol(struct net_device *net, + struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + + wolinfo->supported = SUPPORTED_WAKE; + wolinfo->wolopts = pdata->wolopts; +} + +static int smsc95xx_ethtool_set_wol(struct net_device *net, + struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + int ret; + + pdata->wolopts = wolinfo->wolopts & SUPPORTED_WAKE; + + ret = device_set_wakeup_enable(&dev->udev->dev, pdata->wolopts); + if (ret < 0) + netdev_warn(dev->net, "device_set_wakeup_enable error %d\n", ret); + + return ret; +} + +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, + .get_regs_len = smsc95xx_ethtool_getregslen, + .get_regs = smsc95xx_ethtool_getregs, + .get_wol = smsc95xx_ethtool_get_wol, + .set_wol = smsc95xx_ethtool_set_wol, +}; + +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 eth_random_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) + return ret; + + return smsc95xx_write_reg(dev, ADDRH, addr_hi); +} + +/* starts the TX path */ +static int smsc95xx_start_tx_path(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + unsigned long flags; + int ret; + + /* 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); + + ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); + if (ret < 0) + return ret; + + /* Enable Tx at SCSRs */ + return smsc95xx_write_reg(dev, TX_CFG, TX_CFG_ON_); +} + +/* Starts the Receive path */ +static int smsc95xx_start_rx_path(struct usbnet *dev, int in_pm) +{ + 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); + + return __smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr, in_pm); +} + +static int smsc95xx_phy_initialize(struct usbnet *dev) +{ + int bmcr, ret, 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 */ + ret = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, PHY_INT_SRC); + if (ret < 0) { + netdev_warn(dev->net, "Failed to read PHY_INT_SRC during init\n"); + return ret; + } + + 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"); + + ret = smsc95xx_write_reg(dev, HW_CFG, HW_CFG_LRST_); + if (ret < 0) + return ret; + + timeout = 0; + do { + msleep(10); + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) + return ret; + 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; + } + + ret = smsc95xx_write_reg(dev, PM_CTRL, PM_CTL_PHY_RST_); + if (ret < 0) + return ret; + + timeout = 0; + do { + msleep(10); + ret = smsc95xx_read_reg(dev, PM_CTRL, &read_buf); + if (ret < 0) + return ret; + 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) + 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) + return ret; + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) + 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) + return ret; + + ret = smsc95xx_read_reg(dev, BURST_CAP, &read_buf); + if (ret < 0) + return ret; + + netif_dbg(dev, ifup, dev->net, + "Read Value from BURST_CAP after writing: 0x%08x\n", + read_buf); + + ret = smsc95xx_write_reg(dev, BULK_IN_DLY, DEFAULT_BULK_IN_DELAY); + if (ret < 0) + return ret; + + ret = smsc95xx_read_reg(dev, BULK_IN_DLY, &read_buf); + if (ret < 0) + 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) + 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) + return ret; + + ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); + if (ret < 0) + return ret; + + netif_dbg(dev, ifup, dev->net, + "Read Value from HW_CFG after writing: 0x%08x\n", read_buf); + + ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_); + if (ret < 0) + return ret; + + ret = smsc95xx_read_reg(dev, ID_REV, &read_buf); + if (ret < 0) + 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) + return ret; + + /* Init Tx */ + ret = smsc95xx_write_reg(dev, FLOW, 0); + if (ret < 0) + return ret; + + ret = smsc95xx_write_reg(dev, AFC_CFG, AFC_CFG_DEFAULT); + if (ret < 0) + return ret; + + /* Don't need mac_cr_lock during initialisation */ + ret = smsc95xx_read_reg(dev, MAC_CR, &pdata->mac_cr); + if (ret < 0) + return ret; + + /* Init Rx */ + /* Set Vlan */ + ret = smsc95xx_write_reg(dev, VLAN1, (u32)ETH_P_8021Q); + if (ret < 0) + return ret; + + /* Enable or disable checksum offload engines */ + ret = smsc95xx_set_features(dev->net, dev->net->features); + if (ret < 0) { + netdev_warn(dev->net, "Failed to set checksum offload features\n"); + return ret; + } + + smsc95xx_set_multicast(dev->net); + + ret = smsc95xx_phy_initialize(dev); + if (ret < 0) { + netdev_warn(dev->net, "Failed to init PHY\n"); + return ret; + } + + ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf); + if (ret < 0) + 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) + return ret; + + ret = smsc95xx_start_tx_path(dev); + if (ret < 0) { + netdev_warn(dev->net, "Failed to start TX path\n"); + return ret; + } + + ret = smsc95xx_start_rx_path(dev, 0); + if (ret < 0) { + netdev_warn(dev->net, "Failed to start RX path\n"); + return ret; + } + + 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; + u32 val; + 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) + 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); + + /* detect device revision as different features may be available */ + ret = smsc95xx_read_reg(dev, ID_REV, &val); + if (ret < 0) + return ret; + val >>= 16; + + if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9530_) || + (val == ID_REV_CHIP_ID_89530_) || (val == ID_REV_CHIP_ID_9730_)) + pdata->features = (FEATURE_8_WAKEUP_FILTERS | + FEATURE_PHY_NLP_CROSSOVER | + FEATURE_REMOTE_WAKEUP); + else if (val == ID_REV_CHIP_ID_9512_) + pdata->features = FEATURE_8_WAKEUP_FILTERS; + + 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 u32 smsc_crc(const u8 *buffer, size_t len, int filter) +{ + u32 crc = bitrev16(crc16(0xFFFF, buffer, len)); + return crc << ((filter % 2) * 16); +} + +static int smsc95xx_enable_phy_wakeup_interrupts(struct usbnet *dev, u16 mask) +{ + struct mii_if_info *mii = &dev->mii; + int ret; + + netdev_dbg(dev->net, "enabling PHY wakeup interrupts\n"); + + /* read to clear */ + ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_SRC); + if (ret < 0) + return ret; + + /* enable interrupt source */ + ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_MASK); + if (ret < 0) + return ret; + + ret |= mask; + + smsc95xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_INT_MASK, ret); + + return 0; +} + +static int smsc95xx_link_ok_nopm(struct usbnet *dev) +{ + struct mii_if_info *mii = &dev->mii; + int ret; + + /* first, a dummy read, needed to latch some MII phys */ + ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); + if (ret < 0) + return ret; + + ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); + if (ret < 0) + return ret; + + return !!(ret & BMSR_LSTATUS); +} + +static int smsc95xx_enter_suspend0(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + val &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); + val |= PM_CTL_SUS_MODE_0; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + /* clear wol status */ + val &= ~PM_CTL_WUPS_; + val |= PM_CTL_WUPS_WOL_; + + /* enable energy detection */ + if (pdata->wolopts & WAKE_PHY) + val |= PM_CTL_WUPS_ED_; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + /* read back PM_CTRL */ + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + pdata->suspend_flags |= SUSPEND_SUSPEND0; + + return 0; +} + +static int smsc95xx_enter_suspend1(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + struct mii_if_info *mii = &dev->mii; + u32 val; + int ret; + + /* reconfigure link pulse detection timing for + * compatibility with non-standard link partners + */ + if (pdata->features & FEATURE_PHY_NLP_CROSSOVER) + smsc95xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_EDPD_CONFIG, + PHY_EDPD_CONFIG_DEFAULT); + + /* enable energy detect power-down mode */ + ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_MODE_CTRL_STS); + if (ret < 0) + return ret; + + ret |= MODE_CTRL_STS_EDPWRDOWN_; + + smsc95xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_MODE_CTRL_STS, ret); + + /* enter SUSPEND1 mode */ + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); + val |= PM_CTL_SUS_MODE_1; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + /* clear wol status, enable energy detection */ + val &= ~PM_CTL_WUPS_; + val |= (PM_CTL_WUPS_ED_ | PM_CTL_ED_EN_); + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + pdata->suspend_flags |= SUSPEND_SUSPEND1; + + return 0; +} + +static int smsc95xx_enter_suspend2(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); + val |= PM_CTL_SUS_MODE_2; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + pdata->suspend_flags |= SUSPEND_SUSPEND2; + + return 0; +} + +static int smsc95xx_enter_suspend3(struct usbnet *dev) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 val; + int ret; + + ret = smsc95xx_read_reg_nopm(dev, RX_FIFO_INF, &val); + if (ret < 0) + return ret; + + if (val & 0xFFFF) { + netdev_info(dev->net, "rx fifo not empty in autosuspend\n"); + return -EBUSY; + } + + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); + val |= PM_CTL_SUS_MODE_3 | PM_CTL_RES_CLR_WKP_STS; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + /* clear wol status */ + val &= ~PM_CTL_WUPS_; + val |= PM_CTL_WUPS_WOL_; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + + pdata->suspend_flags |= SUSPEND_SUSPEND3; + + return 0; +} + +static int smsc95xx_autosuspend(struct usbnet *dev, u32 link_up) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + int ret; + + if (!netif_running(dev->net)) { + /* interface is ifconfig down so fully power down hw */ + netdev_dbg(dev->net, "autosuspend entering SUSPEND2\n"); + return smsc95xx_enter_suspend2(dev); + } + + if (!link_up) { + /* link is down so enter EDPD mode, but only if device can + * reliably resume from it. This check should be redundant + * as current FEATURE_REMOTE_WAKEUP parts also support + * FEATURE_PHY_NLP_CROSSOVER but it's included for clarity */ + if (!(pdata->features & FEATURE_PHY_NLP_CROSSOVER)) { + netdev_warn(dev->net, "EDPD not supported\n"); + return -EBUSY; + } + + netdev_dbg(dev->net, "autosuspend entering SUSPEND1\n"); + + /* enable PHY wakeup events for if cable is attached */ + ret = smsc95xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_ANEG_COMP_); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + return ret; + } + + netdev_info(dev->net, "entering SUSPEND1 mode\n"); + return smsc95xx_enter_suspend1(dev); + } + + /* enable PHY wakeup events so we remote wakeup if cable is pulled */ + ret = smsc95xx_enable_phy_wakeup_interrupts(dev, + PHY_INT_MASK_LINK_DOWN_); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + return ret; + } + + netdev_dbg(dev->net, "autosuspend entering SUSPEND3\n"); + return smsc95xx_enter_suspend3(dev); +} + +static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + u32 val, link_up; + int ret; + + ret = usbnet_suspend(intf, message); + if (ret < 0) { + netdev_warn(dev->net, "usbnet_suspend error\n"); + return ret; + } + + if (pdata->suspend_flags) { + netdev_warn(dev->net, "error during last resume\n"); + pdata->suspend_flags = 0; + } + + /* determine if link is up using only _nopm functions */ + link_up = smsc95xx_link_ok_nopm(dev); + + if (message.event == PM_EVENT_AUTO_SUSPEND && + (pdata->features & FEATURE_REMOTE_WAKEUP)) { + ret = smsc95xx_autosuspend(dev, link_up); + goto done; + } + + /* if we get this far we're not autosuspending */ + /* if no wol options set, or if link is down and we're not waking on + * PHY activity, enter lowest power SUSPEND2 mode + */ + if (!(pdata->wolopts & SUPPORTED_WAKE) || + !(link_up || (pdata->wolopts & WAKE_PHY))) { + netdev_info(dev->net, "entering SUSPEND2 mode\n"); + + /* disable energy detect (link up) & wake up events */ + ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) + goto done; + + val &= ~(WUCSR_MPEN_ | WUCSR_WAKE_EN_); + + ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) + goto done; + + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + goto done; + + val &= ~(PM_CTL_ED_EN_ | PM_CTL_WOL_EN_); + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + goto done; + + ret = smsc95xx_enter_suspend2(dev); + goto done; + } + + if (pdata->wolopts & WAKE_PHY) { + ret = smsc95xx_enable_phy_wakeup_interrupts(dev, + (PHY_INT_MASK_ANEG_COMP_ | PHY_INT_MASK_LINK_DOWN_)); + if (ret < 0) { + netdev_warn(dev->net, "error enabling PHY wakeup ints\n"); + goto done; + } + + /* if link is down then configure EDPD and enter SUSPEND1, + * otherwise enter SUSPEND0 below + */ + if (!link_up) { + netdev_info(dev->net, "entering SUSPEND1 mode\n"); + ret = smsc95xx_enter_suspend1(dev); + goto done; + } + } + + if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) { + u32 *filter_mask = kzalloc(sizeof(u32) * 32, GFP_KERNEL); + u32 command[2]; + u32 offset[2]; + u32 crc[4]; + int wuff_filter_count = + (pdata->features & FEATURE_8_WAKEUP_FILTERS) ? + LAN9500A_WUFF_NUM : LAN9500_WUFF_NUM; + int i, filter = 0; + + if (!filter_mask) { + netdev_warn(dev->net, "Unable to allocate filter_mask\n"); + ret = -ENOMEM; + goto done; + } + + memset(command, 0, sizeof(command)); + memset(offset, 0, sizeof(offset)); + memset(crc, 0, sizeof(crc)); + + if (pdata->wolopts & WAKE_BCAST) { + const u8 bcast[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + netdev_info(dev->net, "enabling broadcast detection\n"); + filter_mask[filter * 4] = 0x003F; + filter_mask[filter * 4 + 1] = 0x00; + filter_mask[filter * 4 + 2] = 0x00; + filter_mask[filter * 4 + 3] = 0x00; + command[filter/4] |= 0x05UL << ((filter % 4) * 8); + offset[filter/4] |= 0x00 << ((filter % 4) * 8); + crc[filter/2] |= smsc_crc(bcast, 6, filter); + filter++; + } + + if (pdata->wolopts & WAKE_MCAST) { + const u8 mcast[] = {0x01, 0x00, 0x5E}; + netdev_info(dev->net, "enabling multicast detection\n"); + filter_mask[filter * 4] = 0x0007; + filter_mask[filter * 4 + 1] = 0x00; + filter_mask[filter * 4 + 2] = 0x00; + filter_mask[filter * 4 + 3] = 0x00; + command[filter/4] |= 0x09UL << ((filter % 4) * 8); + offset[filter/4] |= 0x00 << ((filter % 4) * 8); + crc[filter/2] |= smsc_crc(mcast, 3, filter); + filter++; + } + + if (pdata->wolopts & WAKE_ARP) { + const u8 arp[] = {0x08, 0x06}; + netdev_info(dev->net, "enabling ARP detection\n"); + filter_mask[filter * 4] = 0x0003; + filter_mask[filter * 4 + 1] = 0x00; + filter_mask[filter * 4 + 2] = 0x00; + filter_mask[filter * 4 + 3] = 0x00; + command[filter/4] |= 0x05UL << ((filter % 4) * 8); + offset[filter/4] |= 0x0C << ((filter % 4) * 8); + crc[filter/2] |= smsc_crc(arp, 2, filter); + filter++; + } + + if (pdata->wolopts & WAKE_UCAST) { + netdev_info(dev->net, "enabling unicast detection\n"); + filter_mask[filter * 4] = 0x003F; + filter_mask[filter * 4 + 1] = 0x00; + filter_mask[filter * 4 + 2] = 0x00; + filter_mask[filter * 4 + 3] = 0x00; + command[filter/4] |= 0x01UL << ((filter % 4) * 8); + offset[filter/4] |= 0x00 << ((filter % 4) * 8); + crc[filter/2] |= smsc_crc(dev->net->dev_addr, ETH_ALEN, filter); + filter++; + } + + for (i = 0; i < (wuff_filter_count * 4); i++) { + ret = smsc95xx_write_reg_nopm(dev, WUFF, filter_mask[i]); + if (ret < 0) { + kfree(filter_mask); + goto done; + } + } + kfree(filter_mask); + + for (i = 0; i < (wuff_filter_count / 4); i++) { + ret = smsc95xx_write_reg_nopm(dev, WUFF, command[i]); + if (ret < 0) + goto done; + } + + for (i = 0; i < (wuff_filter_count / 4); i++) { + ret = smsc95xx_write_reg_nopm(dev, WUFF, offset[i]); + if (ret < 0) + goto done; + } + + for (i = 0; i < (wuff_filter_count / 2); i++) { + ret = smsc95xx_write_reg_nopm(dev, WUFF, crc[i]); + if (ret < 0) + goto done; + } + + /* clear any pending pattern match packet status */ + ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) + goto done; + + val |= WUCSR_WUFR_; + + ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) + goto done; + } + + if (pdata->wolopts & WAKE_MAGIC) { + /* clear any pending magic packet status */ + ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) + goto done; + + val |= WUCSR_MPR_; + + ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) + goto done; + } + + /* enable/disable wakeup sources */ + ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) + goto done; + + if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) { + netdev_info(dev->net, "enabling pattern match wakeup\n"); + val |= WUCSR_WAKE_EN_; + } else { + netdev_info(dev->net, "disabling pattern match wakeup\n"); + val &= ~WUCSR_WAKE_EN_; + } + + if (pdata->wolopts & WAKE_MAGIC) { + netdev_info(dev->net, "enabling magic packet wakeup\n"); + val |= WUCSR_MPEN_; + } else { + netdev_info(dev->net, "disabling magic packet wakeup\n"); + val &= ~WUCSR_MPEN_; + } + + ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) + goto done; + + /* enable wol wakeup source */ + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + goto done; + + val |= PM_CTL_WOL_EN_; + + /* phy energy detect wakeup source */ + if (pdata->wolopts & WAKE_PHY) + val |= PM_CTL_ED_EN_; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + goto done; + + /* enable receiver to enable frame reception */ + smsc95xx_start_rx_path(dev, 1); + + /* some wol options are enabled, so enter SUSPEND0 */ + netdev_info(dev->net, "entering SUSPEND0 mode\n"); + ret = smsc95xx_enter_suspend0(dev); + +done: + /* + * TODO: resume() might need to handle the suspend failure + * in system sleep + */ + if (ret && PMSG_IS_AUTO(message)) + usbnet_resume(intf); + return ret; +} + +static int smsc95xx_resume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct smsc95xx_priv *pdata; + u8 suspend_flags; + int ret; + u32 val; + + BUG_ON(!dev); + pdata = (struct smsc95xx_priv *)(dev->data[0]); + suspend_flags = pdata->suspend_flags; + + netdev_dbg(dev->net, "resume suspend_flags=0x%02x\n", suspend_flags); + + /* do this first to ensure it's cleared even in error case */ + pdata->suspend_flags = 0; + + if (suspend_flags & SUSPEND_ALLMODES) { + /* clear wake-up sources */ + ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); + if (ret < 0) + return ret; + + val &= ~(WUCSR_WAKE_EN_ | WUCSR_MPEN_); + + ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); + if (ret < 0) + return ret; + + /* clear wake-up status */ + ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); + if (ret < 0) + return ret; + + val &= ~PM_CTL_WOL_EN_; + val |= PM_CTL_WUPS_; + + ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); + if (ret < 0) + return ret; + } + + ret = usbnet_resume(intf); + if (ret < 0) + netdev_warn(dev->net, "usbnet_resume error\n"); + + return ret; +} + +static int smsc95xx_reset_resume(struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + int ret; + + ret = smsc95xx_reset(dev); + if (ret < 0) + return ret; + + return smsc95xx_resume(intf); +} + +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); + cpu_to_le32s(&csum_preamble); + 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 int smsc95xx_manage_power(struct usbnet *dev, int on) +{ + struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); + + dev->intf->needs_remote_wakeup = on; + + if (pdata->features & FEATURE_REMOTE_WAKEUP) + return 0; + + /* this chip revision isn't capable of remote wakeup */ + netdev_info(dev->net, "hardware isn't capable of remote wakeup\n"); + + if (on) + usb_autopm_get_interface_no_resume(dev->intf); + else + usb_autopm_put_interface(dev->intf); + + return 0; +} + +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, + .manage_power = smsc95xx_manage_power, + .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 = smsc95xx_suspend, + .resume = smsc95xx_resume, + .reset_resume = smsc95xx_reset_resume, + .disconnect = usbnet_disconnect, + .disable_hub_initiated_lpm = 1, + .supports_autosuspend = 1, +}; + +module_usb_driver(smsc95xx_driver); + +MODULE_AUTHOR("Nancy Lin"); +MODULE_AUTHOR("Steve Glendinning <steve.glendinning@shawell.net>"); +MODULE_DESCRIPTION("SMSC95XX USB 2.0 Ethernet Devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/smsc95xx.h b/drivers/net/usb/smsc95xx.h new file mode 100644 index 000000000..526faa0c4 --- /dev/null +++ b/drivers/net/usb/smsc95xx.h @@ -0,0 +1,290 @@ + /*************************************************************************** + * + * 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, see <http://www.gnu.org/licenses/>. + * + *****************************************************************************/ + +#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 ID_REV_CHIP_ID_9500A_ (0x9E00) +#define ID_REV_CHIP_ID_9512_ (0xEC00) +#define ID_REV_CHIP_ID_9530_ (0x9530) +#define ID_REV_CHIP_ID_89530_ (0x9E08) +#define ID_REV_CHIP_ID_9730_ (0x9730) + +#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 INT_STS_CLEAR_ALL_ (0xFFFFFFFF) + +#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 RX_FIFO_INF (0x18) + +#define PM_CTRL (0x20) +#define PM_CTL_RES_CLR_WKP_STS (0x00000200) +#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 (0x00000040) +#define PM_CTL_SUS_MODE_3 (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 LAN9500_WUFF_NUM (4) +#define LAN9500A_WUFF_NUM (8) + +#define WUCSR (0x12C) +#define WUCSR_WFF_PTR_RST_ (0x80000000) +#define WUCSR_GUE_ (0x00000200) +#define WUCSR_WUFR_ (0x00000040) +#define WUCSR_MPR_ (0x00000020) +#define WUCSR_WAKE_EN_ (0x00000004) +#define WUCSR_MPEN_ (0x00000002) + +#define COE_CR (0x130) +#define Tx_COE_EN_ (0x00010000) +#define Rx_COE_MODE_ (0x00000002) +#define Rx_COE_EN_ (0x00000001) + +/* Vendor-specific PHY Definitions */ + +/* EDPD NLP / crossover time configuration (LAN9500A only) */ +#define PHY_EDPD_CONFIG (16) +#define PHY_EDPD_CONFIG_TX_NLP_EN_ ((u16)0x8000) +#define PHY_EDPD_CONFIG_TX_NLP_1000_ ((u16)0x0000) +#define PHY_EDPD_CONFIG_TX_NLP_768_ ((u16)0x2000) +#define PHY_EDPD_CONFIG_TX_NLP_512_ ((u16)0x4000) +#define PHY_EDPD_CONFIG_TX_NLP_256_ ((u16)0x6000) +#define PHY_EDPD_CONFIG_RX_1_NLP_ ((u16)0x1000) +#define PHY_EDPD_CONFIG_RX_NLP_64_ ((u16)0x0000) +#define PHY_EDPD_CONFIG_RX_NLP_256_ ((u16)0x0400) +#define PHY_EDPD_CONFIG_RX_NLP_512_ ((u16)0x0800) +#define PHY_EDPD_CONFIG_RX_NLP_1000_ ((u16)0x0C00) +#define PHY_EDPD_CONFIG_EXT_CROSSOVER_ ((u16)0x0001) +#define PHY_EDPD_CONFIG_DEFAULT (PHY_EDPD_CONFIG_TX_NLP_EN_ | \ + PHY_EDPD_CONFIG_TX_NLP_768_ | \ + PHY_EDPD_CONFIG_RX_1_NLP_) + +/* 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/drivers/net/usb/sr9700.c b/drivers/net/usb/sr9700.c new file mode 100644 index 000000000..4a1e9c489 --- /dev/null +++ b/drivers/net/usb/sr9700.c @@ -0,0 +1,559 @@ +/* + * CoreChip-sz SR9700 one chip USB 1.1 Ethernet Devices + * + * Author : Liu Junliang <liujunliang_ljl@163.com> + * + * Based on dm9601.c + * + * 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. + */ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/stddef.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 "sr9700.h" + +static int sr_read(struct usbnet *dev, u8 reg, u16 length, void *data) +{ + int err; + + err = usbnet_read_cmd(dev, SR_RD_REGS, SR_REQ_RD_REG, 0, reg, data, + length); + if ((err != length) && (err >= 0)) + err = -EINVAL; + return err; +} + +static int sr_write(struct usbnet *dev, u8 reg, u16 length, void *data) +{ + int err; + + err = usbnet_write_cmd(dev, SR_WR_REGS, SR_REQ_WR_REG, 0, reg, data, + length); + if ((err >= 0) && (err < length)) + err = -EINVAL; + return err; +} + +static int sr_read_reg(struct usbnet *dev, u8 reg, u8 *value) +{ + return sr_read(dev, reg, 1, value); +} + +static int sr_write_reg(struct usbnet *dev, u8 reg, u8 value) +{ + return usbnet_write_cmd(dev, SR_WR_REGS, SR_REQ_WR_REG, + value, reg, NULL, 0); +} + +static void sr_write_async(struct usbnet *dev, u8 reg, u16 length, void *data) +{ + usbnet_write_cmd_async(dev, SR_WR_REGS, SR_REQ_WR_REG, + 0, reg, data, length); +} + +static void sr_write_reg_async(struct usbnet *dev, u8 reg, u8 value) +{ + usbnet_write_cmd_async(dev, SR_WR_REGS, SR_REQ_WR_REG, + value, reg, NULL, 0); +} + +static int wait_phy_eeprom_ready(struct usbnet *dev, int phy) +{ + int i; + + for (i = 0; i < SR_SHARE_TIMEOUT; i++) { + u8 tmp = 0; + int ret; + + udelay(1); + ret = sr_read_reg(dev, SR_EPCR, &tmp); + if (ret < 0) + return ret; + + /* ready */ + if (!(tmp & EPCR_ERRE)) + return 0; + } + + netdev_err(dev->net, "%s write timed out!\n", phy ? "phy" : "eeprom"); + + return -EIO; +} + +static int sr_share_read_word(struct usbnet *dev, int phy, u8 reg, + __le16 *value) +{ + int ret; + + mutex_lock(&dev->phy_mutex); + + sr_write_reg(dev, SR_EPAR, phy ? (reg | EPAR_PHY_ADR) : reg); + sr_write_reg(dev, SR_EPCR, phy ? (EPCR_EPOS | EPCR_ERPRR) : EPCR_ERPRR); + + ret = wait_phy_eeprom_ready(dev, phy); + if (ret < 0) + goto out_unlock; + + sr_write_reg(dev, SR_EPCR, 0x0); + ret = sr_read(dev, SR_EPDR, 2, value); + + netdev_dbg(dev->net, "read shared %d 0x%02x returned 0x%04x, %d\n", + phy, reg, *value, ret); + +out_unlock: + mutex_unlock(&dev->phy_mutex); + return ret; +} + +static int sr_share_write_word(struct usbnet *dev, int phy, u8 reg, + __le16 value) +{ + int ret; + + mutex_lock(&dev->phy_mutex); + + ret = sr_write(dev, SR_EPDR, 2, &value); + if (ret < 0) + goto out_unlock; + + sr_write_reg(dev, SR_EPAR, phy ? (reg | EPAR_PHY_ADR) : reg); + sr_write_reg(dev, SR_EPCR, phy ? (EPCR_WEP | EPCR_EPOS | EPCR_ERPRW) : + (EPCR_WEP | EPCR_ERPRW)); + + ret = wait_phy_eeprom_ready(dev, phy); + if (ret < 0) + goto out_unlock; + + sr_write_reg(dev, SR_EPCR, 0x0); + +out_unlock: + mutex_unlock(&dev->phy_mutex); + return ret; +} + +static int sr_read_eeprom_word(struct usbnet *dev, u8 offset, void *value) +{ + return sr_share_read_word(dev, 0, offset, value); +} + +static int sr9700_get_eeprom_len(struct net_device *netdev) +{ + return SR_EEPROM_LEN; +} + +static int sr9700_get_eeprom(struct net_device *netdev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct usbnet *dev = netdev_priv(netdev); + __le16 *buf = (__le16 *)data; + int ret = 0; + int i; + + /* access is 16bit */ + if ((eeprom->offset & 0x01) || (eeprom->len & 0x01)) + return -EINVAL; + + for (i = 0; i < eeprom->len / 2; i++) { + ret = sr_read_eeprom_word(dev, eeprom->offset / 2 + i, buf + i); + if (ret < 0) + break; + } + + return ret; +} + +static int sr_mdio_read(struct net_device *netdev, int phy_id, int loc) +{ + struct usbnet *dev = netdev_priv(netdev); + __le16 res; + int rc = 0; + + if (phy_id) { + netdev_dbg(netdev, "Only internal phy supported\n"); + return 0; + } + + /* Access NSR_LINKST bit for link status instead of MII_BMSR */ + if (loc == MII_BMSR) { + u8 value; + + sr_read_reg(dev, SR_NSR, &value); + if (value & NSR_LINKST) + rc = 1; + } + sr_share_read_word(dev, 1, loc, &res); + if (rc == 1) + res = le16_to_cpu(res) | BMSR_LSTATUS; + else + res = le16_to_cpu(res) & ~BMSR_LSTATUS; + + netdev_dbg(netdev, "sr_mdio_read() phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n", + phy_id, loc, res); + + return res; +} + +static void sr_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(netdev, "Only internal phy supported\n"); + return; + } + + netdev_dbg(netdev, "sr_mdio_write() phy_id=0x%02x, loc=0x%02x, val=0x%04x\n", + phy_id, loc, val); + + sr_share_write_word(dev, 1, loc, res); +} + +static u32 sr9700_get_link(struct net_device *netdev) +{ + struct usbnet *dev = netdev_priv(netdev); + u8 value = 0; + int rc = 0; + + /* Get the Link Status directly */ + sr_read_reg(dev, SR_NSR, &value); + if (value & NSR_LINKST) + rc = 1; + + return rc; +} + +static int sr9700_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) +{ + struct usbnet *dev = netdev_priv(netdev); + + return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL); +} + +static const struct ethtool_ops sr9700_ethtool_ops = { + .get_drvinfo = usbnet_get_drvinfo, + .get_link = sr9700_get_link, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, + .get_eeprom_len = sr9700_get_eeprom_len, + .get_eeprom = sr9700_get_eeprom, + .get_settings = usbnet_get_settings, + .set_settings = usbnet_set_settings, + .nway_reset = usbnet_nway_reset, +}; + +static void sr9700_set_multicast(struct net_device *netdev) +{ + struct usbnet *dev = netdev_priv(netdev); + /* 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; + /* rx_ctl setting : enable, disable_long, disable_crc */ + u8 rx_ctl = RCR_RXEN | RCR_DIS_CRC | RCR_DIS_LONG; + + memset(hashes, 0x00, SR_MCAST_SIZE); + /* broadcast address */ + hashes[SR_MCAST_SIZE - 1] |= SR_MCAST_ADDR_FLAG; + if (netdev->flags & IFF_PROMISC) { + rx_ctl |= RCR_PRMSC; + } else if (netdev->flags & IFF_ALLMULTI || + netdev_mc_count(netdev) > SR_MCAST_MAX) { + rx_ctl |= RCR_RUNT; + } else if (!netdev_mc_empty(netdev)) { + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, netdev) { + u32 crc = ether_crc(ETH_ALEN, ha->addr) >> 26; + hashes[crc >> 3] |= 1 << (crc & 0x7); + } + } + + sr_write_async(dev, SR_MAR, SR_MCAST_SIZE, hashes); + sr_write_reg_async(dev, SR_RCR, rx_ctl); +} + +static int sr9700_set_mac_address(struct net_device *netdev, void *p) +{ + struct usbnet *dev = netdev_priv(netdev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) { + netdev_err(netdev, "not setting invalid mac address %pM\n", + addr->sa_data); + return -EINVAL; + } + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + sr_write_async(dev, SR_PAR, 6, netdev->dev_addr); + + return 0; +} + +static const struct net_device_ops sr9700_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 = sr9700_ioctl, + .ndo_set_rx_mode = sr9700_set_multicast, + .ndo_set_mac_address = sr9700_set_mac_address, +}; + +static int sr9700_bind(struct usbnet *dev, struct usb_interface *intf) +{ + struct net_device *netdev; + struct mii_if_info *mii; + int ret; + + ret = usbnet_get_endpoints(dev, intf); + if (ret) + goto out; + + netdev = dev->net; + + netdev->netdev_ops = &sr9700_netdev_ops; + netdev->ethtool_ops = &sr9700_ethtool_ops; + netdev->hard_header_len += SR_TX_OVERHEAD; + dev->hard_mtu = netdev->mtu + netdev->hard_header_len; + /* bulkin buffer is preferably not less than 3K */ + dev->rx_urb_size = 3072; + + mii = &dev->mii; + mii->dev = netdev; + mii->mdio_read = sr_mdio_read; + mii->mdio_write = sr_mdio_write; + mii->phy_id_mask = 0x1f; + mii->reg_num_mask = 0x1f; + + sr_write_reg(dev, SR_NCR, NCR_RST); + udelay(20); + + /* read MAC + * After Chip Power on, the Chip will reload the MAC from + * EEPROM automatically to PAR. In case there is no EEPROM externally, + * a default MAC address is stored in PAR for making chip work properly. + */ + if (sr_read(dev, SR_PAR, ETH_ALEN, netdev->dev_addr) < 0) { + netdev_err(netdev, "Error reading MAC address\n"); + ret = -ENODEV; + goto out; + } + + /* power up and reset phy */ + sr_write_reg(dev, SR_PRR, PRR_PHY_RST); + /* at least 10ms, here 20ms for safe */ + mdelay(20); + sr_write_reg(dev, SR_PRR, 0); + /* at least 1ms, here 2ms for reading right register */ + udelay(2 * 1000); + + /* receive broadcast packets */ + sr9700_set_multicast(netdev); + + sr_mdio_write(netdev, mii->phy_id, MII_BMCR, BMCR_RESET); + sr_mdio_write(netdev, mii->phy_id, MII_ADVERTISE, ADVERTISE_ALL | + ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP); + mii_nway_restart(mii); + +out: + return ret; +} + +static int sr9700_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + struct sk_buff *sr_skb; + int len; + + /* skb content (packets) format : + * p0 p1 p2 ...... pm + * / \ + * / \ + * / \ + * / \ + * p0b0 p0b1 p0b2 p0b3 ...... p0b(n-4) p0b(n-3)...p0bn + * + * p0 : packet 0 + * p0b0 : packet 0 byte 0 + * + * b0: rx status + * b1: packet length (incl crc) low + * b2: packet length (incl crc) high + * b3..n-4: packet data + * bn-3..bn: ethernet packet crc + */ + if (unlikely(skb->len < SR_RX_OVERHEAD)) { + netdev_err(dev->net, "unexpected tiny rx frame\n"); + return 0; + } + + /* one skb may contains multiple packets */ + while (skb->len > SR_RX_OVERHEAD) { + if (skb->data[0] != 0x40) + return 0; + + /* ignore the CRC length */ + len = (skb->data[1] | (skb->data[2] << 8)) - 4; + + if (len > ETH_FRAME_LEN) + return 0; + + /* the last packet of current skb */ + if (skb->len == (len + SR_RX_OVERHEAD)) { + skb_pull(skb, 3); + skb->len = len; + skb_set_tail_pointer(skb, len); + skb->truesize = len + sizeof(struct sk_buff); + return 2; + } + + /* skb_clone is used for address align */ + sr_skb = skb_clone(skb, GFP_ATOMIC); + if (!sr_skb) + return 0; + + sr_skb->len = len; + sr_skb->data = skb->data + 3; + skb_set_tail_pointer(sr_skb, len); + sr_skb->truesize = len + sizeof(struct sk_buff); + usbnet_skb_return(dev, sr_skb); + + skb_pull(skb, len + SR_RX_OVERHEAD); + }; + + return 0; +} + +static struct sk_buff *sr9700_tx_fixup(struct usbnet *dev, struct sk_buff *skb, + gfp_t flags) +{ + int len; + + /* SR9700 can only send out one ethernet packet at once. + * + * b0 b1 b2 b3 ...... b(n-4) b(n-3)...bn + * + * b0: rx status + * b1: packet length (incl crc) low + * b2: packet length (incl crc) high + * b3..n-4: packet data + * bn-3..bn: ethernet packet crc + */ + + len = skb->len; + + if (skb_headroom(skb) < SR_TX_OVERHEAD) { + struct sk_buff *skb2; + + skb2 = skb_copy_expand(skb, SR_TX_OVERHEAD, 0, flags); + dev_kfree_skb_any(skb); + skb = skb2; + if (!skb) + return NULL; + } + + __skb_push(skb, SR_TX_OVERHEAD); + + /* usbnet adds padding if length is a multiple of packet size + * if so, adjust length value in header + */ + if ((skb->len % dev->maxpacket) == 0) + len++; + + skb->data[0] = len; + skb->data[1] = len >> 8; + + return skb; +} + +static void sr9700_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) { + usbnet_link_change(dev, link, 1); + netdev_dbg(dev->net, "Link Status is: %d\n", link); + } +} + +static int sr9700_link_reset(struct usbnet *dev) +{ + struct ethtool_cmd ecmd; + + mii_check_media(&dev->mii, 1, 1); + mii_ethtool_gset(&dev->mii, &ecmd); + + netdev_dbg(dev->net, "link_reset() speed: %d duplex: %d\n", + ecmd.speed, ecmd.duplex); + + return 0; +} + +static const struct driver_info sr9700_driver_info = { + .description = "CoreChip SR9700 USB Ethernet", + .flags = FLAG_ETHER, + .bind = sr9700_bind, + .rx_fixup = sr9700_rx_fixup, + .tx_fixup = sr9700_tx_fixup, + .status = sr9700_status, + .link_reset = sr9700_link_reset, + .reset = sr9700_link_reset, +}; + +static const struct usb_device_id products[] = { + { + USB_DEVICE(0x0fe6, 0x9700), /* SR9700 device */ + .driver_info = (unsigned long)&sr9700_driver_info, + }, + {}, /* END */ +}; + +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver sr9700_usb_driver = { + .name = "sr9700", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(sr9700_usb_driver); + +MODULE_AUTHOR("liujl <liujunliang_ljl@163.com>"); +MODULE_DESCRIPTION("SR9700 one chip USB 1.1 USB to Ethernet device from http://www.corechip-sz.com/"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/sr9700.h b/drivers/net/usb/sr9700.h new file mode 100644 index 000000000..258b03027 --- /dev/null +++ b/drivers/net/usb/sr9700.h @@ -0,0 +1,173 @@ +/* + * CoreChip-sz SR9700 one chip USB 1.1 Ethernet Devices + * + * Author : Liu Junliang <liujunliang_ljl@163.com> + * + * 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 _SR9700_H +#define _SR9700_H + +/* sr9700 spec. register table on Linux platform */ + +/* Network Control Reg */ +#define SR_NCR 0x00 +#define NCR_RST (1 << 0) +#define NCR_LBK (3 << 1) +#define NCR_FDX (1 << 3) +#define NCR_WAKEEN (1 << 6) +/* Network Status Reg */ +#define SR_NSR 0x01 +#define NSR_RXRDY (1 << 0) +#define NSR_RXOV (1 << 1) +#define NSR_TX1END (1 << 2) +#define NSR_TX2END (1 << 3) +#define NSR_TXFULL (1 << 4) +#define NSR_WAKEST (1 << 5) +#define NSR_LINKST (1 << 6) +#define NSR_SPEED (1 << 7) +/* Tx Control Reg */ +#define SR_TCR 0x02 +#define TCR_CRC_DIS (1 << 1) +#define TCR_PAD_DIS (1 << 2) +#define TCR_LC_CARE (1 << 3) +#define TCR_CRS_CARE (1 << 4) +#define TCR_EXCECM (1 << 5) +#define TCR_LF_EN (1 << 6) +/* Tx Status Reg for Packet Index 1 */ +#define SR_TSR1 0x03 +#define TSR1_EC (1 << 2) +#define TSR1_COL (1 << 3) +#define TSR1_LC (1 << 4) +#define TSR1_NC (1 << 5) +#define TSR1_LOC (1 << 6) +#define TSR1_TLF (1 << 7) +/* Tx Status Reg for Packet Index 2 */ +#define SR_TSR2 0x04 +#define TSR2_EC (1 << 2) +#define TSR2_COL (1 << 3) +#define TSR2_LC (1 << 4) +#define TSR2_NC (1 << 5) +#define TSR2_LOC (1 << 6) +#define TSR2_TLF (1 << 7) +/* Rx Control Reg*/ +#define SR_RCR 0x05 +#define RCR_RXEN (1 << 0) +#define RCR_PRMSC (1 << 1) +#define RCR_RUNT (1 << 2) +#define RCR_ALL (1 << 3) +#define RCR_DIS_CRC (1 << 4) +#define RCR_DIS_LONG (1 << 5) +/* Rx Status Reg */ +#define SR_RSR 0x06 +#define RSR_AE (1 << 2) +#define RSR_MF (1 << 6) +#define RSR_RF (1 << 7) +/* Rx Overflow Counter Reg */ +#define SR_ROCR 0x07 +#define ROCR_ROC (0x7F << 0) +#define ROCR_RXFU (1 << 7) +/* Back Pressure Threshold Reg */ +#define SR_BPTR 0x08 +#define BPTR_JPT (0x0F << 0) +#define BPTR_BPHW (0x0F << 4) +/* Flow Control Threshold Reg */ +#define SR_FCTR 0x09 +#define FCTR_LWOT (0x0F << 0) +#define FCTR_HWOT (0x0F << 4) +/* rx/tx Flow Control Reg */ +#define SR_FCR 0x0A +#define FCR_FLCE (1 << 0) +#define FCR_BKPA (1 << 4) +#define FCR_TXPEN (1 << 5) +#define FCR_TXPF (1 << 6) +#define FCR_TXP0 (1 << 7) +/* Eeprom & Phy Control Reg */ +#define SR_EPCR 0x0B +#define EPCR_ERRE (1 << 0) +#define EPCR_ERPRW (1 << 1) +#define EPCR_ERPRR (1 << 2) +#define EPCR_EPOS (1 << 3) +#define EPCR_WEP (1 << 4) +/* Eeprom & Phy Address Reg */ +#define SR_EPAR 0x0C +#define EPAR_EROA (0x3F << 0) +#define EPAR_PHY_ADR_MASK (0x03 << 6) +#define EPAR_PHY_ADR (0x01 << 6) +/* Eeprom & Phy Data Reg */ +#define SR_EPDR 0x0D /* 0x0D ~ 0x0E for Data Reg Low & High */ +/* Wakeup Control Reg */ +#define SR_WCR 0x0F +#define WCR_MAGICST (1 << 0) +#define WCR_LINKST (1 << 2) +#define WCR_MAGICEN (1 << 3) +#define WCR_LINKEN (1 << 5) +/* Physical Address Reg */ +#define SR_PAR 0x10 /* 0x10 ~ 0x15 6 bytes for PAR */ +/* Multicast Address Reg */ +#define SR_MAR 0x16 /* 0x16 ~ 0x1D 8 bytes for MAR */ +/* 0x1e unused */ +/* Phy Reset Reg */ +#define SR_PRR 0x1F +#define PRR_PHY_RST (1 << 0) +/* Tx sdram Write Pointer Address Low */ +#define SR_TWPAL 0x20 +/* Tx sdram Write Pointer Address High */ +#define SR_TWPAH 0x21 +/* Tx sdram Read Pointer Address Low */ +#define SR_TRPAL 0x22 +/* Tx sdram Read Pointer Address High */ +#define SR_TRPAH 0x23 +/* Rx sdram Write Pointer Address Low */ +#define SR_RWPAL 0x24 +/* Rx sdram Write Pointer Address High */ +#define SR_RWPAH 0x25 +/* Rx sdram Read Pointer Address Low */ +#define SR_RRPAL 0x26 +/* Rx sdram Read Pointer Address High */ +#define SR_RRPAH 0x27 +/* Vendor ID register */ +#define SR_VID 0x28 /* 0x28 ~ 0x29 2 bytes for VID */ +/* Product ID register */ +#define SR_PID 0x2A /* 0x2A ~ 0x2B 2 bytes for PID */ +/* CHIP Revision register */ +#define SR_CHIPR 0x2C +/* 0x2D --> 0xEF unused */ +/* USB Device Address */ +#define SR_USBDA 0xF0 +#define USBDA_USBFA (0x7F << 0) +/* RX packet Counter Reg */ +#define SR_RXC 0xF1 +/* Tx packet Counter & USB Status Reg */ +#define SR_TXC_USBS 0xF2 +#define TXC_USBS_TXC0 (1 << 0) +#define TXC_USBS_TXC1 (1 << 1) +#define TXC_USBS_TXC2 (1 << 2) +#define TXC_USBS_EP1RDY (1 << 5) +#define TXC_USBS_SUSFLAG (1 << 6) +#define TXC_USBS_RXFAULT (1 << 7) +/* USB Control register */ +#define SR_USBC 0xF4 +#define USBC_EP3NAK (1 << 4) +#define USBC_EP3ACK (1 << 5) + +/* Register access commands and flags */ +#define SR_RD_REGS 0x00 +#define SR_WR_REGS 0x01 +#define SR_WR_REG 0x03 +#define SR_REQ_RD_REG (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) +#define SR_REQ_WR_REG (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE) + +/* parameters */ +#define SR_SHARE_TIMEOUT 1000 +#define SR_EEPROM_LEN 256 +#define SR_MCAST_SIZE 8 +#define SR_MCAST_ADDR_FLAG 0x80 +#define SR_MCAST_MAX 64 +#define SR_TX_OVERHEAD 2 /* 2bytes header */ +#define SR_RX_OVERHEAD 7 /* 3bytes header + 4crc tail */ + +#endif /* _SR9700_H */ diff --git a/drivers/net/usb/sr9800.c b/drivers/net/usb/sr9800.c new file mode 100644 index 000000000..953de1326 --- /dev/null +++ b/drivers/net/usb/sr9800.c @@ -0,0 +1,875 @@ +/* CoreChip-sz SR9800 one chip USB 2.0 Ethernet Devices + * + * Author : Liu Junliang <liujunliang_ljl@163.com> + * + * Based on asix_common.c, asix_devices.c + * + * 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.* + */ + +#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> + +#include "sr9800.h" + +static int sr_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int err; + + err = usbnet_read_cmd(dev, cmd, SR_REQ_RD_REG, value, index, + data, size); + if ((err != size) && (err >= 0)) + err = -EINVAL; + + return err; +} + +static int sr_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + int err; + + err = usbnet_write_cmd(dev, cmd, SR_REQ_WR_REG, value, index, + data, size); + if ((err != size) && (err >= 0)) + err = -EINVAL; + + return err; +} + +static void +sr_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + usbnet_write_cmd_async(dev, cmd, SR_REQ_WR_REG, value, index, data, + size); +} + +static int sr_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + int offset = 0; + + /* This check is no longer done by usbnet */ + if (skb->len < dev->net->hard_header_len) + return 0; + + while (offset + sizeof(u32) < skb->len) { + struct sk_buff *sr_skb; + u16 size; + u32 header = get_unaligned_le32(skb->data + offset); + + offset += sizeof(u32); + /* get the packet length */ + size = (u16) (header & 0x7ff); + if (size != ((~header >> 16) & 0x07ff)) { + netdev_err(dev->net, "%s : Bad Header Length\n", + __func__); + return 0; + } + + if ((size > dev->net->mtu + ETH_HLEN + VLAN_HLEN) || + (size + offset > skb->len)) { + netdev_err(dev->net, "%s : Bad RX Length %d\n", + __func__, size); + return 0; + } + sr_skb = netdev_alloc_skb_ip_align(dev->net, size); + if (!sr_skb) + return 0; + + skb_put(sr_skb, size); + memcpy(sr_skb->data, skb->data + offset, size); + usbnet_skb_return(dev, sr_skb); + + offset += (size + 1) & 0xfffe; + } + + if (skb->len != offset) { + netdev_err(dev->net, "%s : Bad SKB Length %d\n", __func__, + skb->len); + return 0; + } + + return 1; +} + +static struct sk_buff *sr_tx_fixup(struct usbnet *dev, struct sk_buff *skb, + gfp_t flags) +{ + int headroom = skb_headroom(skb); + int tailroom = skb_tailroom(skb); + u32 padbytes = 0xffff0000; + u32 packet_len; + int padlen; + + 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)); + } + + usbnet_set_skb_tx_stats(skb, 1, 0); + return skb; +} + +static void sr_status(struct usbnet *dev, struct urb *urb) +{ + struct sr9800_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) { + usbnet_link_change(dev, link, 1); + netdev_dbg(dev->net, "Link Status is: %d\n", link); + } + + return; +} + +static inline int sr_set_sw_mii(struct usbnet *dev) +{ + int ret; + + ret = sr_write_cmd(dev, SR_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 sr_set_hw_mii(struct usbnet *dev) +{ + int ret; + + ret = sr_write_cmd(dev, SR_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 sr_get_phy_addr(struct usbnet *dev) +{ + u8 buf[2]; + int ret; + + ret = sr_read_cmd(dev, SR_CMD_READ_PHY_ID, 0, 0, 2, buf); + if (ret < 0) { + netdev_err(dev->net, "%s : Error reading PHYID register:%02x\n", + __func__, ret); + goto out; + } + netdev_dbg(dev->net, "%s : returning 0x%04x\n", __func__, + *((__le16 *)buf)); + + ret = buf[1]; + +out: + return ret; +} + +static int sr_sw_reset(struct usbnet *dev, u8 flags) +{ + int ret; + + ret = sr_write_cmd(dev, SR_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 sr_read_rx_ctl(struct usbnet *dev) +{ + __le16 v; + int ret; + + ret = sr_read_cmd(dev, SR_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 sr_write_rx_ctl(struct usbnet *dev, u16 mode) +{ + int ret; + + netdev_dbg(dev->net, "%s : mode = 0x%04x\n", __func__, mode); + ret = sr_write_cmd(dev, SR_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 sr_read_medium_status(struct usbnet *dev) +{ + __le16 v; + int ret; + + ret = sr_read_cmd(dev, SR_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 sr_write_medium_mode(struct usbnet *dev, u16 mode) +{ + int ret; + + netdev_dbg(dev->net, "%s : mode = 0x%04x\n", __func__, mode); + ret = sr_write_cmd(dev, SR_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 sr_write_gpio(struct usbnet *dev, u16 value, int sleep) +{ + int ret; + + netdev_dbg(dev->net, "%s : value = 0x%04x\n", __func__, value); + ret = sr_write_cmd(dev, SR_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; +} + +/* SR9800 have a 16-bit RX_CTL value */ +static void sr_set_multicast(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct sr_data *data = (struct sr_data *)&dev->data; + u16 rx_ctl = SR_DEFAULT_RX_CTL; + + if (net->flags & IFF_PROMISC) { + rx_ctl |= SR_RX_CTL_PRO; + } else if (net->flags & IFF_ALLMULTI || + netdev_mc_count(net) > SR_MAX_MCAST) { + rx_ctl |= SR_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, SR_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); + } + + sr_write_cmd_async(dev, SR_CMD_WRITE_MULTI_FILTER, 0, 0, + SR_MCAST_FILTER_SIZE, data->multi_filter); + + rx_ctl |= SR_RX_CTL_AM; + } + + sr_write_cmd_async(dev, SR_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); +} + +static int sr_mdio_read(struct net_device *net, int phy_id, int loc) +{ + struct usbnet *dev = netdev_priv(net); + __le16 res; + + mutex_lock(&dev->phy_mutex); + sr_set_sw_mii(dev); + sr_read_cmd(dev, SR_CMD_READ_MII_REG, phy_id, (__u16)loc, 2, &res); + sr_set_hw_mii(dev); + mutex_unlock(&dev->phy_mutex); + + netdev_dbg(dev->net, + "%s : phy_id=0x%02x, loc=0x%02x, returns=0x%04x\n", __func__, + phy_id, loc, le16_to_cpu(res)); + + return le16_to_cpu(res); +} + +static void +sr_mdio_write(struct net_device *net, int phy_id, int loc, int val) +{ + struct usbnet *dev = netdev_priv(net); + __le16 res = cpu_to_le16(val); + + netdev_dbg(dev->net, + "%s : phy_id=0x%02x, loc=0x%02x, val=0x%04x\n", __func__, + phy_id, loc, val); + mutex_lock(&dev->phy_mutex); + sr_set_sw_mii(dev); + sr_write_cmd(dev, SR_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, &res); + sr_set_hw_mii(dev); + mutex_unlock(&dev->phy_mutex); +} + +/* Get the PHY Identifier from the PHYSID1 & PHYSID2 MII registers */ +static u32 sr_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 = sr_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 = sr_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 +sr_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + u8 opt; + + if (sr_read_cmd(dev, SR_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 & SR_MONITOR_LINK) + wolinfo->wolopts |= WAKE_PHY; + if (opt & SR_MONITOR_MAGIC) + wolinfo->wolopts |= WAKE_MAGIC; +} + +static int +sr_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 |= SR_MONITOR_LINK; + if (wolinfo->wolopts & WAKE_MAGIC) + opt |= SR_MONITOR_MAGIC; + + if (sr_write_cmd(dev, SR_CMD_WRITE_MONITOR_MODE, + opt, 0, 0, NULL) < 0) + return -EINVAL; + + return 0; +} + +static int sr_get_eeprom_len(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct sr_data *data = (struct sr_data *)&dev->data; + + return data->eeprom_len; +} + +static int sr_get_eeprom(struct net_device *net, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct usbnet *dev = netdev_priv(net); + __le16 *ebuf = (__le16 *)data; + int ret; + 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 = SR_EEPROM_MAGIC; + + /* sr9800 returns 2 bytes from eeprom on read */ + for (i = 0; i < eeprom->len / 2; i++) { + ret = sr_read_cmd(dev, SR_CMD_READ_EEPROM, eeprom->offset + i, + 0, 2, &ebuf[i]); + if (ret < 0) + return -EINVAL; + } + return 0; +} + +static void sr_get_drvinfo(struct net_device *net, + struct ethtool_drvinfo *info) +{ + struct usbnet *dev = netdev_priv(net); + struct sr_data *data = (struct sr_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 sr_get_link(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + return mii_link_ok(&dev->mii); +} + +static int sr_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 sr_set_mac_address(struct net_device *net, void *p) +{ + struct usbnet *dev = netdev_priv(net); + struct sr_data *data = (struct sr_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); + sr_write_cmd_async(dev, SR_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, + data->mac_addr); + + return 0; +} + +static const struct ethtool_ops sr9800_ethtool_ops = { + .get_drvinfo = sr_get_drvinfo, + .get_link = sr_get_link, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, + .get_wol = sr_get_wol, + .set_wol = sr_set_wol, + .get_eeprom_len = sr_get_eeprom_len, + .get_eeprom = sr_get_eeprom, + .get_settings = usbnet_get_settings, + .set_settings = usbnet_set_settings, + .nway_reset = usbnet_nway_reset, +}; + +static int sr9800_link_reset(struct usbnet *dev) +{ + struct ethtool_cmd ecmd = { .cmd = ETHTOOL_GSET }; + u16 mode; + + mii_check_media(&dev->mii, 1, 1); + mii_ethtool_gset(&dev->mii, &ecmd); + mode = SR9800_MEDIUM_DEFAULT; + + if (ethtool_cmd_speed(&ecmd) != SPEED_100) + mode &= ~SR_MEDIUM_PS; + + if (ecmd.duplex != DUPLEX_FULL) + mode &= ~SR_MEDIUM_FD; + + netdev_dbg(dev->net, "%s : speed: %u duplex: %d mode: 0x%04x\n", + __func__, ethtool_cmd_speed(&ecmd), ecmd.duplex, mode); + + sr_write_medium_mode(dev, mode); + + return 0; +} + + +static int sr9800_set_default_mode(struct usbnet *dev) +{ + u16 rx_ctl; + int ret; + + sr_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET); + sr_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_CSMA); + mii_nway_restart(&dev->mii); + + ret = sr_write_medium_mode(dev, SR9800_MEDIUM_DEFAULT); + if (ret < 0) + goto out; + + ret = sr_write_cmd(dev, SR_CMD_WRITE_IPG012, + SR9800_IPG0_DEFAULT | SR9800_IPG1_DEFAULT, + SR9800_IPG2_DEFAULT, 0, NULL); + if (ret < 0) { + netdev_dbg(dev->net, "Write IPG,IPG1,IPG2 failed: %d\n", ret); + goto out; + } + + /* Set RX_CTL to default values with 2k buffer, and enable cactus */ + ret = sr_write_rx_ctl(dev, SR_DEFAULT_RX_CTL); + if (ret < 0) + goto out; + + rx_ctl = sr_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after all initializations\n", + rx_ctl); + + rx_ctl = sr_read_medium_status(dev); + netdev_dbg(dev->net, "Medium Status:0x%04x after all initializations\n", + rx_ctl); + + return 0; +out: + return ret; +} + +static int sr9800_reset(struct usbnet *dev) +{ + struct sr_data *data = (struct sr_data *)&dev->data; + int ret, embd_phy; + u16 rx_ctl; + + ret = sr_write_gpio(dev, + SR_GPIO_RSE | SR_GPIO_GPO_2 | SR_GPIO_GPO2EN, 5); + if (ret < 0) + goto out; + + embd_phy = ((sr_get_phy_addr(dev) & 0x1f) == 0x10 ? 1 : 0); + + ret = sr_write_cmd(dev, SR_CMD_SW_PHY_SELECT, embd_phy, 0, 0, NULL); + if (ret < 0) { + netdev_dbg(dev->net, "Select PHY #1 failed: %d\n", ret); + goto out; + } + + ret = sr_sw_reset(dev, SR_SWRESET_IPPD | SR_SWRESET_PRL); + if (ret < 0) + goto out; + + msleep(150); + + ret = sr_sw_reset(dev, SR_SWRESET_CLEAR); + if (ret < 0) + goto out; + + msleep(150); + + if (embd_phy) { + ret = sr_sw_reset(dev, SR_SWRESET_IPRL); + if (ret < 0) + goto out; + } else { + ret = sr_sw_reset(dev, SR_SWRESET_PRTE); + if (ret < 0) + goto out; + } + + msleep(150); + rx_ctl = sr_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after software reset\n", rx_ctl); + ret = sr_write_rx_ctl(dev, 0x0000); + if (ret < 0) + goto out; + + rx_ctl = sr_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x setting to 0x0000\n", rx_ctl); + + ret = sr_sw_reset(dev, SR_SWRESET_PRL); + if (ret < 0) + goto out; + + msleep(150); + + ret = sr_sw_reset(dev, SR_SWRESET_IPRL | SR_SWRESET_PRL); + if (ret < 0) + goto out; + + msleep(150); + + ret = sr9800_set_default_mode(dev); + if (ret < 0) + goto out; + + /* Rewrite MAC address */ + memcpy(data->mac_addr, dev->net->dev_addr, ETH_ALEN); + ret = sr_write_cmd(dev, SR_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, + data->mac_addr); + if (ret < 0) + goto out; + + return 0; + +out: + return ret; +} + +static const struct net_device_ops sr9800_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 = sr_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_do_ioctl = sr_ioctl, + .ndo_set_rx_mode = sr_set_multicast, +}; + +static int sr9800_phy_powerup(struct usbnet *dev) +{ + int ret; + + /* set the embedded Ethernet PHY in power-down state */ + ret = sr_sw_reset(dev, SR_SWRESET_IPPD | SR_SWRESET_IPRL); + if (ret < 0) { + netdev_err(dev->net, "Failed to power down PHY : %d\n", ret); + return ret; + } + msleep(20); + + /* set the embedded Ethernet PHY in power-up state */ + ret = sr_sw_reset(dev, SR_SWRESET_IPRL); + if (ret < 0) { + netdev_err(dev->net, "Failed to reset PHY: %d\n", ret); + return ret; + } + msleep(600); + + /* set the embedded Ethernet PHY in reset state */ + ret = sr_sw_reset(dev, SR_SWRESET_CLEAR); + if (ret < 0) { + netdev_err(dev->net, "Failed to power up PHY: %d\n", ret); + return ret; + } + msleep(20); + + /* set the embedded Ethernet PHY in power-up state */ + ret = sr_sw_reset(dev, SR_SWRESET_IPRL); + if (ret < 0) { + netdev_err(dev->net, "Failed to reset PHY: %d\n", ret); + return ret; + } + + return 0; +} + +static int sr9800_bind(struct usbnet *dev, struct usb_interface *intf) +{ + struct sr_data *data = (struct sr_data *)&dev->data; + u16 led01_mux, led23_mux; + int ret, embd_phy; + u32 phyid; + u16 rx_ctl; + + data->eeprom_len = SR9800_EEPROM_LEN; + + usbnet_get_endpoints(dev, intf); + + /* LED Setting Rule : + * AABB:CCDD + * AA : MFA0(LED0) + * BB : MFA1(LED1) + * CC : MFA2(LED2), Reserved for SR9800 + * DD : MFA3(LED3), Reserved for SR9800 + */ + led01_mux = (SR_LED_MUX_LINK_ACTIVE << 8) | SR_LED_MUX_LINK; + led23_mux = (SR_LED_MUX_LINK_ACTIVE << 8) | SR_LED_MUX_TX_ACTIVE; + ret = sr_write_cmd(dev, SR_CMD_LED_MUX, led01_mux, led23_mux, 0, NULL); + if (ret < 0) { + netdev_err(dev->net, "set LINK LED failed : %d\n", ret); + goto out; + } + + /* Get the MAC address */ + ret = sr_read_cmd(dev, SR_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, + dev->net->dev_addr); + if (ret < 0) { + netdev_dbg(dev->net, "Failed to read MAC address: %d\n", ret); + return ret; + } + netdev_dbg(dev->net, "mac addr : %pM\n", dev->net->dev_addr); + + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = sr_mdio_read; + dev->mii.mdio_write = sr_mdio_write; + dev->mii.phy_id_mask = 0x1f; + dev->mii.reg_num_mask = 0x1f; + dev->mii.phy_id = sr_get_phy_addr(dev); + + dev->net->netdev_ops = &sr9800_netdev_ops; + dev->net->ethtool_ops = &sr9800_ethtool_ops; + + embd_phy = ((dev->mii.phy_id & 0x1f) == 0x10 ? 1 : 0); + /* Reset the PHY to normal operation mode */ + ret = sr_write_cmd(dev, SR_CMD_SW_PHY_SELECT, embd_phy, 0, 0, NULL); + if (ret < 0) { + netdev_dbg(dev->net, "Select PHY #1 failed: %d\n", ret); + return ret; + } + + /* Init PHY routine */ + ret = sr9800_phy_powerup(dev); + if (ret < 0) + goto out; + + rx_ctl = sr_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x after software reset\n", rx_ctl); + ret = sr_write_rx_ctl(dev, 0x0000); + if (ret < 0) + goto out; + + rx_ctl = sr_read_rx_ctl(dev); + netdev_dbg(dev->net, "RX_CTL is 0x%04x setting to 0x0000\n", rx_ctl); + + /* Read PHYID register *AFTER* the PHY was reset properly */ + phyid = sr_get_phyid(dev); + netdev_dbg(dev->net, "PHYID=0x%08x\n", phyid); + + /* medium mode setting */ + ret = sr9800_set_default_mode(dev); + if (ret < 0) + goto out; + + if (dev->udev->speed == USB_SPEED_HIGH) { + ret = sr_write_cmd(dev, SR_CMD_BULKIN_SIZE, + SR9800_BULKIN_SIZE[SR9800_MAX_BULKIN_4K].byte_cnt, + SR9800_BULKIN_SIZE[SR9800_MAX_BULKIN_4K].threshold, + 0, NULL); + if (ret < 0) { + netdev_err(dev->net, "Reset RX_CTL failed: %d\n", ret); + goto out; + } + dev->rx_urb_size = + SR9800_BULKIN_SIZE[SR9800_MAX_BULKIN_4K].size; + } else { + ret = sr_write_cmd(dev, SR_CMD_BULKIN_SIZE, + SR9800_BULKIN_SIZE[SR9800_MAX_BULKIN_2K].byte_cnt, + SR9800_BULKIN_SIZE[SR9800_MAX_BULKIN_2K].threshold, + 0, NULL); + if (ret < 0) { + netdev_err(dev->net, "Reset RX_CTL failed: %d\n", ret); + goto out; + } + dev->rx_urb_size = + SR9800_BULKIN_SIZE[SR9800_MAX_BULKIN_2K].size; + } + netdev_dbg(dev->net, "%s : setting rx_urb_size with : %zu\n", __func__, + dev->rx_urb_size); + return 0; + +out: + return ret; +} + +static const struct driver_info sr9800_driver_info = { + .description = "CoreChip SR9800 USB 2.0 Ethernet", + .bind = sr9800_bind, + .status = sr_status, + .link_reset = sr9800_link_reset, + .reset = sr9800_reset, + .flags = DRIVER_FLAG, + .rx_fixup = sr_rx_fixup, + .tx_fixup = sr_tx_fixup, +}; + +static const struct usb_device_id products[] = { + { + USB_DEVICE(0x0fe6, 0x9800), /* SR9800 Device */ + .driver_info = (unsigned long) &sr9800_driver_info, + }, + {}, /* END */ +}; + +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver sr_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(sr_driver); + +MODULE_AUTHOR("Liu Junliang <liujunliang_ljl@163.com"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_DESCRIPTION("SR9800 USB 2.0 USB2NET Dev : http://www.corechip-sz.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/sr9800.h b/drivers/net/usb/sr9800.h new file mode 100644 index 000000000..18f670251 --- /dev/null +++ b/drivers/net/usb/sr9800.h @@ -0,0 +1,202 @@ +/* CoreChip-sz SR9800 one chip USB 2.0 Ethernet Devices + * + * Author : Liu Junliang <liujunliang_ljl@163.com> + * + * 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. + */ + +#ifndef _SR9800_H +#define _SR9800_H + +/* SR9800 spec. command table on Linux Platform */ + +/* command : Software Station Management Control Reg */ +#define SR_CMD_SET_SW_MII 0x06 +/* command : PHY Read Reg */ +#define SR_CMD_READ_MII_REG 0x07 +/* command : PHY Write Reg */ +#define SR_CMD_WRITE_MII_REG 0x08 +/* command : Hardware Station Management Control Reg */ +#define SR_CMD_SET_HW_MII 0x0a +/* command : SROM Read Reg */ +#define SR_CMD_READ_EEPROM 0x0b +/* command : SROM Write Reg */ +#define SR_CMD_WRITE_EEPROM 0x0c +/* command : SROM Write Enable Reg */ +#define SR_CMD_WRITE_ENABLE 0x0d +/* command : SROM Write Disable Reg */ +#define SR_CMD_WRITE_DISABLE 0x0e +/* command : RX Control Read Reg */ +#define SR_CMD_READ_RX_CTL 0x0f +#define SR_RX_CTL_PRO (1 << 0) +#define SR_RX_CTL_AMALL (1 << 1) +#define SR_RX_CTL_SEP (1 << 2) +#define SR_RX_CTL_AB (1 << 3) +#define SR_RX_CTL_AM (1 << 4) +#define SR_RX_CTL_AP (1 << 5) +#define SR_RX_CTL_ARP (1 << 6) +#define SR_RX_CTL_SO (1 << 7) +#define SR_RX_CTL_RH1M (1 << 8) +#define SR_RX_CTL_RH2M (1 << 9) +#define SR_RX_CTL_RH3M (1 << 10) +/* command : RX Control Write Reg */ +#define SR_CMD_WRITE_RX_CTL 0x10 +/* command : IPG0/IPG1/IPG2 Control Read Reg */ +#define SR_CMD_READ_IPG012 0x11 +/* command : IPG0/IPG1/IPG2 Control Write Reg */ +#define SR_CMD_WRITE_IPG012 0x12 +/* command : Node ID Read Reg */ +#define SR_CMD_READ_NODE_ID 0x13 +/* command : Node ID Write Reg */ +#define SR_CMD_WRITE_NODE_ID 0x14 +/* command : Multicast Filter Array Read Reg */ +#define SR_CMD_READ_MULTI_FILTER 0x15 +/* command : Multicast Filter Array Write Reg */ +#define SR_CMD_WRITE_MULTI_FILTER 0x16 +/* command : Eth/HomePNA PHY Address Reg */ +#define SR_CMD_READ_PHY_ID 0x19 +/* command : Medium Status Read Reg */ +#define SR_CMD_READ_MEDIUM_STATUS 0x1a +#define SR_MONITOR_LINK (1 << 1) +#define SR_MONITOR_MAGIC (1 << 2) +#define SR_MONITOR_HSFS (1 << 4) +/* command : Medium Status Write Reg */ +#define SR_CMD_WRITE_MEDIUM_MODE 0x1b +#define SR_MEDIUM_GM (1 << 0) +#define SR_MEDIUM_FD (1 << 1) +#define SR_MEDIUM_AC (1 << 2) +#define SR_MEDIUM_ENCK (1 << 3) +#define SR_MEDIUM_RFC (1 << 4) +#define SR_MEDIUM_TFC (1 << 5) +#define SR_MEDIUM_JFE (1 << 6) +#define SR_MEDIUM_PF (1 << 7) +#define SR_MEDIUM_RE (1 << 8) +#define SR_MEDIUM_PS (1 << 9) +#define SR_MEDIUM_RSV (1 << 10) +#define SR_MEDIUM_SBP (1 << 11) +#define SR_MEDIUM_SM (1 << 12) +/* command : Monitor Mode Status Read Reg */ +#define SR_CMD_READ_MONITOR_MODE 0x1c +/* command : Monitor Mode Status Write Reg */ +#define SR_CMD_WRITE_MONITOR_MODE 0x1d +/* command : GPIO Status Read Reg */ +#define SR_CMD_READ_GPIOS 0x1e +#define SR_GPIO_GPO0EN (1 << 0) /* GPIO0 Output enable */ +#define SR_GPIO_GPO_0 (1 << 1) /* GPIO0 Output value */ +#define SR_GPIO_GPO1EN (1 << 2) /* GPIO1 Output enable */ +#define SR_GPIO_GPO_1 (1 << 3) /* GPIO1 Output value */ +#define SR_GPIO_GPO2EN (1 << 4) /* GPIO2 Output enable */ +#define SR_GPIO_GPO_2 (1 << 5) /* GPIO2 Output value */ +#define SR_GPIO_RESERVED (1 << 6) /* Reserved */ +#define SR_GPIO_RSE (1 << 7) /* Reload serial EEPROM */ +/* command : GPIO Status Write Reg */ +#define SR_CMD_WRITE_GPIOS 0x1f +/* command : Eth PHY Power and Reset Control Reg */ +#define SR_CMD_SW_RESET 0x20 +#define SR_SWRESET_CLEAR 0x00 +#define SR_SWRESET_RR (1 << 0) +#define SR_SWRESET_RT (1 << 1) +#define SR_SWRESET_PRTE (1 << 2) +#define SR_SWRESET_PRL (1 << 3) +#define SR_SWRESET_BZ (1 << 4) +#define SR_SWRESET_IPRL (1 << 5) +#define SR_SWRESET_IPPD (1 << 6) +/* command : Software Interface Selection Status Read Reg */ +#define SR_CMD_SW_PHY_STATUS 0x21 +/* command : Software Interface Selection Status Write Reg */ +#define SR_CMD_SW_PHY_SELECT 0x22 +/* command : BULK in Buffer Size Reg */ +#define SR_CMD_BULKIN_SIZE 0x2A +/* command : LED_MUX Control Reg */ +#define SR_CMD_LED_MUX 0x70 +#define SR_LED_MUX_TX_ACTIVE (1 << 0) +#define SR_LED_MUX_RX_ACTIVE (1 << 1) +#define SR_LED_MUX_COLLISION (1 << 2) +#define SR_LED_MUX_DUP_COL (1 << 3) +#define SR_LED_MUX_DUP (1 << 4) +#define SR_LED_MUX_SPEED (1 << 5) +#define SR_LED_MUX_LINK_ACTIVE (1 << 6) +#define SR_LED_MUX_LINK (1 << 7) + +/* Register Access Flags */ +#define SR_REQ_RD_REG (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE) +#define SR_REQ_WR_REG (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE) + +/* Multicast Filter Array size & Max Number */ +#define SR_MCAST_FILTER_SIZE 8 +#define SR_MAX_MCAST 64 + +/* IPG0/1/2 Default Value */ +#define SR9800_IPG0_DEFAULT 0x15 +#define SR9800_IPG1_DEFAULT 0x0c +#define SR9800_IPG2_DEFAULT 0x12 + +/* Medium Status Default Mode */ +#define SR9800_MEDIUM_DEFAULT \ + (SR_MEDIUM_FD | SR_MEDIUM_RFC | \ + SR_MEDIUM_TFC | SR_MEDIUM_PS | \ + SR_MEDIUM_AC | SR_MEDIUM_RE) + +/* RX Control Default Setting */ +#define SR_DEFAULT_RX_CTL \ + (SR_RX_CTL_SO | SR_RX_CTL_AB | SR_RX_CTL_RH1M) + +/* EEPROM Magic Number & EEPROM Size */ +#define SR_EEPROM_MAGIC 0xdeadbeef +#define SR9800_EEPROM_LEN 0xff + +/* SR9800 Driver Version and Driver Name */ +#define DRIVER_VERSION "11-Nov-2013" +#define DRIVER_NAME "CoreChips" +#define DRIVER_FLAG \ + (FLAG_ETHER | FLAG_FRAMING_AX | FLAG_LINK_INTR | FLAG_MULTI_PACKET) + +/* SR9800 BULKIN Buffer Size */ +#define SR9800_MAX_BULKIN_2K 0 +#define SR9800_MAX_BULKIN_4K 1 +#define SR9800_MAX_BULKIN_6K 2 +#define SR9800_MAX_BULKIN_8K 3 +#define SR9800_MAX_BULKIN_16K 4 +#define SR9800_MAX_BULKIN_20K 5 +#define SR9800_MAX_BULKIN_24K 6 +#define SR9800_MAX_BULKIN_32K 7 + +struct {unsigned short size, byte_cnt, threshold; } SR9800_BULKIN_SIZE[] = { + /* 2k */ + {2048, 0x8000, 0x8001}, + /* 4k */ + {4096, 0x8100, 0x8147}, + /* 6k */ + {6144, 0x8200, 0x81EB}, + /* 8k */ + {8192, 0x8300, 0x83D7}, + /* 16 */ + {16384, 0x8400, 0x851E}, + /* 20k */ + {20480, 0x8500, 0x8666}, + /* 24k */ + {24576, 0x8600, 0x87AE}, + /* 32k */ + {32768, 0x8700, 0x8A3D}, +}; + +/* This structure cannot exceed sizeof(unsigned long [5]) AKA 20 bytes */ +struct sr_data { + u8 multi_filter[SR_MCAST_FILTER_SIZE]; + u8 mac_addr[ETH_ALEN]; + u8 phymode; + u8 ledmode; + u8 eeprom_len; +}; + +struct sr9800_int_data { + __le16 res1; + u8 link; + __le16 res2; + u8 status; + __le16 res3; +} __packed; + +#endif /* _SR9800_H */ diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c new file mode 100644 index 000000000..3c86b1072 --- /dev/null +++ b/drivers/net/usb/usbnet.c @@ -0,0 +1,2107 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +/* + * 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). + * + * 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 MAX_QUEUE_MEMORY (60 * 1518) +#define RX_QLEN(dev) ((dev)->rx_qlen) +#define TX_QLEN(dev) ((dev)->tx_qlen) + +// reawaken network queue this soon after stopping; else watchdog barks +#define TX_TIMEOUT_JIFFIES (5*HZ) + +/* throttle rx/tx briefly after some faults, so hub_wq 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 = -1, ret; + unsigned char buf [13]; + + ret = usb_string(dev->udev, iMACAddress, buf, sizeof buf); + if (ret == 12) + tmp = hex2bin(dev->net->dev_addr, buf, 6); + if (tmp < 0) { + dev_dbg(&dev->udev->dev, + "bad MAC string %d fetch, %d\n", iMACAddress, tmp); + if (ret >= 0) + ret = -EINVAL; + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_get_ethernet_addr); + +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; + } + + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status != 0) + netif_err(dev, timer, dev->net, + "intr resubmit --> %d\n", status); +} + +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; +} + +/* Submit the interrupt URB if not previously submitted, increasing refcount */ +int usbnet_status_start(struct usbnet *dev, gfp_t mem_flags) +{ + int ret = 0; + + WARN_ON_ONCE(dev->interrupt == NULL); + if (dev->interrupt) { + mutex_lock(&dev->interrupt_mutex); + + if (++dev->interrupt_count == 1) + ret = usb_submit_urb(dev->interrupt, mem_flags); + + dev_dbg(&dev->udev->dev, "incremented interrupt URB count to %d\n", + dev->interrupt_count); + mutex_unlock(&dev->interrupt_mutex); + } + return ret; +} +EXPORT_SYMBOL_GPL(usbnet_status_start); + +/* For resume; submit interrupt URB if previously submitted */ +static int __usbnet_status_start_force(struct usbnet *dev, gfp_t mem_flags) +{ + int ret = 0; + + mutex_lock(&dev->interrupt_mutex); + if (dev->interrupt_count) { + ret = usb_submit_urb(dev->interrupt, mem_flags); + dev_dbg(&dev->udev->dev, + "submitted interrupt URB for resume\n"); + } + mutex_unlock(&dev->interrupt_mutex); + return ret; +} + +/* Kill the interrupt URB if all submitters want it killed */ +void usbnet_status_stop(struct usbnet *dev) +{ + if (dev->interrupt) { + mutex_lock(&dev->interrupt_mutex); + WARN_ON(dev->interrupt_count == 0); + + if (dev->interrupt_count && --dev->interrupt_count == 0) + usb_kill_urb(dev->interrupt); + + dev_dbg(&dev->udev->dev, + "decremented interrupt URB count to %d\n", + dev->interrupt_count); + mutex_unlock(&dev->interrupt_mutex); + } +} +EXPORT_SYMBOL_GPL(usbnet_status_stop); + +/* For suspend; always kill interrupt URB */ +static void __usbnet_status_stop_force(struct usbnet *dev) +{ + if (dev->interrupt) { + mutex_lock(&dev->interrupt_mutex); + usb_kill_urb(dev->interrupt); + dev_dbg(&dev->udev->dev, "killed interrupt URB for suspend\n"); + mutex_unlock(&dev->interrupt_mutex); + } +} + +/* 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); + +/* must be called if hard_mtu or rx_urb_size changed */ +void usbnet_update_max_qlen(struct usbnet *dev) +{ + enum usb_device_speed speed = dev->udev->speed; + + switch (speed) { + case USB_SPEED_HIGH: + dev->rx_qlen = MAX_QUEUE_MEMORY / dev->rx_urb_size; + dev->tx_qlen = MAX_QUEUE_MEMORY / dev->hard_mtu; + break; + case USB_SPEED_SUPER: + /* + * Not take default 5ms qlen for super speed HC to + * save memory, and iperf tests show 2.5ms qlen can + * work well + */ + dev->rx_qlen = 5 * MAX_QUEUE_MEMORY / dev->rx_urb_size; + dev->tx_qlen = 5 * MAX_QUEUE_MEMORY / dev->hard_mtu; + break; + default: + dev->rx_qlen = dev->tx_qlen = 4; + } +} +EXPORT_SYMBOL_GPL(usbnet_update_max_qlen); + + +/*------------------------------------------------------------------------- + * + * 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); + } + + /* max qlen depend on hard_mtu and rx_urb_size */ + usbnet_update_max_qlen(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)) { + if (net_ratelimit()) + 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; + + /* prevent rx skb allocation when error ratio is high */ + if (test_bit(EVENT_RX_KILL, &dev->flags)) { + usb_free_urb(urb); + return -ENOLINK; + } + + 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 hub_wq disconnect() delays. + * throttle down resubmits, to avoid log floods; just temporarily, + * so we still recover when the fault isn't a hub_wq 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; + } + + /* stop rx if packet error rate is high */ + if (++dev->pkt_cnt > 30) { + dev->pkt_cnt = 0; + dev->pkt_err = 0; + } else { + if (state == rx_cleanup) + dev->pkt_err++; + if (dev->pkt_err > 20) + set_bit(EVENT_RX_KILL, &dev->flags); + } + + 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"); +} + +/*-------------------------------------------------------------------------*/ +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_WAITQUEUE(wait, current); + int temp; + + /* ensure there are no more active urbs */ + add_wait_queue(&dev->wait, &wait); + set_current_state(TASK_UNINTERRUPTIBLE); + 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); + remove_wait_queue(&dev->wait, &wait); +} + +int usbnet_stop (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct driver_info *info = dev->driver_info; + int retval, pm; + + 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); + + /* to not race resume */ + pm = usb_autopm_get_interface(dev->intf); + /* 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); + + usbnet_status_stop(dev); + + 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 (!pm) + usb_autopm_put_interface(dev->intf); + + if (info->manage_power && + !test_and_clear_bit(EVENT_NO_RUNTIME_PM, &dev->flags)) + 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; + } + + /* hard_mtu or rx_urb_size may change in reset() */ + usbnet_update_max_qlen(dev); + + // 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 = usbnet_status_start(dev, 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"); + + /* reset rx error state */ + dev->pkt_cnt = 0; + dev->pkt_err = 0; + clear_bit(EVENT_RX_KILL, &dev->flags); + + // 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) { + retval = 0; + set_bit(EVENT_NO_RUNTIME_PM, &dev->flags); + } else { + 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); + + /* hard_mtu or rx_urb_size may change in link_reset() */ + usbnet_update_max_qlen(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); + + strlcpy (info->driver, dev->driver_name, sizeof info->driver); + strlcpy (info->version, DRIVER_VERSION, sizeof info->version); + strlcpy (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, + .get_ts_info = ethtool_op_get_ts_info, +}; + +/*-------------------------------------------------------------------------*/ + +static void __handle_link_change(struct usbnet *dev) +{ + if (!test_bit(EVENT_DEV_OPEN, &dev->flags)) + return; + + if (!netif_carrier_ok(dev->net)) { + /* kill URBs for reading packets to save bus bandwidth */ + unlink_urbs(dev, &dev->rxq); + + /* + * tx_timeout will unlink URBs for sending packets and + * tx queue is stopped by netcore after link becomes off + */ + } else { + /* submitting URBs for reading packets */ + tasklet_schedule(&dev->bh); + } + + /* hard_mtu or rx_urb_size may change during link change */ + usbnet_update_max_qlen(dev); + + clear_bit(EVENT_LINK_CHANGE, &dev->flags); +} + +static void usbnet_set_rx_mode(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + usbnet_defer_kevent(dev, EVENT_SET_RX_MODE); +} + +static void __handle_set_rx_mode(struct usbnet *dev) +{ + if (dev->driver_info->set_rx_mode) + (dev->driver_info->set_rx_mode)(dev); + + clear_bit(EVENT_SET_RX_MODE, &dev->flags); +} + +/* 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 +usbnet_deferred_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); + } + + /* handle link change from link resetting */ + __handle_link_change(dev); + } + + if (test_bit (EVENT_LINK_CHANGE, &dev->flags)) + __handle_link_change(dev); + + if (test_bit (EVENT_SET_RX_MODE, &dev->flags)) + __handle_set_rx_mode(dev); + + + 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) { + dev->net->stats.tx_packets += entry->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 hub_wq + * 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); + /* this needs to be handled individually because the generic layer + * doesn't know what is sufficient and could not restore private + * information if a remedy of an unconditional reset were used. + */ + if (dev->driver_info->recover) + (dev->driver_info->recover)(dev); +} +EXPORT_SYMBOL_GPL(usbnet_tx_timeout); + +/*-------------------------------------------------------------------------*/ + +static int build_dma_sg(const struct sk_buff *skb, struct urb *urb) +{ + unsigned num_sgs, total_len = 0; + int i, s = 0; + + num_sgs = skb_shinfo(skb)->nr_frags + 1; + if (num_sgs == 1) + return 0; + + /* reserve one for zero packet */ + urb->sg = kmalloc((num_sgs + 1) * sizeof(struct scatterlist), + GFP_ATOMIC); + if (!urb->sg) + return -ENOMEM; + + urb->num_sgs = num_sgs; + sg_init_table(urb->sg, urb->num_sgs + 1); + + sg_set_buf(&urb->sg[s++], skb->data, skb_headlen(skb)); + total_len += skb_headlen(skb); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + struct skb_frag_struct *f = &skb_shinfo(skb)->frags[i]; + + total_len += skb_frag_size(f); + sg_set_page(&urb->sg[i + s], f->page.p, f->size, + f->page_offset); + } + urb->transfer_buffer_length = total_len; + + return 1; +} + +netdev_tx_t usbnet_start_xmit (struct sk_buff *skb, + struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + unsigned 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) { + /* packet collected; minidriver waiting for more */ + if (info->flags & FLAG_MULTI_PACKET) + goto not_drop; + netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n"); + goto drop; + } + } + + 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; + + usb_fill_bulk_urb (urb, dev->udev, dev->out, + skb->data, skb->len, tx_complete, skb); + if (dev->can_dma_sg) { + if (build_dma_sg(skb, urb) < 0) + goto drop; + } + length = urb->transfer_buffer_length; + + /* 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)) { + length++; + if (skb_tailroom(skb) && !urb->num_sgs) { + skb->data[skb->len] = 0; + __skb_put(skb, 1); + } else if (urb->num_sgs) + sg_set_buf(&urb->sg[urb->num_sgs++], + dev->padding_pkt, 1); + } + } else + urb->transfer_flags |= URB_ZERO_PACKET; + } + urb->transfer_buffer_length = length; + + if (info->flags & FLAG_MULTI_PACKET) { + /* Driver has set number of packets and a length delta. + * Calculate the complete length and ensure that it's + * positive. + */ + entry->length += length; + if (WARN_ON_ONCE(entry->length <= 0)) + entry->length = length; + } else { + usbnet_set_skb_tx_stats(skb, 1, length); + } + + 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); + if (urb) { + kfree(urb->sg); + usb_free_urb(urb); + } + } else + netif_dbg(dev, tx_queued, dev->net, + "> tx, len %u, type 0x%x\n", length, skb->protocol); +#ifdef CONFIG_PM +deferred: +#endif + return NETDEV_TX_OK; +} +EXPORT_SYMBOL_GPL(usbnet_start_xmit); + +static int rx_alloc_submit(struct usbnet *dev, gfp_t flags) +{ + struct urb *urb; + int i; + int ret = 0; + + /* don't refill the queue all at once */ + for (i = 0; i < 10 && dev->rxq.qlen < RX_QLEN(dev); i++) { + urb = usb_alloc_urb(0, flags); + if (urb != NULL) { + ret = rx_submit(dev, urb, flags); + if (ret) + goto err; + } else { + ret = -ENOMEM; + goto err; + } + } +err: + return ret; +} + +/*-------------------------------------------------------------------------*/ + +// 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: + kfree(entry->urb->sg); + 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); + } + } + + /* restart RX again after disabling due to high error rate */ + clear_bit(EVENT_RX_KILL, &dev->flags); + + /* waiting for all pending urbs to complete? + * only then can we forgo submitting anew + */ + if (waitqueue_active(&dev->wait)) { + if (dev->txq.qlen + dev->rxq.qlen + dev->done.qlen == 0) + wake_up_all(&dev->wait); + + // or are we maybe short a few urbs? + } else if (netif_running (dev->net) && + netif_device_present (dev->net) && + netif_carrier_ok(dev->net) && + !timer_pending (&dev->delay) && + !test_bit (EVENT_RX_HALT, &dev->flags)) { + int temp = dev->rxq.qlen; + + if (temp < RX_QLEN(dev)) { + if (rx_alloc_submit(dev, 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 < RX_QLEN(dev)) + 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); + kfree(dev->padding_pkt); + + free_netdev(net); +} +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_set_rx_mode = usbnet_set_rx_mode, + .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 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; + + 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); + init_waitqueue_head(&dev->wait); + 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, usbnet_deferred_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); + mutex_init(&dev->interrupt_mutex); + dev->interrupt_count = 0; + + 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"); + + /* devices that cannot do ARP */ + if ((dev->driver_info->flags & FLAG_NOARP) != 0) + net->flags |= IFF_NOARP; + + /* 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); + + /* let userspace know we have a random address */ + if (ether_addr_equal(net->dev_addr, node_id)) + net->addr_assign_type = NET_ADDR_RANDOM; + + 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); + + /* initialize max rx_qlen and tx_qlen */ + usbnet_update_max_qlen(dev); + + if (dev->can_dma_sg && !(info->flags & FLAG_SEND_ZLP) && + !(info->flags & FLAG_MULTI_PACKET)) { + dev->padding_pkt = kzalloc(1, GFP_KERNEL); + if (!dev->padding_pkt) { + status = -ENOMEM; + goto out4; + } + } + + status = register_netdev (net); + if (status) + goto out5; + 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) + usbnet_link_change(dev, 0, 0); + + return 0; + +out5: + kfree(dev->padding_pkt); +out4: + usb_free_urb(dev->interrupt); +out3: + if (info->unbind) + info->unbind (dev, udev); +out1: + free_netdev(net); +out: + 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)) { + dev->suspend_count--; + 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); + __usbnet_status_stop_force(dev); + + /* + * 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 URB if it was previously submitted */ + __usbnet_status_start_force(dev, 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); + kfree(res->sg); + 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)) { + /* handle remote wakeup ASAP + * we cannot race against stop + */ + if (netif_device_present(dev->net) && + !timer_pending(&dev->delay) && + !test_bit(EVENT_RX_HALT, &dev->flags)) + rx_alloc_submit(dev, GFP_NOIO); + + if (!(dev->txq.qlen >= TX_QLEN(dev))) + netif_tx_wake_all_queues(dev->net); + tasklet_schedule (&dev->bh); + } + } + + if (test_and_clear_bit(EVENT_DEVICE_REPORT_IDLE, &dev->flags)) + usb_autopm_get_interface_no_resume(intf); + + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_resume); + +/* + * Either a subdriver implements manage_power, then it is assumed to always + * be ready to be suspended or it reports the readiness to be suspended + * explicitly + */ +void usbnet_device_suggests_idle(struct usbnet *dev) +{ + if (!test_and_set_bit(EVENT_DEVICE_REPORT_IDLE, &dev->flags)) { + dev->intf->needs_remote_wakeup = 1; + usb_autopm_put_interface_async(dev->intf); + } +} +EXPORT_SYMBOL(usbnet_device_suggests_idle); + +/* + * For devices that can do without special commands + */ +int usbnet_manage_power(struct usbnet *dev, int on) +{ + dev->intf->needs_remote_wakeup = on; + return 0; +} +EXPORT_SYMBOL(usbnet_manage_power); + +void usbnet_link_change(struct usbnet *dev, bool link, bool need_reset) +{ + /* update link after link is reseted */ + if (link && !need_reset) + netif_carrier_on(dev->net); + else + netif_carrier_off(dev->net); + + if (need_reset && link) + usbnet_defer_kevent(dev, EVENT_LINK_RESET); + else + usbnet_defer_kevent(dev, EVENT_LINK_CHANGE); +} +EXPORT_SYMBOL(usbnet_link_change); + +/*-------------------------------------------------------------------------*/ +static int __usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, void *data, u16 size) +{ + void *buf = NULL; + int err = -ENOMEM; + + netdev_dbg(dev->net, "usbnet_read_cmd cmd=0x%02x reqtype=%02x" + " value=0x%04x index=0x%04x size=%d\n", + cmd, reqtype, value, index, size); + + if (data) { + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + goto out; + } + + err = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + cmd, reqtype, value, index, buf, size, + USB_CTRL_GET_TIMEOUT); + if (err > 0 && err <= size) + memcpy(data, buf, err); + kfree(buf); +out: + return err; +} + +static int __usbnet_write_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, + u16 size) +{ + void *buf = NULL; + int err = -ENOMEM; + + netdev_dbg(dev->net, "usbnet_write_cmd cmd=0x%02x reqtype=%02x" + " value=0x%04x index=0x%04x size=%d\n", + cmd, reqtype, 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, reqtype, value, index, buf, size, + USB_CTRL_SET_TIMEOUT); + kfree(buf); + +out: + return err; +} + +/* + * The function can't be called inside suspend/resume callback, + * otherwise deadlock will be caused. + */ +int usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, void *data, u16 size) +{ + int ret; + + if (usb_autopm_get_interface(dev->intf) < 0) + return -ENODEV; + ret = __usbnet_read_cmd(dev, cmd, reqtype, value, index, + data, size); + usb_autopm_put_interface(dev->intf); + return ret; +} +EXPORT_SYMBOL_GPL(usbnet_read_cmd); + +/* + * The function can't be called inside suspend/resume callback, + * otherwise deadlock will be caused. + */ +int usbnet_write_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, u16 size) +{ + int ret; + + if (usb_autopm_get_interface(dev->intf) < 0) + return -ENODEV; + ret = __usbnet_write_cmd(dev, cmd, reqtype, value, index, + data, size); + usb_autopm_put_interface(dev->intf); + return ret; +} +EXPORT_SYMBOL_GPL(usbnet_write_cmd); + +/* + * The function can be called inside suspend/resume callback safely + * and should only be called by suspend/resume callback generally. + */ +int usbnet_read_cmd_nopm(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, void *data, u16 size) +{ + return __usbnet_read_cmd(dev, cmd, reqtype, value, index, + data, size); +} +EXPORT_SYMBOL_GPL(usbnet_read_cmd_nopm); + +/* + * The function can be called inside suspend/resume callback safely + * and should only be called by suspend/resume callback generally. + */ +int usbnet_write_cmd_nopm(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, + u16 size) +{ + return __usbnet_write_cmd(dev, cmd, reqtype, value, index, + data, size); +} +EXPORT_SYMBOL_GPL(usbnet_write_cmd_nopm); + +static void usbnet_async_cmd_cb(struct urb *urb) +{ + struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context; + int status = urb->status; + + if (status < 0) + dev_dbg(&urb->dev->dev, "%s failed with %d", + __func__, status); + + kfree(req); + usb_free_urb(urb); +} + +/* + * The caller must make sure that device can't be put into suspend + * state until the control URB completes. + */ +int usbnet_write_cmd_async(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, u16 size) +{ + struct usb_ctrlrequest *req = NULL; + struct urb *urb; + int err = -ENOMEM; + void *buf = NULL; + + netdev_dbg(dev->net, "usbnet_write_cmd cmd=0x%02x reqtype=%02x" + " value=0x%04x index=0x%04x size=%d\n", + cmd, reqtype, value, index, size); + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + netdev_err(dev->net, "Error allocating URB in" + " %s!\n", __func__); + goto fail; + } + + if (data) { + buf = kmemdup(data, size, GFP_ATOMIC); + if (!buf) { + netdev_err(dev->net, "Error allocating buffer" + " in %s!\n", __func__); + goto fail_free; + } + } + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC); + if (!req) + goto fail_free_buf; + + req->bRequestType = reqtype; + 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, buf, size, + usbnet_async_cmd_cb, req); + urb->transfer_flags |= URB_FREE_BUFFER; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + netdev_err(dev->net, "Error submitting the control" + " message: status=%d\n", err); + goto fail_free; + } + return 0; + +fail_free_buf: + kfree(buf); +fail_free: + kfree(req); + usb_free_urb(urb); +fail: + return err; + +} +EXPORT_SYMBOL_GPL(usbnet_write_cmd_async); +/*-------------------------------------------------------------------------*/ + +static int __init usbnet_init(void) +{ + /* Compiler should optimize this out. */ + BUILD_BUG_ON( + FIELD_SIZEOF(struct sk_buff, cb) < sizeof(struct skb_data)); + + eth_random_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/drivers/net/usb/zaurus.c b/drivers/net/usb/zaurus.c new file mode 100644 index 000000000..6aaa6eb9d --- /dev/null +++ b/drivers/net/usb/zaurus.c @@ -0,0 +1,385 @@ +/* + * 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, see <http://www.gnu.org/licenses/>. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/module.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, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(zaurus_driver); + +MODULE_AUTHOR("Pavel Machek, David Brownell"); +MODULE_DESCRIPTION("Sharp Zaurus PDA, and compatible products"); +MODULE_LICENSE("GPL"); |