aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorDiego Giagio <diego@giagio.com>2010-04-18 04:35:16 -0400
committerDavid S. Miller <davem@davemloft.net>2010-04-22 01:43:18 -0400
commita19259c3d589a014e5f47f148f74dfc44422c82b (patch)
treea4cb49752cf1dcc669b7dcf4553c19ffa7bb87a6 /drivers
parent9441cad99b4b09d6b627351c2d282833868c116c (diff)
drivers/net/usb: Add new driver ipheth
Add new driver to use tethering with an iPhone device. After initial submission, apply fixes to fit the new driver into the kernel standards. There are still a couple of minor (almost cosmetic-level) issues, but the driver is fully functional right now. Signed-off-by: L. Alberto Giménez <agimenez@sysvalve.es> Signed-off-by: Diego Giagio <diego@giagio.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/usb/Kconfig12
-rw-r--r--drivers/net/usb/Makefile1
-rw-r--r--drivers/net/usb/ipheth.c568
4 files changed, 582 insertions, 0 deletions
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index a583b50d9de8..12b280afdd51 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -273,6 +273,7 @@ obj-$(CONFIG_USB_RTL8150) += usb/
273obj-$(CONFIG_USB_HSO) += usb/ 273obj-$(CONFIG_USB_HSO) += usb/
274obj-$(CONFIG_USB_USBNET) += usb/ 274obj-$(CONFIG_USB_USBNET) += usb/
275obj-$(CONFIG_USB_ZD1201) += usb/ 275obj-$(CONFIG_USB_ZD1201) += usb/
276obj-$(CONFIG_USB_IPHETH) += usb/
276 277
277obj-y += wireless/ 278obj-y += wireless/
278obj-$(CONFIG_NET_TULIP) += tulip/ 279obj-$(CONFIG_NET_TULIP) += tulip/
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index ba56ce4382d9..63be4caec70e 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -385,4 +385,16 @@ config USB_CDC_PHONET
385 cellular modem, as found on most Nokia handsets with the 385 cellular modem, as found on most Nokia handsets with the
386 "PC suite" USB profile. 386 "PC suite" USB profile.
387 387
388config USB_IPHETH
389 tristate "Apple iPhone USB Ethernet driver"
390 default n
391 ---help---
392 Module used to share Internet connection (tethering) from your
393 iPhone (Original, 3G and 3GS) to your system.
394 Note that you need userspace libraries and programs that are needed
395 to pair your device with your system and that understand the iPhone
396 protocol.
397
398 For more information: http://giagio.com/wiki/moin.cgi/iPhoneEthernetDriver
399
388endmenu 400endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 82ea62955b56..edb09c0ddf8e 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -23,4 +23,5 @@ obj-$(CONFIG_USB_NET_MCS7830) += mcs7830.o
23obj-$(CONFIG_USB_USBNET) += usbnet.o 23obj-$(CONFIG_USB_USBNET) += usbnet.o
24obj-$(CONFIG_USB_NET_INT51X1) += int51x1.o 24obj-$(CONFIG_USB_NET_INT51X1) += int51x1.o
25obj-$(CONFIG_USB_CDC_PHONET) += cdc-phonet.o 25obj-$(CONFIG_USB_CDC_PHONET) += cdc-phonet.o
26obj-$(CONFIG_USB_IPHETH) += ipheth.o
26 27
diff --git a/drivers/net/usb/ipheth.c b/drivers/net/usb/ipheth.c
new file mode 100644
index 000000000000..fd1033130a81
--- /dev/null
+++ b/drivers/net/usb/ipheth.c
@@ -0,0 +1,568 @@
1/*
2 * ipheth.c - Apple iPhone USB Ethernet driver
3 *
4 * Copyright (c) 2009 Diego Giagio <diego@giagio.com>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of GIAGIO.COM nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * Alternatively, provided that this notice is retained in full, this
20 * software may be distributed under the terms of the GNU General
21 * Public License ("GPL") version 2, in which case the provisions of the
22 * GPL apply INSTEAD OF those given above.
23 *
24 * The provided data structures and external interfaces from this code
25 * are not restricted to be used by modules with a GPL compatible license.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
38 * DAMAGE.
39 *
40 *
41 * Attention: iPhone device must be paired, otherwise it won't respond to our
42 * driver. For more info: http://giagio.com/wiki/moin.cgi/iPhoneEthernetDriver
43 *
44 */
45
46#include <linux/kernel.h>
47#include <linux/errno.h>
48#include <linux/init.h>
49#include <linux/slab.h>
50#include <linux/module.h>
51#include <linux/netdevice.h>
52#include <linux/etherdevice.h>
53#include <linux/ethtool.h>
54#include <linux/usb.h>
55#include <linux/workqueue.h>
56
57#define USB_VENDOR_APPLE 0x05ac
58#define USB_PRODUCT_IPHONE 0x1290
59#define USB_PRODUCT_IPHONE_3G 0x1292
60#define USB_PRODUCT_IPHONE_3GS 0x1294
61
62#define IPHETH_USBINTF_CLASS 255
63#define IPHETH_USBINTF_SUBCLASS 253
64#define IPHETH_USBINTF_PROTO 1
65
66#define IPHETH_BUF_SIZE 1516
67#define IPHETH_TX_TIMEOUT (5 * HZ)
68
69#define IPHETH_INTFNUM 2
70#define IPHETH_ALT_INTFNUM 1
71
72#define IPHETH_CTRL_ENDP 0x00
73#define IPHETH_CTRL_BUF_SIZE 0x40
74#define IPHETH_CTRL_TIMEOUT (5 * HZ)
75
76#define IPHETH_CMD_GET_MACADDR 0x00
77#define IPHETH_CMD_CARRIER_CHECK 0x45
78
79#define IPHETH_CARRIER_CHECK_TIMEOUT round_jiffies_relative(1 * HZ)
80#define IPHETH_CARRIER_ON 0x04
81
82static struct usb_device_id ipheth_table[] = {
83 { USB_DEVICE_AND_INTERFACE_INFO(
84 USB_VENDOR_APPLE, USB_PRODUCT_IPHONE,
85 IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
86 IPHETH_USBINTF_PROTO) },
87 { USB_DEVICE_AND_INTERFACE_INFO(
88 USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_3G,
89 IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
90 IPHETH_USBINTF_PROTO) },
91 { USB_DEVICE_AND_INTERFACE_INFO(
92 USB_VENDOR_APPLE, USB_PRODUCT_IPHONE_3GS,
93 IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
94 IPHETH_USBINTF_PROTO) },
95 { }
96};
97MODULE_DEVICE_TABLE(usb, ipheth_table);
98
99struct ipheth_device {
100 struct usb_device *udev;
101 struct usb_interface *intf;
102 struct net_device *net;
103 struct sk_buff *tx_skb;
104 struct urb *tx_urb;
105 struct urb *rx_urb;
106 unsigned char *tx_buf;
107 unsigned char *rx_buf;
108 unsigned char *ctrl_buf;
109 u8 bulk_in;
110 u8 bulk_out;
111 struct delayed_work carrier_work;
112};
113
114static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags);
115
116static int ipheth_alloc_urbs(struct ipheth_device *iphone)
117{
118 struct urb *tx_urb = NULL;
119 struct urb *rx_urb = NULL;
120 u8 *tx_buf = NULL;
121 u8 *rx_buf = NULL;
122
123 tx_urb = usb_alloc_urb(0, GFP_KERNEL);
124 if (tx_urb == NULL)
125 goto error;
126
127 rx_urb = usb_alloc_urb(0, GFP_KERNEL);
128 if (rx_urb == NULL)
129 goto error;
130
131 tx_buf = usb_buffer_alloc(iphone->udev,
132 IPHETH_BUF_SIZE,
133 GFP_KERNEL,
134 &tx_urb->transfer_dma);
135 if (tx_buf == NULL)
136 goto error;
137
138 rx_buf = usb_buffer_alloc(iphone->udev,
139 IPHETH_BUF_SIZE,
140 GFP_KERNEL,
141 &rx_urb->transfer_dma);
142 if (rx_buf == NULL)
143 goto error;
144
145
146 iphone->tx_urb = tx_urb;
147 iphone->rx_urb = rx_urb;
148 iphone->tx_buf = tx_buf;
149 iphone->rx_buf = rx_buf;
150 return 0;
151
152error:
153 usb_buffer_free(iphone->udev, IPHETH_BUF_SIZE, rx_buf,
154 rx_urb->transfer_dma);
155 usb_buffer_free(iphone->udev, IPHETH_BUF_SIZE, tx_buf,
156 tx_urb->transfer_dma);
157 usb_free_urb(rx_urb);
158 usb_free_urb(tx_urb);
159 return -ENOMEM;
160}
161
162static void ipheth_free_urbs(struct ipheth_device *iphone)
163{
164 usb_buffer_free(iphone->udev, IPHETH_BUF_SIZE, iphone->rx_buf,
165 iphone->rx_urb->transfer_dma);
166 usb_buffer_free(iphone->udev, IPHETH_BUF_SIZE, iphone->tx_buf,
167 iphone->tx_urb->transfer_dma);
168 usb_free_urb(iphone->rx_urb);
169 usb_free_urb(iphone->tx_urb);
170}
171
172static void ipheth_kill_urbs(struct ipheth_device *dev)
173{
174 usb_kill_urb(dev->tx_urb);
175 usb_kill_urb(dev->rx_urb);
176}
177
178static void ipheth_rcvbulk_callback(struct urb *urb)
179{
180 struct ipheth_device *dev;
181 struct sk_buff *skb;
182 int status;
183 char *buf;
184 int len;
185
186 dev = urb->context;
187 if (dev == NULL)
188 return;
189
190 status = urb->status;
191 switch (status) {
192 case -ENOENT:
193 case -ECONNRESET:
194 case -ESHUTDOWN:
195 return;
196 case 0:
197 break;
198 default:
199 err("%s: urb status: %d", __func__, urb->status);
200 return;
201 }
202
203 len = urb->actual_length;
204 buf = urb->transfer_buffer;
205
206 skb = dev_alloc_skb(NET_IP_ALIGN + len);
207 if (!skb) {
208 err("%s: dev_alloc_skb: -ENOMEM", __func__);
209 dev->net->stats.rx_dropped++;
210 return;
211 }
212
213 skb_reserve(skb, NET_IP_ALIGN);
214 memcpy(skb_put(skb, len), buf + NET_IP_ALIGN, len - NET_IP_ALIGN);
215 skb->dev = dev->net;
216 skb->protocol = eth_type_trans(skb, dev->net);
217
218 dev->net->stats.rx_packets++;
219 dev->net->stats.rx_bytes += len;
220
221 netif_rx(skb);
222 ipheth_rx_submit(dev, GFP_ATOMIC);
223}
224
225static void ipheth_sndbulk_callback(struct urb *urb)
226{
227 struct ipheth_device *dev;
228
229 dev = urb->context;
230 if (dev == NULL)
231 return;
232
233 if (urb->status != 0 &&
234 urb->status != -ENOENT &&
235 urb->status != -ECONNRESET &&
236 urb->status != -ESHUTDOWN)
237 err("%s: urb status: %d", __func__, urb->status);
238
239 dev_kfree_skb_irq(dev->tx_skb);
240 netif_wake_queue(dev->net);
241}
242
243static int ipheth_carrier_set(struct ipheth_device *dev)
244{
245 struct usb_device *udev = dev->udev;
246 int retval;
247
248 retval = usb_control_msg(udev,
249 usb_rcvctrlpipe(udev, IPHETH_CTRL_ENDP),
250 IPHETH_CMD_CARRIER_CHECK, /* request */
251 0xc0, /* request type */
252 0x00, /* value */
253 0x02, /* index */
254 dev->ctrl_buf, IPHETH_CTRL_BUF_SIZE,
255 IPHETH_CTRL_TIMEOUT);
256 if (retval < 0) {
257 err("%s: usb_control_msg: %d", __func__, retval);
258 return retval;
259 }
260
261 if (dev->ctrl_buf[0] == IPHETH_CARRIER_ON)
262 netif_carrier_on(dev->net);
263 else
264 netif_carrier_off(dev->net);
265
266 return 0;
267}
268
269static void ipheth_carrier_check_work(struct work_struct *work)
270{
271 struct ipheth_device *dev = container_of(work, struct ipheth_device,
272 carrier_work.work);
273
274 ipheth_carrier_set(dev);
275 schedule_delayed_work(&dev->carrier_work, IPHETH_CARRIER_CHECK_TIMEOUT);
276}
277
278static int ipheth_get_macaddr(struct ipheth_device *dev)
279{
280 struct usb_device *udev = dev->udev;
281 struct net_device *net = dev->net;
282 int retval;
283
284 retval = usb_control_msg(udev,
285 usb_rcvctrlpipe(udev, IPHETH_CTRL_ENDP),
286 IPHETH_CMD_GET_MACADDR, /* request */
287 0xc0, /* request type */
288 0x00, /* value */
289 0x02, /* index */
290 dev->ctrl_buf,
291 IPHETH_CTRL_BUF_SIZE,
292 IPHETH_CTRL_TIMEOUT);
293 if (retval < 0) {
294 err("%s: usb_control_msg: %d", __func__, retval);
295 } else if (retval < ETH_ALEN) {
296 err("%s: usb_control_msg: short packet: %d bytes",
297 __func__, retval);
298 retval = -EINVAL;
299 } else {
300 memcpy(net->dev_addr, dev->ctrl_buf, ETH_ALEN);
301 retval = 0;
302 }
303
304 return retval;
305}
306
307static int ipheth_rx_submit(struct ipheth_device *dev, gfp_t mem_flags)
308{
309 struct usb_device *udev = dev->udev;
310 int retval;
311
312 usb_fill_bulk_urb(dev->rx_urb, udev,
313 usb_rcvbulkpipe(udev, dev->bulk_in),
314 dev->rx_buf, IPHETH_BUF_SIZE,
315 ipheth_rcvbulk_callback,
316 dev);
317 dev->rx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
318
319 retval = usb_submit_urb(dev->rx_urb, mem_flags);
320 if (retval)
321 err("%s: usb_submit_urb: %d", __func__, retval);
322 return retval;
323}
324
325static int ipheth_open(struct net_device *net)
326{
327 struct ipheth_device *dev = netdev_priv(net);
328 struct usb_device *udev = dev->udev;
329 int retval = 0;
330
331 usb_set_interface(udev, IPHETH_INTFNUM, IPHETH_ALT_INTFNUM);
332
333 retval = ipheth_carrier_set(dev);
334 if (retval)
335 return retval;
336
337 retval = ipheth_rx_submit(dev, GFP_KERNEL);
338 if (retval)
339 return retval;
340
341 schedule_delayed_work(&dev->carrier_work, IPHETH_CARRIER_CHECK_TIMEOUT);
342 netif_start_queue(net);
343 return retval;
344}
345
346static int ipheth_close(struct net_device *net)
347{
348 struct ipheth_device *dev = netdev_priv(net);
349
350 cancel_delayed_work_sync(&dev->carrier_work);
351 netif_stop_queue(net);
352 return 0;
353}
354
355static int ipheth_tx(struct sk_buff *skb, struct net_device *net)
356{
357 struct ipheth_device *dev = netdev_priv(net);
358 struct usb_device *udev = dev->udev;
359 int retval;
360
361 /* Paranoid */
362 if (skb->len > IPHETH_BUF_SIZE) {
363 WARN(1, "%s: skb too large: %d bytes", __func__, skb->len);
364 dev->net->stats.tx_dropped++;
365 dev_kfree_skb_irq(skb);
366 return NETDEV_TX_OK;
367 }
368
369 memcpy(dev->tx_buf, skb->data, skb->len);
370 if (skb->len < IPHETH_BUF_SIZE)
371 memset(dev->tx_buf + skb->len, 0, IPHETH_BUF_SIZE - skb->len);
372
373 usb_fill_bulk_urb(dev->tx_urb, udev,
374 usb_sndbulkpipe(udev, dev->bulk_out),
375 dev->tx_buf, IPHETH_BUF_SIZE,
376 ipheth_sndbulk_callback,
377 dev);
378 dev->tx_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
379
380 retval = usb_submit_urb(dev->tx_urb, GFP_ATOMIC);
381 if (retval) {
382 err("%s: usb_submit_urb: %d", __func__, retval);
383 dev->net->stats.tx_errors++;
384 dev_kfree_skb_irq(skb);
385 } else {
386 dev->tx_skb = skb;
387
388 dev->net->stats.tx_packets++;
389 dev->net->stats.tx_bytes += skb->len;
390 netif_stop_queue(net);
391 }
392
393 return NETDEV_TX_OK;
394}
395
396static void ipheth_tx_timeout(struct net_device *net)
397{
398 struct ipheth_device *dev = netdev_priv(net);
399
400 err("%s: TX timeout", __func__);
401 dev->net->stats.tx_errors++;
402 usb_unlink_urb(dev->tx_urb);
403}
404
405static struct net_device_stats *ipheth_stats(struct net_device *net)
406{
407 struct ipheth_device *dev = netdev_priv(net);
408 return &dev->net->stats;
409}
410
411static u32 ipheth_ethtool_op_get_link(struct net_device *net)
412{
413 struct ipheth_device *dev = netdev_priv(net);
414 return netif_carrier_ok(dev->net);
415}
416
417static struct ethtool_ops ops = {
418 .get_link = ipheth_ethtool_op_get_link
419};
420
421static const struct net_device_ops ipheth_netdev_ops = {
422 .ndo_open = &ipheth_open,
423 .ndo_stop = &ipheth_close,
424 .ndo_start_xmit = &ipheth_tx,
425 .ndo_tx_timeout = &ipheth_tx_timeout,
426 .ndo_get_stats = &ipheth_stats,
427};
428
429static struct device_type ipheth_type = {
430 .name = "wwan",
431};
432
433static int ipheth_probe(struct usb_interface *intf,
434 const struct usb_device_id *id)
435{
436 struct usb_device *udev = interface_to_usbdev(intf);
437 struct usb_host_interface *hintf;
438 struct usb_endpoint_descriptor *endp;
439 struct ipheth_device *dev;
440 struct net_device *netdev;
441 int i;
442 int retval;
443
444 netdev = alloc_etherdev(sizeof(struct ipheth_device));
445 if (!netdev)
446 return -ENOMEM;
447
448 netdev->netdev_ops = &ipheth_netdev_ops;
449 netdev->watchdog_timeo = IPHETH_TX_TIMEOUT;
450 strcpy(netdev->name, "wwan%d");
451
452 dev = netdev_priv(netdev);
453 dev->udev = udev;
454 dev->net = netdev;
455 dev->intf = intf;
456
457 /* Set up endpoints */
458 hintf = usb_altnum_to_altsetting(intf, IPHETH_ALT_INTFNUM);
459 if (hintf == NULL) {
460 retval = -ENODEV;
461 err("Unable to find alternate settings interface");
462 goto err_endpoints;
463 }
464
465 for (i = 0; i < hintf->desc.bNumEndpoints; i++) {
466 endp = &hintf->endpoint[i].desc;
467 if (usb_endpoint_is_bulk_in(endp))
468 dev->bulk_in = endp->bEndpointAddress;
469 else if (usb_endpoint_is_bulk_out(endp))
470 dev->bulk_out = endp->bEndpointAddress;
471 }
472 if (!(dev->bulk_in && dev->bulk_out)) {
473 retval = -ENODEV;
474 err("Unable to find endpoints");
475 goto err_endpoints;
476 }
477
478 dev->ctrl_buf = kmalloc(IPHETH_CTRL_BUF_SIZE, GFP_KERNEL);
479 if (dev->ctrl_buf == NULL) {
480 retval = -ENOMEM;
481 goto err_alloc_ctrl_buf;
482 }
483
484 retval = ipheth_get_macaddr(dev);
485 if (retval)
486 goto err_get_macaddr;
487
488 INIT_DELAYED_WORK(&dev->carrier_work, ipheth_carrier_check_work);
489
490 retval = ipheth_alloc_urbs(dev);
491 if (retval) {
492 err("error allocating urbs: %d", retval);
493 goto err_alloc_urbs;
494 }
495
496 usb_set_intfdata(intf, dev);
497
498 SET_NETDEV_DEV(netdev, &intf->dev);
499 SET_ETHTOOL_OPS(netdev, &ops);
500 SET_NETDEV_DEVTYPE(netdev, &ipheth_type);
501
502 retval = register_netdev(netdev);
503 if (retval) {
504 err("error registering netdev: %d", retval);
505 retval = -EIO;
506 goto err_register_netdev;
507 }
508
509 dev_info(&intf->dev, "Apple iPhone USB Ethernet device attached\n");
510 return 0;
511
512err_register_netdev:
513 ipheth_free_urbs(dev);
514err_alloc_urbs:
515err_get_macaddr:
516err_alloc_ctrl_buf:
517 kfree(dev->ctrl_buf);
518err_endpoints:
519 free_netdev(netdev);
520 return retval;
521}
522
523static void ipheth_disconnect(struct usb_interface *intf)
524{
525 struct ipheth_device *dev;
526
527 dev = usb_get_intfdata(intf);
528 if (dev != NULL) {
529 unregister_netdev(dev->net);
530 ipheth_kill_urbs(dev);
531 ipheth_free_urbs(dev);
532 kfree(dev->ctrl_buf);
533 free_netdev(dev->net);
534 }
535 usb_set_intfdata(intf, NULL);
536 dev_info(&intf->dev, "Apple iPhone USB Ethernet now disconnected\n");
537}
538
539static struct usb_driver ipheth_driver = {
540 .name = "ipheth",
541 .probe = ipheth_probe,
542 .disconnect = ipheth_disconnect,
543 .id_table = ipheth_table,
544};
545
546static int __init ipheth_init(void)
547{
548 int retval;
549
550 retval = usb_register(&ipheth_driver);
551 if (retval) {
552 err("usb_register failed: %d", retval);
553 return retval;
554 }
555 return 0;
556}
557
558static void __exit ipheth_exit(void)
559{
560 usb_deregister(&ipheth_driver);
561}
562
563module_init(ipheth_init);
564module_exit(ipheth_exit);
565
566MODULE_AUTHOR("Diego Giagio <diego@giagio.com>");
567MODULE_DESCRIPTION("Apple iPhone USB Ethernet driver");
568MODULE_LICENSE("Dual BSD/GPL");