// SPDX-License-Identifier: GPL-2.0-or-later
/*
* USB CDC EEM network interface driver
* Copyright ( C ) 2009 Oberthur Technologies
* by Omar Laazimani , Olivier Condemine
*/
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ctype.h>
#include <linux/ethtool.h>
#include <linux/workqueue.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/crc32.h>
#include <linux/usb/cdc.h>
#include <linux/usb/usbnet.h>
#include <linux/gfp.h>
#include <linux/if_vlan.h>
/*
* This driver is an implementation of the CDC " Ethernet Emulation
* Model " ( EEM ) specification , which encapsulates Ethernet frames
* for transport over USB using a simpler USB device model than the
* previous CDC " Ethernet Control Model " ( ECM , or " CDC Ethernet " ) .
*
* For details , see https : //usb.org/sites/default/files/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);
netdev_warn(dev->net, "link cmd failure\n" );
return ;
}
}
static int eem_bind(struct usbnet *dev, struct usb_interface *intf)
{
int status = 0 ;
status = usbnet_get_endpoints(dev, intf);
if (status < 0 )
return status;
/* no jumbogram (16K) support for now */
dev->net->hard_header_len += EEM_HEAD + ETH_FCS_LEN + VLAN_HLEN;
dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len;
return 0 ;
}
/*
* EEM permits packing multiple Ethernet frames into USB transfers
* ( a " bundle " ) , but for TX we don ' t try to do that .
*/
static struct sk_buff *eem_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
gfp_t flags)
{
struct sk_buff *skb2 = NULL;
u16 len = skb->len;
u32 crc = 0 ;
int padlen = 0 ;
/* When ((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket) is
* zero , stick two bytes of zero length EEM packet on the end .
* Else the framework would add invalid single byte padding ,
* since it can ' t know whether ZLPs will be handled right by
* all the relevant hardware and software .
*/
if (!((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket))
padlen += 2 ;
if (!skb_cloned(skb)) {
int headroom = skb_headroom(skb);
int tailroom = skb_tailroom(skb);
if ((tailroom >= ETH_FCS_LEN + padlen) &&
(headroom >= EEM_HEAD))
goto done;
if ((headroom + tailroom)
> (EEM_HEAD + ETH_FCS_LEN + padlen)) {
skb->data = memmove(skb->head +
EEM_HEAD,
skb->data,
skb->len);
skb_set_tail_pointer(skb, len);
goto done;
}
}
skb2 = skb_copy_expand(skb, EEM_HEAD, ETH_FCS_LEN + padlen, flags);
dev_kfree_skb_any(skb);
if (!skb2)
return NULL;
skb = skb2;
done:
/* we don't use the "no Ethernet CRC" option */
crc = crc32_le(~0 , skb->data, skb->len);
crc = ~crc;
put_unaligned_le32(crc, skb_put(skb, 4 ));
/* EEM packet header format:
* b0 . . 13 : length of ethernet frame
* b14 : bmCRC ( 1 = = valid Ethernet CRC )
* b15 : bmType ( 0 = = data )
*/
len = skb->len;
put_unaligned_le16(BIT(14 ) | len, skb_push(skb, 2 ));
/* Bundle a zero length EEM packet if needed */
if (padlen)
put_unaligned_le16(0 , skb_put(skb, 2 ));
return skb;
}
static int eem_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
/*
* Our task here is to strip off framing , leaving skb with one
* data frame for the usbnet framework code to process . But we
* may have received multiple EEM payloads , or command payloads .
* So we must process _ everything_ as if it ' s a header , except
* maybe the last data payload
*
* REVISIT the framework needs updating so that when we consume
* all payloads ( the last or only message was a command , or a
* zero length EEM packet ) that is not accounted as an rx_error .
*/
do {
struct sk_buff *skb2 = NULL;
u16 header;
u16 len = 0 ;
/* incomplete EEM header? */
if (skb->len < EEM_HEAD)
return 0 ;
/*
* EEM packet header format :
* b0 . . 14 : EEM type dependent ( Data or Command )
* b15 : bmType
*/
header = get_unaligned_le16(skb->data);
skb_pull(skb, EEM_HEAD);
/*
* The bmType bit helps to denote when EEM
* packet is data or command :
* bmType = 0 : EEM data payload
* bmType = 1 : EEM ( link ) command
*/
if (header & BIT(15 )) {
u16 bmEEMCmd;
/*
* EEM ( link ) command packet :
* b0 . . 10 : bmEEMCmdParam
* b11 . . 13 : bmEEMCmd
* b14 : bmReserved ( must be 0 )
* b15 : 1 ( EEM command )
*/
if (header & BIT(14 )) {
netdev_dbg(dev->net, "reserved command %04x\n" ,
header);
continue ;
}
bmEEMCmd = (header >> 11 ) & 0 x7;
switch (bmEEMCmd) {
/* Responding to echo requests is mandatory. */
case 0 : /* Echo command */
len = header & 0 x7FF;
/* 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 ) | BIT(11 ) | len,
skb_push(skb2, 2 ));
eem_linkcmd(dev, skb2);
break ;
/*
* Host may choose to ignore hints .
* - suspend : peripheral ready to suspend
* - response : suggest N millisec polling
* - response complete : suggest N sec polling
*
* Suspend is reported and maybe heeded .
*/
case 2 : /* Suspend hint */
usbnet_device_suggests_idle(dev);
continue ;
case 3 : /* Response hint */
case 4 : /* Response complete hint */
continue ;
/*
* Hosts should never receive host - to - peripheral
* or reserved command codes ; or responses to an
* echo command we didn ' t send .
*/
case 1 : /* Echo response */
case 5 : /* Tickle */
default : /* reserved */
netdev_warn(dev->net,
"unexpected link command %d\n" ,
bmEEMCmd);
continue ;
}
} else {
u32 crc, crc2;
int is_last;
/* zero length EEM packet? */
if (header == 0 )
continue ;
/*
* EEM data packet header :
* b0 . . 13 : length of ethernet frame
* b14 : bmCRC
* b15 : 0 ( EEM data )
*/
len = header & 0 x3FFF;
/* 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 = 0 xDEADBEEF
*/
if (header & BIT(14 )) {
crc = get_unaligned_le32(skb2->data
+ len - ETH_FCS_LEN);
crc2 = ~crc32_le(~0 , skb2->data, skb2->len
- ETH_FCS_LEN);
} else {
crc = get_unaligned_be32(skb2->data
+ len - ETH_FCS_LEN);
crc2 = 0 xdeadbeef;
}
skb_trim(skb2, len - ETH_FCS_LEN);
if (is_last)
return crc == crc2;
if (unlikely(crc != crc2)) {
dev->net->stats.rx_errors++;
dev_kfree_skb_any(skb2);
} else
usbnet_skb_return(dev, skb2);
}
next:
skb_pull(skb, len);
} while (skb->len);
return 1 ;
}
static const struct driver_info eem_info = {
.description = "CDC EEM Device" ,
.flags = FLAG_ETHER | FLAG_POINTTOPOINT,
.bind = eem_bind,
.rx_fixup = eem_rx_fixup,
.tx_fixup = eem_tx_fixup,
};
/*-------------------------------------------------------------------------*/
static const struct usb_device_id products[] = {
{
USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_EEM,
USB_CDC_PROTO_EEM),
.driver_info = (unsigned long ) &eem_info,
},
{
/* EMPTY == end of list */
},
};
MODULE_DEVICE_TABLE(usb, products);
static struct usb_driver eem_driver = {
.name = "cdc_eem" ,
.id_table = products,
.probe = usbnet_probe,
.disconnect = usbnet_disconnect,
.suspend = usbnet_suspend,
.resume = usbnet_resume,
.disable_hub_initiated_lpm = 1 ,
};
module_usb_driver(eem_driver);
MODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>" );
MODULE_DESCRIPTION("USB CDC EEM" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=91 H=94 G=92
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland