1*9f722c09SOmar Laazimani /* 2*9f722c09SOmar Laazimani * USB CDC EEM network interface driver 3*9f722c09SOmar Laazimani * Copyright (C) 2009 Oberthur Technologies 4*9f722c09SOmar Laazimani * by Omar Laazimani, Olivier Condemine 5*9f722c09SOmar Laazimani * 6*9f722c09SOmar Laazimani * This program is free software; you can redistribute it and/or modify 7*9f722c09SOmar Laazimani * it under the terms of the GNU General Public License as published by 8*9f722c09SOmar Laazimani * the Free Software Foundation; either version 2 of the License, or 9*9f722c09SOmar Laazimani * (at your option) any later version. 10*9f722c09SOmar Laazimani * 11*9f722c09SOmar Laazimani * This program is distributed in the hope that it will be useful, 12*9f722c09SOmar Laazimani * but WITHOUT ANY WARRANTY; without even the implied warranty of 13*9f722c09SOmar Laazimani * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14*9f722c09SOmar Laazimani * GNU General Public License for more details. 15*9f722c09SOmar Laazimani * 16*9f722c09SOmar Laazimani * You should have received a copy of the GNU General Public License 17*9f722c09SOmar Laazimani * along with this program; if not, write to the Free Software 18*9f722c09SOmar Laazimani * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19*9f722c09SOmar Laazimani */ 20*9f722c09SOmar Laazimani 21*9f722c09SOmar Laazimani #include <linux/module.h> 22*9f722c09SOmar Laazimani #include <linux/init.h> 23*9f722c09SOmar Laazimani #include <linux/netdevice.h> 24*9f722c09SOmar Laazimani #include <linux/etherdevice.h> 25*9f722c09SOmar Laazimani #include <linux/ctype.h> 26*9f722c09SOmar Laazimani #include <linux/ethtool.h> 27*9f722c09SOmar Laazimani #include <linux/workqueue.h> 28*9f722c09SOmar Laazimani #include <linux/mii.h> 29*9f722c09SOmar Laazimani #include <linux/usb.h> 30*9f722c09SOmar Laazimani #include <linux/crc32.h> 31*9f722c09SOmar Laazimani #include <linux/usb/cdc.h> 32*9f722c09SOmar Laazimani #include <linux/usb/usbnet.h> 33*9f722c09SOmar Laazimani 34*9f722c09SOmar Laazimani 35*9f722c09SOmar Laazimani /* 36*9f722c09SOmar Laazimani * This driver is an implementation of the CDC "Ethernet Emulation 37*9f722c09SOmar Laazimani * Model" (EEM) specification, which encapsulates Ethernet frames 38*9f722c09SOmar Laazimani * for transport over USB using a simpler USB device model than the 39*9f722c09SOmar Laazimani * previous CDC "Ethernet Control Model" (ECM, or "CDC Ethernet"). 40*9f722c09SOmar Laazimani * 41*9f722c09SOmar Laazimani * For details, see www.usb.org/developers/devclass_docs/CDC_EEM10.pdf 42*9f722c09SOmar Laazimani * 43*9f722c09SOmar Laazimani * This version has been tested with GIGAntIC WuaoW SIM Smart Card on 2.6.24, 44*9f722c09SOmar Laazimani * 2.6.27 and 2.6.30rc2 kernel. 45*9f722c09SOmar Laazimani * It has also been validated on Openmoko Om 2008.12 (based on 2.6.24 kernel). 46*9f722c09SOmar Laazimani * build on 23-April-2009 47*9f722c09SOmar Laazimani */ 48*9f722c09SOmar Laazimani 49*9f722c09SOmar Laazimani #define EEM_HEAD 2 /* 2 byte header */ 50*9f722c09SOmar Laazimani 51*9f722c09SOmar Laazimani /*-------------------------------------------------------------------------*/ 52*9f722c09SOmar Laazimani 53*9f722c09SOmar Laazimani static void eem_linkcmd_complete(struct urb *urb) 54*9f722c09SOmar Laazimani { 55*9f722c09SOmar Laazimani dev_kfree_skb(urb->context); 56*9f722c09SOmar Laazimani usb_free_urb(urb); 57*9f722c09SOmar Laazimani } 58*9f722c09SOmar Laazimani 59*9f722c09SOmar Laazimani static void eem_linkcmd(struct usbnet *dev, struct sk_buff *skb) 60*9f722c09SOmar Laazimani { 61*9f722c09SOmar Laazimani struct urb *urb; 62*9f722c09SOmar Laazimani int status; 63*9f722c09SOmar Laazimani 64*9f722c09SOmar Laazimani urb = usb_alloc_urb(0, GFP_ATOMIC); 65*9f722c09SOmar Laazimani if (!urb) 66*9f722c09SOmar Laazimani goto fail; 67*9f722c09SOmar Laazimani 68*9f722c09SOmar Laazimani usb_fill_bulk_urb(urb, dev->udev, dev->out, 69*9f722c09SOmar Laazimani skb->data, skb->len, eem_linkcmd_complete, skb); 70*9f722c09SOmar Laazimani 71*9f722c09SOmar Laazimani status = usb_submit_urb(urb, GFP_ATOMIC); 72*9f722c09SOmar Laazimani if (status) { 73*9f722c09SOmar Laazimani usb_free_urb(urb); 74*9f722c09SOmar Laazimani fail: 75*9f722c09SOmar Laazimani dev_kfree_skb(skb); 76*9f722c09SOmar Laazimani devwarn(dev, "link cmd failure\n"); 77*9f722c09SOmar Laazimani return; 78*9f722c09SOmar Laazimani } 79*9f722c09SOmar Laazimani } 80*9f722c09SOmar Laazimani 81*9f722c09SOmar Laazimani static int eem_bind(struct usbnet *dev, struct usb_interface *intf) 82*9f722c09SOmar Laazimani { 83*9f722c09SOmar Laazimani int status = 0; 84*9f722c09SOmar Laazimani 85*9f722c09SOmar Laazimani status = usbnet_get_endpoints(dev, intf); 86*9f722c09SOmar Laazimani if (status < 0) { 87*9f722c09SOmar Laazimani usb_set_intfdata(intf, NULL); 88*9f722c09SOmar Laazimani usb_driver_release_interface(driver_of(intf), intf); 89*9f722c09SOmar Laazimani return status; 90*9f722c09SOmar Laazimani } 91*9f722c09SOmar Laazimani 92*9f722c09SOmar Laazimani /* no jumbogram (16K) support for now */ 93*9f722c09SOmar Laazimani 94*9f722c09SOmar Laazimani dev->net->hard_header_len += EEM_HEAD + ETH_FCS_LEN; 95*9f722c09SOmar Laazimani 96*9f722c09SOmar Laazimani return 0; 97*9f722c09SOmar Laazimani } 98*9f722c09SOmar Laazimani 99*9f722c09SOmar Laazimani /* 100*9f722c09SOmar Laazimani * EEM permits packing multiple Ethernet frames into USB transfers 101*9f722c09SOmar Laazimani * (a "bundle"), but for TX we don't try to do that. 102*9f722c09SOmar Laazimani */ 103*9f722c09SOmar Laazimani static struct sk_buff *eem_tx_fixup(struct usbnet *dev, struct sk_buff *skb, 104*9f722c09SOmar Laazimani gfp_t flags) 105*9f722c09SOmar Laazimani { 106*9f722c09SOmar Laazimani struct sk_buff *skb2 = NULL; 107*9f722c09SOmar Laazimani u16 len = skb->len; 108*9f722c09SOmar Laazimani u32 crc = 0; 109*9f722c09SOmar Laazimani int padlen = 0; 110*9f722c09SOmar Laazimani 111*9f722c09SOmar Laazimani /* When ((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket) is 112*9f722c09SOmar Laazimani * zero, stick two bytes of zero length EEM packet on the end. 113*9f722c09SOmar Laazimani * Else the framework would add invalid single byte padding, 114*9f722c09SOmar Laazimani * since it can't know whether ZLPs will be handled right by 115*9f722c09SOmar Laazimani * all the relevant hardware and software. 116*9f722c09SOmar Laazimani */ 117*9f722c09SOmar Laazimani if (!((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket)) 118*9f722c09SOmar Laazimani padlen += 2; 119*9f722c09SOmar Laazimani 120*9f722c09SOmar Laazimani if (!skb_cloned(skb)) { 121*9f722c09SOmar Laazimani int headroom = skb_headroom(skb); 122*9f722c09SOmar Laazimani int tailroom = skb_tailroom(skb); 123*9f722c09SOmar Laazimani 124*9f722c09SOmar Laazimani if ((tailroom >= ETH_FCS_LEN + padlen) 125*9f722c09SOmar Laazimani && (headroom >= EEM_HEAD)) 126*9f722c09SOmar Laazimani goto done; 127*9f722c09SOmar Laazimani 128*9f722c09SOmar Laazimani if ((headroom + tailroom) 129*9f722c09SOmar Laazimani > (EEM_HEAD + ETH_FCS_LEN + padlen)) { 130*9f722c09SOmar Laazimani skb->data = memmove(skb->head + 131*9f722c09SOmar Laazimani EEM_HEAD, 132*9f722c09SOmar Laazimani skb->data, 133*9f722c09SOmar Laazimani skb->len); 134*9f722c09SOmar Laazimani skb_set_tail_pointer(skb, len); 135*9f722c09SOmar Laazimani goto done; 136*9f722c09SOmar Laazimani } 137*9f722c09SOmar Laazimani } 138*9f722c09SOmar Laazimani 139*9f722c09SOmar Laazimani skb2 = skb_copy_expand(skb, EEM_HEAD, ETH_FCS_LEN + padlen, flags); 140*9f722c09SOmar Laazimani if (!skb2) 141*9f722c09SOmar Laazimani return NULL; 142*9f722c09SOmar Laazimani 143*9f722c09SOmar Laazimani dev_kfree_skb_any(skb); 144*9f722c09SOmar Laazimani skb = skb2; 145*9f722c09SOmar Laazimani 146*9f722c09SOmar Laazimani done: 147*9f722c09SOmar Laazimani /* we don't use the "no Ethernet CRC" option */ 148*9f722c09SOmar Laazimani crc = crc32_le(~0, skb->data, skb->len); 149*9f722c09SOmar Laazimani crc = ~crc; 150*9f722c09SOmar Laazimani 151*9f722c09SOmar Laazimani put_unaligned_le32(crc, skb_put(skb, 4)); 152*9f722c09SOmar Laazimani 153*9f722c09SOmar Laazimani /* EEM packet header format: 154*9f722c09SOmar Laazimani * b0..13: length of ethernet frame 155*9f722c09SOmar Laazimani * b14: bmCRC (1 == valid Ethernet CRC) 156*9f722c09SOmar Laazimani * b15: bmType (0 == data) 157*9f722c09SOmar Laazimani */ 158*9f722c09SOmar Laazimani len = skb->len; 159*9f722c09SOmar Laazimani put_unaligned_le16(BIT(14) | len, skb_push(skb, 2)); 160*9f722c09SOmar Laazimani 161*9f722c09SOmar Laazimani /* Bundle a zero length EEM packet if needed */ 162*9f722c09SOmar Laazimani if (padlen) 163*9f722c09SOmar Laazimani put_unaligned_le16(0, skb_put(skb, 2)); 164*9f722c09SOmar Laazimani 165*9f722c09SOmar Laazimani return skb; 166*9f722c09SOmar Laazimani } 167*9f722c09SOmar Laazimani 168*9f722c09SOmar Laazimani static int eem_rx_fixup(struct usbnet *dev, struct sk_buff *skb) 169*9f722c09SOmar Laazimani { 170*9f722c09SOmar Laazimani /* 171*9f722c09SOmar Laazimani * Our task here is to strip off framing, leaving skb with one 172*9f722c09SOmar Laazimani * data frame for the usbnet framework code to process. But we 173*9f722c09SOmar Laazimani * may have received multiple EEM payloads, or command payloads. 174*9f722c09SOmar Laazimani * So we must process _everything_ as if it's a header, except 175*9f722c09SOmar Laazimani * maybe the last data payload 176*9f722c09SOmar Laazimani * 177*9f722c09SOmar Laazimani * REVISIT the framework needs updating so that when we consume 178*9f722c09SOmar Laazimani * all payloads (the last or only message was a command, or a 179*9f722c09SOmar Laazimani * zero length EEM packet) that is not accounted as an rx_error. 180*9f722c09SOmar Laazimani */ 181*9f722c09SOmar Laazimani do { 182*9f722c09SOmar Laazimani struct sk_buff *skb2 = NULL; 183*9f722c09SOmar Laazimani u16 header; 184*9f722c09SOmar Laazimani u16 len = 0; 185*9f722c09SOmar Laazimani 186*9f722c09SOmar Laazimani /* incomplete EEM header? */ 187*9f722c09SOmar Laazimani if (skb->len < EEM_HEAD) 188*9f722c09SOmar Laazimani return 0; 189*9f722c09SOmar Laazimani 190*9f722c09SOmar Laazimani /* 191*9f722c09SOmar Laazimani * EEM packet header format: 192*9f722c09SOmar Laazimani * b0..14: EEM type dependant (Data or Command) 193*9f722c09SOmar Laazimani * b15: bmType 194*9f722c09SOmar Laazimani */ 195*9f722c09SOmar Laazimani header = get_unaligned_le16(skb->data); 196*9f722c09SOmar Laazimani skb_pull(skb, EEM_HEAD); 197*9f722c09SOmar Laazimani 198*9f722c09SOmar Laazimani /* 199*9f722c09SOmar Laazimani * The bmType bit helps to denote when EEM 200*9f722c09SOmar Laazimani * packet is data or command : 201*9f722c09SOmar Laazimani * bmType = 0 : EEM data payload 202*9f722c09SOmar Laazimani * bmType = 1 : EEM (link) command 203*9f722c09SOmar Laazimani */ 204*9f722c09SOmar Laazimani if (header & BIT(15)) { 205*9f722c09SOmar Laazimani u16 bmEEMCmd; 206*9f722c09SOmar Laazimani 207*9f722c09SOmar Laazimani /* 208*9f722c09SOmar Laazimani * EEM (link) command packet: 209*9f722c09SOmar Laazimani * b0..10: bmEEMCmdParam 210*9f722c09SOmar Laazimani * b11..13: bmEEMCmd 211*9f722c09SOmar Laazimani * b14: bmReserved (must be 0) 212*9f722c09SOmar Laazimani * b15: 1 (EEM command) 213*9f722c09SOmar Laazimani */ 214*9f722c09SOmar Laazimani if (header & BIT(14)) { 215*9f722c09SOmar Laazimani devdbg(dev, "reserved command %04x\n", header); 216*9f722c09SOmar Laazimani continue; 217*9f722c09SOmar Laazimani } 218*9f722c09SOmar Laazimani 219*9f722c09SOmar Laazimani bmEEMCmd = (header >> 11) & 0x7; 220*9f722c09SOmar Laazimani switch (bmEEMCmd) { 221*9f722c09SOmar Laazimani 222*9f722c09SOmar Laazimani /* Responding to echo requests is mandatory. */ 223*9f722c09SOmar Laazimani case 0: /* Echo command */ 224*9f722c09SOmar Laazimani len = header & 0x7FF; 225*9f722c09SOmar Laazimani 226*9f722c09SOmar Laazimani /* bogus command? */ 227*9f722c09SOmar Laazimani if (skb->len < len) 228*9f722c09SOmar Laazimani return 0; 229*9f722c09SOmar Laazimani 230*9f722c09SOmar Laazimani skb2 = skb_clone(skb, GFP_ATOMIC); 231*9f722c09SOmar Laazimani if (unlikely(!skb2)) 232*9f722c09SOmar Laazimani goto next; 233*9f722c09SOmar Laazimani skb_trim(skb2, len); 234*9f722c09SOmar Laazimani put_unaligned_le16(BIT(15) | (1 << 11) | len, 235*9f722c09SOmar Laazimani skb_push(skb2, 2)); 236*9f722c09SOmar Laazimani eem_linkcmd(dev, skb2); 237*9f722c09SOmar Laazimani break; 238*9f722c09SOmar Laazimani 239*9f722c09SOmar Laazimani /* 240*9f722c09SOmar Laazimani * Host may choose to ignore hints. 241*9f722c09SOmar Laazimani * - suspend: peripheral ready to suspend 242*9f722c09SOmar Laazimani * - response: suggest N millisec polling 243*9f722c09SOmar Laazimani * - response complete: suggest N sec polling 244*9f722c09SOmar Laazimani */ 245*9f722c09SOmar Laazimani case 2: /* Suspend hint */ 246*9f722c09SOmar Laazimani case 3: /* Response hint */ 247*9f722c09SOmar Laazimani case 4: /* Response complete hint */ 248*9f722c09SOmar Laazimani continue; 249*9f722c09SOmar Laazimani 250*9f722c09SOmar Laazimani /* 251*9f722c09SOmar Laazimani * Hosts should never receive host-to-peripheral 252*9f722c09SOmar Laazimani * or reserved command codes; or responses to an 253*9f722c09SOmar Laazimani * echo command we didn't send. 254*9f722c09SOmar Laazimani */ 255*9f722c09SOmar Laazimani case 1: /* Echo response */ 256*9f722c09SOmar Laazimani case 5: /* Tickle */ 257*9f722c09SOmar Laazimani default: /* reserved */ 258*9f722c09SOmar Laazimani devwarn(dev, "unexpected link command %d\n", 259*9f722c09SOmar Laazimani bmEEMCmd); 260*9f722c09SOmar Laazimani continue; 261*9f722c09SOmar Laazimani } 262*9f722c09SOmar Laazimani 263*9f722c09SOmar Laazimani } else { 264*9f722c09SOmar Laazimani u32 crc, crc2; 265*9f722c09SOmar Laazimani int is_last; 266*9f722c09SOmar Laazimani 267*9f722c09SOmar Laazimani /* zero length EEM packet? */ 268*9f722c09SOmar Laazimani if (header == 0) 269*9f722c09SOmar Laazimani continue; 270*9f722c09SOmar Laazimani 271*9f722c09SOmar Laazimani /* 272*9f722c09SOmar Laazimani * EEM data packet header : 273*9f722c09SOmar Laazimani * b0..13: length of ethernet frame 274*9f722c09SOmar Laazimani * b14: bmCRC 275*9f722c09SOmar Laazimani * b15: 0 (EEM data) 276*9f722c09SOmar Laazimani */ 277*9f722c09SOmar Laazimani len = header & 0x3FFF; 278*9f722c09SOmar Laazimani 279*9f722c09SOmar Laazimani /* bogus EEM payload? */ 280*9f722c09SOmar Laazimani if (skb->len < len) 281*9f722c09SOmar Laazimani return 0; 282*9f722c09SOmar Laazimani 283*9f722c09SOmar Laazimani /* bogus ethernet frame? */ 284*9f722c09SOmar Laazimani if (len < (ETH_HLEN + ETH_FCS_LEN)) 285*9f722c09SOmar Laazimani goto next; 286*9f722c09SOmar Laazimani 287*9f722c09SOmar Laazimani /* 288*9f722c09SOmar Laazimani * Treat the last payload differently: framework 289*9f722c09SOmar Laazimani * code expects our "fixup" to have stripped off 290*9f722c09SOmar Laazimani * headers, so "skb" is a data packet (or error). 291*9f722c09SOmar Laazimani * Else if it's not the last payload, keep "skb" 292*9f722c09SOmar Laazimani * for further processing. 293*9f722c09SOmar Laazimani */ 294*9f722c09SOmar Laazimani is_last = (len == skb->len); 295*9f722c09SOmar Laazimani if (is_last) 296*9f722c09SOmar Laazimani skb2 = skb; 297*9f722c09SOmar Laazimani else { 298*9f722c09SOmar Laazimani skb2 = skb_clone(skb, GFP_ATOMIC); 299*9f722c09SOmar Laazimani if (unlikely(!skb2)) 300*9f722c09SOmar Laazimani return 0; 301*9f722c09SOmar Laazimani } 302*9f722c09SOmar Laazimani 303*9f722c09SOmar Laazimani crc = get_unaligned_le32(skb2->data 304*9f722c09SOmar Laazimani + len - ETH_FCS_LEN); 305*9f722c09SOmar Laazimani skb_trim(skb2, len - ETH_FCS_LEN); 306*9f722c09SOmar Laazimani 307*9f722c09SOmar Laazimani /* 308*9f722c09SOmar Laazimani * The bmCRC helps to denote when the CRC field in 309*9f722c09SOmar Laazimani * the Ethernet frame contains a calculated CRC: 310*9f722c09SOmar Laazimani * bmCRC = 1 : CRC is calculated 311*9f722c09SOmar Laazimani * bmCRC = 0 : CRC = 0xDEADBEEF 312*9f722c09SOmar Laazimani */ 313*9f722c09SOmar Laazimani if (header & BIT(14)) 314*9f722c09SOmar Laazimani crc2 = ~crc32_le(~0, skb2->data, len); 315*9f722c09SOmar Laazimani else 316*9f722c09SOmar Laazimani crc2 = 0xdeadbeef; 317*9f722c09SOmar Laazimani 318*9f722c09SOmar Laazimani if (is_last) 319*9f722c09SOmar Laazimani return crc == crc2; 320*9f722c09SOmar Laazimani 321*9f722c09SOmar Laazimani if (unlikely(crc != crc2)) { 322*9f722c09SOmar Laazimani dev->stats.rx_errors++; 323*9f722c09SOmar Laazimani dev_kfree_skb_any(skb2); 324*9f722c09SOmar Laazimani } else 325*9f722c09SOmar Laazimani usbnet_skb_return(dev, skb2); 326*9f722c09SOmar Laazimani } 327*9f722c09SOmar Laazimani 328*9f722c09SOmar Laazimani next: 329*9f722c09SOmar Laazimani skb_pull(skb, len); 330*9f722c09SOmar Laazimani } while (skb->len); 331*9f722c09SOmar Laazimani 332*9f722c09SOmar Laazimani return 1; 333*9f722c09SOmar Laazimani } 334*9f722c09SOmar Laazimani 335*9f722c09SOmar Laazimani static const struct driver_info eem_info = { 336*9f722c09SOmar Laazimani .description = "CDC EEM Device", 337*9f722c09SOmar Laazimani .flags = FLAG_ETHER, 338*9f722c09SOmar Laazimani .bind = eem_bind, 339*9f722c09SOmar Laazimani .rx_fixup = eem_rx_fixup, 340*9f722c09SOmar Laazimani .tx_fixup = eem_tx_fixup, 341*9f722c09SOmar Laazimani }; 342*9f722c09SOmar Laazimani 343*9f722c09SOmar Laazimani /*-------------------------------------------------------------------------*/ 344*9f722c09SOmar Laazimani 345*9f722c09SOmar Laazimani static const struct usb_device_id products[] = { 346*9f722c09SOmar Laazimani { 347*9f722c09SOmar Laazimani USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_EEM, 348*9f722c09SOmar Laazimani USB_CDC_PROTO_EEM), 349*9f722c09SOmar Laazimani .driver_info = (unsigned long) &eem_info, 350*9f722c09SOmar Laazimani }, 351*9f722c09SOmar Laazimani { 352*9f722c09SOmar Laazimani /* EMPTY == end of list */ 353*9f722c09SOmar Laazimani }, 354*9f722c09SOmar Laazimani }; 355*9f722c09SOmar Laazimani MODULE_DEVICE_TABLE(usb, products); 356*9f722c09SOmar Laazimani 357*9f722c09SOmar Laazimani static struct usb_driver eem_driver = { 358*9f722c09SOmar Laazimani .name = "cdc_eem", 359*9f722c09SOmar Laazimani .id_table = products, 360*9f722c09SOmar Laazimani .probe = usbnet_probe, 361*9f722c09SOmar Laazimani .disconnect = usbnet_disconnect, 362*9f722c09SOmar Laazimani .suspend = usbnet_suspend, 363*9f722c09SOmar Laazimani .resume = usbnet_resume, 364*9f722c09SOmar Laazimani }; 365*9f722c09SOmar Laazimani 366*9f722c09SOmar Laazimani 367*9f722c09SOmar Laazimani static int __init eem_init(void) 368*9f722c09SOmar Laazimani { 369*9f722c09SOmar Laazimani return usb_register(&eem_driver); 370*9f722c09SOmar Laazimani } 371*9f722c09SOmar Laazimani module_init(eem_init); 372*9f722c09SOmar Laazimani 373*9f722c09SOmar Laazimani static void __exit eem_exit(void) 374*9f722c09SOmar Laazimani { 375*9f722c09SOmar Laazimani usb_deregister(&eem_driver); 376*9f722c09SOmar Laazimani } 377*9f722c09SOmar Laazimani module_exit(eem_exit); 378*9f722c09SOmar Laazimani 379*9f722c09SOmar Laazimani MODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>"); 380*9f722c09SOmar Laazimani MODULE_DESCRIPTION("USB CDC EEM"); 381*9f722c09SOmar Laazimani MODULE_LICENSE("GPL"); 382