2005-08-31 09:54:20 -07:00
/*
* CDC Ethernet based networking peripherals
* Copyright ( C ) 2003 - 2005 by David Brownell
2006-12-14 16:01:28 -08:00
* Copyright ( C ) 2006 by Ole Andre Vadla Ravnas ( ActiveSync )
2005-08-31 09:54:20 -07:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
// #define DEBUG // error path messages, extra info
// #define VERBOSE // more; success messages
# include <linux/module.h>
# include <linux/init.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/ethtool.h>
# include <linux/workqueue.h>
# include <linux/mii.h>
# include <linux/usb.h>
2006-06-13 09:57:47 -07:00
# include <linux/usb/cdc.h>
2008-01-26 00:51:45 +02:00
# include <linux/usb/usbnet.h>
2005-08-31 09:54:20 -07:00
2006-12-14 16:01:28 -08:00
# if defined(CONFIG_USB_NET_RNDIS_HOST) || defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)
static int is_rndis ( struct usb_interface_descriptor * desc )
{
return desc - > bInterfaceClass = = USB_CLASS_COMM
& & desc - > bInterfaceSubClass = = 2
& & desc - > bInterfaceProtocol = = 0xff ;
}
static int is_activesync ( struct usb_interface_descriptor * desc )
{
return desc - > bInterfaceClass = = USB_CLASS_MISC
& & desc - > bInterfaceSubClass = = 1
& & desc - > bInterfaceProtocol = = 1 ;
}
2008-07-22 13:55:58 -07:00
static int is_wireless_rndis ( struct usb_interface_descriptor * desc )
{
return desc - > bInterfaceClass = = USB_CLASS_WIRELESS_CONTROLLER
& & desc - > bInterfaceSubClass = = 1
& & desc - > bInterfaceProtocol = = 3 ;
}
2006-12-14 16:01:28 -08:00
# else
# define is_rndis(desc) 0
# define is_activesync(desc) 0
2008-07-22 13:55:58 -07:00
# define is_wireless_rndis(desc) 0
2006-12-14 16:01:28 -08:00
# endif
2005-08-31 09:54:20 -07:00
/*
* 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 ;
struct usb_driver * driver = driver_of ( intf ) ;
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 ;
if ( len )
dev_dbg ( & intf - > dev ,
" CDC descriptors on config \n " ) ;
}
2007-04-29 10:09:47 -07:00
/* Maybe CDC descriptors are after the endpoint? This bug has
* been seen on some 2 Wire 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 " ) ;
}
2005-08-31 09:54:20 -07:00
/* this assumes that if there's a non-RNDIS vendor variant
* of cdc - acm , it ' ll fail RNDIS requests cleanly .
*/
2006-12-14 16:01:28 -08:00
rndis = is_rndis ( & intf - > cur_altsetting - > desc )
2008-07-22 13:55:58 -07:00
| | is_activesync ( & intf - > cur_altsetting - > desc )
| | is_wireless_rndis ( & intf - > cur_altsetting - > desc ) ;
2005-08-31 09:54:20 -07:00
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 ;
2006-12-14 16:01:28 -08:00
case USB_CDC_ACM_TYPE :
/* paranoia: disambiguate a "real" vendor-specific
* modem interface from an RNDIS non - modem .
*/
if ( rndis ) {
2007-07-01 11:47:59 -07:00
struct usb_cdc_acm_descriptor * acm ;
2006-12-14 16:01:28 -08:00
2007-07-01 11:47:59 -07:00
acm = ( void * ) buf ;
if ( acm - > bmCapabilities ) {
2006-12-14 16:01:28 -08:00
dev_dbg ( & intf - > dev ,
" ACM capabilities %02x, "
" not really RNDIS? \n " ,
2007-07-01 11:47:59 -07:00
acm - > bmCapabilities ) ;
2006-12-14 16:01:28 -08:00
goto bad_desc ;
}
}
break ;
2005-08-31 09:54:20 -07:00
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 ) ;
goto bad_desc ;
}
if ( info - > control ! = intf ) {
dev_dbg ( & intf - > dev , " bogus CDC Union \n " ) ;
/* Ambit USB Cable Modem (and maybe others)
* interchanges master and slave interface .
*/
if ( info - > data = = intf ) {
info - > data = info - > control ;
info - > control = intf ;
} else
goto bad_desc ;
}
/* a data interface altsetting does the real i/o */
d = & info - > data - > cur_altsetting - > desc ;
if ( d - > bInterfaceClass ! = USB_CLASS_CDC_DATA ) {
dev_dbg ( & intf - > dev , " slave class %u \n " ,
d - > bInterfaceClass ) ;
goto bad_desc ;
}
break ;
case USB_CDC_ETHERNET_TYPE :
if ( info - > ether ) {
dev_dbg ( & intf - > dev , " extra CDC ether \n " ) ;
goto bad_desc ;
}
info - > ether = ( void * ) buf ;
if ( info - > ether - > 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 ;
}
next_desc :
len - = buf [ 0 ] ; /* bLength */
buf + = buf [ 0 ] ;
}
2008-01-26 00:50:44 +02:00
/* Microsoft ActiveSync based and some regular RNDIS devices lack the
* CDC descriptors , so we ' ll hard - wire the interfaces and not check
* for descriptors .
2006-12-14 16:01:28 -08:00
*/
2008-01-26 00:50:44 +02:00
if ( rndis & & ! info - > u ) {
2006-12-14 16:01:28 -08:00
info - > control = usb_ifnum_to_if ( dev - > udev , 0 ) ;
info - > data = usb_ifnum_to_if ( dev - > udev , 1 ) ;
if ( ! info - > control | | ! info - > data ) {
dev_dbg ( & intf - > dev ,
2008-01-26 00:50:44 +02:00
" rndis: master #0/%p slave #1/%p \n " ,
2006-12-14 16:01:28 -08:00
info - > control ,
info - > data ) ;
goto bad_desc ;
}
} else if ( ! info - > header | | ! info - > u | | ( ! rndis & & ! info - > ether ) ) {
2005-08-31 09:54:20 -07:00
dev_dbg ( & intf - > dev , " missing cdc %s%s%sdescriptor \n " ,
info - > header ? " " : " header " ,
info - > u ? " " : " union " ,
info - > ether ? " " : " ether " ) ;
goto bad_desc ;
}
/* claim data interface and set it up ... with side effects.
* network traffic can ' t flow until an altsetting is enabled .
*/
status = usb_driver_claim_interface ( driver , info - > data , dev ) ;
if ( status < 0 )
return status ;
status = usbnet_get_endpoints ( dev , info - > data ) ;
if ( status < 0 ) {
/* ensure immediate exit from usbnet_disconnect */
usb_set_intfdata ( info - > data , NULL ) ;
usb_driver_release_interface ( driver , info - > data ) ;
return status ;
}
/* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */
dev - > status = NULL ;
if ( info - > control - > cur_altsetting - > desc . bNumEndpoints = = 1 ) {
struct usb_endpoint_descriptor * desc ;
dev - > status = & info - > control - > cur_altsetting - > endpoint [ 0 ] ;
desc = & dev - > status - > desc ;
2006-10-26 13:02:47 -03:00
if ( ! usb_endpoint_is_int_in ( desc )
2005-08-31 09:54:20 -07:00
| | ( le16_to_cpu ( desc - > wMaxPacketSize )
< sizeof ( struct usb_cdc_notification ) )
| | ! desc - > bInterval ) {
dev_dbg ( & intf - > dev , " bad notification endpoint \n " ) ;
dev - > status = NULL ;
}
}
if ( rndis & & ! dev - > status ) {
dev_dbg ( & intf - > dev , " missing RNDIS status endpoint \n " ) ;
usb_set_intfdata ( info - > data , NULL ) ;
usb_driver_release_interface ( driver , info - > data ) ;
return - ENODEV ;
}
return 0 ;
bad_desc :
dev_info ( & dev - > udev - > dev , " bad CDC descriptors \n " ) ;
return - ENODEV ;
}
EXPORT_SYMBOL_GPL ( usbnet_generic_cdc_bind ) ;
void usbnet_cdc_unbind ( struct usbnet * dev , struct usb_interface * intf )
{
struct cdc_state * info = ( void * ) & dev - > data ;
struct usb_driver * driver = driver_of ( intf ) ;
/* disconnect master --> disconnect slave */
if ( intf = = info - > control & & info - > data ) {
/* ensure immediate exit from usbnet_disconnect */
usb_set_intfdata ( info - > data , NULL ) ;
usb_driver_release_interface ( driver , info - > data ) ;
info - > data = NULL ;
}
/* and vice versa (just in case) */
else if ( intf = = info - > data & & info - > control ) {
/* ensure immediate exit from usbnet_disconnect */
usb_set_intfdata ( info - > control , NULL ) ;
usb_driver_release_interface ( driver , info - > control ) ;
info - > control = NULL ;
}
}
EXPORT_SYMBOL_GPL ( usbnet_cdc_unbind ) ;
/*-------------------------------------------------------------------------
*
* Communications Device Class , Ethernet Control model
*
* Takes two interfaces . The DATA interface is inactive till an altsetting
* is selected . Configuration data includes class descriptors . There ' s
* an optional status endpoint on the control interface .
*
* This should interop with whatever the 2.4 " CDCEther.c " driver
* ( by Brad Hards ) talked with , with more functionality .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void dumpspeed ( struct usbnet * dev , __le32 * speeds )
{
if ( netif_msg_timer ( dev ) )
devinfo ( dev , " link speeds: %u kbps up, %u kbps down " ,
__le32_to_cpu ( speeds [ 0 ] ) / 1000 ,
__le32_to_cpu ( speeds [ 1 ] ) / 1000 ) ;
}
static void 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 :
if ( netif_msg_timer ( dev ) )
devdbg ( dev , " CDC: carrier %s " ,
event - > wValue ? " on " : " off " ) ;
if ( event - > wValue )
netif_carrier_on ( dev - > net ) ;
else
netif_carrier_off ( dev - > net ) ;
break ;
case USB_CDC_NOTIFY_SPEED_CHANGE : /* tx/rx rates */
if ( netif_msg_timer ( dev ) )
devdbg ( dev , " CDC: speed change (len %d) " ,
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 :
deverr ( dev , " CDC: unexpected notification %02x! " ,
event - > bNotificationType ) ;
break ;
}
}
static int cdc_bind ( struct usbnet * dev , struct usb_interface * intf )
{
int status ;
struct cdc_state * info = ( void * ) & dev - > data ;
status = usbnet_generic_cdc_bind ( dev , intf ) ;
if ( status < 0 )
return status ;
2009-04-18 07:24:17 +00:00
status = usbnet_get_ethernet_addr ( dev , info - > ether - > iMACAddress ) ;
2005-08-31 09:54:20 -07:00
if ( status < 0 ) {
usb_set_intfdata ( info - > data , NULL ) ;
usb_driver_release_interface ( driver_of ( intf ) , info - > data ) ;
return status ;
}
/* FIXME cdc-ether has some multicast code too, though it complains
* in routine cases . info - > ether describes the multicast support .
* Implement that here , manipulating the cdc filter as needed .
*/
return 0 ;
}
static const struct driver_info cdc_info = {
. description = " CDC Ethernet Device " ,
. flags = FLAG_ETHER ,
// .check_connect = cdc_check_connect,
. bind = cdc_bind ,
. unbind = usbnet_cdc_unbind ,
. status = cdc_status ,
} ;
/*-------------------------------------------------------------------------*/
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 ,
} ,
2006-05-30 20:49:29 -07:00
/* 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 ,
} ,
2005-08-31 09:54:20 -07:00
/*
* 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 ) .
*/
{
USB_INTERFACE_INFO ( USB_CLASS_COMM , USB_CDC_SUBCLASS_ETHERNET ,
USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
2009-02-25 04:33:58 +00:00
} , {
/* Ericsson F3507g */
USB_DEVICE_AND_INTERFACE_INFO ( 0x0bdb , 0x1900 , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_MDLM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
2009-06-18 01:50:52 +00:00
} , {
/* Ericsson F3507g ver. 2 */
USB_DEVICE_AND_INTERFACE_INFO ( 0x0bdb , 0x1902 , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_MDLM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
} , {
/* Ericsson F3607gw */
USB_DEVICE_AND_INTERFACE_INFO ( 0x0bdb , 0x1904 , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_MDLM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
} , {
/* Ericsson F3307 */
USB_DEVICE_AND_INTERFACE_INFO ( 0x0bdb , 0x1906 , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_MDLM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
} , {
/* Toshiba F3507g */
USB_DEVICE_AND_INTERFACE_INFO ( 0x0930 , 0x130b , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_MDLM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
} , {
/* Dell F3507g */
USB_DEVICE_AND_INTERFACE_INFO ( 0x413c , 0x8147 , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_MDLM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_info ,
2005-08-31 09:54:20 -07:00
} ,
{ } , // 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 ,
} ;
static int __init cdc_init ( void )
{
2006-10-08 16:02:00 +04:00
BUILD_BUG_ON ( ( sizeof ( ( ( struct usbnet * ) 0 ) - > data )
2005-08-31 09:54:20 -07:00
< sizeof ( struct cdc_state ) ) ) ;
return usb_register ( & cdc_driver ) ;
}
module_init ( cdc_init ) ;
static void __exit cdc_exit ( void )
{
usb_deregister ( & cdc_driver ) ;
}
module_exit ( cdc_exit ) ;
MODULE_AUTHOR ( " David Brownell " ) ;
MODULE_DESCRIPTION ( " USB CDC Ethernet devices " ) ;
MODULE_LICENSE ( " GPL " ) ;