18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * USB CDC EEM network interface driver 48c2ecf20Sopenharmony_ci * Copyright (C) 2009 Oberthur Technologies 58c2ecf20Sopenharmony_ci * by Omar Laazimani, Olivier Condemine 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 108c2ecf20Sopenharmony_ci#include <linux/etherdevice.h> 118c2ecf20Sopenharmony_ci#include <linux/ctype.h> 128c2ecf20Sopenharmony_ci#include <linux/ethtool.h> 138c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 148c2ecf20Sopenharmony_ci#include <linux/mii.h> 158c2ecf20Sopenharmony_ci#include <linux/usb.h> 168c2ecf20Sopenharmony_ci#include <linux/crc32.h> 178c2ecf20Sopenharmony_ci#include <linux/usb/cdc.h> 188c2ecf20Sopenharmony_ci#include <linux/usb/usbnet.h> 198c2ecf20Sopenharmony_ci#include <linux/gfp.h> 208c2ecf20Sopenharmony_ci#include <linux/if_vlan.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* 248c2ecf20Sopenharmony_ci * This driver is an implementation of the CDC "Ethernet Emulation 258c2ecf20Sopenharmony_ci * Model" (EEM) specification, which encapsulates Ethernet frames 268c2ecf20Sopenharmony_ci * for transport over USB using a simpler USB device model than the 278c2ecf20Sopenharmony_ci * previous CDC "Ethernet Control Model" (ECM, or "CDC Ethernet"). 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * For details, see www.usb.org/developers/devclass_docs/CDC_EEM10.pdf 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * This version has been tested with GIGAntIC WuaoW SIM Smart Card on 2.6.24, 328c2ecf20Sopenharmony_ci * 2.6.27 and 2.6.30rc2 kernel. 338c2ecf20Sopenharmony_ci * It has also been validated on Openmoko Om 2008.12 (based on 2.6.24 kernel). 348c2ecf20Sopenharmony_ci * build on 23-April-2009 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define EEM_HEAD 2 /* 2 byte header */ 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci/*-------------------------------------------------------------------------*/ 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic void eem_linkcmd_complete(struct urb *urb) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci dev_kfree_skb(urb->context); 448c2ecf20Sopenharmony_ci usb_free_urb(urb); 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic void eem_linkcmd(struct usbnet *dev, struct sk_buff *skb) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci struct urb *urb; 508c2ecf20Sopenharmony_ci int status; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci urb = usb_alloc_urb(0, GFP_ATOMIC); 538c2ecf20Sopenharmony_ci if (!urb) 548c2ecf20Sopenharmony_ci goto fail; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci usb_fill_bulk_urb(urb, dev->udev, dev->out, 578c2ecf20Sopenharmony_ci skb->data, skb->len, eem_linkcmd_complete, skb); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci status = usb_submit_urb(urb, GFP_ATOMIC); 608c2ecf20Sopenharmony_ci if (status) { 618c2ecf20Sopenharmony_ci usb_free_urb(urb); 628c2ecf20Sopenharmony_cifail: 638c2ecf20Sopenharmony_ci dev_kfree_skb(skb); 648c2ecf20Sopenharmony_ci netdev_warn(dev->net, "link cmd failure\n"); 658c2ecf20Sopenharmony_ci return; 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int eem_bind(struct usbnet *dev, struct usb_interface *intf) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci int status = 0; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci status = usbnet_get_endpoints(dev, intf); 748c2ecf20Sopenharmony_ci if (status < 0) 758c2ecf20Sopenharmony_ci return status; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* no jumbogram (16K) support for now */ 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci dev->net->hard_header_len += EEM_HEAD + ETH_FCS_LEN + VLAN_HLEN; 808c2ecf20Sopenharmony_ci dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci return 0; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci/* 868c2ecf20Sopenharmony_ci * EEM permits packing multiple Ethernet frames into USB transfers 878c2ecf20Sopenharmony_ci * (a "bundle"), but for TX we don't try to do that. 888c2ecf20Sopenharmony_ci */ 898c2ecf20Sopenharmony_cistatic struct sk_buff *eem_tx_fixup(struct usbnet *dev, struct sk_buff *skb, 908c2ecf20Sopenharmony_ci gfp_t flags) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct sk_buff *skb2 = NULL; 938c2ecf20Sopenharmony_ci u16 len = skb->len; 948c2ecf20Sopenharmony_ci u32 crc = 0; 958c2ecf20Sopenharmony_ci int padlen = 0; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci /* When ((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket) is 988c2ecf20Sopenharmony_ci * zero, stick two bytes of zero length EEM packet on the end. 998c2ecf20Sopenharmony_ci * Else the framework would add invalid single byte padding, 1008c2ecf20Sopenharmony_ci * since it can't know whether ZLPs will be handled right by 1018c2ecf20Sopenharmony_ci * all the relevant hardware and software. 1028c2ecf20Sopenharmony_ci */ 1038c2ecf20Sopenharmony_ci if (!((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket)) 1048c2ecf20Sopenharmony_ci padlen += 2; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci if (!skb_cloned(skb)) { 1078c2ecf20Sopenharmony_ci int headroom = skb_headroom(skb); 1088c2ecf20Sopenharmony_ci int tailroom = skb_tailroom(skb); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if ((tailroom >= ETH_FCS_LEN + padlen) && 1118c2ecf20Sopenharmony_ci (headroom >= EEM_HEAD)) 1128c2ecf20Sopenharmony_ci goto done; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci if ((headroom + tailroom) 1158c2ecf20Sopenharmony_ci > (EEM_HEAD + ETH_FCS_LEN + padlen)) { 1168c2ecf20Sopenharmony_ci skb->data = memmove(skb->head + 1178c2ecf20Sopenharmony_ci EEM_HEAD, 1188c2ecf20Sopenharmony_ci skb->data, 1198c2ecf20Sopenharmony_ci skb->len); 1208c2ecf20Sopenharmony_ci skb_set_tail_pointer(skb, len); 1218c2ecf20Sopenharmony_ci goto done; 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci skb2 = skb_copy_expand(skb, EEM_HEAD, ETH_FCS_LEN + padlen, flags); 1268c2ecf20Sopenharmony_ci dev_kfree_skb_any(skb); 1278c2ecf20Sopenharmony_ci if (!skb2) 1288c2ecf20Sopenharmony_ci return NULL; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci skb = skb2; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cidone: 1338c2ecf20Sopenharmony_ci /* we don't use the "no Ethernet CRC" option */ 1348c2ecf20Sopenharmony_ci crc = crc32_le(~0, skb->data, skb->len); 1358c2ecf20Sopenharmony_ci crc = ~crc; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci put_unaligned_le32(crc, skb_put(skb, 4)); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci /* EEM packet header format: 1408c2ecf20Sopenharmony_ci * b0..13: length of ethernet frame 1418c2ecf20Sopenharmony_ci * b14: bmCRC (1 == valid Ethernet CRC) 1428c2ecf20Sopenharmony_ci * b15: bmType (0 == data) 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_ci len = skb->len; 1458c2ecf20Sopenharmony_ci put_unaligned_le16(BIT(14) | len, skb_push(skb, 2)); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci /* Bundle a zero length EEM packet if needed */ 1488c2ecf20Sopenharmony_ci if (padlen) 1498c2ecf20Sopenharmony_ci put_unaligned_le16(0, skb_put(skb, 2)); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return skb; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic int eem_rx_fixup(struct usbnet *dev, struct sk_buff *skb) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci /* 1578c2ecf20Sopenharmony_ci * Our task here is to strip off framing, leaving skb with one 1588c2ecf20Sopenharmony_ci * data frame for the usbnet framework code to process. But we 1598c2ecf20Sopenharmony_ci * may have received multiple EEM payloads, or command payloads. 1608c2ecf20Sopenharmony_ci * So we must process _everything_ as if it's a header, except 1618c2ecf20Sopenharmony_ci * maybe the last data payload 1628c2ecf20Sopenharmony_ci * 1638c2ecf20Sopenharmony_ci * REVISIT the framework needs updating so that when we consume 1648c2ecf20Sopenharmony_ci * all payloads (the last or only message was a command, or a 1658c2ecf20Sopenharmony_ci * zero length EEM packet) that is not accounted as an rx_error. 1668c2ecf20Sopenharmony_ci */ 1678c2ecf20Sopenharmony_ci do { 1688c2ecf20Sopenharmony_ci struct sk_buff *skb2 = NULL; 1698c2ecf20Sopenharmony_ci u16 header; 1708c2ecf20Sopenharmony_ci u16 len = 0; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* incomplete EEM header? */ 1738c2ecf20Sopenharmony_ci if (skb->len < EEM_HEAD) 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* 1778c2ecf20Sopenharmony_ci * EEM packet header format: 1788c2ecf20Sopenharmony_ci * b0..14: EEM type dependent (Data or Command) 1798c2ecf20Sopenharmony_ci * b15: bmType 1808c2ecf20Sopenharmony_ci */ 1818c2ecf20Sopenharmony_ci header = get_unaligned_le16(skb->data); 1828c2ecf20Sopenharmony_ci skb_pull(skb, EEM_HEAD); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* 1858c2ecf20Sopenharmony_ci * The bmType bit helps to denote when EEM 1868c2ecf20Sopenharmony_ci * packet is data or command : 1878c2ecf20Sopenharmony_ci * bmType = 0 : EEM data payload 1888c2ecf20Sopenharmony_ci * bmType = 1 : EEM (link) command 1898c2ecf20Sopenharmony_ci */ 1908c2ecf20Sopenharmony_ci if (header & BIT(15)) { 1918c2ecf20Sopenharmony_ci u16 bmEEMCmd; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci /* 1948c2ecf20Sopenharmony_ci * EEM (link) command packet: 1958c2ecf20Sopenharmony_ci * b0..10: bmEEMCmdParam 1968c2ecf20Sopenharmony_ci * b11..13: bmEEMCmd 1978c2ecf20Sopenharmony_ci * b14: bmReserved (must be 0) 1988c2ecf20Sopenharmony_ci * b15: 1 (EEM command) 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_ci if (header & BIT(14)) { 2018c2ecf20Sopenharmony_ci netdev_dbg(dev->net, "reserved command %04x\n", 2028c2ecf20Sopenharmony_ci header); 2038c2ecf20Sopenharmony_ci continue; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci bmEEMCmd = (header >> 11) & 0x7; 2078c2ecf20Sopenharmony_ci switch (bmEEMCmd) { 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* Responding to echo requests is mandatory. */ 2108c2ecf20Sopenharmony_ci case 0: /* Echo command */ 2118c2ecf20Sopenharmony_ci len = header & 0x7FF; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* bogus command? */ 2148c2ecf20Sopenharmony_ci if (skb->len < len) 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci skb2 = skb_clone(skb, GFP_ATOMIC); 2188c2ecf20Sopenharmony_ci if (unlikely(!skb2)) 2198c2ecf20Sopenharmony_ci goto next; 2208c2ecf20Sopenharmony_ci skb_trim(skb2, len); 2218c2ecf20Sopenharmony_ci put_unaligned_le16(BIT(15) | (1 << 11) | len, 2228c2ecf20Sopenharmony_ci skb_push(skb2, 2)); 2238c2ecf20Sopenharmony_ci eem_linkcmd(dev, skb2); 2248c2ecf20Sopenharmony_ci break; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* 2278c2ecf20Sopenharmony_ci * Host may choose to ignore hints. 2288c2ecf20Sopenharmony_ci * - suspend: peripheral ready to suspend 2298c2ecf20Sopenharmony_ci * - response: suggest N millisec polling 2308c2ecf20Sopenharmony_ci * - response complete: suggest N sec polling 2318c2ecf20Sopenharmony_ci * 2328c2ecf20Sopenharmony_ci * Suspend is reported and maybe heeded. 2338c2ecf20Sopenharmony_ci */ 2348c2ecf20Sopenharmony_ci case 2: /* Suspend hint */ 2358c2ecf20Sopenharmony_ci usbnet_device_suggests_idle(dev); 2368c2ecf20Sopenharmony_ci continue; 2378c2ecf20Sopenharmony_ci case 3: /* Response hint */ 2388c2ecf20Sopenharmony_ci case 4: /* Response complete hint */ 2398c2ecf20Sopenharmony_ci continue; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci /* 2428c2ecf20Sopenharmony_ci * Hosts should never receive host-to-peripheral 2438c2ecf20Sopenharmony_ci * or reserved command codes; or responses to an 2448c2ecf20Sopenharmony_ci * echo command we didn't send. 2458c2ecf20Sopenharmony_ci */ 2468c2ecf20Sopenharmony_ci case 1: /* Echo response */ 2478c2ecf20Sopenharmony_ci case 5: /* Tickle */ 2488c2ecf20Sopenharmony_ci default: /* reserved */ 2498c2ecf20Sopenharmony_ci netdev_warn(dev->net, 2508c2ecf20Sopenharmony_ci "unexpected link command %d\n", 2518c2ecf20Sopenharmony_ci bmEEMCmd); 2528c2ecf20Sopenharmony_ci continue; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci } else { 2568c2ecf20Sopenharmony_ci u32 crc, crc2; 2578c2ecf20Sopenharmony_ci int is_last; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci /* zero length EEM packet? */ 2608c2ecf20Sopenharmony_ci if (header == 0) 2618c2ecf20Sopenharmony_ci continue; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci /* 2648c2ecf20Sopenharmony_ci * EEM data packet header : 2658c2ecf20Sopenharmony_ci * b0..13: length of ethernet frame 2668c2ecf20Sopenharmony_ci * b14: bmCRC 2678c2ecf20Sopenharmony_ci * b15: 0 (EEM data) 2688c2ecf20Sopenharmony_ci */ 2698c2ecf20Sopenharmony_ci len = header & 0x3FFF; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* bogus EEM payload? */ 2728c2ecf20Sopenharmony_ci if (skb->len < len) 2738c2ecf20Sopenharmony_ci return 0; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* bogus ethernet frame? */ 2768c2ecf20Sopenharmony_ci if (len < (ETH_HLEN + ETH_FCS_LEN)) 2778c2ecf20Sopenharmony_ci goto next; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci /* 2808c2ecf20Sopenharmony_ci * Treat the last payload differently: framework 2818c2ecf20Sopenharmony_ci * code expects our "fixup" to have stripped off 2828c2ecf20Sopenharmony_ci * headers, so "skb" is a data packet (or error). 2838c2ecf20Sopenharmony_ci * Else if it's not the last payload, keep "skb" 2848c2ecf20Sopenharmony_ci * for further processing. 2858c2ecf20Sopenharmony_ci */ 2868c2ecf20Sopenharmony_ci is_last = (len == skb->len); 2878c2ecf20Sopenharmony_ci if (is_last) 2888c2ecf20Sopenharmony_ci skb2 = skb; 2898c2ecf20Sopenharmony_ci else { 2908c2ecf20Sopenharmony_ci skb2 = skb_clone(skb, GFP_ATOMIC); 2918c2ecf20Sopenharmony_ci if (unlikely(!skb2)) 2928c2ecf20Sopenharmony_ci return 0; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* 2968c2ecf20Sopenharmony_ci * The bmCRC helps to denote when the CRC field in 2978c2ecf20Sopenharmony_ci * the Ethernet frame contains a calculated CRC: 2988c2ecf20Sopenharmony_ci * bmCRC = 1 : CRC is calculated 2998c2ecf20Sopenharmony_ci * bmCRC = 0 : CRC = 0xDEADBEEF 3008c2ecf20Sopenharmony_ci */ 3018c2ecf20Sopenharmony_ci if (header & BIT(14)) { 3028c2ecf20Sopenharmony_ci crc = get_unaligned_le32(skb2->data 3038c2ecf20Sopenharmony_ci + len - ETH_FCS_LEN); 3048c2ecf20Sopenharmony_ci crc2 = ~crc32_le(~0, skb2->data, skb2->len 3058c2ecf20Sopenharmony_ci - ETH_FCS_LEN); 3068c2ecf20Sopenharmony_ci } else { 3078c2ecf20Sopenharmony_ci crc = get_unaligned_be32(skb2->data 3088c2ecf20Sopenharmony_ci + len - ETH_FCS_LEN); 3098c2ecf20Sopenharmony_ci crc2 = 0xdeadbeef; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci skb_trim(skb2, len - ETH_FCS_LEN); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci if (is_last) 3148c2ecf20Sopenharmony_ci return crc == crc2; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci if (unlikely(crc != crc2)) { 3178c2ecf20Sopenharmony_ci dev->net->stats.rx_errors++; 3188c2ecf20Sopenharmony_ci dev_kfree_skb_any(skb2); 3198c2ecf20Sopenharmony_ci } else 3208c2ecf20Sopenharmony_ci usbnet_skb_return(dev, skb2); 3218c2ecf20Sopenharmony_ci } 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_cinext: 3248c2ecf20Sopenharmony_ci skb_pull(skb, len); 3258c2ecf20Sopenharmony_ci } while (skb->len); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci return 1; 3288c2ecf20Sopenharmony_ci} 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_cistatic const struct driver_info eem_info = { 3318c2ecf20Sopenharmony_ci .description = "CDC EEM Device", 3328c2ecf20Sopenharmony_ci .flags = FLAG_ETHER | FLAG_POINTTOPOINT, 3338c2ecf20Sopenharmony_ci .bind = eem_bind, 3348c2ecf20Sopenharmony_ci .rx_fixup = eem_rx_fixup, 3358c2ecf20Sopenharmony_ci .tx_fixup = eem_tx_fixup, 3368c2ecf20Sopenharmony_ci}; 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci/*-------------------------------------------------------------------------*/ 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_cistatic const struct usb_device_id products[] = { 3418c2ecf20Sopenharmony_ci{ 3428c2ecf20Sopenharmony_ci USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_EEM, 3438c2ecf20Sopenharmony_ci USB_CDC_PROTO_EEM), 3448c2ecf20Sopenharmony_ci .driver_info = (unsigned long) &eem_info, 3458c2ecf20Sopenharmony_ci}, 3468c2ecf20Sopenharmony_ci{ 3478c2ecf20Sopenharmony_ci /* EMPTY == end of list */ 3488c2ecf20Sopenharmony_ci}, 3498c2ecf20Sopenharmony_ci}; 3508c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, products); 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_cistatic struct usb_driver eem_driver = { 3538c2ecf20Sopenharmony_ci .name = "cdc_eem", 3548c2ecf20Sopenharmony_ci .id_table = products, 3558c2ecf20Sopenharmony_ci .probe = usbnet_probe, 3568c2ecf20Sopenharmony_ci .disconnect = usbnet_disconnect, 3578c2ecf20Sopenharmony_ci .suspend = usbnet_suspend, 3588c2ecf20Sopenharmony_ci .resume = usbnet_resume, 3598c2ecf20Sopenharmony_ci .disable_hub_initiated_lpm = 1, 3608c2ecf20Sopenharmony_ci}; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_cimodule_usb_driver(eem_driver); 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ciMODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>"); 3658c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("USB CDC EEM"); 3668c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 367