Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

575
drivers/net/usb/Kconfig Normal file
View file

@ -0,0 +1,575 @@
#
# 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
* 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
boolean "ALi M5632 based 'USB 2.0 Data Link' cables"
depends on USB_NET_CDC_SUBSET
help
Choose this option if you're using a host-to-host cable
based on this design, which supports USB 2.0 high speed.
config USB_AN2720
boolean "AnchorChips 2720 based cables (Xircom PGUNET, ...)"
depends on USB_NET_CDC_SUBSET
help
Choose this option if you're using a host-to-host cable
based on this design. Note that AnchorChips is now a
Cypress brand.
config USB_BELKIN
boolean "eTEK based host-to-host cables (Advance, Belkin, ...)"
depends on USB_NET_CDC_SUBSET
default y
help
Choose this option if you're using a host-to-host cable
based on this design: two NetChip 2890 chips and an Atmel
microcontroller, with LEDs that indicate traffic.
config USB_ARMLINUX
boolean "Embedded ARM Linux links (iPaq, ...)"
depends on USB_NET_CDC_SUBSET
default y
help
Choose this option to support the "usb-eth" networking driver
used by most of the ARM Linux community with device controllers
such as the SA-11x0 and PXA-25x UDCs, or the tftp capabilities
in some PXA versions of the "blob" boot loader.
Linux-based "Gumstix" PXA-25x based systems use this protocol
to talk with other Linux systems.
Although the ROMs shipped with Sharp Zaurus products use a
different link level framing protocol, you can have them use
this simpler protocol by installing a different kernel.
config USB_EPSON2888
boolean "Epson 2888 based firmware (DEVELOPMENT)"
depends on USB_NET_CDC_SUBSET
help
Choose this option to support the usb networking links used
by some sample firmware from Epson.
config USB_KC2190
boolean "KT Technology KC2190 based cables (InstaNet)"
depends on USB_NET_CDC_SUBSET
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

40
drivers/net/usb/Makefile Normal file
View file

@ -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

234
drivers/net/usb/asix.h Normal file
View file

@ -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 */

View file

@ -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;
}

File diff suppressed because it is too large Load diff

422
drivers/net/usb/ax88172a.c Normal file
View file

@ -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,
};

File diff suppressed because it is too large Load diff

965
drivers/net/usb/catc.c Normal file
View file

@ -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;
memset(broadcast, 0xff, ETH_ALEN);
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");
memset(broadcast, 0xff, ETH_ALEN);
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);

View file

@ -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 = __skb_alloc_page(gfp_flags | __GFP_NOMEMALLOC, NULL);
if (!page)
return -ENOMEM;
usb_fill_bulk_urb(req, pnd->usb, pnd->rx_pipe, page_address(page),
PAGE_SIZE, rx_complete, dev);
req->transfer_flags = 0;
err = usb_submit_urb(req, gfp_flags);
if (unlikely(err)) {
dev_dbg(&dev->dev, "RX submit error (%d)\n", err);
put_page(page);
}
return err;
}
static void rx_complete(struct urb *req)
{
struct net_device *dev = req->context;
struct usbpn_dev *pnd = netdev_priv(dev);
struct page *page = virt_to_page(req->transfer_buffer);
struct sk_buff *skb;
unsigned long flags;
int status = req->status;
switch (status) {
case 0:
spin_lock_irqsave(&pnd->rx_lock, flags);
skb = pnd->rx_skb;
if (!skb) {
skb = pnd->rx_skb = netdev_alloc_skb(dev, 12);
if (likely(skb)) {
/* Can't use pskb_pull() on page in IRQ */
memcpy(skb_put(skb, 1), page_address(page), 1);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
page, 1, req->actual_length,
PAGE_SIZE);
page = NULL;
}
} else {
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
page, 0, req->actual_length,
PAGE_SIZE);
page = NULL;
}
if (req->actual_length < PAGE_SIZE)
pnd->rx_skb = NULL; /* Last fragment */
else
skb = NULL;
spin_unlock_irqrestore(&pnd->rx_lock, flags);
if (skb) {
skb->protocol = htons(ETH_P_PHONET);
skb_reset_mac_header(skb);
__skb_pull(skb, 1);
skb->dev = dev;
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
netif_rx(skb);
}
goto resubmit;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
req = NULL;
break;
case -EOVERFLOW:
dev->stats.rx_over_errors++;
dev_dbg(&dev->dev, "RX overflow\n");
break;
case -EILSEQ:
dev->stats.rx_crc_errors++;
break;
}
dev->stats.rx_errors++;
resubmit:
if (page)
put_page(page);
if (req)
rx_submit(pnd, req, GFP_ATOMIC | __GFP_COLD);
}
static int usbpn_close(struct net_device *dev);
static int usbpn_open(struct net_device *dev)
{
struct usbpn_dev *pnd = netdev_priv(dev);
int err;
unsigned i;
unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
err = usb_set_interface(pnd->usb, num, pnd->active_setting);
if (err)
return err;
for (i = 0; i < rxq_size; i++) {
struct urb *req = usb_alloc_urb(0, GFP_KERNEL);
if (!req || rx_submit(pnd, req, GFP_KERNEL | __GFP_COLD)) {
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");

381
drivers/net/usb/cdc_eem.c Normal file
View file

@ -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");

784
drivers/net/usb/cdc_ether.c Normal file
View file

@ -0,0 +1,784 @@
/*
* 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;
u16 cdc_filter =
USB_CDC_PACKET_TYPE_ALL_MULTICAST | USB_CDC_PACKET_TYPE_DIRECTED |
USB_CDC_PACKET_TYPE_BROADCAST;
if (dev->net->flags & IFF_PROMISC)
cdc_filter |= USB_CDC_PACKET_TYPE_PROMISCUOUS;
/* FIXME cdc-ether has some multicast code too, though it complains
* in routine cases. info->ether describes the multicast support.
* Implement that here, manipulating the cdc filter as needed.
*/
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
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,
},
/* 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");

625
drivers/net/usb/cdc_mbim.c Normal file
View file

@ -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;
memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
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");

1616
drivers/net/usb/cdc_ncm.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -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");

View file

@ -0,0 +1,335 @@
/*
* 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
/* first command after power on can take around 8 seconds */
#define CMD_TIMEOUT 15000
#define CMD_REPLY_RETRY 5
#define CX82310_MTU 1514
#define CMD_EP 0x01
/*
* execute control command
* - optionally send some data (command parameters)
* - optionally wait for the reply
* - optionally read some data from the reply
*/
static int cx82310_cmd(struct usbnet *dev, enum cx82310_cmd cmd, bool reply,
u8 *wdata, int wlen, u8 *rdata, int rlen)
{
int actual_len, retries, ret;
struct usb_device *udev = dev->udev;
u8 *buf = kzalloc(CMD_PACKET_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* create command packet */
buf[0] = cmd;
if (wdata)
memcpy(buf + 4, wdata, min_t(int, wlen, CMD_PACKET_SIZE - 4));
/* send command packet */
ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, CMD_EP), buf,
CMD_PACKET_SIZE, &actual_len, CMD_TIMEOUT);
if (ret < 0) {
dev_err(&dev->udev->dev, "send command %#x: error %d\n",
cmd, ret);
goto end;
}
if (reply) {
/* wait for reply, retry if it's empty */
for (retries = 0; retries < CMD_REPLY_RETRY; retries++) {
ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, CMD_EP),
buf, CMD_PACKET_SIZE, &actual_len,
CMD_TIMEOUT);
if (ret < 0) {
dev_err(&dev->udev->dev,
"reply receive error %d\n", ret);
goto end;
}
if (actual_len > 0)
break;
}
if (actual_len == 0) {
dev_err(&dev->udev->dev, "no reply to command %#x\n",
cmd);
ret = -EIO;
goto end;
}
if (buf[0] != cmd) {
dev_err(&dev->udev->dev,
"got reply to command %#x, expected: %#x\n",
buf[0], cmd);
ret = -EIO;
goto end;
}
if (buf[1] != STATUS_SUCCESS) {
dev_err(&dev->udev->dev, "command %#x failed: %#x\n",
cmd, buf[1]);
ret = -EIO;
goto end;
}
if (rdata)
memcpy(rdata, buf + 4,
min_t(int, rlen, CMD_PACKET_SIZE - 4));
}
end:
kfree(buf);
return ret;
}
#define partial_len data[0] /* length of partial packet data */
#define partial_rem data[1] /* remaining (missing) data length */
#define partial_data data[2] /* partial packet data */
static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
{
int ret;
char buf[15];
struct usb_device *udev = dev->udev;
/* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */
if (usb_string(udev, udev->descriptor.iProduct, buf, sizeof(buf)) > 0
&& strcmp(buf, "USB NET CARD")) {
dev_info(&udev->dev, "ignoring: probably an ADSL modem\n");
return -ENODEV;
}
ret = usbnet_get_endpoints(dev, intf);
if (ret)
return ret;
/*
* this must not include ethernet header as the device can send partial
* packets with no header (and sometimes even empty URBs)
*/
dev->net->hard_header_len = 0;
/* we can send at most 1514 bytes of data (+ 2-byte header) per URB */
dev->hard_mtu = CX82310_MTU + 2;
/* we can receive URBs up to 4KB from the device */
dev->rx_urb_size = 4096;
dev->partial_data = (unsigned long) kmalloc(dev->hard_mtu, GFP_KERNEL);
if (!dev->partial_data)
return -ENOMEM;
/* enable ethernet mode (?) */
ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
if (ret) {
dev_err(&udev->dev, "unable to enable ethernet mode: %d\n",
ret);
goto err;
}
/* get the MAC address */
ret = cx82310_cmd(dev, CMD_GET_MAC_ADDR, true, NULL, 0,
dev->net->dev_addr, ETH_ALEN);
if (ret) {
dev_err(&udev->dev, "unable to read MAC address: %d\n", ret);
goto err;
}
/* start (does not seem to have any effect?) */
ret = cx82310_cmd(dev, CMD_START, false, NULL, 0, NULL, 0);
if (ret)
goto err;
return 0;
err:
kfree((void *)dev->partial_data);
return ret;
}
static void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf)
{
kfree((void *)dev->partial_data);
}
/*
* RX is NOT easy - we can receive multiple packets per skb, each having 2-byte
* packet length at the beginning.
* The last packet might be incomplete (when it crosses the 4KB URB size),
* continuing in the next skb (without any headers).
* If a packet has odd length, there is one extra byte at the end (before next
* packet or at the end of the URB).
*/
static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
int len;
struct sk_buff *skb2;
/*
* If the last skb ended with an incomplete packet, this skb contains
* end of that packet at the beginning.
*/
if (dev->partial_rem) {
len = dev->partial_len + dev->partial_rem;
skb2 = alloc_skb(len, GFP_ATOMIC);
if (!skb2)
return 0;
skb_put(skb2, len);
memcpy(skb2->data, (void *)dev->partial_data,
dev->partial_len);
memcpy(skb2->data + dev->partial_len, skb->data,
dev->partial_rem);
usbnet_skb_return(dev, skb2);
skb_pull(skb, (dev->partial_rem + 1) & ~1);
dev->partial_rem = 0;
if (skb->len < 2)
return 1;
}
/* a skb can contain multiple packets */
while (skb->len > 1) {
/* first two bytes are packet length */
len = skb->data[0] | (skb->data[1] << 8);
skb_pull(skb, 2);
/* if last packet in the skb, let usbnet to process it */
if (len == skb->len || len + 1 == skb->len) {
skb_trim(skb, len);
break;
}
if (len > CX82310_MTU) {
dev_err(&dev->udev->dev, "RX packet too long: %d B\n",
len);
return 0;
}
/* incomplete packet, save it for the next skb */
if (len > skb->len) {
dev->partial_len = skb->len;
dev->partial_rem = len - skb->len;
memcpy((void *)dev->partial_data, skb->data,
dev->partial_len);
skb_pull(skb, skb->len);
break;
}
skb2 = alloc_skb(len, GFP_ATOMIC);
if (!skb2)
return 0;
skb_put(skb2, len);
memcpy(skb2->data, skb->data, len);
/* process the packet */
usbnet_skb_return(dev, skb2);
skb_pull(skb, (len + 1) & ~1);
}
/* let usbnet process the last packet */
return 1;
}
/* TX is easy, just add 2 bytes of length at the beginning */
static struct sk_buff *cx82310_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
gfp_t flags)
{
int len = skb->len;
if (skb_headroom(skb) < 2) {
struct sk_buff *skb2 = skb_copy_expand(skb, 2, 0, flags);
dev_kfree_skb_any(skb);
skb = skb2;
if (!skb)
return NULL;
}
skb_push(skb, 2);
skb->data[0] = len;
skb->data[1] = len >> 8;
return skb;
}
static const struct driver_info cx82310_info = {
.description = "Conexant CX82310 USB ethernet",
.flags = FLAG_ETHER,
.bind = cx82310_bind,
.unbind = cx82310_unbind,
.rx_fixup = cx82310_rx_fixup,
.tx_fixup = cx82310_tx_fixup,
};
#define USB_DEVICE_CLASS(vend, prod, cl, sc, pr) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
USB_DEVICE_ID_MATCH_DEV_INFO, \
.idVendor = (vend), \
.idProduct = (prod), \
.bDeviceClass = (cl), \
.bDeviceSubClass = (sc), \
.bDeviceProtocol = (pr)
static const struct usb_device_id products[] = {
{
USB_DEVICE_CLASS(0x0572, 0xcb01, 0xff, 0, 0),
.driver_info = (unsigned long) &cx82310_info
},
{ },
};
MODULE_DEVICE_TABLE(usb, products);
static struct usb_driver cx82310_driver = {
.name = "cx82310_eth",
.id_table = products,
.probe = usbnet_probe,
.disconnect = usbnet_disconnect,
.suspend = usbnet_suspend,
.resume = usbnet_resume,
.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");

647
drivers/net/usb/dm9601.c Normal file
View file

@ -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");

242
drivers/net/usb/gl620a.c Normal file
View file

@ -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");

3327
drivers/net/usb/hso.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -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");

199
drivers/net/usb/int51x1.c Normal file
View file

@ -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");

588
drivers/net/usb/ipheth.c Normal file
View file

@ -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");

366
drivers/net/usb/kalmia.c Normal file
View file

@ -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");

1331
drivers/net/usb/kaweth.c Normal file

File diff suppressed because it is too large Load diff

353
drivers/net/usb/lg-vl600.c Normal file
View file

@ -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(&ethhdr->h_proto) == ETH_P_ARP &&
buf->len > 0x26) {
/* Copy the addresses from packet contents */
memcpy(ethhdr->h_source,
&buf->data[sizeof(*ethhdr) + 0x8],
ETH_ALEN);
memcpy(ethhdr->h_dest,
&buf->data[sizeof(*ethhdr) + 0x12],
ETH_ALEN);
} else {
memset(ethhdr->h_source, 0, ETH_ALEN);
memcpy(ethhdr->h_dest, dev->net->dev_addr, ETH_ALEN);
/* Inbound IPv6 packets have an IPv4 ethertype (0x800)
* for some reason. Peek at the L3 header to check
* for IPv6 packets, and set the ethertype to IPv6
* (0x86dd) so Linux can understand it.
*/
if ((buf->data[sizeof(*ethhdr)] & 0xf0) == 0x60)
ethhdr->h_proto = 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");

643
drivers/net/usb/mcs7830.c Normal file
View file

@ -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");

544
drivers/net/usb/net1080.c Normal file
View file

@ -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");

1335
drivers/net/usb/pegasus.c Normal file

File diff suppressed because it is too large Load diff

308
drivers/net/usb/pegasus.h Normal file
View file

@ -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 */

162
drivers/net/usb/plusb.c Normal file
View file

@ -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");

879
drivers/net/usb/qmi_wwan.c Normal file
View file

@ -0,0 +1,879 @@
/*
* 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};
/* Make up an ethernet header if the packet doesn't have one.
*
* A firmware bug common among several devices cause them to send raw
* IP packets under some circumstances. There is no way for the
* driver/host to know when this will happen. And even when the bug
* hits, some packets will still arrive with an intact header.
*
* The supported devices are only capably of sending IPv4, IPv6 and
* ARP packets on a point-to-point link. Any packet with an ethernet
* header will have either our address or a broadcast/multicast
* address as destination. ARP packets will always have a header.
*
* This means that this function will reliably add the appropriate
* header iff necessary, provided our hardware address does not start
* with 4 or 6.
*
* Another common firmware bug results in all packets being addressed
* to 00:a0:c6:00:00:00 despite the host address being different.
* This function will also fixup such packets.
*/
static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
__be16 proto;
/* This check is no longer done by usbnet */
if (skb->len < dev->net->hard_header_len)
return 0;
switch (skb->data[0] & 0xf0) {
case 0x40:
proto = htons(ETH_P_IP);
break;
case 0x60:
proto = htons(ETH_P_IPV6);
break;
case 0x00:
if (is_multicast_ether_addr(skb->data))
return 1;
/* possibly bogus destination - rewrite just in case */
skb_reset_mac_header(skb);
goto fix_dest;
default:
/* pass along other packets without modifications */
return 1;
}
if (skb_headroom(skb) < ETH_HLEN)
return 0;
skb_push(skb, ETH_HLEN);
skb_reset_mac_header(skb);
eth_hdr(skb)->h_proto = proto;
memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
fix_dest:
memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
return 1;
}
/* very simplistic detection of IPv4 or IPv6 headers */
static bool possibly_iphdr(const char *data)
{
return (data[0] & 0xd0) == 0x40;
}
/* disallow addresses which may be confused with IP headers */
static int qmi_wwan_mac_addr(struct net_device *dev, void *p)
{
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.
*/
if (ether_addr_equal(dev->net->dev_addr, default_modem_addr))
eth_hw_addr_random(dev->net);
/* make MAC addr easily distinguishable from an IP header */
if (possibly_iphdr(dev->net->dev_addr)) {
dev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
}
dev->net->netdev_ops = &qmi_wwan_netdev_ops;
err:
return 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");

3913
drivers/net/usb/r8152.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -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),
&notification, 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");

950
drivers/net/usb/rtl8150.c Normal file
View file

@ -0,0 +1,950 @@
/*
* 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);
int res = 0;
netif_stop_queue(netdev);
if (!test_bit(RTL8150_UNPLUG, &dev->flags))
disable_net_traffic(dev);
unlink_all_urbs(dev);
return res;
}
static void rtl8150_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info)
{
rtl8150_t *dev = netdev_priv(netdev);
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");

View file

@ -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(&eth->h_dest, dev->net->dev_addr, ETH_ALEN);
eth->h_proto = cpu_to_be16(ETH_P_IP);
/* prepare shutdown message template */
memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg));
/* set context index initially to 0 - prepares tx hdr template */
sierra_net_set_ctx_index(priv, 0);
/* 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");

2286
drivers/net/usb/smsc75xx.c Normal file

File diff suppressed because it is too large Load diff

421
drivers/net/usb/smsc75xx.h Normal file
View file

@ -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 */

2030
drivers/net/usb/smsc95xx.c Normal file

File diff suppressed because it is too large Load diff

290
drivers/net/usb/smsc95xx.h Normal file
View file

@ -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 */

559
drivers/net/usb/sr9700.c Normal file
View file

@ -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, 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, EPAR, phy ? (reg | EPAR_PHY_ADR) : reg);
sr_write_reg(dev, 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, EPCR, 0x0);
ret = sr_read(dev, 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, EPDR, 2, &value);
if (ret < 0)
goto out_unlock;
sr_write_reg(dev, EPAR, phy ? (reg | EPAR_PHY_ADR) : reg);
sr_write_reg(dev, 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, 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, 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, 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, MAR, SR_MCAST_SIZE, hashes);
sr_write_reg_async(dev, 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, 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, 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, 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, PRR, PRR_PHY_RST);
/* at least 10ms, here 20ms for safe */
mdelay(20);
sr_write_reg(dev, 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");

173
drivers/net/usb/sr9700.h Normal file
View file

@ -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 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 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 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 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 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 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 RSR 0x06
#define RSR_AE (1 << 2)
#define RSR_MF (1 << 6)
#define RSR_RF (1 << 7)
/* Rx Overflow Counter Reg */
#define ROCR 0x07
#define ROCR_ROC (0x7F << 0)
#define ROCR_RXFU (1 << 7)
/* Back Pressure Threshold Reg */
#define BPTR 0x08
#define BPTR_JPT (0x0F << 0)
#define BPTR_BPHW (0x0F << 4)
/* Flow Control Threshold Reg */
#define FCTR 0x09
#define FCTR_LWOT (0x0F << 0)
#define FCTR_HWOT (0x0F << 4)
/* rx/tx Flow Control Reg */
#define 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 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 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 EPDR 0x0D /* 0x0D ~ 0x0E for Data Reg Low & High */
/* Wakeup Control Reg */
#define 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 PAR 0x10 /* 0x10 ~ 0x15 6 bytes for PAR */
/* Multicast Address Reg */
#define MAR 0x16 /* 0x16 ~ 0x1D 8 bytes for MAR */
/* 0x1e unused */
/* Phy Reset Reg */
#define PRR 0x1F
#define PRR_PHY_RST (1 << 0)
/* Tx sdram Write Pointer Address Low */
#define TWPAL 0x20
/* Tx sdram Write Pointer Address High */
#define TWPAH 0x21
/* Tx sdram Read Pointer Address Low */
#define TRPAL 0x22
/* Tx sdram Read Pointer Address High */
#define TRPAH 0x23
/* Rx sdram Write Pointer Address Low */
#define RWPAL 0x24
/* Rx sdram Write Pointer Address High */
#define RWPAH 0x25
/* Rx sdram Read Pointer Address Low */
#define RRPAL 0x26
/* Rx sdram Read Pointer Address High */
#define RRPAH 0x27
/* Vendor ID register */
#define VID 0x28 /* 0x28 ~ 0x29 2 bytes for VID */
/* Product ID register */
#define PID 0x2A /* 0x2A ~ 0x2B 2 bytes for PID */
/* CHIP Revision register */
#define CHIPR 0x2C
/* 0x2D --> 0xEF unused */
/* USB Device Address */
#define USBDA 0xF0
#define USBDA_USBFA (0x7F << 0)
/* RX packet Counter Reg */
#define RXC 0xF1
/* Tx packet Counter & USB Status Reg */
#define 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 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 */

875
drivers/net/usb/sr9800.c Normal file
View file

@ -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");

202
drivers/net/usb/sr9800.h Normal file
View file

@ -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 */

2108
drivers/net/usb/usbnet.c Normal file

File diff suppressed because it is too large Load diff

385
drivers/net/usb/zaurus.c Normal file
View file

@ -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");