root/drivers/net/usb/cdc_eem.c
// 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.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);
        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) & 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) | 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 & 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");