mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
8.7 KiB
366 lines
8.7 KiB
// 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 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.30rc2 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); |
|
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 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) & 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 |
|
* |
|
* 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 & 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 |
|
*/ |
|
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 = 0xdeadbeef; |
|
} |
|
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 <[email protected]>"); |
|
MODULE_DESCRIPTION("USB CDC EEM"); |
|
MODULE_LICENSE("GPL");
|
|
|