diff options
author | David Brownell <david-b@pacbell.net> | 2005-08-31 12:54:20 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2005-09-08 19:28:32 -0400 |
commit | 4324fd493430c0ab99dd7e89d50540b5e70f8098 (patch) | |
tree | 66f1d16d895fd660ad8d5c7279e8bd08e6787eb8 /drivers/usb/net/cdc_ether.c | |
parent | 0aa599c5644fddd3052433c5335260108a8a39a2 (diff) |
[PATCH] USB: usbnet (7/9) module for CDC Ethernet
Makes the CDC Ethernet support live in a separate driver module.
This module is a bit special since it exports utility functions
that are reused by the the Zaurus and RNDIS drivers, but it's
not "core" like usbnet itself.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/net/cdc_ether.c')
-rw-r--r-- | drivers/usb/net/cdc_ether.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/drivers/usb/net/cdc_ether.c b/drivers/usb/net/cdc_ether.c new file mode 100644 index 000000000000..652b04bbf6af --- /dev/null +++ b/drivers/usb/net/cdc_ether.c | |||
@@ -0,0 +1,509 @@ | |||
1 | /* | ||
2 | * CDC Ethernet based networking peripherals | ||
3 | * Copyright (C) 2003-2005 by David Brownell | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
18 | */ | ||
19 | |||
20 | // #define DEBUG // error path messages, extra info | ||
21 | // #define VERBOSE // more; success messages | ||
22 | |||
23 | #include <linux/config.h> | ||
24 | #ifdef CONFIG_USB_DEBUG | ||
25 | # define DEBUG | ||
26 | #endif | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/sched.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/netdevice.h> | ||
31 | #include <linux/etherdevice.h> | ||
32 | #include <linux/ctype.h> | ||
33 | #include <linux/ethtool.h> | ||
34 | #include <linux/workqueue.h> | ||
35 | #include <linux/mii.h> | ||
36 | #include <linux/usb.h> | ||
37 | #include <linux/usb_cdc.h> | ||
38 | |||
39 | #include "usbnet.h" | ||
40 | |||
41 | |||
42 | /* | ||
43 | * probes control interface, claims data interface, collects the bulk | ||
44 | * endpoints, activates data interface (if needed), maybe sets MTU. | ||
45 | * all pure cdc, except for certain firmware workarounds, and knowing | ||
46 | * that rndis uses one different rule. | ||
47 | */ | ||
48 | int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf) | ||
49 | { | ||
50 | u8 *buf = intf->cur_altsetting->extra; | ||
51 | int len = intf->cur_altsetting->extralen; | ||
52 | struct usb_interface_descriptor *d; | ||
53 | struct cdc_state *info = (void *) &dev->data; | ||
54 | int status; | ||
55 | int rndis; | ||
56 | struct usb_driver *driver = driver_of(intf); | ||
57 | |||
58 | if (sizeof dev->data < sizeof *info) | ||
59 | return -EDOM; | ||
60 | |||
61 | /* expect strict spec conformance for the descriptors, but | ||
62 | * cope with firmware which stores them in the wrong place | ||
63 | */ | ||
64 | if (len == 0 && dev->udev->actconfig->extralen) { | ||
65 | /* Motorola SB4100 (and others: Brad Hards says it's | ||
66 | * from a Broadcom design) put CDC descriptors here | ||
67 | */ | ||
68 | buf = dev->udev->actconfig->extra; | ||
69 | len = dev->udev->actconfig->extralen; | ||
70 | if (len) | ||
71 | dev_dbg(&intf->dev, | ||
72 | "CDC descriptors on config\n"); | ||
73 | } | ||
74 | |||
75 | /* this assumes that if there's a non-RNDIS vendor variant | ||
76 | * of cdc-acm, it'll fail RNDIS requests cleanly. | ||
77 | */ | ||
78 | rndis = (intf->cur_altsetting->desc.bInterfaceProtocol == 0xff); | ||
79 | |||
80 | memset(info, 0, sizeof *info); | ||
81 | info->control = intf; | ||
82 | while (len > 3) { | ||
83 | if (buf [1] != USB_DT_CS_INTERFACE) | ||
84 | goto next_desc; | ||
85 | |||
86 | /* use bDescriptorSubType to identify the CDC descriptors. | ||
87 | * We expect devices with CDC header and union descriptors. | ||
88 | * For CDC Ethernet we need the ethernet descriptor. | ||
89 | * For RNDIS, ignore two (pointless) CDC modem descriptors | ||
90 | * in favor of a complicated OID-based RPC scheme doing what | ||
91 | * CDC Ethernet achieves with a simple descriptor. | ||
92 | */ | ||
93 | switch (buf [2]) { | ||
94 | case USB_CDC_HEADER_TYPE: | ||
95 | if (info->header) { | ||
96 | dev_dbg(&intf->dev, "extra CDC header\n"); | ||
97 | goto bad_desc; | ||
98 | } | ||
99 | info->header = (void *) buf; | ||
100 | if (info->header->bLength != sizeof *info->header) { | ||
101 | dev_dbg(&intf->dev, "CDC header len %u\n", | ||
102 | info->header->bLength); | ||
103 | goto bad_desc; | ||
104 | } | ||
105 | break; | ||
106 | case USB_CDC_UNION_TYPE: | ||
107 | if (info->u) { | ||
108 | dev_dbg(&intf->dev, "extra CDC union\n"); | ||
109 | goto bad_desc; | ||
110 | } | ||
111 | info->u = (void *) buf; | ||
112 | if (info->u->bLength != sizeof *info->u) { | ||
113 | dev_dbg(&intf->dev, "CDC union len %u\n", | ||
114 | info->u->bLength); | ||
115 | goto bad_desc; | ||
116 | } | ||
117 | |||
118 | /* we need a master/control interface (what we're | ||
119 | * probed with) and a slave/data interface; union | ||
120 | * descriptors sort this all out. | ||
121 | */ | ||
122 | info->control = usb_ifnum_to_if(dev->udev, | ||
123 | info->u->bMasterInterface0); | ||
124 | info->data = usb_ifnum_to_if(dev->udev, | ||
125 | info->u->bSlaveInterface0); | ||
126 | if (!info->control || !info->data) { | ||
127 | dev_dbg(&intf->dev, | ||
128 | "master #%u/%p slave #%u/%p\n", | ||
129 | info->u->bMasterInterface0, | ||
130 | info->control, | ||
131 | info->u->bSlaveInterface0, | ||
132 | info->data); | ||
133 | goto bad_desc; | ||
134 | } | ||
135 | if (info->control != intf) { | ||
136 | dev_dbg(&intf->dev, "bogus CDC Union\n"); | ||
137 | /* Ambit USB Cable Modem (and maybe others) | ||
138 | * interchanges master and slave interface. | ||
139 | */ | ||
140 | if (info->data == intf) { | ||
141 | info->data = info->control; | ||
142 | info->control = intf; | ||
143 | } else | ||
144 | goto bad_desc; | ||
145 | } | ||
146 | |||
147 | /* a data interface altsetting does the real i/o */ | ||
148 | d = &info->data->cur_altsetting->desc; | ||
149 | if (d->bInterfaceClass != USB_CLASS_CDC_DATA) { | ||
150 | dev_dbg(&intf->dev, "slave class %u\n", | ||
151 | d->bInterfaceClass); | ||
152 | goto bad_desc; | ||
153 | } | ||
154 | break; | ||
155 | case USB_CDC_ETHERNET_TYPE: | ||
156 | if (info->ether) { | ||
157 | dev_dbg(&intf->dev, "extra CDC ether\n"); | ||
158 | goto bad_desc; | ||
159 | } | ||
160 | info->ether = (void *) buf; | ||
161 | if (info->ether->bLength != sizeof *info->ether) { | ||
162 | dev_dbg(&intf->dev, "CDC ether len %u\n", | ||
163 | info->ether->bLength); | ||
164 | goto bad_desc; | ||
165 | } | ||
166 | dev->hard_mtu = le16_to_cpu( | ||
167 | info->ether->wMaxSegmentSize); | ||
168 | /* because of Zaurus, we may be ignoring the host | ||
169 | * side link address we were given. | ||
170 | */ | ||
171 | break; | ||
172 | } | ||
173 | next_desc: | ||
174 | len -= buf [0]; /* bLength */ | ||
175 | buf += buf [0]; | ||
176 | } | ||
177 | |||
178 | if (!info->header || !info->u || (!rndis && !info->ether)) { | ||
179 | dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n", | ||
180 | info->header ? "" : "header ", | ||
181 | info->u ? "" : "union ", | ||
182 | info->ether ? "" : "ether "); | ||
183 | goto bad_desc; | ||
184 | } | ||
185 | |||
186 | /* claim data interface and set it up ... with side effects. | ||
187 | * network traffic can't flow until an altsetting is enabled. | ||
188 | */ | ||
189 | status = usb_driver_claim_interface(driver, info->data, dev); | ||
190 | if (status < 0) | ||
191 | return status; | ||
192 | status = usbnet_get_endpoints(dev, info->data); | ||
193 | if (status < 0) { | ||
194 | /* ensure immediate exit from usbnet_disconnect */ | ||
195 | usb_set_intfdata(info->data, NULL); | ||
196 | usb_driver_release_interface(driver, info->data); | ||
197 | return status; | ||
198 | } | ||
199 | |||
200 | /* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */ | ||
201 | dev->status = NULL; | ||
202 | if (info->control->cur_altsetting->desc.bNumEndpoints == 1) { | ||
203 | struct usb_endpoint_descriptor *desc; | ||
204 | |||
205 | dev->status = &info->control->cur_altsetting->endpoint [0]; | ||
206 | desc = &dev->status->desc; | ||
207 | if (desc->bmAttributes != USB_ENDPOINT_XFER_INT | ||
208 | || !(desc->bEndpointAddress & USB_DIR_IN) | ||
209 | || (le16_to_cpu(desc->wMaxPacketSize) | ||
210 | < sizeof(struct usb_cdc_notification)) | ||
211 | || !desc->bInterval) { | ||
212 | dev_dbg(&intf->dev, "bad notification endpoint\n"); | ||
213 | dev->status = NULL; | ||
214 | } | ||
215 | } | ||
216 | if (rndis && !dev->status) { | ||
217 | dev_dbg(&intf->dev, "missing RNDIS status endpoint\n"); | ||
218 | usb_set_intfdata(info->data, NULL); | ||
219 | usb_driver_release_interface(driver, info->data); | ||
220 | return -ENODEV; | ||
221 | } | ||
222 | return 0; | ||
223 | |||
224 | bad_desc: | ||
225 | dev_info(&dev->udev->dev, "bad CDC descriptors\n"); | ||
226 | return -ENODEV; | ||
227 | } | ||
228 | EXPORT_SYMBOL_GPL(usbnet_generic_cdc_bind); | ||
229 | |||
230 | void usbnet_cdc_unbind(struct usbnet *dev, struct usb_interface *intf) | ||
231 | { | ||
232 | struct cdc_state *info = (void *) &dev->data; | ||
233 | struct usb_driver *driver = driver_of(intf); | ||
234 | |||
235 | /* disconnect master --> disconnect slave */ | ||
236 | if (intf == info->control && info->data) { | ||
237 | /* ensure immediate exit from usbnet_disconnect */ | ||
238 | usb_set_intfdata(info->data, NULL); | ||
239 | usb_driver_release_interface(driver, info->data); | ||
240 | info->data = NULL; | ||
241 | } | ||
242 | |||
243 | /* and vice versa (just in case) */ | ||
244 | else if (intf == info->data && info->control) { | ||
245 | /* ensure immediate exit from usbnet_disconnect */ | ||
246 | usb_set_intfdata(info->control, NULL); | ||
247 | usb_driver_release_interface(driver, info->control); | ||
248 | info->control = NULL; | ||
249 | } | ||
250 | } | ||
251 | EXPORT_SYMBOL_GPL(usbnet_cdc_unbind); | ||
252 | |||
253 | |||
254 | /*------------------------------------------------------------------------- | ||
255 | * | ||
256 | * Communications Device Class, Ethernet Control model | ||
257 | * | ||
258 | * Takes two interfaces. The DATA interface is inactive till an altsetting | ||
259 | * is selected. Configuration data includes class descriptors. There's | ||
260 | * an optional status endpoint on the control interface. | ||
261 | * | ||
262 | * This should interop with whatever the 2.4 "CDCEther.c" driver | ||
263 | * (by Brad Hards) talked with, with more functionality. | ||
264 | * | ||
265 | *-------------------------------------------------------------------------*/ | ||
266 | |||
267 | static void dumpspeed(struct usbnet *dev, __le32 *speeds) | ||
268 | { | ||
269 | if (netif_msg_timer(dev)) | ||
270 | devinfo(dev, "link speeds: %u kbps up, %u kbps down", | ||
271 | __le32_to_cpu(speeds[0]) / 1000, | ||
272 | __le32_to_cpu(speeds[1]) / 1000); | ||
273 | } | ||
274 | |||
275 | static void cdc_status(struct usbnet *dev, struct urb *urb) | ||
276 | { | ||
277 | struct usb_cdc_notification *event; | ||
278 | |||
279 | if (urb->actual_length < sizeof *event) | ||
280 | return; | ||
281 | |||
282 | /* SPEED_CHANGE can get split into two 8-byte packets */ | ||
283 | if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) { | ||
284 | dumpspeed(dev, (__le32 *) urb->transfer_buffer); | ||
285 | return; | ||
286 | } | ||
287 | |||
288 | event = urb->transfer_buffer; | ||
289 | switch (event->bNotificationType) { | ||
290 | case USB_CDC_NOTIFY_NETWORK_CONNECTION: | ||
291 | if (netif_msg_timer(dev)) | ||
292 | devdbg(dev, "CDC: carrier %s", | ||
293 | event->wValue ? "on" : "off"); | ||
294 | if (event->wValue) | ||
295 | netif_carrier_on(dev->net); | ||
296 | else | ||
297 | netif_carrier_off(dev->net); | ||
298 | break; | ||
299 | case USB_CDC_NOTIFY_SPEED_CHANGE: /* tx/rx rates */ | ||
300 | if (netif_msg_timer(dev)) | ||
301 | devdbg(dev, "CDC: speed change (len %d)", | ||
302 | urb->actual_length); | ||
303 | if (urb->actual_length != (sizeof *event + 8)) | ||
304 | set_bit(EVENT_STS_SPLIT, &dev->flags); | ||
305 | else | ||
306 | dumpspeed(dev, (__le32 *) &event[1]); | ||
307 | break; | ||
308 | /* USB_CDC_NOTIFY_RESPONSE_AVAILABLE can happen too (e.g. RNDIS), | ||
309 | * but there are no standard formats for the response data. | ||
310 | */ | ||
311 | default: | ||
312 | deverr(dev, "CDC: unexpected notification %02x!", | ||
313 | event->bNotificationType); | ||
314 | break; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | static u8 nibble(unsigned char c) | ||
319 | { | ||
320 | if (likely(isdigit(c))) | ||
321 | return c - '0'; | ||
322 | c = toupper(c); | ||
323 | if (likely(isxdigit(c))) | ||
324 | return 10 + c - 'A'; | ||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | static inline int | ||
329 | get_ethernet_addr(struct usbnet *dev, struct usb_cdc_ether_desc *e) | ||
330 | { | ||
331 | int tmp, i; | ||
332 | unsigned char buf [13]; | ||
333 | |||
334 | tmp = usb_string(dev->udev, e->iMACAddress, buf, sizeof buf); | ||
335 | if (tmp != 12) { | ||
336 | dev_dbg(&dev->udev->dev, | ||
337 | "bad MAC string %d fetch, %d\n", e->iMACAddress, tmp); | ||
338 | if (tmp >= 0) | ||
339 | tmp = -EINVAL; | ||
340 | return tmp; | ||
341 | } | ||
342 | for (i = tmp = 0; i < 6; i++, tmp += 2) | ||
343 | dev->net->dev_addr [i] = | ||
344 | (nibble(buf [tmp]) << 4) + nibble(buf [tmp + 1]); | ||
345 | return 0; | ||
346 | } | ||
347 | |||
348 | static int cdc_bind(struct usbnet *dev, struct usb_interface *intf) | ||
349 | { | ||
350 | int status; | ||
351 | struct cdc_state *info = (void *) &dev->data; | ||
352 | |||
353 | status = usbnet_generic_cdc_bind(dev, intf); | ||
354 | if (status < 0) | ||
355 | return status; | ||
356 | |||
357 | status = get_ethernet_addr(dev, info->ether); | ||
358 | if (status < 0) { | ||
359 | usb_set_intfdata(info->data, NULL); | ||
360 | usb_driver_release_interface(driver_of(intf), info->data); | ||
361 | return status; | ||
362 | } | ||
363 | |||
364 | /* FIXME cdc-ether has some multicast code too, though it complains | ||
365 | * in routine cases. info->ether describes the multicast support. | ||
366 | * Implement that here, manipulating the cdc filter as needed. | ||
367 | */ | ||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static const struct driver_info cdc_info = { | ||
372 | .description = "CDC Ethernet Device", | ||
373 | .flags = FLAG_ETHER, | ||
374 | // .check_connect = cdc_check_connect, | ||
375 | .bind = cdc_bind, | ||
376 | .unbind = usbnet_cdc_unbind, | ||
377 | .status = cdc_status, | ||
378 | }; | ||
379 | |||
380 | /*-------------------------------------------------------------------------*/ | ||
381 | |||
382 | |||
383 | static const struct usb_device_id products [] = { | ||
384 | /* | ||
385 | * BLACKLIST !! | ||
386 | * | ||
387 | * First blacklist any products that are egregiously nonconformant | ||
388 | * with the CDC Ethernet specs. Minor braindamage we cope with; when | ||
389 | * they're not even trying, needing a separate driver is only the first | ||
390 | * of the differences to show up. | ||
391 | */ | ||
392 | |||
393 | #define ZAURUS_MASTER_INTERFACE \ | ||
394 | .bInterfaceClass = USB_CLASS_COMM, \ | ||
395 | .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, \ | ||
396 | .bInterfaceProtocol = USB_CDC_PROTO_NONE | ||
397 | |||
398 | /* SA-1100 based Sharp Zaurus ("collie"), or compatible; | ||
399 | * wire-incompatible with true CDC Ethernet implementations. | ||
400 | * (And, it seems, needlessly so...) | ||
401 | */ | ||
402 | { | ||
403 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
404 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
405 | .idVendor = 0x04DD, | ||
406 | .idProduct = 0x8004, | ||
407 | ZAURUS_MASTER_INTERFACE, | ||
408 | .driver_info = 0, | ||
409 | }, | ||
410 | |||
411 | /* PXA-25x based Sharp Zaurii. Note that it seems some of these | ||
412 | * (later models especially) may have shipped only with firmware | ||
413 | * advertising false "CDC MDLM" compatibility ... but we're not | ||
414 | * clear which models did that, so for now let's assume the worst. | ||
415 | */ | ||
416 | { | ||
417 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
418 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
419 | .idVendor = 0x04DD, | ||
420 | .idProduct = 0x8005, /* A-300 */ | ||
421 | ZAURUS_MASTER_INTERFACE, | ||
422 | .driver_info = 0, | ||
423 | }, { | ||
424 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
425 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
426 | .idVendor = 0x04DD, | ||
427 | .idProduct = 0x8006, /* B-500/SL-5600 */ | ||
428 | ZAURUS_MASTER_INTERFACE, | ||
429 | .driver_info = 0, | ||
430 | }, { | ||
431 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
432 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
433 | .idVendor = 0x04DD, | ||
434 | .idProduct = 0x8007, /* C-700 */ | ||
435 | ZAURUS_MASTER_INTERFACE, | ||
436 | .driver_info = 0, | ||
437 | }, { | ||
438 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
439 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
440 | .idVendor = 0x04DD, | ||
441 | .idProduct = 0x9031, /* C-750 C-760 */ | ||
442 | ZAURUS_MASTER_INTERFACE, | ||
443 | .driver_info = 0, | ||
444 | }, { | ||
445 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
446 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
447 | .idVendor = 0x04DD, | ||
448 | .idProduct = 0x9032, /* SL-6000 */ | ||
449 | ZAURUS_MASTER_INTERFACE, | ||
450 | .driver_info = 0, | ||
451 | }, { | ||
452 | .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | ||
453 | | USB_DEVICE_ID_MATCH_DEVICE, | ||
454 | .idVendor = 0x04DD, | ||
455 | /* reported with some C860 units */ | ||
456 | .idProduct = 0x9050, /* C-860 */ | ||
457 | ZAURUS_MASTER_INTERFACE, | ||
458 | .driver_info = 0, | ||
459 | }, | ||
460 | |||
461 | /* | ||
462 | * WHITELIST!!! | ||
463 | * | ||
464 | * CDC Ether uses two interfaces, not necessarily consecutive. | ||
465 | * We match the main interface, ignoring the optional device | ||
466 | * class so we could handle devices that aren't exclusively | ||
467 | * CDC ether. | ||
468 | * | ||
469 | * NOTE: this match must come AFTER entries blacklisting devices | ||
470 | * because of bugs/quirks in a given product (like Zaurus, above). | ||
471 | */ | ||
472 | { | ||
473 | USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET, | ||
474 | USB_CDC_PROTO_NONE), | ||
475 | .driver_info = (unsigned long) &cdc_info, | ||
476 | }, | ||
477 | { }, // END | ||
478 | }; | ||
479 | MODULE_DEVICE_TABLE(usb, products); | ||
480 | |||
481 | static struct usb_driver cdc_driver = { | ||
482 | .owner = THIS_MODULE, | ||
483 | .name = "cdc_ether", | ||
484 | .id_table = products, | ||
485 | .probe = usbnet_probe, | ||
486 | .disconnect = usbnet_disconnect, | ||
487 | .suspend = usbnet_suspend, | ||
488 | .resume = usbnet_resume, | ||
489 | }; | ||
490 | |||
491 | |||
492 | static int __init cdc_init(void) | ||
493 | { | ||
494 | BUG_ON((sizeof(((struct usbnet *)0)->data) | ||
495 | < sizeof(struct cdc_state))); | ||
496 | |||
497 | return usb_register(&cdc_driver); | ||
498 | } | ||
499 | module_init(cdc_init); | ||
500 | |||
501 | static void __exit cdc_exit(void) | ||
502 | { | ||
503 | usb_deregister(&cdc_driver); | ||
504 | } | ||
505 | module_exit(cdc_exit); | ||
506 | |||
507 | MODULE_AUTHOR("David Brownell"); | ||
508 | MODULE_DESCRIPTION("USB CDC Ethernet devices"); | ||
509 | MODULE_LICENSE("GPL"); | ||