2009-05-04 23:01:43 +04:00
/*
* USB CDC EEM network interface driver
* Copyright ( C ) 2009 Oberthur Technologies
* by Omar Laazimani , Olivier Condemine
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/ctype.h>
# include <linux/ethtool.h>
# include <linux/workqueue.h>
# include <linux/mii.h>
# include <linux/usb.h>
# include <linux/crc32.h>
# include <linux/usb/cdc.h>
# include <linux/usb/usbnet.h>
/*
* 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 .30 rc2 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 ) ;
devwarn ( dev , " 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 ;
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 ) ;
2009-12-03 10:58:21 +03:00
if ( ( tailroom > = ETH_FCS_LEN + padlen ) & &
( headroom > = EEM_HEAD ) )
2009-05-04 23:01:43 +04:00
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 dependant ( 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 ) ) {
devdbg ( dev , " reserved command %04x \n " , header ) ;
continue ;
}
bmEEMCmd = ( header > > 11 ) & 0x7 ;
switch ( bmEEMCmd ) {
/* Responding to echo requests is mandatory. */
case 0 : /* Echo command */
len = header & 0x7FF ;
/* bogus command? */
if ( skb - > len < len )
return 0 ;
skb2 = skb_clone ( skb , GFP_ATOMIC ) ;
if ( unlikely ( ! skb2 ) )
goto next ;
skb_trim ( skb2 , len ) ;
put_unaligned_le16 ( BIT ( 15 ) | ( 1 < < 11 ) | len ,
skb_push ( skb2 , 2 ) ) ;
eem_linkcmd ( dev , skb2 ) ;
break ;
/*
* Host may choose to ignore hints .
* - suspend : peripheral ready to suspend
* - response : suggest N millisec polling
* - response complete : suggest N sec polling
*/
case 2 : /* Suspend hint */
case 3 : /* Response hint */
case 4 : /* Response complete hint */
continue ;
/*
* Hosts should never receive host - to - peripheral
* or reserved command codes ; or responses to an
* echo command we didn ' t send .
*/
case 1 : /* Echo response */
case 5 : /* Tickle */
default : /* reserved */
devwarn ( dev , " 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
*/
2009-08-11 01:46:59 +04:00
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 ) ;
2009-05-04 23:01:43 +04:00
crc2 = 0xdeadbeef ;
2009-08-11 01:46:59 +04:00
}
skb_trim ( skb2 , len - ETH_FCS_LEN ) ;
2009-05-04 23:01:43 +04:00
if ( is_last )
return crc = = crc2 ;
if ( unlikely ( crc ! = crc2 ) ) {
2009-06-29 20:49:40 +04:00
dev - > net - > stats . rx_errors + + ;
2009-05-04 23:01:43 +04:00
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 ,
. 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 ,
} ;
static int __init eem_init ( void )
{
return usb_register ( & eem_driver ) ;
}
module_init ( eem_init ) ;
static void __exit eem_exit ( void )
{
usb_deregister ( & eem_driver ) ;
}
module_exit ( eem_exit ) ;
MODULE_AUTHOR ( " Omar Laazimani <omar.oberthur@gmail.com> " ) ;
MODULE_DESCRIPTION ( " USB CDC EEM " ) ;
MODULE_LICENSE ( " GPL " ) ;