2010-11-30 02:23:28 +03:00
/*
* cdc_ncm . c
*
2012-03-14 15:26:10 +04:00
* Copyright ( C ) ST - Ericsson 2010 - 2012
2010-11-30 02:23:28 +03:00
* Contact : Alexey Orishko < alexey . orishko @ stericsson . com >
* Original author : Hans Petter Selasky < hans . petter . selasky @ stericsson . com >
*
* USB Host Driver for Network Control Model ( NCM )
2015-07-11 18:30:01 +03:00
* http : //www.usb.org/developers/docs/devclass_docs/NCM10_012011.zip
2010-11-30 02:23:28 +03:00
*
* The NCM encoding , decoding and initialization logic
* derives from FreeBSD 8. x . if_cdce . c and if_cdcereg . h
*
* This software is available to you under a choice of one of two
* licenses . You may choose this file to be licensed under the terms
* of the GNU General Public License ( GPL ) Version 2 or the 2 - clause
* BSD license listed below :
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
* 1. Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* 2. Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ` ` AS IS ' ' AND
* ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED . IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT
* LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE .
*/
# include <linux/module.h>
# include <linux/netdevice.h>
# include <linux/ctype.h>
2015-12-23 15:42:43 +03:00
# include <linux/etherdevice.h>
2010-11-30 02:23:28 +03:00
# include <linux/ethtool.h>
# include <linux/workqueue.h>
# include <linux/mii.h>
# include <linux/crc32.h>
# include <linux/usb.h>
2012-03-14 15:26:10 +04:00
# include <linux/hrtimer.h>
2010-11-30 02:23:28 +03:00
# include <linux/atomic.h>
# include <linux/usb/usbnet.h>
# include <linux/usb/cdc.h>
2012-10-22 14:56:35 +04:00
# include <linux/usb/cdc_ncm.h>
2010-11-30 02:23:28 +03:00
2013-03-14 05:05:13 +04:00
# if IS_ENABLED(CONFIG_USB_NET_CDC_MBIM)
static bool prefer_mbim = true ;
# else
static bool prefer_mbim ;
# endif
module_param ( prefer_mbim , bool , S_IRUGO | S_IWUSR ) ;
MODULE_PARM_DESC ( prefer_mbim , " Prefer MBIM setting on dual NCM/MBIM functions " ) ;
2012-03-14 15:26:10 +04:00
static void cdc_ncm_txpath_bh ( unsigned long param ) ;
static void cdc_ncm_tx_timeout_start ( struct cdc_ncm_ctx * ctx ) ;
static enum hrtimer_restart cdc_ncm_tx_timer_cb ( struct hrtimer * hr_timer ) ;
2010-11-30 02:23:28 +03:00
static struct usb_driver cdc_ncm_driver ;
2014-05-16 23:48:25 +04:00
struct cdc_ncm_stats {
char stat_string [ ETH_GSTRING_LEN ] ;
int sizeof_stat ;
int stat_offset ;
} ;
# define CDC_NCM_STAT(str, m) { \
. stat_string = str , \
. sizeof_stat = sizeof ( ( ( struct cdc_ncm_ctx * ) 0 ) - > m ) , \
. stat_offset = offsetof ( struct cdc_ncm_ctx , m ) }
# define CDC_NCM_SIMPLE_STAT(m) CDC_NCM_STAT(__stringify(m), m)
static const struct cdc_ncm_stats cdc_ncm_gstrings_stats [ ] = {
CDC_NCM_SIMPLE_STAT ( tx_reason_ntb_full ) ,
CDC_NCM_SIMPLE_STAT ( tx_reason_ndp_full ) ,
CDC_NCM_SIMPLE_STAT ( tx_reason_timeout ) ,
CDC_NCM_SIMPLE_STAT ( tx_reason_max_datagram ) ,
CDC_NCM_SIMPLE_STAT ( tx_overhead ) ,
CDC_NCM_SIMPLE_STAT ( tx_ntbs ) ,
CDC_NCM_SIMPLE_STAT ( rx_overhead ) ,
CDC_NCM_SIMPLE_STAT ( rx_ntbs ) ,
} ;
static int cdc_ncm_get_sset_count ( struct net_device __always_unused * netdev , int sset )
{
switch ( sset ) {
case ETH_SS_STATS :
return ARRAY_SIZE ( cdc_ncm_gstrings_stats ) ;
default :
return - EOPNOTSUPP ;
}
}
static void cdc_ncm_get_ethtool_stats ( struct net_device * netdev ,
struct ethtool_stats __always_unused * stats ,
u64 * data )
{
struct usbnet * dev = netdev_priv ( netdev ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
int i ;
char * p = NULL ;
for ( i = 0 ; i < ARRAY_SIZE ( cdc_ncm_gstrings_stats ) ; i + + ) {
p = ( char * ) ctx + cdc_ncm_gstrings_stats [ i ] . stat_offset ;
data [ i ] = ( cdc_ncm_gstrings_stats [ i ] . sizeof_stat = = sizeof ( u64 ) ) ? * ( u64 * ) p : * ( u32 * ) p ;
}
}
static void cdc_ncm_get_strings ( struct net_device __always_unused * netdev , u32 stringset , u8 * data )
{
u8 * p = data ;
int i ;
switch ( stringset ) {
case ETH_SS_STATS :
for ( i = 0 ; i < ARRAY_SIZE ( cdc_ncm_gstrings_stats ) ; i + + ) {
memcpy ( p , cdc_ncm_gstrings_stats [ i ] . stat_string , ETH_GSTRING_LEN ) ;
p + = ETH_GSTRING_LEN ;
}
}
}
2014-05-16 23:48:22 +04:00
static void cdc_ncm_update_rxtx_max ( struct usbnet * dev , u32 new_rx , u32 new_tx ) ;
static const struct ethtool_ops cdc_ncm_ethtool_ops = {
. get_settings = usbnet_get_settings ,
. set_settings = usbnet_set_settings ,
. get_link = usbnet_get_link ,
. nway_reset = usbnet_nway_reset ,
. get_drvinfo = usbnet_get_drvinfo ,
. get_msglevel = usbnet_get_msglevel ,
. set_msglevel = usbnet_set_msglevel ,
. get_ts_info = ethtool_op_get_ts_info ,
2014-05-16 23:48:25 +04:00
. get_sset_count = cdc_ncm_get_sset_count ,
. get_strings = cdc_ncm_get_strings ,
. get_ethtool_stats = cdc_ncm_get_ethtool_stats ,
2014-05-16 23:48:22 +04:00
} ;
2014-05-30 11:31:06 +04:00
static u32 cdc_ncm_check_rx_max ( struct usbnet * dev , u32 new_rx )
2014-05-16 23:48:18 +04:00
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
u32 val , max , min ;
/* clamp new_rx to sane values */
min = USB_CDC_NCM_NTB_MIN_IN_SIZE ;
max = min_t ( u32 , CDC_NCM_NTB_MAX_SIZE_RX , le32_to_cpu ( ctx - > ncm_parm . dwNtbInMaxSize ) ) ;
/* dwNtbInMaxSize spec violation? Use MIN size for both limits */
if ( max < min ) {
dev_warn ( & dev - > intf - > dev , " dwNtbInMaxSize=%u is too small. Using %u \n " ,
le32_to_cpu ( ctx - > ncm_parm . dwNtbInMaxSize ) , min ) ;
max = min ;
}
val = clamp_t ( u32 , new_rx , min , max ) ;
2014-05-30 11:31:06 +04:00
if ( val ! = new_rx )
dev_dbg ( & dev - > intf - > dev , " rx_max must be in the [%u, %u] range \n " , min , max ) ;
return val ;
}
static u32 cdc_ncm_check_tx_max ( struct usbnet * dev , u32 new_tx )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
u32 val , max , min ;
/* clamp new_tx to sane values */
min = ctx - > max_datagram_size + ctx - > max_ndp_size + sizeof ( struct usb_cdc_ncm_nth16 ) ;
max = min_t ( u32 , CDC_NCM_NTB_MAX_SIZE_TX , le32_to_cpu ( ctx - > ncm_parm . dwNtbOutMaxSize ) ) ;
/* some devices set dwNtbOutMaxSize too low for the above default */
min = min ( min , max ) ;
val = clamp_t ( u32 , new_tx , min , max ) ;
if ( val ! = new_tx )
dev_dbg ( & dev - > intf - > dev , " tx_max must be in the [%u, %u] range \n " , min , max ) ;
return val ;
}
2014-05-30 11:31:09 +04:00
static ssize_t cdc_ncm_show_min_tx_pkt ( struct device * d , struct device_attribute * attr , char * buf )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
return sprintf ( buf , " %u \n " , ctx - > min_tx_pkt ) ;
}
2014-05-30 11:31:06 +04:00
static ssize_t cdc_ncm_show_rx_max ( struct device * d , struct device_attribute * attr , char * buf )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
return sprintf ( buf , " %u \n " , ctx - > rx_max ) ;
}
static ssize_t cdc_ncm_show_tx_max ( struct device * d , struct device_attribute * attr , char * buf )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
return sprintf ( buf , " %u \n " , ctx - > tx_max ) ;
}
static ssize_t cdc_ncm_show_tx_timer_usecs ( struct device * d , struct device_attribute * attr , char * buf )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
return sprintf ( buf , " %u \n " , ctx - > timer_interval / ( u32 ) NSEC_PER_USEC ) ;
}
2014-05-30 11:31:09 +04:00
static ssize_t cdc_ncm_store_min_tx_pkt ( struct device * d , struct device_attribute * attr , const char * buf , size_t len )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
unsigned long val ;
/* no need to restrict values - anything from 0 to infinity is OK */
if ( kstrtoul ( buf , 0 , & val ) )
return - EINVAL ;
ctx - > min_tx_pkt = val ;
return len ;
}
2014-05-30 11:31:06 +04:00
static ssize_t cdc_ncm_store_rx_max ( struct device * d , struct device_attribute * attr , const char * buf , size_t len )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
unsigned long val ;
if ( kstrtoul ( buf , 0 , & val ) | | cdc_ncm_check_rx_max ( dev , val ) ! = val )
return - EINVAL ;
cdc_ncm_update_rxtx_max ( dev , val , ctx - > tx_max ) ;
return len ;
}
static ssize_t cdc_ncm_store_tx_max ( struct device * d , struct device_attribute * attr , const char * buf , size_t len )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
unsigned long val ;
if ( kstrtoul ( buf , 0 , & val ) | | cdc_ncm_check_tx_max ( dev , val ) ! = val )
return - EINVAL ;
cdc_ncm_update_rxtx_max ( dev , ctx - > rx_max , val ) ;
return len ;
}
static ssize_t cdc_ncm_store_tx_timer_usecs ( struct device * d , struct device_attribute * attr , const char * buf , size_t len )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
ssize_t ret ;
unsigned long val ;
ret = kstrtoul ( buf , 0 , & val ) ;
if ( ret )
return ret ;
if ( val & & ( val < CDC_NCM_TIMER_INTERVAL_MIN | | val > CDC_NCM_TIMER_INTERVAL_MAX ) )
return - EINVAL ;
spin_lock_bh ( & ctx - > mtx ) ;
ctx - > timer_interval = val * NSEC_PER_USEC ;
if ( ! ctx - > timer_interval )
ctx - > tx_timer_pending = 0 ;
spin_unlock_bh ( & ctx - > mtx ) ;
return len ;
}
2014-05-30 11:31:09 +04:00
static DEVICE_ATTR ( min_tx_pkt , S_IRUGO | S_IWUSR , cdc_ncm_show_min_tx_pkt , cdc_ncm_store_min_tx_pkt ) ;
2014-05-30 11:31:06 +04:00
static DEVICE_ATTR ( rx_max , S_IRUGO | S_IWUSR , cdc_ncm_show_rx_max , cdc_ncm_store_rx_max ) ;
static DEVICE_ATTR ( tx_max , S_IRUGO | S_IWUSR , cdc_ncm_show_tx_max , cdc_ncm_store_tx_max ) ;
static DEVICE_ATTR ( tx_timer_usecs , S_IRUGO | S_IWUSR , cdc_ncm_show_tx_timer_usecs , cdc_ncm_store_tx_timer_usecs ) ;
2015-12-07 00:47:15 +03:00
static ssize_t ndp_to_end_show ( struct device * d , struct device_attribute * attr , char * buf )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
return sprintf ( buf , " %c \n " , ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ? ' Y ' : ' N ' ) ;
}
static ssize_t ndp_to_end_store ( struct device * d , struct device_attribute * attr , const char * buf , size_t len )
{
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
bool enable ;
if ( strtobool ( buf , & enable ) )
return - EINVAL ;
/* no change? */
if ( enable = = ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ) )
return len ;
if ( enable & & ! ctx - > delayed_ndp16 ) {
ctx - > delayed_ndp16 = kzalloc ( ctx - > max_ndp_size , GFP_KERNEL ) ;
if ( ! ctx - > delayed_ndp16 )
return - ENOMEM ;
}
/* flush pending data before changing flag */
netif_tx_lock_bh ( dev - > net ) ;
usbnet_start_xmit ( NULL , dev - > net ) ;
spin_lock_bh ( & ctx - > mtx ) ;
if ( enable )
ctx - > drvflags | = CDC_NCM_FLAG_NDP_TO_END ;
else
ctx - > drvflags & = ~ CDC_NCM_FLAG_NDP_TO_END ;
spin_unlock_bh ( & ctx - > mtx ) ;
netif_tx_unlock_bh ( dev - > net ) ;
return len ;
}
static DEVICE_ATTR_RW ( ndp_to_end ) ;
2014-05-30 11:31:08 +04:00
# define NCM_PARM_ATTR(name, format, tocpu) \
static ssize_t cdc_ncm_show_ # # name ( struct device * d , struct device_attribute * attr , char * buf ) \
{ \
struct usbnet * dev = netdev_priv ( to_net_dev ( d ) ) ; \
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ; \
return sprintf ( buf , format " \n " , tocpu ( ctx - > ncm_parm . name ) ) ; \
} \
static DEVICE_ATTR ( name , S_IRUGO , cdc_ncm_show_ # # name , NULL )
NCM_PARM_ATTR ( bmNtbFormatsSupported , " 0x%04x " , le16_to_cpu ) ;
NCM_PARM_ATTR ( dwNtbInMaxSize , " %u " , le32_to_cpu ) ;
NCM_PARM_ATTR ( wNdpInDivisor , " %u " , le16_to_cpu ) ;
NCM_PARM_ATTR ( wNdpInPayloadRemainder , " %u " , le16_to_cpu ) ;
NCM_PARM_ATTR ( wNdpInAlignment , " %u " , le16_to_cpu ) ;
NCM_PARM_ATTR ( dwNtbOutMaxSize , " %u " , le32_to_cpu ) ;
NCM_PARM_ATTR ( wNdpOutDivisor , " %u " , le16_to_cpu ) ;
NCM_PARM_ATTR ( wNdpOutPayloadRemainder , " %u " , le16_to_cpu ) ;
NCM_PARM_ATTR ( wNdpOutAlignment , " %u " , le16_to_cpu ) ;
NCM_PARM_ATTR ( wNtbOutMaxDatagrams , " %u " , le16_to_cpu ) ;
2014-05-30 11:31:06 +04:00
static struct attribute * cdc_ncm_sysfs_attrs [ ] = {
2014-05-30 11:31:09 +04:00
& dev_attr_min_tx_pkt . attr ,
2015-12-07 00:47:15 +03:00
& dev_attr_ndp_to_end . attr ,
2014-05-30 11:31:06 +04:00
& dev_attr_rx_max . attr ,
& dev_attr_tx_max . attr ,
& dev_attr_tx_timer_usecs . attr ,
2014-05-30 11:31:08 +04:00
& dev_attr_bmNtbFormatsSupported . attr ,
& dev_attr_dwNtbInMaxSize . attr ,
& dev_attr_wNdpInDivisor . attr ,
& dev_attr_wNdpInPayloadRemainder . attr ,
& dev_attr_wNdpInAlignment . attr ,
& dev_attr_dwNtbOutMaxSize . attr ,
& dev_attr_wNdpOutDivisor . attr ,
& dev_attr_wNdpOutPayloadRemainder . attr ,
& dev_attr_wNdpOutAlignment . attr ,
& dev_attr_wNtbOutMaxDatagrams . attr ,
2014-05-30 11:31:06 +04:00
NULL ,
} ;
static struct attribute_group cdc_ncm_sysfs_attr_group = {
. name = " cdc_ncm " ,
. attrs = cdc_ncm_sysfs_attrs ,
} ;
/* handle rx_max and tx_max changes */
static void cdc_ncm_update_rxtx_max ( struct usbnet * dev , u32 new_rx , u32 new_tx )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
u8 iface_no = ctx - > control - > cur_altsetting - > desc . bInterfaceNumber ;
u32 val ;
val = cdc_ncm_check_rx_max ( dev , new_rx ) ;
2014-05-16 23:48:18 +04:00
/* inform device about NTB input size changes */
if ( val ! = ctx - > rx_max ) {
__le32 dwNtbInMaxSize = cpu_to_le32 ( val ) ;
dev_info ( & dev - > intf - > dev , " setting rx_max = %u \n " , val ) ;
2014-05-16 23:48:21 +04:00
/* tell device to use new size */
2014-05-16 23:48:18 +04:00
if ( usbnet_write_cmd ( dev , USB_CDC_SET_NTB_INPUT_SIZE ,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE ,
0 , iface_no , & dwNtbInMaxSize , 4 ) < 0 )
dev_dbg ( & dev - > intf - > dev , " Setting NTB Input Size failed \n " ) ;
else
ctx - > rx_max = val ;
}
2014-05-30 11:31:05 +04:00
/* usbnet use these values for sizing rx queues */
if ( dev - > rx_urb_size ! = ctx - > rx_max ) {
dev - > rx_urb_size = ctx - > rx_max ;
if ( netif_running ( dev - > net ) )
usbnet_unlink_rx_urbs ( dev ) ;
}
2014-05-30 11:31:06 +04:00
val = cdc_ncm_check_tx_max ( dev , new_tx ) ;
2014-05-16 23:48:18 +04:00
if ( val ! = ctx - > tx_max )
dev_info ( & dev - > intf - > dev , " setting tx_max = %u \n " , val ) ;
2014-05-16 23:48:20 +04:00
/* Adding a pad byte here if necessary simplifies the handling
* in cdc_ncm_fill_tx_frame , making tx_max always represent
* the real skb max size .
*
* We cannot use dev - > maxpacket here because this is called from
* . bind which is called before usbnet sets up dev - > maxpacket
*/
2014-05-16 23:48:21 +04:00
if ( val ! = le32_to_cpu ( ctx - > ncm_parm . dwNtbOutMaxSize ) & &
val % usb_maxpacket ( dev - > udev , dev - > out , 1 ) = = 0 )
val + + ;
/* we might need to flush any pending tx buffers if running */
if ( netif_running ( dev - > net ) & & val > ctx - > tx_max ) {
netif_tx_lock_bh ( dev - > net ) ;
usbnet_start_xmit ( NULL , dev - > net ) ;
2014-05-30 11:31:04 +04:00
/* make sure tx_curr_skb is reallocated if it was empty */
if ( ctx - > tx_curr_skb ) {
dev_kfree_skb_any ( ctx - > tx_curr_skb ) ;
ctx - > tx_curr_skb = NULL ;
}
2014-05-16 23:48:21 +04:00
ctx - > tx_max = val ;
netif_tx_unlock_bh ( dev - > net ) ;
} else {
ctx - > tx_max = val ;
}
2014-05-16 23:48:20 +04:00
dev - > hard_mtu = ctx - > tx_max ;
2014-05-16 23:48:21 +04:00
/* max qlen depend on hard_mtu and rx_urb_size */
usbnet_update_max_qlen ( dev ) ;
2014-05-16 23:48:24 +04:00
/* never pad more than 3 full USB packets per transfer */
ctx - > min_tx_pkt = clamp_t ( u16 , ctx - > tx_max - 3 * usb_maxpacket ( dev - > udev , dev - > out , 1 ) ,
CDC_NCM_MIN_TX_PKT , ctx - > tx_max ) ;
2014-05-16 23:48:18 +04:00
}
2014-05-16 23:48:19 +04:00
/* helpers for NCM and MBIM differences */
static u8 cdc_ncm_flags ( struct usbnet * dev )
2010-11-30 02:23:28 +03:00
{
2013-11-01 14:16:42 +04:00
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
2010-11-30 02:23:28 +03:00
2014-05-16 23:48:19 +04:00
if ( cdc_ncm_comm_intf_is_mbim ( dev - > intf - > cur_altsetting ) & & ctx - > mbim_desc )
return ctx - > mbim_desc - > bmNetworkCapabilities ;
if ( ctx - > func_desc )
return ctx - > func_desc - > bmNetworkCapabilities ;
return 0 ;
}
static int cdc_ncm_eth_hlen ( struct usbnet * dev )
{
if ( cdc_ncm_comm_intf_is_mbim ( dev - > intf - > cur_altsetting ) )
return 0 ;
return ETH_HLEN ;
}
static u32 cdc_ncm_min_dgram_size ( struct usbnet * dev )
{
if ( cdc_ncm_comm_intf_is_mbim ( dev - > intf - > cur_altsetting ) )
return CDC_MBIM_MIN_DATAGRAM_SIZE ;
return CDC_NCM_MIN_DATAGRAM_SIZE ;
}
static u32 cdc_ncm_max_dgram_size ( struct usbnet * dev )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
if ( cdc_ncm_comm_intf_is_mbim ( dev - > intf - > cur_altsetting ) & & ctx - > mbim_desc )
return le16_to_cpu ( ctx - > mbim_desc - > wMaxSegmentSize ) ;
if ( ctx - > ether_desc )
return le16_to_cpu ( ctx - > ether_desc - > wMaxSegmentSize ) ;
return CDC_NCM_MAX_DATAGRAM_SIZE ;
}
/* initial one-time device setup. MUST be called with the data interface
* in altsetting 0
*/
static int cdc_ncm_init ( struct usbnet * dev )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
u8 iface_no = ctx - > control - > cur_altsetting - > desc . bInterfaceNumber ;
int err ;
2010-11-30 02:23:28 +03:00
2012-10-24 23:46:56 +04:00
err = usbnet_read_cmd ( dev , USB_CDC_GET_NTB_PARAMETERS ,
USB_TYPE_CLASS | USB_DIR_IN
| USB_RECIP_INTERFACE ,
2014-03-17 19:25:18 +04:00
0 , iface_no , & ctx - > ncm_parm ,
sizeof ( ctx - > ncm_parm ) ) ;
2011-08-04 02:10:29 +04:00
if ( err < 0 ) {
2013-11-01 14:16:59 +04:00
dev_err ( & dev - > intf - > dev , " failed GET_NTB_PARAMETERS \n " ) ;
return err ; /* GET_NTB_PARAMETERS is required */
2010-11-30 02:23:28 +03:00
}
2014-05-16 23:48:19 +04:00
/* set CRC Mode */
if ( cdc_ncm_flags ( dev ) & USB_CDC_NCM_NCAP_CRC_MODE ) {
dev_dbg ( & dev - > intf - > dev , " Setting CRC mode off \n " ) ;
err = usbnet_write_cmd ( dev , USB_CDC_SET_CRC_MODE ,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE ,
USB_CDC_NCM_CRC_NOT_APPENDED ,
iface_no , NULL , 0 ) ;
if ( err < 0 )
dev_err ( & dev - > intf - > dev , " SET_CRC_MODE failed \n " ) ;
}
/* set NTB format, if both formats are supported.
*
* " The host shall only send this command while the NCM Data
* Interface is in alternate setting 0. "
*/
2014-05-22 11:52:29 +04:00
if ( le16_to_cpu ( ctx - > ncm_parm . bmNtbFormatsSupported ) &
USB_CDC_NCM_NTB32_SUPPORTED ) {
2014-05-16 23:48:19 +04:00
dev_dbg ( & dev - > intf - > dev , " Setting NTB format to 16-bit \n " ) ;
err = usbnet_write_cmd ( dev , USB_CDC_SET_NTB_FORMAT ,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE ,
USB_CDC_NCM_NTB16_FORMAT ,
iface_no , NULL , 0 ) ;
if ( err < 0 )
dev_err ( & dev - > intf - > dev , " SET_NTB_FORMAT failed \n " ) ;
}
/* set initial device values */
2014-03-17 19:25:18 +04:00
ctx - > rx_max = le32_to_cpu ( ctx - > ncm_parm . dwNtbInMaxSize ) ;
ctx - > tx_max = le32_to_cpu ( ctx - > ncm_parm . dwNtbOutMaxSize ) ;
ctx - > tx_remainder = le16_to_cpu ( ctx - > ncm_parm . wNdpOutPayloadRemainder ) ;
ctx - > tx_modulus = le16_to_cpu ( ctx - > ncm_parm . wNdpOutDivisor ) ;
ctx - > tx_ndp_modulus = le16_to_cpu ( ctx - > ncm_parm . wNdpOutAlignment ) ;
2011-02-07 12:45:10 +03:00
/* devices prior to NCM Errata shall set this field to zero */
2014-03-17 19:25:18 +04:00
ctx - > tx_max_datagrams = le16_to_cpu ( ctx - > ncm_parm . wNtbOutMaxDatagrams ) ;
2013-11-01 14:16:58 +04:00
2013-11-01 14:16:54 +04:00
dev_dbg ( & dev - > intf - > dev ,
" dwNtbInMaxSize=%u dwNtbOutMaxSize=%u wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u wNdpOutAlignment=%u wNtbOutMaxDatagrams=%u flags=0x%x \n " ,
ctx - > rx_max , ctx - > tx_max , ctx - > tx_remainder , ctx - > tx_modulus ,
2014-05-16 23:48:19 +04:00
ctx - > tx_ndp_modulus , ctx - > tx_max_datagrams , cdc_ncm_flags ( dev ) ) ;
2010-11-30 02:23:28 +03:00
2011-02-07 12:45:10 +03:00
/* max count of tx datagrams */
if ( ( ctx - > tx_max_datagrams = = 0 ) | |
( ctx - > tx_max_datagrams > CDC_NCM_DPT_DATAGRAMS_MAX ) )
ctx - > tx_max_datagrams = CDC_NCM_DPT_DATAGRAMS_MAX ;
2010-11-30 02:23:28 +03:00
2014-05-16 23:48:23 +04:00
/* set up maximum NDP size */
ctx - > max_ndp_size = sizeof ( struct usb_cdc_ncm_ndp16 ) + ( ctx - > tx_max_datagrams + 1 ) * sizeof ( struct usb_cdc_ncm_dpe16 ) ;
2014-05-16 23:48:22 +04:00
/* initial coalescing timer interval */
ctx - > timer_interval = CDC_NCM_TIMER_INTERVAL_USEC * NSEC_PER_USEC ;
2014-05-16 23:48:19 +04:00
return 0 ;
}
/* set a new max datagram size */
static void cdc_ncm_set_dgram_size ( struct usbnet * dev , int new_size )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
u8 iface_no = ctx - > control - > cur_altsetting - > desc . bInterfaceNumber ;
__le16 max_datagram_size ;
u16 mbim_mtu ;
int err ;
/* set default based on descriptors */
ctx - > max_datagram_size = clamp_t ( u32 , new_size ,
cdc_ncm_min_dgram_size ( dev ) ,
CDC_NCM_MAX_DATAGRAM_SIZE ) ;
/* inform the device about the selected Max Datagram Size? */
if ( ! ( cdc_ncm_flags ( dev ) & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE ) )
goto out ;
/* read current mtu value from device */
err = usbnet_read_cmd ( dev , USB_CDC_GET_MAX_DATAGRAM_SIZE ,
USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE ,
0 , iface_no , & max_datagram_size , 2 ) ;
if ( err < 0 ) {
dev_dbg ( & dev - > intf - > dev , " GET_MAX_DATAGRAM_SIZE failed \n " ) ;
goto out ;
}
if ( le16_to_cpu ( max_datagram_size ) = = ctx - > max_datagram_size )
goto out ;
max_datagram_size = cpu_to_le16 ( ctx - > max_datagram_size ) ;
err = usbnet_write_cmd ( dev , USB_CDC_SET_MAX_DATAGRAM_SIZE ,
USB_TYPE_CLASS | USB_DIR_OUT | USB_RECIP_INTERFACE ,
0 , iface_no , & max_datagram_size , 2 ) ;
if ( err < 0 )
dev_dbg ( & dev - > intf - > dev , " SET_MAX_DATAGRAM_SIZE failed \n " ) ;
out :
/* set MTU to max supported by the device if necessary */
dev - > net - > mtu = min_t ( int , dev - > net - > mtu , ctx - > max_datagram_size - cdc_ncm_eth_hlen ( dev ) ) ;
/* do not exceed operater preferred MTU */
if ( ctx - > mbim_extended_desc ) {
mbim_mtu = le16_to_cpu ( ctx - > mbim_extended_desc - > wMTU ) ;
if ( mbim_mtu ! = 0 & & mbim_mtu < dev - > net - > mtu )
dev - > net - > mtu = mbim_mtu ;
}
}
static void cdc_ncm_fix_modulus ( struct usbnet * dev )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
u32 val ;
2010-11-30 02:23:28 +03:00
/*
* verify that the structure alignment is :
* - power of two
* - not greater than the maximum transmit length
* - not less than four bytes
*/
val = ctx - > tx_ndp_modulus ;
if ( ( val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE ) | |
( val ! = ( ( - val ) & val ) ) | | ( val > = ctx - > tx_max ) ) {
2013-11-01 14:16:54 +04:00
dev_dbg ( & dev - > intf - > dev , " Using default alignment: 4 bytes \n " ) ;
2010-11-30 02:23:28 +03:00
ctx - > tx_ndp_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE ;
}
/*
* verify that the payload alignment is :
* - power of two
* - not greater than the maximum transmit length
* - not less than four bytes
*/
val = ctx - > tx_modulus ;
if ( ( val < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE ) | |
( val ! = ( ( - val ) & val ) ) | | ( val > = ctx - > tx_max ) ) {
2013-11-01 14:16:54 +04:00
dev_dbg ( & dev - > intf - > dev , " Using default transmit modulus: 4 bytes \n " ) ;
2010-11-30 02:23:28 +03:00
ctx - > tx_modulus = USB_CDC_NCM_NDP_ALIGN_MIN_SIZE ;
}
/* verify the payload remainder */
if ( ctx - > tx_remainder > = ctx - > tx_modulus ) {
2013-11-01 14:16:54 +04:00
dev_dbg ( & dev - > intf - > dev , " Using default transmit remainder: 0 bytes \n " ) ;
2010-11-30 02:23:28 +03:00
ctx - > tx_remainder = 0 ;
}
/* adjust TX-remainder according to NCM specification. */
2014-05-16 23:48:19 +04:00
ctx - > tx_remainder = ( ( ctx - > tx_remainder - cdc_ncm_eth_hlen ( dev ) ) &
2012-10-22 14:56:30 +04:00
( ctx - > tx_modulus - 1 ) ) ;
2014-05-16 23:48:19 +04:00
}
2010-11-30 02:23:28 +03:00
2014-05-16 23:48:19 +04:00
static int cdc_ncm_setup ( struct usbnet * dev )
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
2014-05-16 23:48:26 +04:00
u32 def_rx , def_tx ;
/* be conservative when selecting intial buffer size to
* increase the number of hosts this will work for
*/
def_rx = min_t ( u32 , CDC_NCM_NTB_DEF_SIZE_RX ,
le32_to_cpu ( ctx - > ncm_parm . dwNtbInMaxSize ) ) ;
def_tx = min_t ( u32 , CDC_NCM_NTB_DEF_SIZE_TX ,
le32_to_cpu ( ctx - > ncm_parm . dwNtbOutMaxSize ) ) ;
2010-11-30 02:23:28 +03:00
2014-05-16 23:48:19 +04:00
/* clamp rx_max and tx_max and inform device */
2014-05-16 23:48:26 +04:00
cdc_ncm_update_rxtx_max ( dev , def_rx , def_tx ) ;
2014-03-20 01:00:06 +04:00
2014-05-16 23:48:19 +04:00
/* sanitize the modulus and remainder values */
cdc_ncm_fix_modulus ( dev ) ;
2014-03-20 01:00:06 +04:00
2014-05-16 23:48:19 +04:00
/* set max datagram size */
cdc_ncm_set_dgram_size ( dev , cdc_ncm_max_dgram_size ( dev ) ) ;
2010-11-30 02:23:28 +03:00
return 0 ;
}
static void
2013-11-01 14:16:41 +04:00
cdc_ncm_find_endpoints ( struct usbnet * dev , struct usb_interface * intf )
2010-11-30 02:23:28 +03:00
{
2013-11-01 14:16:41 +04:00
struct usb_host_endpoint * e , * in = NULL , * out = NULL ;
2010-11-30 02:23:28 +03:00
u8 ep ;
for ( ep = 0 ; ep < intf - > cur_altsetting - > desc . bNumEndpoints ; ep + + ) {
e = intf - > cur_altsetting - > endpoint + ep ;
switch ( e - > desc . bmAttributes & USB_ENDPOINT_XFERTYPE_MASK ) {
case USB_ENDPOINT_XFER_INT :
if ( usb_endpoint_dir_in ( & e - > desc ) ) {
2013-11-01 14:16:41 +04:00
if ( ! dev - > status )
dev - > status = e ;
2010-11-30 02:23:28 +03:00
}
break ;
case USB_ENDPOINT_XFER_BULK :
if ( usb_endpoint_dir_in ( & e - > desc ) ) {
2013-11-01 14:16:41 +04:00
if ( ! in )
in = e ;
2010-11-30 02:23:28 +03:00
} else {
2013-11-01 14:16:41 +04:00
if ( ! out )
out = e ;
2010-11-30 02:23:28 +03:00
}
break ;
default :
break ;
}
}
2013-11-01 14:16:41 +04:00
if ( in & & ! dev - > in )
dev - > in = usb_rcvbulkpipe ( dev - > udev ,
in - > desc . bEndpointAddress &
USB_ENDPOINT_NUMBER_MASK ) ;
if ( out & & ! dev - > out )
dev - > out = usb_sndbulkpipe ( dev - > udev ,
out - > desc . bEndpointAddress &
USB_ENDPOINT_NUMBER_MASK ) ;
2010-11-30 02:23:28 +03:00
}
static void cdc_ncm_free ( struct cdc_ncm_ctx * ctx )
{
if ( ctx = = NULL )
return ;
if ( ctx - > tx_rem_skb ! = NULL ) {
dev_kfree_skb_any ( ctx - > tx_rem_skb ) ;
ctx - > tx_rem_skb = NULL ;
}
if ( ctx - > tx_curr_skb ! = NULL ) {
dev_kfree_skb_any ( ctx - > tx_curr_skb ) ;
ctx - > tx_curr_skb = NULL ;
}
2015-07-08 14:05:57 +03:00
kfree ( ctx - > delayed_ndp16 ) ;
2010-11-30 02:23:28 +03:00
kfree ( ctx ) ;
}
2015-12-23 15:42:43 +03:00
/* we need to override the usbnet change_mtu ndo for two reasons:
* - respect the negotiated maximum datagram size
* - avoid unwanted changes to rx and tx buffers
*/
int cdc_ncm_change_mtu ( struct net_device * net , int new_mtu )
{
struct usbnet * dev = netdev_priv ( net ) ;
2016-05-19 12:56:09 +03:00
int maxmtu = cdc_ncm_max_dgram_size ( dev ) - cdc_ncm_eth_hlen ( dev ) ;
2015-12-23 15:42:43 +03:00
if ( new_mtu < = 0 | | new_mtu > maxmtu )
return - EINVAL ;
2016-05-19 12:56:09 +03:00
2015-12-23 15:42:43 +03:00
net - > mtu = new_mtu ;
2016-05-19 12:56:09 +03:00
cdc_ncm_set_dgram_size ( dev , new_mtu + cdc_ncm_eth_hlen ( dev ) ) ;
2015-12-23 15:42:43 +03:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( cdc_ncm_change_mtu ) ;
static const struct net_device_ops cdc_ncm_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 = cdc_ncm_change_mtu ,
. ndo_set_mac_address = eth_mac_addr ,
. ndo_validate_addr = eth_validate_addr ,
} ;
2015-07-08 14:05:57 +03:00
int cdc_ncm_bind_common ( struct usbnet * dev , struct usb_interface * intf , u8 data_altsetting , int drvflags )
2010-11-30 02:23:28 +03:00
{
struct cdc_ncm_ctx * ctx ;
struct usb_driver * driver ;
u8 * buf ;
int len ;
int temp ;
u8 iface_no ;
2015-09-07 17:05:39 +03:00
struct usb_cdc_parsed_header hdr ;
2010-11-30 02:23:28 +03:00
2011-11-17 16:43:40 +04:00
ctx = kzalloc ( sizeof ( * ctx ) , GFP_KERNEL ) ;
2013-03-30 03:03:22 +04:00
if ( ! ctx )
return - ENOMEM ;
2010-11-30 02:23:28 +03:00
2012-03-14 15:26:10 +04:00
hrtimer_init ( & ctx - > tx_timer , CLOCK_MONOTONIC , HRTIMER_MODE_REL ) ;
ctx - > tx_timer . function = & cdc_ncm_tx_timer_cb ;
2013-11-01 14:16:42 +04:00
ctx - > bh . data = ( unsigned long ) dev ;
2012-03-14 15:26:10 +04:00
ctx - > bh . func = cdc_ncm_txpath_bh ;
atomic_set ( & ctx - > stop , 0 ) ;
2010-11-30 02:23:28 +03:00
spin_lock_init ( & ctx - > mtx ) ;
/* store ctx pointer in device data field */
dev - > data [ 0 ] = ( unsigned long ) ctx ;
2013-11-01 14:16:48 +04:00
/* only the control interface can be successfully probed */
ctx - > control = intf ;
2010-11-30 02:23:28 +03:00
/* get some pointers */
driver = driver_of ( intf ) ;
buf = intf - > cur_altsetting - > extra ;
len = intf - > cur_altsetting - > extralen ;
/* parse through descriptors associated with control interface */
2015-09-07 17:05:39 +03:00
cdc_parse_cdc_header ( & hdr , intf , buf , len ) ;
2015-11-23 16:32:10 +03:00
if ( hdr . usb_cdc_union_desc )
ctx - > data = usb_ifnum_to_if ( dev - > udev ,
hdr . usb_cdc_union_desc - > bSlaveInterface0 ) ;
2015-09-07 17:05:39 +03:00
ctx - > ether_desc = hdr . usb_cdc_ether_desc ;
ctx - > func_desc = hdr . usb_cdc_ncm_desc ;
ctx - > mbim_desc = hdr . usb_cdc_mbim_desc ;
ctx - > mbim_extended_desc = hdr . usb_cdc_mbim_extended_desc ;
2010-11-30 02:23:28 +03:00
2013-01-21 09:50:38 +04:00
/* some buggy devices have an IAD but no CDC Union */
2015-11-23 16:32:10 +03:00
if ( ! hdr . usb_cdc_union_desc & & intf - > intf_assoc & & intf - > intf_assoc - > bInterfaceCount = = 2 ) {
2013-01-26 03:36:59 +04:00
ctx - > data = usb_ifnum_to_if ( dev - > udev , intf - > cur_altsetting - > desc . bInterfaceNumber + 1 ) ;
dev_dbg ( & intf - > dev , " CDC Union missing - got slave from IAD \n " ) ;
2013-01-21 09:50:38 +04:00
}
2010-11-30 02:23:28 +03:00
/* check if we got everything */
2014-05-16 23:48:19 +04:00
if ( ! ctx - > data ) {
dev_dbg ( & intf - > dev , " CDC Union missing and no IAD found \n " ) ;
2010-11-30 02:23:28 +03:00
goto error ;
2013-11-01 14:17:00 +04:00
}
2014-05-16 23:48:19 +04:00
if ( cdc_ncm_comm_intf_is_mbim ( intf - > cur_altsetting ) ) {
if ( ! ctx - > mbim_desc ) {
dev_dbg ( & intf - > dev , " MBIM functional descriptor missing \n " ) ;
goto error ;
}
} else {
if ( ! ctx - > ether_desc | | ! ctx - > func_desc ) {
dev_dbg ( & intf - > dev , " NCM or ECM functional descriptors missing \n " ) ;
goto error ;
}
}
2010-11-30 02:23:28 +03:00
2012-11-13 07:19:43 +04:00
/* claim data interface, if different from control */
if ( ctx - > data ! = ctx - > control ) {
temp = usb_driver_claim_interface ( driver , ctx - > data , dev ) ;
2013-11-01 14:17:00 +04:00
if ( temp ) {
dev_dbg ( & intf - > dev , " failed to claim data intf \n " ) ;
2012-11-13 07:19:43 +04:00
goto error ;
2013-11-01 14:17:00 +04:00
}
2012-11-13 07:19:43 +04:00
}
2010-11-30 02:23:28 +03:00
iface_no = ctx - > data - > cur_altsetting - > desc . bInterfaceNumber ;
2016-03-04 00:20:53 +03:00
/* Reset data interface. Some devices will not reset properly
* unless they are configured first . Toggle the altsetting to
* force a reset
*/
usb_set_interface ( dev - > udev , iface_no , data_altsetting ) ;
2010-11-30 02:23:28 +03:00
temp = usb_set_interface ( dev - > udev , iface_no , 0 ) ;
2013-11-01 14:17:00 +04:00
if ( temp ) {
dev_dbg ( & intf - > dev , " set interface failed \n " ) ;
2011-05-24 09:26:13 +04:00
goto error2 ;
2013-11-01 14:17:00 +04:00
}
2010-11-30 02:23:28 +03:00
2014-05-16 23:48:20 +04:00
/* initialize basic device settings */
if ( cdc_ncm_init ( dev ) )
2014-03-17 19:25:18 +04:00
goto error2 ;
2010-11-30 02:23:28 +03:00
/* configure data interface */
2012-10-22 14:56:31 +04:00
temp = usb_set_interface ( dev - > udev , iface_no , data_altsetting ) ;
2013-11-01 14:17:00 +04:00
if ( temp ) {
dev_dbg ( & intf - > dev , " set interface failed \n " ) ;
2011-05-24 09:26:13 +04:00
goto error2 ;
2013-11-01 14:17:00 +04:00
}
2010-11-30 02:23:28 +03:00
2013-11-01 14:16:41 +04:00
cdc_ncm_find_endpoints ( dev , ctx - > data ) ;
cdc_ncm_find_endpoints ( dev , ctx - > control ) ;
2013-11-01 14:17:00 +04:00
if ( ! dev - > in | | ! dev - > out | | ! dev - > status ) {
dev_dbg ( & intf - > dev , " failed to collect endpoints \n " ) ;
2011-05-24 09:26:13 +04:00
goto error2 ;
2013-11-01 14:17:00 +04:00
}
2010-11-30 02:23:28 +03:00
usb_set_intfdata ( ctx - > data , dev ) ;
usb_set_intfdata ( ctx - > control , dev ) ;
2012-10-22 14:56:31 +04:00
if ( ctx - > ether_desc ) {
temp = usbnet_get_ethernet_addr ( dev , ctx - > ether_desc - > iMACAddress ) ;
2013-11-01 14:17:00 +04:00
if ( temp ) {
dev_dbg ( & intf - > dev , " failed to get mac address \n " ) ;
2012-10-22 14:56:31 +04:00
goto error2 ;
2013-11-01 14:17:00 +04:00
}
dev_info ( & intf - > dev , " MAC-Address: %pM \n " , dev - > net - > dev_addr ) ;
2012-10-22 14:56:31 +04:00
}
2010-11-30 02:23:28 +03:00
2014-05-16 23:48:20 +04:00
/* finish setting up the device specific data */
cdc_ncm_setup ( dev ) ;
2014-03-17 19:25:18 +04:00
2015-07-08 14:05:57 +03:00
/* Device-specific flags */
ctx - > drvflags = drvflags ;
/* Allocate the delayed NDP if needed. */
if ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ) {
ctx - > delayed_ndp16 = kzalloc ( ctx - > max_ndp_size , GFP_KERNEL ) ;
if ( ! ctx - > delayed_ndp16 )
goto error2 ;
dev_info ( & intf - > dev , " NDP will be placed at end of frame for this device. " ) ;
}
2014-05-16 23:48:22 +04:00
/* override ethtool_ops */
dev - > net - > ethtool_ops = & cdc_ncm_ethtool_ops ;
2014-05-30 11:31:06 +04:00
/* add our sysfs attrs */
dev - > net - > sysfs_groups [ 0 ] = & cdc_ncm_sysfs_attr_group ;
2015-12-23 15:42:43 +03:00
/* must handle MTU changes */
dev - > net - > netdev_ops = & cdc_ncm_netdev_ops ;
2010-11-30 02:23:28 +03:00
return 0 ;
2011-05-24 09:26:13 +04:00
error2 :
usb_set_intfdata ( ctx - > control , NULL ) ;
usb_set_intfdata ( ctx - > data , NULL ) ;
2013-01-21 09:50:40 +04:00
if ( ctx - > data ! = ctx - > control )
usb_driver_release_interface ( driver , ctx - > data ) ;
2010-11-30 02:23:28 +03:00
error :
cdc_ncm_free ( ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ) ;
dev - > data [ 0 ] = 0 ;
2013-11-01 14:17:00 +04:00
dev_info ( & intf - > dev , " bind() failure \n " ) ;
2010-11-30 02:23:28 +03:00
return - ENODEV ;
}
2012-10-22 14:56:35 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_bind_common ) ;
2010-11-30 02:23:28 +03:00
2012-10-22 14:56:35 +04:00
void cdc_ncm_unbind ( struct usbnet * dev , struct usb_interface * intf )
2010-11-30 02:23:28 +03:00
{
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
2011-05-24 09:26:13 +04:00
struct usb_driver * driver = driver_of ( intf ) ;
2010-11-30 02:23:28 +03:00
if ( ctx = = NULL )
return ; /* no setup */
2012-03-14 15:26:10 +04:00
atomic_set ( & ctx - > stop , 1 ) ;
if ( hrtimer_active ( & ctx - > tx_timer ) )
hrtimer_cancel ( & ctx - > tx_timer ) ;
tasklet_kill ( & ctx - > bh ) ;
2012-11-13 07:19:43 +04:00
/* handle devices with combined control and data interface */
if ( ctx - > control = = ctx - > data )
ctx - > data = NULL ;
2011-05-24 09:26:13 +04:00
/* disconnect master --> disconnect slave */
if ( intf = = ctx - > control & & ctx - > data ) {
usb_set_intfdata ( ctx - > data , NULL ) ;
2010-11-30 02:23:28 +03:00
usb_driver_release_interface ( driver , ctx - > data ) ;
2011-05-24 09:26:13 +04:00
ctx - > data = NULL ;
2010-11-30 02:23:28 +03:00
2011-05-24 09:26:13 +04:00
} else if ( intf = = ctx - > data & & ctx - > control ) {
usb_set_intfdata ( ctx - > control , NULL ) ;
2010-11-30 02:23:28 +03:00
usb_driver_release_interface ( driver , ctx - > control ) ;
2011-05-24 09:26:13 +04:00
ctx - > control = NULL ;
2010-11-30 02:23:28 +03:00
}
2013-11-01 14:16:40 +04:00
usb_set_intfdata ( intf , NULL ) ;
2010-11-30 02:23:28 +03:00
cdc_ncm_free ( ctx ) ;
}
2012-10-22 14:56:35 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_unbind ) ;
2010-11-30 02:23:28 +03:00
2014-05-11 12:47:15 +04:00
/* Return the number of the MBIM control interface altsetting iff it
* is preferred and available ,
2013-03-14 05:05:13 +04:00
*/
2014-05-11 12:47:15 +04:00
u8 cdc_ncm_select_altsetting ( struct usb_interface * intf )
2012-10-22 14:56:31 +04:00
{
2013-03-14 05:05:13 +04:00
struct usb_host_interface * alt ;
2012-10-22 14:56:31 +04:00
2012-10-22 14:56:38 +04:00
/* The MBIM spec defines a NCM compatible default altsetting,
* which we may have matched :
*
* " Functions that implement both NCM 1.0 and MBIM (an
* “ NCM / MBIM function ” ) according to this recommendation
* shall provide two alternate settings for the
* Communication Interface . Alternate setting 0 , and the
* associated class and endpoint descriptors , shall be
* constructed according to the rules given for the
* Communication Interface in section 5 of [ USBNCM10 ] .
* Alternate setting 1 , and the associated class and
* endpoint descriptors , shall be constructed according to
* the rules given in section 6 ( USB Device Model ) of this
* specification . "
*/
2014-05-11 12:47:15 +04:00
if ( intf - > num_altsetting < 2 )
return intf - > cur_altsetting - > desc . bAlternateSetting ;
if ( prefer_mbim ) {
2013-03-14 05:05:13 +04:00
alt = usb_altnum_to_altsetting ( intf , CDC_NCM_COMM_ALTSETTING_MBIM ) ;
2014-05-11 12:47:15 +04:00
if ( alt & & cdc_ncm_comm_intf_is_mbim ( alt ) )
return CDC_NCM_COMM_ALTSETTING_MBIM ;
2013-02-13 16:09:52 +04:00
}
2014-05-11 12:47:15 +04:00
return CDC_NCM_COMM_ALTSETTING_NCM ;
2013-03-14 05:05:13 +04:00
}
EXPORT_SYMBOL_GPL ( cdc_ncm_select_altsetting ) ;
static int cdc_ncm_bind ( struct usbnet * dev , struct usb_interface * intf )
{
/* MBIM backwards compatible function? */
2014-05-11 12:47:15 +04:00
if ( cdc_ncm_select_altsetting ( intf ) ! = CDC_NCM_COMM_ALTSETTING_NCM )
2013-03-14 05:05:13 +04:00
return - ENODEV ;
2012-10-22 14:56:38 +04:00
2015-07-08 14:05:57 +03:00
/* The NCM data altsetting is fixed, so we hard-coded it.
* Additionally , generic NCM devices are assumed to accept arbitrarily
* placed NDP .
*/
2016-03-07 23:15:36 +03:00
return cdc_ncm_bind_common ( dev , intf , CDC_NCM_DATA_ALTSETTING_NCM , 0 ) ;
2012-10-22 14:56:31 +04:00
}
2012-10-22 14:56:34 +04:00
static void cdc_ncm_align_tail ( struct sk_buff * skb , size_t modulus , size_t remainder , size_t max )
2010-11-30 02:23:28 +03:00
{
2012-10-22 14:56:34 +04:00
size_t align = ALIGN ( skb - > len , modulus ) - skb - > len + remainder ;
if ( skb - > len + align > max )
align = max - skb - > len ;
if ( align & & skb_tailroom ( skb ) > = align )
memset ( skb_put ( skb , align ) , 0 , align ) ;
}
/* return a pointer to a valid struct usb_cdc_ncm_ndp16 of type sign, possibly
* allocating a new one within skb
*/
static struct usb_cdc_ncm_ndp16 * cdc_ncm_ndp ( struct cdc_ncm_ctx * ctx , struct sk_buff * skb , __le32 sign , size_t reserve )
{
struct usb_cdc_ncm_ndp16 * ndp16 = NULL ;
struct usb_cdc_ncm_nth16 * nth16 = ( void * ) skb - > data ;
size_t ndpoffset = le16_to_cpu ( nth16 - > wNdpIndex ) ;
2015-07-08 14:05:57 +03:00
/* If NDP should be moved to the end of the NCM package, we can't follow the
* NTH16 header as we would normally do . NDP isn ' t written to the SKB yet , and
* the wNdpIndex field in the header is actually not consistent with reality . It will be later .
*/
2015-12-05 15:01:50 +03:00
if ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ) {
2015-07-08 14:05:57 +03:00
if ( ctx - > delayed_ndp16 - > dwSignature = = sign )
return ctx - > delayed_ndp16 ;
2015-12-05 15:01:50 +03:00
/* We can only push a single NDP to the end. Return
* NULL to send what we ' ve already got and queue this
* skb for later .
*/
else if ( ctx - > delayed_ndp16 - > dwSignature )
return NULL ;
}
2012-10-22 14:56:34 +04:00
/* follow the chain of NDPs, looking for a match */
while ( ndpoffset ) {
ndp16 = ( struct usb_cdc_ncm_ndp16 * ) ( skb - > data + ndpoffset ) ;
if ( ndp16 - > dwSignature = = sign )
return ndp16 ;
ndpoffset = le16_to_cpu ( ndp16 - > wNextNdpIndex ) ;
}
/* align new NDP */
2015-07-08 14:05:57 +03:00
if ( ! ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ) )
cdc_ncm_align_tail ( skb , ctx - > tx_ndp_modulus , 0 , ctx - > tx_max ) ;
2012-10-22 14:56:34 +04:00
/* verify that there is room for the NDP and the datagram (reserve) */
2014-05-16 23:48:23 +04:00
if ( ( ctx - > tx_max - skb - > len - reserve ) < ctx - > max_ndp_size )
2012-10-22 14:56:34 +04:00
return NULL ;
/* link to it */
if ( ndp16 )
ndp16 - > wNextNdpIndex = cpu_to_le16 ( skb - > len ) ;
else
nth16 - > wNdpIndex = cpu_to_le16 ( skb - > len ) ;
/* push a new empty NDP */
2015-07-08 14:05:57 +03:00
if ( ! ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ) )
ndp16 = ( struct usb_cdc_ncm_ndp16 * ) memset ( skb_put ( skb , ctx - > max_ndp_size ) , 0 , ctx - > max_ndp_size ) ;
else
ndp16 = ctx - > delayed_ndp16 ;
2012-10-22 14:56:34 +04:00
ndp16 - > dwSignature = sign ;
ndp16 - > wLength = cpu_to_le16 ( sizeof ( struct usb_cdc_ncm_ndp16 ) + sizeof ( struct usb_cdc_ncm_dpe16 ) ) ;
return ndp16 ;
2010-11-30 02:23:28 +03:00
}
2012-10-22 14:56:35 +04:00
struct sk_buff *
2013-11-01 14:16:42 +04:00
cdc_ncm_fill_tx_frame ( struct usbnet * dev , struct sk_buff * skb , __le32 sign )
2010-11-30 02:23:28 +03:00
{
2013-11-01 14:16:42 +04:00
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
2012-10-22 14:56:34 +04:00
struct usb_cdc_ncm_nth16 * nth16 ;
struct usb_cdc_ncm_ndp16 * ndp16 ;
2010-11-30 02:23:28 +03:00
struct sk_buff * skb_out ;
2012-10-22 14:56:34 +04:00
u16 n = 0 , index , ndplen ;
2011-02-07 12:45:10 +03:00
u8 ready2send = 0 ;
2015-07-08 14:05:57 +03:00
u32 delayed_ndp_size ;
/* When our NDP gets written in cdc_ncm_ndp(), then skb_out->len gets updated
* accordingly . Otherwise , we should check here .
*/
if ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END )
delayed_ndp_size = ctx - > max_ndp_size ;
else
delayed_ndp_size = 0 ;
2010-11-30 02:23:28 +03:00
/* if there is a remaining skb, it gets priority */
2012-10-22 14:56:34 +04:00
if ( skb ! = NULL ) {
2010-11-30 02:23:28 +03:00
swap ( skb , ctx - > tx_rem_skb ) ;
2012-10-22 14:56:34 +04:00
swap ( sign , ctx - > tx_rem_sign ) ;
} else {
2011-02-07 12:45:10 +03:00
ready2send = 1 ;
2012-10-22 14:56:34 +04:00
}
2010-11-30 02:23:28 +03:00
/* check if we are resuming an OUT skb */
2012-10-22 14:56:34 +04:00
skb_out = ctx - > tx_curr_skb ;
2010-11-30 02:23:28 +03:00
2012-10-22 14:56:34 +04:00
/* allocate a new OUT skb */
if ( ! skb_out ) {
2013-11-01 14:16:38 +04:00
skb_out = alloc_skb ( ctx - > tx_max , GFP_ATOMIC ) ;
2010-11-30 02:23:28 +03:00
if ( skb_out = = NULL ) {
if ( skb ! = NULL ) {
dev_kfree_skb_any ( skb ) ;
2013-11-01 14:16:42 +04:00
dev - > net - > stats . tx_dropped + + ;
2010-11-30 02:23:28 +03:00
}
goto exit_no_skb ;
}
2012-10-22 14:56:34 +04:00
/* fill out the initial 16-bit NTB header */
nth16 = ( struct usb_cdc_ncm_nth16 * ) memset ( skb_put ( skb_out , sizeof ( struct usb_cdc_ncm_nth16 ) ) , 0 , sizeof ( struct usb_cdc_ncm_nth16 ) ) ;
nth16 - > dwSignature = cpu_to_le32 ( USB_CDC_NCM_NTH16_SIGN ) ;
nth16 - > wHeaderLength = cpu_to_le16 ( sizeof ( struct usb_cdc_ncm_nth16 ) ) ;
nth16 - > wSequence = cpu_to_le16 ( ctx - > tx_seq + + ) ;
2010-11-30 02:23:28 +03:00
2012-10-22 14:56:34 +04:00
/* count total number of frames in this NTB */
2010-11-30 02:23:28 +03:00
ctx - > tx_curr_frame_num = 0 ;
2014-05-16 23:48:25 +04:00
/* recent payload counter for this skb_out */
ctx - > tx_curr_frame_payload = 0 ;
2010-11-30 02:23:28 +03:00
}
2012-10-22 14:56:34 +04:00
for ( n = ctx - > tx_curr_frame_num ; n < ctx - > tx_max_datagrams ; n + + ) {
/* send any remaining skb first */
2010-11-30 02:23:28 +03:00
if ( skb = = NULL ) {
skb = ctx - > tx_rem_skb ;
2012-10-22 14:56:34 +04:00
sign = ctx - > tx_rem_sign ;
2010-11-30 02:23:28 +03:00
ctx - > tx_rem_skb = NULL ;
/* check for end of skb */
if ( skb = = NULL )
break ;
}
2012-10-22 14:56:34 +04:00
/* get the appropriate NDP for this skb */
ndp16 = cdc_ncm_ndp ( ctx , skb_out , sign , skb - > len + ctx - > tx_modulus + ctx - > tx_remainder ) ;
/* align beginning of next frame */
cdc_ncm_align_tail ( skb_out , ctx - > tx_modulus , ctx - > tx_remainder , ctx - > tx_max ) ;
/* check if we had enough room left for both NDP and frame */
2015-07-08 14:05:57 +03:00
if ( ! ndp16 | | skb_out - > len + skb - > len + delayed_ndp_size > ctx - > tx_max ) {
2010-11-30 02:23:28 +03:00
if ( n = = 0 ) {
/* won't fit, MTU problem? */
dev_kfree_skb_any ( skb ) ;
skb = NULL ;
2013-11-01 14:16:42 +04:00
dev - > net - > stats . tx_dropped + + ;
2010-11-30 02:23:28 +03:00
} else {
/* no room for skb - store for later */
if ( ctx - > tx_rem_skb ! = NULL ) {
dev_kfree_skb_any ( ctx - > tx_rem_skb ) ;
2013-11-01 14:16:42 +04:00
dev - > net - > stats . tx_dropped + + ;
2010-11-30 02:23:28 +03:00
}
ctx - > tx_rem_skb = skb ;
2012-10-22 14:56:34 +04:00
ctx - > tx_rem_sign = sign ;
2010-11-30 02:23:28 +03:00
skb = NULL ;
2011-02-07 12:45:10 +03:00
ready2send = 1 ;
2014-05-16 23:48:25 +04:00
ctx - > tx_reason_ntb_full + + ; /* count reason for transmitting */
2010-11-30 02:23:28 +03:00
}
break ;
}
2012-10-22 14:56:34 +04:00
/* calculate frame number withing this NDP */
ndplen = le16_to_cpu ( ndp16 - > wLength ) ;
index = ( ndplen - sizeof ( struct usb_cdc_ncm_ndp16 ) ) / sizeof ( struct usb_cdc_ncm_dpe16 ) - 1 ;
2010-11-30 02:23:28 +03:00
2012-10-22 14:56:34 +04:00
/* OK, add this skb */
ndp16 - > dpe16 [ index ] . wDatagramLength = cpu_to_le16 ( skb - > len ) ;
ndp16 - > dpe16 [ index ] . wDatagramIndex = cpu_to_le16 ( skb_out - > len ) ;
ndp16 - > wLength = cpu_to_le16 ( ndplen + sizeof ( struct usb_cdc_ncm_dpe16 ) ) ;
memcpy ( skb_put ( skb_out , skb - > len ) , skb - > data , skb - > len ) ;
2014-05-16 23:48:25 +04:00
ctx - > tx_curr_frame_payload + = skb - > len ; /* count real tx payload data */
2010-11-30 02:23:28 +03:00
dev_kfree_skb_any ( skb ) ;
skb = NULL ;
2012-10-22 14:56:34 +04:00
/* send now if this NDP is full */
if ( index > = CDC_NCM_DPT_DATAGRAMS_MAX ) {
ready2send = 1 ;
2014-05-16 23:48:25 +04:00
ctx - > tx_reason_ndp_full + + ; /* count reason for transmitting */
2012-10-22 14:56:34 +04:00
break ;
}
2010-11-30 02:23:28 +03:00
}
/* free up any dangling skb */
if ( skb ! = NULL ) {
dev_kfree_skb_any ( skb ) ;
skb = NULL ;
2013-11-01 14:16:42 +04:00
dev - > net - > stats . tx_dropped + + ;
2010-11-30 02:23:28 +03:00
}
ctx - > tx_curr_frame_num = n ;
if ( n = = 0 ) {
/* wait for more frames */
/* push variables */
ctx - > tx_curr_skb = skb_out ;
goto exit_no_skb ;
2014-05-16 23:48:22 +04:00
} else if ( ( n < ctx - > tx_max_datagrams ) & & ( ready2send = = 0 ) & & ( ctx - > timer_interval > 0 ) ) {
2010-11-30 02:23:28 +03:00
/* wait for more frames */
/* push variables */
ctx - > tx_curr_skb = skb_out ;
/* set the pending count */
if ( n < CDC_NCM_RESTART_TIMER_DATAGRAM_CNT )
2012-03-14 15:26:10 +04:00
ctx - > tx_timer_pending = CDC_NCM_TIMER_PENDING_CNT ;
2010-11-30 02:23:28 +03:00
goto exit_no_skb ;
} else {
2014-05-16 23:48:25 +04:00
if ( n = = ctx - > tx_max_datagrams )
ctx - > tx_reason_max_datagram + + ; /* count reason for transmitting */
2010-11-30 02:23:28 +03:00
/* frame goes out */
/* variables will be reset at next call */
}
2015-07-08 14:05:57 +03:00
/* If requested, put NDP at end of frame. */
if ( ctx - > drvflags & CDC_NCM_FLAG_NDP_TO_END ) {
nth16 = ( struct usb_cdc_ncm_nth16 * ) skb_out - > data ;
cdc_ncm_align_tail ( skb_out , ctx - > tx_ndp_modulus , 0 , ctx - > tx_max ) ;
nth16 - > wNdpIndex = cpu_to_le16 ( skb_out - > len ) ;
memcpy ( skb_put ( skb_out , ctx - > max_ndp_size ) , ctx - > delayed_ndp16 , ctx - > max_ndp_size ) ;
/* Zero out delayed NDP - signature checking will naturally fail. */
ndp16 = memset ( ctx - > delayed_ndp16 , 0 , ctx - > max_ndp_size ) ;
}
2014-05-16 23:48:24 +04:00
/* If collected data size is less or equal ctx->min_tx_pkt
2013-11-01 14:16:38 +04:00
* bytes , we send buffers as it is . If we get more data , it
* would be more efficient for USB HS mobile device with DMA
* engine to receive a full size NTB , than canceling DMA
* transfer and receiving a short packet .
2013-11-01 14:16:49 +04:00
*
* This optimization support is pointless if we end up sending
* a ZLP after full sized NTBs .
2010-11-30 02:23:28 +03:00
*/
2013-11-01 14:16:49 +04:00
if ( ! ( dev - > driver_info - > flags & FLAG_SEND_ZLP ) & &
2014-05-16 23:48:24 +04:00
skb_out - > len > ctx - > min_tx_pkt )
2013-11-01 14:16:38 +04:00
memset ( skb_put ( skb_out , ctx - > tx_max - skb_out - > len ) , 0 ,
ctx - > tx_max - skb_out - > len ) ;
2014-05-03 01:27:00 +04:00
else if ( skb_out - > len < ctx - > tx_max & & ( skb_out - > len % dev - > maxpacket ) = = 0 )
2012-10-22 14:56:34 +04:00
* skb_put ( skb_out , 1 ) = 0 ; /* force short packet */
2010-11-30 02:23:28 +03:00
2012-10-22 14:56:34 +04:00
/* set final frame length */
nth16 = ( struct usb_cdc_ncm_nth16 * ) skb_out - > data ;
nth16 - > wBlockLength = cpu_to_le16 ( skb_out - > len ) ;
2010-11-30 02:23:28 +03:00
/* return skb */
ctx - > tx_curr_skb = NULL ;
2014-05-16 23:48:25 +04:00
/* keep private stats: framing overhead and number of NTBs */
ctx - > tx_overhead + = skb_out - > len - ctx - > tx_curr_frame_payload ;
ctx - > tx_ntbs + + ;
2015-03-25 23:41:33 +03:00
/* usbnet will count all the framing overhead by default.
2014-05-16 23:48:25 +04:00
* Adjust the stats so that the tx_bytes counter show real
* payload data instead .
*/
2015-03-25 23:41:33 +03:00
usbnet_set_skb_tx_stats ( skb_out , n ,
2015-05-22 14:15:22 +03:00
( long ) ctx - > tx_curr_frame_payload - skb_out - > len ) ;
2015-02-26 22:34:37 +03:00
2010-11-30 02:23:28 +03:00
return skb_out ;
exit_no_skb :
2014-05-16 23:48:29 +04:00
/* Start timer, if there is a remaining non-empty skb */
if ( ctx - > tx_curr_skb ! = NULL & & n > 0 )
2012-03-14 15:26:10 +04:00
cdc_ncm_tx_timeout_start ( ctx ) ;
2010-11-30 02:23:28 +03:00
return NULL ;
}
2012-10-22 14:56:35 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_fill_tx_frame ) ;
2010-11-30 02:23:28 +03:00
static void cdc_ncm_tx_timeout_start ( struct cdc_ncm_ctx * ctx )
{
/* start timer, if not already started */
2012-03-14 15:26:10 +04:00
if ( ! ( hrtimer_active ( & ctx - > tx_timer ) | | atomic_read ( & ctx - > stop ) ) )
hrtimer_start ( & ctx - > tx_timer ,
2014-05-16 23:48:22 +04:00
ktime_set ( 0 , ctx - > timer_interval ) ,
2012-03-14 15:26:10 +04:00
HRTIMER_MODE_REL ) ;
2010-11-30 02:23:28 +03:00
}
2012-03-14 15:26:10 +04:00
static enum hrtimer_restart cdc_ncm_tx_timer_cb ( struct hrtimer * timer )
2010-11-30 02:23:28 +03:00
{
2012-03-14 15:26:10 +04:00
struct cdc_ncm_ctx * ctx =
container_of ( timer , struct cdc_ncm_ctx , tx_timer ) ;
2010-11-30 02:23:28 +03:00
2012-03-14 15:26:10 +04:00
if ( ! atomic_read ( & ctx - > stop ) )
tasklet_schedule ( & ctx - > bh ) ;
return HRTIMER_NORESTART ;
}
2010-11-30 02:23:28 +03:00
2012-03-14 15:26:10 +04:00
static void cdc_ncm_txpath_bh ( unsigned long param )
{
2013-11-01 14:16:42 +04:00
struct usbnet * dev = ( struct usbnet * ) param ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
2010-11-30 02:23:28 +03:00
2012-03-14 15:26:10 +04:00
spin_lock_bh ( & ctx - > mtx ) ;
if ( ctx - > tx_timer_pending ! = 0 ) {
ctx - > tx_timer_pending - - ;
2010-11-30 02:23:28 +03:00
cdc_ncm_tx_timeout_start ( ctx ) ;
2012-03-14 15:26:10 +04:00
spin_unlock_bh ( & ctx - > mtx ) ;
2013-11-01 14:16:42 +04:00
} else if ( dev - > net ! = NULL ) {
2014-05-16 23:48:25 +04:00
ctx - > tx_reason_timeout + + ; /* count reason for transmitting */
2012-03-14 15:26:10 +04:00
spin_unlock_bh ( & ctx - > mtx ) ;
2013-11-01 14:16:42 +04:00
netif_tx_lock_bh ( dev - > net ) ;
usbnet_start_xmit ( NULL , dev - > net ) ;
netif_tx_unlock_bh ( dev - > net ) ;
2012-10-26 01:44:09 +04:00
} else {
spin_unlock_bh ( & ctx - > mtx ) ;
2011-01-17 10:07:25 +03:00
}
2010-11-30 02:23:28 +03:00
}
2013-11-04 12:50:47 +04:00
struct sk_buff *
2010-11-30 02:23:28 +03:00
cdc_ncm_tx_fixup ( struct usbnet * dev , struct sk_buff * skb , gfp_t flags )
{
struct sk_buff * skb_out ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
/*
* The Ethernet API we are using does not support transmitting
* multiple Ethernet frames in a single call . This driver will
* accumulate multiple Ethernet frames and send out a larger
* USB frame when the USB buffer is full or when a single jiffies
* timeout happens .
*/
if ( ctx = = NULL )
goto error ;
2012-03-14 15:26:10 +04:00
spin_lock_bh ( & ctx - > mtx ) ;
2013-11-01 14:16:42 +04:00
skb_out = cdc_ncm_fill_tx_frame ( dev , skb , cpu_to_le32 ( USB_CDC_NCM_NDP16_NOCRC_SIGN ) ) ;
2012-03-14 15:26:10 +04:00
spin_unlock_bh ( & ctx - > mtx ) ;
2010-11-30 02:23:28 +03:00
return skb_out ;
error :
if ( skb ! = NULL )
dev_kfree_skb_any ( skb ) ;
return NULL ;
}
2013-11-04 12:50:47 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_tx_fixup ) ;
2010-11-30 02:23:28 +03:00
2012-10-22 14:56:33 +04:00
/* verify NTB header and return offset of first NDP, or negative error */
2012-10-22 14:56:35 +04:00
int cdc_ncm_rx_verify_nth16 ( struct cdc_ncm_ctx * ctx , struct sk_buff * skb_in )
2010-11-30 02:23:28 +03:00
{
2013-11-01 14:16:54 +04:00
struct usbnet * dev = netdev_priv ( skb_in - > dev ) ;
2012-03-14 15:26:12 +04:00
struct usb_cdc_ncm_nth16 * nth16 ;
2012-10-22 14:56:33 +04:00
int len ;
int ret = - EINVAL ;
2010-11-30 02:23:28 +03:00
if ( ctx = = NULL )
goto error ;
2012-03-14 15:26:12 +04:00
if ( skb_in - > len < ( sizeof ( struct usb_cdc_ncm_nth16 ) +
sizeof ( struct usb_cdc_ncm_ndp16 ) ) ) {
2013-11-01 14:16:54 +04:00
netif_dbg ( dev , rx_err , dev - > net , " frame too short \n " ) ;
2010-11-30 02:23:28 +03:00
goto error ;
}
2012-03-14 15:26:12 +04:00
nth16 = ( struct usb_cdc_ncm_nth16 * ) skb_in - > data ;
2010-11-30 02:23:28 +03:00
2013-11-01 14:16:56 +04:00
if ( nth16 - > dwSignature ! = cpu_to_le32 ( USB_CDC_NCM_NTH16_SIGN ) ) {
2013-11-01 14:16:55 +04:00
netif_dbg ( dev , rx_err , dev - > net ,
" invalid NTH16 signature <%#010x> \n " ,
le32_to_cpu ( nth16 - > dwSignature ) ) ;
2010-11-30 02:23:28 +03:00
goto error ;
}
2012-03-14 15:26:12 +04:00
len = le16_to_cpu ( nth16 - > wBlockLength ) ;
if ( len > ctx - > rx_max ) {
2013-11-01 14:16:54 +04:00
netif_dbg ( dev , rx_err , dev - > net ,
" unsupported NTB block length %u/%u \n " , len ,
ctx - > rx_max ) ;
2010-11-30 02:23:28 +03:00
goto error ;
}
2012-03-14 15:26:12 +04:00
if ( ( ctx - > rx_seq + 1 ) ! = le16_to_cpu ( nth16 - > wSequence ) & &
2013-11-01 14:16:54 +04:00
( ctx - > rx_seq | | le16_to_cpu ( nth16 - > wSequence ) ) & &
! ( ( ctx - > rx_seq = = 0xffff ) & & ! le16_to_cpu ( nth16 - > wSequence ) ) ) {
netif_dbg ( dev , rx_err , dev - > net ,
" sequence number glitch prev=%d curr=%d \n " ,
ctx - > rx_seq , le16_to_cpu ( nth16 - > wSequence ) ) ;
2012-03-14 15:26:12 +04:00
}
ctx - > rx_seq = le16_to_cpu ( nth16 - > wSequence ) ;
2012-10-22 14:56:33 +04:00
ret = le16_to_cpu ( nth16 - > wNdpIndex ) ;
error :
return ret ;
}
2012-10-22 14:56:35 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_rx_verify_nth16 ) ;
2012-10-22 14:56:33 +04:00
/* verify NDP header and return number of datagrams, or negative error */
2012-10-22 14:56:35 +04:00
int cdc_ncm_rx_verify_ndp16 ( struct sk_buff * skb_in , int ndpoffset )
2012-10-22 14:56:33 +04:00
{
2013-11-01 14:16:54 +04:00
struct usbnet * dev = netdev_priv ( skb_in - > dev ) ;
2012-10-22 14:56:33 +04:00
struct usb_cdc_ncm_ndp16 * ndp16 ;
int ret = - EINVAL ;
2012-10-22 14:56:32 +04:00
if ( ( ndpoffset + sizeof ( struct usb_cdc_ncm_ndp16 ) ) > skb_in - > len ) {
2013-11-01 14:16:54 +04:00
netif_dbg ( dev , rx_err , dev - > net , " invalid NDP offset <%u> \n " ,
ndpoffset ) ;
2010-11-30 02:23:28 +03:00
goto error ;
}
2012-10-22 14:56:32 +04:00
ndp16 = ( struct usb_cdc_ncm_ndp16 * ) ( skb_in - > data + ndpoffset ) ;
2010-11-30 02:23:28 +03:00
2012-03-14 15:26:12 +04:00
if ( le16_to_cpu ( ndp16 - > wLength ) < USB_CDC_NCM_NDP16_LENGTH_MIN ) {
2013-11-01 14:16:54 +04:00
netif_dbg ( dev , rx_err , dev - > net , " invalid DPT16 length <%u> \n " ,
le16_to_cpu ( ndp16 - > wLength ) ) ;
2012-10-22 14:56:33 +04:00
goto error ;
2010-11-30 02:23:28 +03:00
}
2012-10-22 14:56:33 +04:00
ret = ( ( le16_to_cpu ( ndp16 - > wLength ) -
2010-11-30 02:23:28 +03:00
sizeof ( struct usb_cdc_ncm_ndp16 ) ) /
sizeof ( struct usb_cdc_ncm_dpe16 ) ) ;
2012-10-22 14:56:33 +04:00
ret - - ; /* we process NDP entries except for the last one */
2010-11-30 02:23:28 +03:00
2013-11-01 14:16:54 +04:00
if ( ( sizeof ( struct usb_cdc_ncm_ndp16 ) +
ret * ( sizeof ( struct usb_cdc_ncm_dpe16 ) ) ) > skb_in - > len ) {
netif_dbg ( dev , rx_err , dev - > net , " Invalid nframes = %d \n " , ret ) ;
2012-10-22 14:56:33 +04:00
ret = - EINVAL ;
2010-11-30 02:23:28 +03:00
}
2012-10-22 14:56:33 +04:00
error :
return ret ;
}
2012-10-22 14:56:35 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_rx_verify_ndp16 ) ;
2012-10-22 14:56:33 +04:00
2013-11-04 12:50:47 +04:00
int cdc_ncm_rx_fixup ( struct usbnet * dev , struct sk_buff * skb_in )
2012-10-22 14:56:33 +04:00
{
struct sk_buff * skb ;
struct cdc_ncm_ctx * ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
int len ;
int nframes ;
int x ;
int offset ;
struct usb_cdc_ncm_ndp16 * ndp16 ;
struct usb_cdc_ncm_dpe16 * dpe16 ;
int ndpoffset ;
int loopcount = 50 ; /* arbitrary max preventing infinite loop */
2014-05-16 23:48:25 +04:00
u32 payload = 0 ;
2012-10-22 14:56:33 +04:00
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 ) ;
2013-11-01 14:16:56 +04:00
if ( ndp16 - > dwSignature ! = cpu_to_le32 ( USB_CDC_NCM_NDP16_NOCRC_SIGN ) ) {
2013-11-01 14:16:55 +04:00
netif_dbg ( dev , rx_err , dev - > net ,
" invalid DPT16 signature <%#010x> \n " ,
le32_to_cpu ( ndp16 - > dwSignature ) ) ;
2012-10-22 14:56:33 +04:00
goto err_ndp ;
}
dpe16 = ndp16 - > dpe16 ;
2010-11-30 02:23:28 +03:00
2012-03-14 15:26:12 +04:00
for ( x = 0 ; x < nframes ; x + + , dpe16 + + ) {
offset = le16_to_cpu ( dpe16 - > wDatagramIndex ) ;
len = le16_to_cpu ( dpe16 - > wDatagramLength ) ;
2010-11-30 02:23:28 +03:00
/*
* CDC NCM ch . 3.7
* All entries after first NULL entry are to be ignored
*/
2012-03-14 15:26:12 +04:00
if ( ( offset = = 0 ) | | ( len = = 0 ) ) {
2010-11-30 02:23:28 +03:00
if ( ! x )
2012-10-22 14:56:32 +04:00
goto err_ndp ; /* empty NTB */
2010-11-30 02:23:28 +03:00
break ;
}
/* sanity checking */
2012-03-14 15:26:12 +04:00
if ( ( ( offset + len ) > skb_in - > len ) | |
( len > ctx - > rx_max ) | | ( len < ETH_HLEN ) ) {
2013-11-01 14:16:54 +04:00
netif_dbg ( dev , rx_err , dev - > net ,
" invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p \n " ,
x , offset , len , skb_in ) ;
2010-11-30 02:23:28 +03:00
if ( ! x )
2012-10-22 14:56:32 +04:00
goto err_ndp ;
2010-11-30 02:23:28 +03:00
break ;
} else {
2014-05-30 11:31:03 +04:00
/* create a fresh copy to reduce truesize */
skb = netdev_alloc_skb_ip_align ( dev - > net , len ) ;
2011-01-13 14:40:11 +03:00
if ( ! skb )
goto error ;
2014-05-30 11:31:03 +04:00
memcpy ( skb_put ( skb , len ) , skb_in - > data + offset , len ) ;
2010-11-30 02:23:28 +03:00
usbnet_skb_return ( dev , skb ) ;
2014-05-16 23:48:25 +04:00
payload + = len ; /* count payload bytes in this NTB */
2010-11-30 02:23:28 +03:00
}
}
2012-10-22 14:56:32 +04:00
err_ndp :
/* are there more NDPs to process? */
ndpoffset = le16_to_cpu ( ndp16 - > wNextNdpIndex ) ;
if ( ndpoffset & & loopcount - - )
goto next_ndp ;
2014-05-16 23:48:25 +04:00
/* update stats */
ctx - > rx_overhead + = skb_in - > len - payload ;
ctx - > rx_ntbs + + ;
2010-11-30 02:23:28 +03:00
return 1 ;
error :
return 0 ;
}
2013-11-04 12:50:47 +04:00
EXPORT_SYMBOL_GPL ( cdc_ncm_rx_fixup ) ;
2010-11-30 02:23:28 +03:00
static void
2013-11-01 14:16:42 +04:00
cdc_ncm_speed_change ( struct usbnet * dev ,
2011-02-07 12:45:10 +03:00
struct usb_cdc_speed_change * data )
2010-11-30 02:23:28 +03:00
{
2011-02-07 12:45:10 +03:00
uint32_t rx_speed = le32_to_cpu ( data - > DLBitRRate ) ;
uint32_t tx_speed = le32_to_cpu ( data - > ULBitRate ) ;
2010-11-30 02:23:28 +03:00
/*
* Currently the USB - NET API does not support reporting the actual
* device speed . Do print it instead .
*/
2013-11-01 14:16:44 +04:00
if ( ( tx_speed > 1000000 ) & & ( rx_speed > 1000000 ) ) {
2013-11-01 14:16:54 +04:00
netif_info ( dev , link , dev - > net ,
2014-05-16 23:48:27 +04:00
" %u mbit/s downlink %u mbit/s uplink \n " ,
( unsigned int ) ( rx_speed / 1000000U ) ,
( unsigned int ) ( tx_speed / 1000000U ) ) ;
2013-11-01 14:16:44 +04:00
} else {
2013-11-01 14:16:54 +04:00
netif_info ( dev , link , dev - > net ,
2014-05-16 23:48:27 +04:00
" %u kbit/s downlink %u kbit/s uplink \n " ,
( unsigned int ) ( rx_speed / 1000U ) ,
( unsigned int ) ( tx_speed / 1000U ) ) ;
2010-11-30 02:23:28 +03:00
}
}
static void cdc_ncm_status ( struct usbnet * dev , struct urb * urb )
{
struct cdc_ncm_ctx * ctx ;
struct usb_cdc_notification * event ;
ctx = ( struct cdc_ncm_ctx * ) dev - > data [ 0 ] ;
if ( urb - > actual_length < sizeof ( * event ) )
return ;
/* test for split data in 8-byte chunks */
if ( test_and_clear_bit ( EVENT_STS_SPLIT , & dev - > flags ) ) {
2013-11-01 14:16:42 +04:00
cdc_ncm_speed_change ( dev ,
2011-02-07 12:45:10 +03:00
( struct usb_cdc_speed_change * ) urb - > transfer_buffer ) ;
2010-11-30 02:23:28 +03:00
return ;
}
event = urb - > transfer_buffer ;
switch ( event - > bNotificationType ) {
case USB_CDC_NOTIFY_NETWORK_CONNECTION :
/*
* According to the CDC NCM specification ch .7 .1
* USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be
* sent by device after USB_CDC_NOTIFY_SPEED_CHANGE .
*/
2013-11-01 14:16:54 +04:00
netif_info ( dev , link , dev - > net ,
" network connection: %sconnected \n " ,
2014-05-16 23:48:28 +04:00
! ! event - > wValue ? " " : " dis " ) ;
usbnet_link_change ( dev , ! ! event - > wValue , 0 ) ;
2010-11-30 02:23:28 +03:00
break ;
case USB_CDC_NOTIFY_SPEED_CHANGE :
2011-02-07 12:45:10 +03:00
if ( urb - > actual_length < ( sizeof ( * event ) +
sizeof ( struct usb_cdc_speed_change ) ) )
2010-11-30 02:23:28 +03:00
set_bit ( EVENT_STS_SPLIT , & dev - > flags ) ;
else
2013-11-01 14:16:42 +04:00
cdc_ncm_speed_change ( dev ,
( struct usb_cdc_speed_change * ) & event [ 1 ] ) ;
2010-11-30 02:23:28 +03:00
break ;
default :
2013-04-08 12:26:23 +04:00
dev_dbg ( & dev - > udev - > dev ,
" NCM: unexpected notification 0x%02x! \n " ,
event - > bNotificationType ) ;
2010-11-30 02:23:28 +03:00
break ;
}
}
static const struct driver_info cdc_ncm_info = {
. description = " CDC NCM " ,
2016-03-07 23:15:36 +03:00
. flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET
| FLAG_LINK_INTR ,
2010-11-30 02:23:28 +03:00
. bind = cdc_ncm_bind ,
. unbind = cdc_ncm_unbind ,
2012-12-18 08:46:12 +04:00
. manage_power = usbnet_manage_power ,
2010-11-30 02:23:28 +03:00
. status = cdc_ncm_status ,
. rx_fixup = cdc_ncm_rx_fixup ,
. tx_fixup = cdc_ncm_tx_fixup ,
} ;
2012-07-24 12:43:22 +04:00
/* Same as cdc_ncm_info, but with FLAG_WWAN */
static const struct driver_info wwan_info = {
. description = " Mobile Broadband Network Device " ,
. flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET
2016-03-07 23:15:36 +03:00
| FLAG_LINK_INTR | FLAG_WWAN ,
2012-07-24 12:43:22 +04:00
. bind = cdc_ncm_bind ,
. unbind = cdc_ncm_unbind ,
2012-12-18 08:46:12 +04:00
. manage_power = usbnet_manage_power ,
2012-07-24 12:43:22 +04:00
. status = cdc_ncm_status ,
. rx_fixup = cdc_ncm_rx_fixup ,
. tx_fixup = cdc_ncm_tx_fixup ,
} ;
2013-01-21 10:00:32 +04:00
/* Same as wwan_info, but with FLAG_NOARP */
static const struct driver_info wwan_noarp_info = {
. description = " Mobile Broadband Network Device (NO ARP) " ,
. flags = FLAG_POINTTOPOINT | FLAG_NO_SETINT | FLAG_MULTI_PACKET
2016-03-07 23:15:36 +03:00
| FLAG_LINK_INTR | FLAG_WWAN | FLAG_NOARP ,
2013-01-21 10:00:32 +04:00
. bind = cdc_ncm_bind ,
. unbind = cdc_ncm_unbind ,
. manage_power = usbnet_manage_power ,
. status = cdc_ncm_status ,
. rx_fixup = cdc_ncm_rx_fixup ,
. tx_fixup = cdc_ncm_tx_fixup ,
} ;
2012-07-24 12:43:22 +04:00
static const struct usb_device_id cdc_devs [ ] = {
/* Ericsson MBM devices like F5521gw */
{ . match_flags = USB_DEVICE_ID_MATCH_INT_INFO
| USB_DEVICE_ID_MATCH_VENDOR ,
. idVendor = 0x0bdb ,
. bInterfaceClass = USB_CLASS_COMM ,
. bInterfaceSubClass = USB_CDC_SUBCLASS_NCM ,
. bInterfaceProtocol = USB_CDC_PROTO_NONE ,
. driver_info = ( unsigned long ) & wwan_info ,
} ,
2016-03-31 16:16:47 +03:00
/* Telit LE910 V2 */
{ USB_DEVICE_AND_INTERFACE_INFO ( 0x1bc7 , 0x0036 ,
USB_CLASS_COMM ,
USB_CDC_SUBCLASS_NCM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & wwan_noarp_info ,
} ,
2015-12-18 16:43:33 +03:00
/* DW5812 LTE Verizon Mobile Broadband Card
* Unlike DW5550 this device requires FLAG_NOARP
*/
{ USB_DEVICE_AND_INTERFACE_INFO ( 0x413c , 0x81bb ,
USB_CLASS_COMM ,
USB_CDC_SUBCLASS_NCM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & wwan_noarp_info ,
} ,
2015-12-18 16:43:34 +03:00
/* DW5813 LTE AT&T Mobile Broadband Card
* Unlike DW5550 this device requires FLAG_NOARP
*/
{ USB_DEVICE_AND_INTERFACE_INFO ( 0x413c , 0x81bc ,
USB_CLASS_COMM ,
USB_CDC_SUBCLASS_NCM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & wwan_noarp_info ,
} ,
2012-08-02 06:30:20 +04:00
/* Dell branded MBM devices like DW5550 */
{ . match_flags = USB_DEVICE_ID_MATCH_INT_INFO
| USB_DEVICE_ID_MATCH_VENDOR ,
. idVendor = 0x413c ,
. bInterfaceClass = USB_CLASS_COMM ,
. bInterfaceSubClass = USB_CDC_SUBCLASS_NCM ,
. bInterfaceProtocol = USB_CDC_PROTO_NONE ,
. driver_info = ( unsigned long ) & wwan_info ,
} ,
/* Toshiba branded MBM devices */
{ . match_flags = USB_DEVICE_ID_MATCH_INT_INFO
| USB_DEVICE_ID_MATCH_VENDOR ,
. idVendor = 0x0930 ,
. bInterfaceClass = USB_CLASS_COMM ,
. bInterfaceSubClass = USB_CDC_SUBCLASS_NCM ,
. bInterfaceProtocol = USB_CDC_PROTO_NONE ,
. driver_info = ( unsigned long ) & wwan_info ,
} ,
2013-02-27 07:08:58 +04:00
/* tag Huawei devices as wwan */
{ USB_VENDOR_AND_INTERFACE_INFO ( 0x12d1 ,
USB_CLASS_COMM ,
USB_CDC_SUBCLASS_NCM ,
USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & wwan_info ,
} ,
2013-01-21 10:00:32 +04:00
/* Infineon(now Intel) HSPA Modem platform */
{ USB_DEVICE_AND_INTERFACE_INFO ( 0x1519 , 0x0443 ,
USB_CLASS_COMM ,
USB_CDC_SUBCLASS_NCM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & wwan_noarp_info ,
} ,
2012-07-24 12:43:22 +04:00
/* Generic CDC-NCM devices */
{ USB_INTERFACE_INFO ( USB_CLASS_COMM ,
USB_CDC_SUBCLASS_NCM , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & cdc_ncm_info ,
} ,
{
} ,
} ;
MODULE_DEVICE_TABLE ( usb , cdc_devs ) ;
2010-11-30 02:23:28 +03:00
static struct usb_driver cdc_ncm_driver = {
. name = " cdc_ncm " ,
. id_table = cdc_devs ,
2013-11-01 14:16:50 +04:00
. probe = usbnet_probe ,
. disconnect = usbnet_disconnect ,
2010-11-30 02:23:28 +03:00
. suspend = usbnet_suspend ,
. resume = usbnet_resume ,
2011-06-01 06:01:41 +04:00
. reset_resume = usbnet_resume ,
2010-11-30 02:23:28 +03:00
. supports_autosuspend = 1 ,
2012-04-23 21:08:51 +04:00
. disable_hub_initiated_lpm = 1 ,
2010-11-30 02:23:28 +03:00
} ;
2011-11-18 21:44:20 +04:00
module_usb_driver ( cdc_ncm_driver ) ;
2010-11-30 02:23:28 +03:00
MODULE_AUTHOR ( " Hans Petter Selasky " ) ;
MODULE_DESCRIPTION ( " USB CDC NCM host driver " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;