| // 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 <omar.oberthur@gmail.com>"); | |
| MODULE_DESCRIPTION("USB CDC EEM"); | |
| MODULE_LICENSE("GPL"); |