2019-05-19 15:51:43 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2011-03-28 12:56:33 +00:00
/*
* Ethernet interface part of the LG VL600 LTE modem ( 4 G dongle )
*
* Copyright ( C ) 2011 Intel Corporation
* Author : Andrzej Zaborowski < balrogg @ gmail . com >
*/
# include <linux/etherdevice.h>
# include <linux/ethtool.h>
# include <linux/mii.h>
# include <linux/usb.h>
# include <linux/usb/cdc.h>
# include <linux/usb/usbnet.h>
# include <linux/if_ether.h>
# include <linux/if_arp.h>
# include <linux/inetdevice.h>
2011-07-03 15:21:01 -04:00
# include <linux/module.h>
2011-03-28 12:56:33 +00:00
/*
* The device has a CDC ACM port for modem control ( it claims to be
* CDC ACM anyway ) and a CDC Ethernet port for actual network data .
* It will however ignore data on both ports that is not encapsulated
* in a specific way , any data returned is also encapsulated the same
* way . The headers don ' t seem to follow any popular standard .
*
* This driver adds and strips these headers from the ethernet frames
* sent / received from the CDC Ethernet port . The proprietary header
* replaces the standard ethernet header in a packet so only actual
* ethernet frames are allowed . The headers allow some form of
* multiplexing by using non standard values of the . h_proto field .
* Windows / Mac drivers do send a couple of such frames to the device
* during initialisation , with protocol set to 0x0906 or 0x0b06 and ( what
* seems to be ) a flag in the . dummy_flags . This doesn ' t seem necessary
2021-06-01 22:18:13 +08:00
* for modem operation but can possibly be used for GPS or other functions .
2011-03-28 12:56:33 +00:00
*/
struct vl600_frame_hdr {
__le32 len ;
__le32 serial ;
__le32 pkt_cnt ;
__le32 dummy_flags ;
__le32 dummy ;
__le32 magic ;
} __attribute__ ( ( packed ) ) ;
struct vl600_pkt_hdr {
__le32 dummy [ 2 ] ;
__le32 len ;
__be16 h_proto ;
} __attribute__ ( ( packed ) ) ;
struct vl600_state {
struct sk_buff * current_rx_buf ;
} ;
static int vl600_bind ( struct usbnet * dev , struct usb_interface * intf )
{
int ret ;
struct vl600_state * s = kzalloc ( sizeof ( struct vl600_state ) , GFP_KERNEL ) ;
if ( ! s )
return - ENOMEM ;
ret = usbnet_cdc_bind ( dev , intf ) ;
if ( ret ) {
kfree ( s ) ;
return ret ;
}
dev - > driver_priv = s ;
/* ARP packets don't go through, but they're also of no use. The
* subnet has only two hosts anyway : us and the gateway / DHCP
* server ( probably simulated by modem firmware or network operator )
2021-06-01 22:18:13 +08:00
* whose address changes every time we connect to the intarwebz and
2011-03-28 12:56:33 +00:00
* who doesn ' t bother answering ARP requests either . So hardware
* addresses have no meaning , the destination and the source of every
* packet depend only on whether it is on the IN or OUT endpoint . */
dev - > net - > flags | = IFF_NOARP ;
2011-08-07 22:29:32 -07:00
/* IPv6 NDP relies on multicast. Enable it by default. */
dev - > net - > flags | = IFF_MULTICAST ;
2011-03-28 12:56:33 +00:00
return ret ;
}
static void vl600_unbind ( struct usbnet * dev , struct usb_interface * intf )
{
struct vl600_state * s = dev - > driver_priv ;
2019-08-21 22:16:02 +02:00
dev_kfree_skb ( s - > current_rx_buf ) ;
2011-03-28 12:56:33 +00:00
kfree ( s ) ;
return usbnet_cdc_unbind ( dev , intf ) ;
}
static int vl600_rx_fixup ( struct usbnet * dev , struct sk_buff * skb )
{
struct vl600_frame_hdr * frame ;
struct vl600_pkt_hdr * packet ;
struct ethhdr * ethhdr ;
int packet_len , count ;
struct sk_buff * buf = skb ;
struct sk_buff * clone ;
struct vl600_state * s = dev - > driver_priv ;
/* Frame lengths are generally 4B multiplies but every couple of
* hours there ' s an odd number of bytes sized yet correct frame ,
* so don ' t require this . */
/* Allow a packet (or multiple packets batched together) to be
* split across many frames . We don ' t allow a new batch to
* begin in the same frame another one is ending however , and no
* leading or trailing pad bytes . */
if ( s - > current_rx_buf ) {
frame = ( struct vl600_frame_hdr * ) s - > current_rx_buf - > data ;
if ( skb - > len + s - > current_rx_buf - > len >
le32_to_cpup ( & frame - > len ) ) {
netif_err ( dev , ifup , dev - > net , " Fragment too long \n " ) ;
dev - > net - > stats . rx_length_errors + + ;
goto error ;
}
buf = s - > current_rx_buf ;
networking: introduce and use skb_put_data()
A common pattern with skb_put() is to just want to memcpy()
some data into the new space, introduce skb_put_data() for
this.
An spatch similar to the one for skb_put_zero() converts many
of the places using it:
@@
identifier p, p2;
expression len, skb, data;
type t, t2;
@@
(
-p = skb_put(skb, len);
+p = skb_put_data(skb, data, len);
|
-p = (t)skb_put(skb, len);
+p = skb_put_data(skb, data, len);
)
(
p2 = (t2)p;
-memcpy(p2, data, len);
|
-memcpy(p, data, len);
)
@@
type t, t2;
identifier p, p2;
expression skb, data;
@@
t *p;
...
(
-p = skb_put(skb, sizeof(t));
+p = skb_put_data(skb, data, sizeof(t));
|
-p = (t *)skb_put(skb, sizeof(t));
+p = skb_put_data(skb, data, sizeof(t));
)
(
p2 = (t2)p;
-memcpy(p2, data, sizeof(*p));
|
-memcpy(p, data, sizeof(*p));
)
@@
expression skb, len, data;
@@
-memcpy(skb_put(skb, len), data, len);
+skb_put_data(skb, data, len);
(again, manually post-processed to retain some comments)
Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:20 +02:00
skb_put_data ( buf , skb - > data , skb - > len ) ;
2011-03-28 12:56:33 +00:00
} else if ( skb - > len < 4 ) {
netif_err ( dev , ifup , dev - > net , " Frame too short \n " ) ;
dev - > net - > stats . rx_length_errors + + ;
goto error ;
}
frame = ( struct vl600_frame_hdr * ) buf - > data ;
2011-11-09 11:48:10 +00:00
/* Yes, check that frame->magic == 0x53544448 (or 0x44544d48),
* otherwise we may run out of memory w / a bad packet */
if ( ntohl ( frame - > magic ) ! = 0x53544448 & &
ntohl ( frame - > magic ) ! = 0x44544d48 )
goto error ;
2011-03-28 12:56:33 +00:00
if ( buf - > len < sizeof ( * frame ) | |
buf - > len ! = le32_to_cpup ( & frame - > len ) ) {
/* Save this fragment for later assembly */
if ( s - > current_rx_buf )
return 0 ;
s - > current_rx_buf = skb_copy_expand ( skb , 0 ,
le32_to_cpup ( & frame - > len ) , GFP_ATOMIC ) ;
2018-03-12 08:07:12 -07:00
if ( ! s - > current_rx_buf )
2011-03-28 12:56:33 +00:00
dev - > net - > stats . rx_errors + + ;
return 0 ;
}
count = le32_to_cpup ( & frame - > pkt_cnt ) ;
skb_pull ( buf , sizeof ( * frame ) ) ;
while ( count - - ) {
if ( buf - > len < sizeof ( * packet ) ) {
netif_err ( dev , ifup , dev - > net , " Packet too short \n " ) ;
goto error ;
}
packet = ( struct vl600_pkt_hdr * ) buf - > data ;
packet_len = sizeof ( * packet ) + le32_to_cpup ( & packet - > len ) ;
if ( packet_len > buf - > len ) {
netif_err ( dev , ifup , dev - > net ,
" Bad packet length stored in header \n " ) ;
goto error ;
}
/* Packet header is same size as the ethernet header
* ( sizeof ( * packet ) = = sizeof ( * ethhdr ) ) , additionally
* the h_proto field is in the same place so we just leave it
* alone and fill in the remaining fields .
*/
ethhdr = ( struct ethhdr * ) skb - > data ;
if ( be16_to_cpup ( & ethhdr - > h_proto ) = = ETH_P_ARP & &
buf - > len > 0x26 ) {
/* Copy the addresses from packet contents */
memcpy ( ethhdr - > h_source ,
& buf - > data [ sizeof ( * ethhdr ) + 0x8 ] ,
ETH_ALEN ) ;
memcpy ( ethhdr - > h_dest ,
& buf - > data [ sizeof ( * ethhdr ) + 0x12 ] ,
ETH_ALEN ) ;
} else {
2015-03-02 19:54:48 -08:00
eth_zero_addr ( ethhdr - > h_source ) ;
2011-03-28 12:56:33 +00:00
memcpy ( ethhdr - > h_dest , dev - > net - > dev_addr , ETH_ALEN ) ;
2011-08-07 22:29:32 -07:00
/* Inbound IPv6 packets have an IPv4 ethertype (0x800)
* for some reason . Peek at the L3 header to check
* for IPv6 packets , and set the ethertype to IPv6
* ( 0x86dd ) so Linux can understand it .
*/
if ( ( buf - > data [ sizeof ( * ethhdr ) ] & 0xf0 ) = = 0x60 )
2014-03-12 10:22:37 -07:00
ethhdr - > h_proto = htons ( ETH_P_IPV6 ) ;
2011-03-28 12:56:33 +00:00
}
if ( count ) {
/* Not the last packet in this batch */
clone = skb_clone ( buf , GFP_ATOMIC ) ;
if ( ! clone )
goto error ;
skb_trim ( clone , packet_len ) ;
usbnet_skb_return ( dev , clone ) ;
skb_pull ( buf , ( packet_len + 3 ) & ~ 3 ) ;
} else {
skb_trim ( buf , packet_len ) ;
if ( s - > current_rx_buf ) {
usbnet_skb_return ( dev , buf ) ;
s - > current_rx_buf = NULL ;
return 0 ;
}
return 1 ;
}
}
error :
if ( s - > current_rx_buf ) {
dev_kfree_skb_any ( s - > current_rx_buf ) ;
s - > current_rx_buf = NULL ;
}
dev - > net - > stats . rx_errors + + ;
return 0 ;
}
static struct sk_buff * vl600_tx_fixup ( struct usbnet * dev ,
struct sk_buff * skb , gfp_t flags )
{
struct sk_buff * ret ;
struct vl600_frame_hdr * frame ;
struct vl600_pkt_hdr * packet ;
static uint32_t serial = 1 ;
int orig_len = skb - > len - sizeof ( struct ethhdr ) ;
int full_len = ( skb - > len + sizeof ( struct vl600_frame_hdr ) + 3 ) & ~ 3 ;
frame = ( struct vl600_frame_hdr * ) skb - > data ;
if ( skb - > len > sizeof ( * frame ) & & skb - > len = = le32_to_cpup ( & frame - > len ) )
return skb ; /* Already encapsulated? */
if ( skb - > len < sizeof ( struct ethhdr ) )
/* Drop, device can only deal with ethernet packets */
return NULL ;
if ( ! skb_cloned ( skb ) ) {
int headroom = skb_headroom ( skb ) ;
int tailroom = skb_tailroom ( skb ) ;
if ( tailroom > = full_len - skb - > len - sizeof ( * frame ) & &
headroom > = sizeof ( * frame ) )
/* There's enough head and tail room */
goto encapsulate ;
if ( headroom + tailroom + skb - > len > = full_len ) {
/* There's enough total room, just readjust */
skb - > data = memmove ( skb - > head + sizeof ( * frame ) ,
skb - > data , skb - > len ) ;
skb_set_tail_pointer ( skb , skb - > len ) ;
goto encapsulate ;
}
}
/* Alloc a new skb with the required size */
ret = skb_copy_expand ( skb , sizeof ( struct vl600_frame_hdr ) , full_len -
skb - > len - sizeof ( struct vl600_frame_hdr ) , flags ) ;
dev_kfree_skb_any ( skb ) ;
if ( ! ret )
return ret ;
skb = ret ;
encapsulate :
/* Packet header is same size as ethernet packet header
* ( sizeof ( * packet ) = = sizeof ( struct ethhdr ) ) , additionally the
* h_proto field is in the same place so we just leave it alone and
* overwrite the remaining fields .
*/
packet = ( struct vl600_pkt_hdr * ) skb - > data ;
2011-11-09 11:48:10 +00:00
/* The VL600 wants IPv6 packets to have an IPv4 ethertype
* Since this modem only supports IPv4 and IPv6 , just set all
* frames to 0x0800 ( ETH_P_IP )
*/
packet - > h_proto = htons ( ETH_P_IP ) ;
2011-03-28 12:56:33 +00:00
memset ( & packet - > dummy , 0 , sizeof ( packet - > dummy ) ) ;
packet - > len = cpu_to_le32 ( orig_len ) ;
networking: make skb_push & __skb_push return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions return void * and remove all the casts across
the tree, adding a (u8 *) cast only where the unsigned char pointer
was used directly, all done with the following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_push, __skb_push, skb_push_rcsum };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_push, __skb_push, skb_push_rcsum };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
@@
expression SKB, LEN;
identifier fn = { skb_push, __skb_push, skb_push_rcsum };
@@
- fn(SKB, LEN)[0]
+ *(u8 *)fn(SKB, LEN)
Note that the last part there converts from push(...)[0] to the
more idiomatic *(u8 *)push(...).
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:23 +02:00
frame = skb_push ( skb , sizeof ( * frame ) ) ;
2011-03-28 12:56:33 +00:00
memset ( frame , 0 , sizeof ( * frame ) ) ;
frame - > len = cpu_to_le32 ( full_len ) ;
frame - > serial = cpu_to_le32 ( serial + + ) ;
frame - > pkt_cnt = cpu_to_le32 ( 1 ) ;
if ( skb - > len < full_len ) /* Pad */
skb_put ( skb , full_len - skb - > len ) ;
return skb ;
}
static const struct driver_info vl600_info = {
. description = " LG VL600 modem " ,
2011-11-09 11:48:10 +00:00
. flags = FLAG_RX_ASSEMBLE | FLAG_WWAN ,
2011-03-28 12:56:33 +00:00
. bind = vl600_bind ,
. unbind = vl600_unbind ,
. status = usbnet_cdc_status ,
. rx_fixup = vl600_rx_fixup ,
. tx_fixup = vl600_tx_fixup ,
} ;
static const struct usb_device_id products [ ] = {
{
USB_DEVICE_AND_INTERFACE_INFO ( 0x1004 , 0x61aa , USB_CLASS_COMM ,
USB_CDC_SUBCLASS_ETHERNET , USB_CDC_PROTO_NONE ) ,
. driver_info = ( unsigned long ) & vl600_info ,
} ,
{ } , /* End */
} ;
MODULE_DEVICE_TABLE ( usb , products ) ;
static struct usb_driver lg_vl600_driver = {
. name = " lg-vl600 " ,
. id_table = products ,
. probe = usbnet_probe ,
. disconnect = usbnet_disconnect ,
. suspend = usbnet_suspend ,
. resume = usbnet_resume ,
2012-04-23 10:08:51 -07:00
. disable_hub_initiated_lpm = 1 ,
2011-03-28 12:56:33 +00:00
} ;
2011-11-18 09:44:20 -08:00
module_usb_driver ( lg_vl600_driver ) ;
2011-03-28 12:56:33 +00:00
MODULE_AUTHOR ( " Anrzej Zaborowski " ) ;
MODULE_DESCRIPTION ( " LG-VL600 modem's ethernet link " ) ;
MODULE_LICENSE ( " GPL " ) ;