diff options
-rw-r--r-- | Documentation/devices.txt | 3 | ||||
-rw-r--r-- | drivers/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/input/hid-core.c | 4 | ||||
-rw-r--r-- | drivers/usb/misc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/misc/adutux.c | 900 |
6 files changed, 919 insertions, 0 deletions
diff --git a/Documentation/devices.txt b/Documentation/devices.txt index 66c725f530f3..addc67b1d770 100644 --- a/Documentation/devices.txt +++ b/Documentation/devices.txt | |||
@@ -2543,6 +2543,9 @@ Your cooperation is appreciated. | |||
2543 | 64 = /dev/usb/rio500 Diamond Rio 500 | 2543 | 64 = /dev/usb/rio500 Diamond Rio 500 |
2544 | 65 = /dev/usb/usblcd USBLCD Interface (info@usblcd.de) | 2544 | 65 = /dev/usb/usblcd USBLCD Interface (info@usblcd.de) |
2545 | 66 = /dev/usb/cpad0 Synaptics cPad (mouse/LCD) | 2545 | 66 = /dev/usb/cpad0 Synaptics cPad (mouse/LCD) |
2546 | 67 = /dev/usb/adutux0 1st Ontrak ADU device | ||
2547 | ... | ||
2548 | 76 = /dev/usb/adutux10 10th Ontrak ADU device | ||
2546 | 96 = /dev/usb/hiddev0 1st USB HID device | 2549 | 96 = /dev/usb/hiddev0 1st USB HID device |
2547 | ... | 2550 | ... |
2548 | 111 = /dev/usb/hiddev15 16th USB HID device | 2551 | 111 = /dev/usb/hiddev15 16th USB HID device |
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 4710eb02ed64..84430db4b8d9 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile | |||
@@ -63,6 +63,7 @@ obj-$(CONFIG_USB_USS720) += misc/ | |||
63 | obj-$(CONFIG_USB_PHIDGETSERVO) += misc/ | 63 | obj-$(CONFIG_USB_PHIDGETSERVO) += misc/ |
64 | obj-$(CONFIG_USB_SISUSBVGA) += misc/ | 64 | obj-$(CONFIG_USB_SISUSBVGA) += misc/ |
65 | obj-$(CONFIG_USB_APPLEDISPLAY) += misc/ | 65 | obj-$(CONFIG_USB_APPLEDISPLAY) += misc/ |
66 | obj-$(CONFIG_USB_ADUTUX) += misc/ | ||
66 | 67 | ||
67 | obj-$(CONFIG_USB_ATM) += atm/ | 68 | obj-$(CONFIG_USB_ATM) += atm/ |
68 | obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ | 69 | obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ |
diff --git a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c index 8f8d4af3f6ef..d0d1c0c30bc5 100644 --- a/drivers/usb/input/hid-core.c +++ b/drivers/usb/input/hid-core.c | |||
@@ -1697,7 +1697,11 @@ static const struct hid_blacklist { | |||
1697 | { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_IGNORE }, | 1697 | { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_IGNORE }, |
1698 | { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1, HID_QUIRK_IGNORE }, | 1698 | { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1, HID_QUIRK_IGNORE }, |
1699 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE }, | 1699 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE }, |
1700 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 20, HID_QUIRK_IGNORE }, | ||
1701 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 30, HID_QUIRK_IGNORE }, | ||
1700 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100, HID_QUIRK_IGNORE }, | 1702 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100, HID_QUIRK_IGNORE }, |
1703 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 108, HID_QUIRK_IGNORE }, | ||
1704 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 118, HID_QUIRK_IGNORE }, | ||
1701 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE }, | 1705 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE }, |
1702 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300, HID_QUIRK_IGNORE }, | 1706 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300, HID_QUIRK_IGNORE }, |
1703 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400, HID_QUIRK_IGNORE }, | 1707 | { USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400, HID_QUIRK_IGNORE }, |
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 20539cf9394b..288d301d2bff 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig | |||
@@ -32,6 +32,16 @@ config USB_EMI26 | |||
32 | To compile this driver as a module, choose M here: the | 32 | To compile this driver as a module, choose M here: the |
33 | module will be called emi26. | 33 | module will be called emi26. |
34 | 34 | ||
35 | config USB_ADUTUX | ||
36 | tristate "ADU devices from Ontrak Control Systems (EXPERIMENTAL)" | ||
37 | depends on USB && EXPERIMENTAL | ||
38 | help | ||
39 | Say Y if you want to use an ADU device from Ontrak Control | ||
40 | Systems. | ||
41 | |||
42 | To compile this driver as a module, choose M here. The module | ||
43 | will be called adutux. | ||
44 | |||
35 | config USB_AUERSWALD | 45 | config USB_AUERSWALD |
36 | tristate "USB Auerswald ISDN support (EXPERIMENTAL)" | 46 | tristate "USB Auerswald ISDN support (EXPERIMENTAL)" |
37 | depends on USB && EXPERIMENTAL | 47 | depends on USB && EXPERIMENTAL |
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index d12a84a1c0a4..73fc8be0d8c0 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile | |||
@@ -3,6 +3,7 @@ | |||
3 | # (the ones that don't fit into any other categories) | 3 | # (the ones that don't fit into any other categories) |
4 | # | 4 | # |
5 | 5 | ||
6 | obj-$(CONFIG_USB_ADUTUX) += adutux.o | ||
6 | obj-$(CONFIG_USB_AUERSWALD) += auerswald.o | 7 | obj-$(CONFIG_USB_AUERSWALD) += auerswald.o |
7 | obj-$(CONFIG_USB_CYPRESS_CY7C63)+= cypress_cy7c63.o | 8 | obj-$(CONFIG_USB_CYPRESS_CY7C63)+= cypress_cy7c63.o |
8 | obj-$(CONFIG_USB_CYTHERM) += cytherm.o | 9 | obj-$(CONFIG_USB_CYTHERM) += cytherm.o |
diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c new file mode 100644 index 000000000000..d3963199b6ec --- /dev/null +++ b/drivers/usb/misc/adutux.c | |||
@@ -0,0 +1,900 @@ | |||
1 | /* | ||
2 | * adutux - driver for ADU devices from Ontrak Control Systems | ||
3 | * This is an experimental driver. Use at your own risk. | ||
4 | * This driver is not supported by Ontrak Control Systems. | ||
5 | * | ||
6 | * Copyright (c) 2003 John Homppi (SCO, leave this notice here) | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License as | ||
10 | * published by the Free Software Foundation; either version 2 of | ||
11 | * the License, or (at your option) any later version. | ||
12 | * | ||
13 | * derived from the Lego USB Tower driver 0.56: | ||
14 | * Copyright (c) 2003 David Glance <davidgsf@sourceforge.net> | ||
15 | * 2001 Juergen Stuber <stuber@loria.fr> | ||
16 | * that was derived from USB Skeleton driver - 0.5 | ||
17 | * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com) | ||
18 | * | ||
19 | */ | ||
20 | |||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/errno.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/usb.h> | ||
27 | #include <asm/uaccess.h> | ||
28 | |||
29 | #ifdef CONFIG_USB_DEBUG | ||
30 | static int debug = 5; | ||
31 | #else | ||
32 | static int debug = 1; | ||
33 | #endif | ||
34 | |||
35 | /* Use our own dbg macro */ | ||
36 | #undef dbg | ||
37 | #define dbg(lvl, format, arg...) \ | ||
38 | do { \ | ||
39 | if (debug >= lvl) \ | ||
40 | printk(KERN_DEBUG __FILE__ " : " format " \n", ## arg); \ | ||
41 | } while (0) | ||
42 | |||
43 | |||
44 | /* Version Information */ | ||
45 | #define DRIVER_VERSION "v0.0.13" | ||
46 | #define DRIVER_AUTHOR "John Homppi" | ||
47 | #define DRIVER_DESC "adutux (see www.ontrak.net)" | ||
48 | |||
49 | /* Module parameters */ | ||
50 | module_param(debug, int, S_IRUGO | S_IWUSR); | ||
51 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | ||
52 | |||
53 | /* Define these values to match your device */ | ||
54 | #define ADU_VENDOR_ID 0x0a07 | ||
55 | #define ADU_PRODUCT_ID 0x0064 | ||
56 | |||
57 | /* table of devices that work with this driver */ | ||
58 | static struct usb_device_id device_table [] = { | ||
59 | { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID) }, /* ADU100 */ | ||
60 | { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) }, /* ADU120 */ | ||
61 | { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) }, /* ADU130 */ | ||
62 | { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+100) }, /* ADU200 */ | ||
63 | { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+108) }, /* ADU208 */ | ||
64 | { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+118) }, /* ADU218 */ | ||
65 | { }/* Terminating entry */ | ||
66 | }; | ||
67 | |||
68 | MODULE_DEVICE_TABLE(usb, device_table); | ||
69 | |||
70 | #ifdef CONFIG_USB_DYNAMIC_MINORS | ||
71 | #define ADU_MINOR_BASE 0 | ||
72 | #else | ||
73 | #define ADU_MINOR_BASE 67 | ||
74 | #endif | ||
75 | |||
76 | /* we can have up to this number of device plugged in at once */ | ||
77 | #define MAX_DEVICES 16 | ||
78 | |||
79 | #define COMMAND_TIMEOUT (2*HZ) /* 60 second timeout for a command */ | ||
80 | |||
81 | /* Structure to hold all of our device specific stuff */ | ||
82 | struct adu_device { | ||
83 | struct semaphore sem; /* locks this structure */ | ||
84 | struct usb_device* udev; /* save off the usb device pointer */ | ||
85 | struct usb_interface* interface; | ||
86 | unsigned char minor; /* the starting minor number for this device */ | ||
87 | char serial_number[8]; | ||
88 | |||
89 | int open_count; /* number of times this port has been opened */ | ||
90 | |||
91 | char* read_buffer_primary; | ||
92 | int read_buffer_length; | ||
93 | char* read_buffer_secondary; | ||
94 | int secondary_head; | ||
95 | int secondary_tail; | ||
96 | spinlock_t buflock; | ||
97 | |||
98 | wait_queue_head_t read_wait; | ||
99 | wait_queue_head_t write_wait; | ||
100 | |||
101 | char* interrupt_in_buffer; | ||
102 | struct usb_endpoint_descriptor* interrupt_in_endpoint; | ||
103 | struct urb* interrupt_in_urb; | ||
104 | int read_urb_finished; | ||
105 | |||
106 | char* interrupt_out_buffer; | ||
107 | struct usb_endpoint_descriptor* interrupt_out_endpoint; | ||
108 | struct urb* interrupt_out_urb; | ||
109 | }; | ||
110 | |||
111 | /* prevent races between open() and disconnect */ | ||
112 | static DEFINE_MUTEX(disconnect_mutex); | ||
113 | static struct usb_driver adu_driver; | ||
114 | |||
115 | static void adu_debug_data(int level, const char *function, int size, | ||
116 | const unsigned char *data) | ||
117 | { | ||
118 | int i; | ||
119 | |||
120 | if (debug < level) | ||
121 | return; | ||
122 | |||
123 | printk(KERN_DEBUG __FILE__": %s - length = %d, data = ", | ||
124 | function, size); | ||
125 | for (i = 0; i < size; ++i) | ||
126 | printk("%.2x ", data[i]); | ||
127 | printk("\n"); | ||
128 | } | ||
129 | |||
130 | /** | ||
131 | * adu_abort_transfers | ||
132 | * aborts transfers and frees associated data structures | ||
133 | */ | ||
134 | static void adu_abort_transfers(struct adu_device *dev) | ||
135 | { | ||
136 | dbg(2," %s : enter", __FUNCTION__); | ||
137 | |||
138 | if (dev == NULL) { | ||
139 | dbg(1," %s : dev is null", __FUNCTION__); | ||
140 | goto exit; | ||
141 | } | ||
142 | |||
143 | if (dev->udev == NULL) { | ||
144 | dbg(1," %s : udev is null", __FUNCTION__); | ||
145 | goto exit; | ||
146 | } | ||
147 | |||
148 | dbg(2," %s : udev state %d", __FUNCTION__, dev->udev->state); | ||
149 | if (dev->udev->state == USB_STATE_NOTATTACHED) { | ||
150 | dbg(1," %s : udev is not attached", __FUNCTION__); | ||
151 | goto exit; | ||
152 | } | ||
153 | |||
154 | /* shutdown transfer */ | ||
155 | usb_unlink_urb(dev->interrupt_in_urb); | ||
156 | usb_unlink_urb(dev->interrupt_out_urb); | ||
157 | |||
158 | exit: | ||
159 | dbg(2," %s : leave", __FUNCTION__); | ||
160 | } | ||
161 | |||
162 | static void adu_delete(struct adu_device *dev) | ||
163 | { | ||
164 | dbg(2, "%s enter", __FUNCTION__); | ||
165 | |||
166 | adu_abort_transfers(dev); | ||
167 | |||
168 | /* free data structures */ | ||
169 | usb_free_urb(dev->interrupt_in_urb); | ||
170 | usb_free_urb(dev->interrupt_out_urb); | ||
171 | kfree(dev->read_buffer_primary); | ||
172 | kfree(dev->read_buffer_secondary); | ||
173 | kfree(dev->interrupt_in_buffer); | ||
174 | kfree(dev->interrupt_out_buffer); | ||
175 | kfree(dev); | ||
176 | |||
177 | dbg(2, "%s : leave", __FUNCTION__); | ||
178 | } | ||
179 | |||
180 | static void adu_interrupt_in_callback(struct urb *urb, struct pt_regs *regs) | ||
181 | { | ||
182 | struct adu_device *dev = urb->context; | ||
183 | |||
184 | dbg(4," %s : enter, status %d", __FUNCTION__, urb->status); | ||
185 | adu_debug_data(5, __FUNCTION__, urb->actual_length, | ||
186 | urb->transfer_buffer); | ||
187 | |||
188 | spin_lock(&dev->buflock); | ||
189 | |||
190 | if (urb->status != 0) { | ||
191 | if ((urb->status != -ENOENT) && (urb->status != -ECONNRESET)) { | ||
192 | dbg(1," %s : nonzero status received: %d", | ||
193 | __FUNCTION__, urb->status); | ||
194 | } | ||
195 | goto exit; | ||
196 | } | ||
197 | |||
198 | if (urb->actual_length > 0 && dev->interrupt_in_buffer[0] != 0x00) { | ||
199 | if (dev->read_buffer_length < | ||
200 | (4 * le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize)) - | ||
201 | (urb->actual_length)) { | ||
202 | memcpy (dev->read_buffer_primary + | ||
203 | dev->read_buffer_length, | ||
204 | dev->interrupt_in_buffer, urb->actual_length); | ||
205 | |||
206 | dev->read_buffer_length += urb->actual_length; | ||
207 | dbg(2," %s reading %d ", __FUNCTION__, | ||
208 | urb->actual_length); | ||
209 | } else { | ||
210 | dbg(1," %s : read_buffer overflow", __FUNCTION__); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | exit: | ||
215 | dev->read_urb_finished = 1; | ||
216 | spin_unlock(&dev->buflock); | ||
217 | /* always wake up so we recover from errors */ | ||
218 | wake_up_interruptible(&dev->read_wait); | ||
219 | adu_debug_data(5, __FUNCTION__, urb->actual_length, | ||
220 | urb->transfer_buffer); | ||
221 | dbg(4," %s : leave, status %d", __FUNCTION__, urb->status); | ||
222 | } | ||
223 | |||
224 | static void adu_interrupt_out_callback(struct urb *urb, struct pt_regs *regs) | ||
225 | { | ||
226 | struct adu_device *dev = urb->context; | ||
227 | |||
228 | dbg(4," %s : enter, status %d", __FUNCTION__, urb->status); | ||
229 | adu_debug_data(5,__FUNCTION__, urb->actual_length, urb->transfer_buffer); | ||
230 | |||
231 | if (urb->status != 0) { | ||
232 | if ((urb->status != -ENOENT) && | ||
233 | (urb->status != -ECONNRESET)) { | ||
234 | dbg(1, " %s :nonzero status received: %d", | ||
235 | __FUNCTION__, urb->status); | ||
236 | } | ||
237 | goto exit; | ||
238 | } | ||
239 | |||
240 | wake_up_interruptible(&dev->write_wait); | ||
241 | exit: | ||
242 | |||
243 | adu_debug_data(5, __FUNCTION__, urb->actual_length, | ||
244 | urb->transfer_buffer); | ||
245 | dbg(4," %s : leave, status %d", __FUNCTION__, urb->status); | ||
246 | } | ||
247 | |||
248 | static int adu_open(struct inode *inode, struct file *file) | ||
249 | { | ||
250 | struct adu_device *dev = NULL; | ||
251 | struct usb_interface *interface; | ||
252 | int subminor; | ||
253 | int retval = 0; | ||
254 | |||
255 | dbg(2,"%s : enter", __FUNCTION__); | ||
256 | |||
257 | subminor = iminor(inode); | ||
258 | |||
259 | mutex_lock(&disconnect_mutex); | ||
260 | |||
261 | interface = usb_find_interface(&adu_driver, subminor); | ||
262 | if (!interface) { | ||
263 | err("%s - error, can't find device for minor %d", | ||
264 | __FUNCTION__, subminor); | ||
265 | retval = -ENODEV; | ||
266 | goto exit_no_device; | ||
267 | } | ||
268 | |||
269 | dev = usb_get_intfdata(interface); | ||
270 | if (!dev) { | ||
271 | retval = -ENODEV; | ||
272 | goto exit_no_device; | ||
273 | } | ||
274 | |||
275 | /* lock this device */ | ||
276 | if ((retval = down_interruptible(&dev->sem))) { | ||
277 | dbg(2, "%s : sem down failed", __FUNCTION__); | ||
278 | goto exit_no_device; | ||
279 | } | ||
280 | |||
281 | /* increment our usage count for the device */ | ||
282 | ++dev->open_count; | ||
283 | dbg(2,"%s : open count %d", __FUNCTION__, dev->open_count); | ||
284 | |||
285 | /* save device in the file's private structure */ | ||
286 | file->private_data = dev; | ||
287 | |||
288 | /* initialize in direction */ | ||
289 | dev->read_buffer_length = 0; | ||
290 | |||
291 | /* fixup first read by having urb waiting for it */ | ||
292 | usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, | ||
293 | usb_rcvintpipe(dev->udev, | ||
294 | dev->interrupt_in_endpoint->bEndpointAddress), | ||
295 | dev->interrupt_in_buffer, | ||
296 | le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize), | ||
297 | adu_interrupt_in_callback, dev, | ||
298 | dev->interrupt_in_endpoint->bInterval); | ||
299 | /* dev->interrupt_in_urb->transfer_flags |= URB_ASYNC_UNLINK; */ | ||
300 | dev->read_urb_finished = 0; | ||
301 | usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); | ||
302 | /* we ignore failure */ | ||
303 | /* end of fixup for first read */ | ||
304 | |||
305 | up(&dev->sem); | ||
306 | |||
307 | exit_no_device: | ||
308 | mutex_unlock(&disconnect_mutex); | ||
309 | dbg(2,"%s : leave, return value %d ", __FUNCTION__, retval); | ||
310 | |||
311 | return retval; | ||
312 | } | ||
313 | |||
314 | static int adu_release_internal(struct adu_device *dev) | ||
315 | { | ||
316 | int retval = 0; | ||
317 | |||
318 | dbg(2," %s : enter", __FUNCTION__); | ||
319 | |||
320 | if (dev->udev == NULL) { | ||
321 | /* the device was unplugged before the file was released */ | ||
322 | adu_delete(dev); | ||
323 | goto exit; | ||
324 | } | ||
325 | |||
326 | /* decrement our usage count for the device */ | ||
327 | --dev->open_count; | ||
328 | dbg(2," %s : open count %d", __FUNCTION__, dev->open_count); | ||
329 | if (dev->open_count <= 0) { | ||
330 | adu_abort_transfers(dev); | ||
331 | dev->open_count = 0; | ||
332 | } | ||
333 | |||
334 | exit: | ||
335 | dbg(2," %s : leave", __FUNCTION__); | ||
336 | return retval; | ||
337 | } | ||
338 | |||
339 | static int adu_release(struct inode *inode, struct file *file) | ||
340 | { | ||
341 | struct adu_device *dev = NULL; | ||
342 | int retval = 0; | ||
343 | |||
344 | dbg(2," %s : enter", __FUNCTION__); | ||
345 | |||
346 | if (file == NULL) { | ||
347 | dbg(1," %s : file is NULL", __FUNCTION__); | ||
348 | retval = -ENODEV; | ||
349 | goto exit; | ||
350 | } | ||
351 | |||
352 | dev = file->private_data; | ||
353 | |||
354 | if (dev == NULL) { | ||
355 | dbg(1," %s : object is NULL", __FUNCTION__); | ||
356 | retval = -ENODEV; | ||
357 | goto exit; | ||
358 | } | ||
359 | |||
360 | /* lock our device */ | ||
361 | down(&dev->sem); /* not interruptible */ | ||
362 | |||
363 | if (dev->open_count <= 0) { | ||
364 | dbg(1," %s : device not opened", __FUNCTION__); | ||
365 | retval = -ENODEV; | ||
366 | goto exit; | ||
367 | } | ||
368 | |||
369 | /* do the work */ | ||
370 | retval = adu_release_internal(dev); | ||
371 | |||
372 | exit: | ||
373 | up(&dev->sem); | ||
374 | dbg(2," %s : leave, return value %d", __FUNCTION__, retval); | ||
375 | return retval; | ||
376 | } | ||
377 | |||
378 | static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, | ||
379 | loff_t *ppos) | ||
380 | { | ||
381 | struct adu_device *dev; | ||
382 | size_t bytes_read = 0; | ||
383 | size_t bytes_to_read = count; | ||
384 | int i; | ||
385 | int retval = 0; | ||
386 | int timeout = 0; | ||
387 | int should_submit = 0; | ||
388 | unsigned long flags; | ||
389 | DECLARE_WAITQUEUE(wait, current); | ||
390 | |||
391 | dbg(2," %s : enter, count = %Zd, file=%p", __FUNCTION__, count, file); | ||
392 | |||
393 | dev = file->private_data; | ||
394 | dbg(2," %s : dev=%p", __FUNCTION__, dev); | ||
395 | /* lock this object */ | ||
396 | if (down_interruptible(&dev->sem)) | ||
397 | return -ERESTARTSYS; | ||
398 | |||
399 | /* verify that the device wasn't unplugged */ | ||
400 | if (dev->udev == NULL || dev->minor == 0) { | ||
401 | retval = -ENODEV; | ||
402 | err("No device or device unplugged %d", retval); | ||
403 | goto exit; | ||
404 | } | ||
405 | |||
406 | /* verify that some data was requested */ | ||
407 | if (count == 0) { | ||
408 | dbg(1," %s : read request of 0 bytes", __FUNCTION__); | ||
409 | goto exit; | ||
410 | } | ||
411 | |||
412 | timeout = COMMAND_TIMEOUT; | ||
413 | dbg(2," %s : about to start looping", __FUNCTION__); | ||
414 | while (bytes_to_read) { | ||
415 | int data_in_secondary = dev->secondary_tail - dev->secondary_head; | ||
416 | dbg(2," %s : while, data_in_secondary=%d, status=%d", | ||
417 | __FUNCTION__, data_in_secondary, | ||
418 | dev->interrupt_in_urb->status); | ||
419 | |||
420 | if (data_in_secondary) { | ||
421 | /* drain secondary buffer */ | ||
422 | int amount = bytes_to_read < data_in_secondary ? bytes_to_read : data_in_secondary; | ||
423 | i = copy_to_user(buffer, dev->read_buffer_secondary+dev->secondary_head, amount); | ||
424 | if (i < 0) { | ||
425 | retval = -EFAULT; | ||
426 | goto exit; | ||
427 | } | ||
428 | dev->secondary_head += (amount - i); | ||
429 | bytes_read += (amount - i); | ||
430 | bytes_to_read -= (amount - i); | ||
431 | if (i) { | ||
432 | retval = bytes_read ? bytes_read : -EFAULT; | ||
433 | goto exit; | ||
434 | } | ||
435 | } else { | ||
436 | /* we check the primary buffer */ | ||
437 | spin_lock_irqsave (&dev->buflock, flags); | ||
438 | if (dev->read_buffer_length) { | ||
439 | /* we secure access to the primary */ | ||
440 | char *tmp; | ||
441 | dbg(2," %s : swap, read_buffer_length = %d", | ||
442 | __FUNCTION__, dev->read_buffer_length); | ||
443 | tmp = dev->read_buffer_secondary; | ||
444 | dev->read_buffer_secondary = dev->read_buffer_primary; | ||
445 | dev->read_buffer_primary = tmp; | ||
446 | dev->secondary_head = 0; | ||
447 | dev->secondary_tail = dev->read_buffer_length; | ||
448 | dev->read_buffer_length = 0; | ||
449 | spin_unlock_irqrestore(&dev->buflock, flags); | ||
450 | /* we have a free buffer so use it */ | ||
451 | should_submit = 1; | ||
452 | } else { | ||
453 | /* even the primary was empty - we may need to do IO */ | ||
454 | if (dev->interrupt_in_urb->status == -EINPROGRESS) { | ||
455 | /* somebody is doing IO */ | ||
456 | spin_unlock_irqrestore(&dev->buflock, flags); | ||
457 | dbg(2," %s : submitted already", __FUNCTION__); | ||
458 | } else { | ||
459 | /* we must initiate input */ | ||
460 | dbg(2," %s : initiate input", __FUNCTION__); | ||
461 | dev->read_urb_finished = 0; | ||
462 | |||
463 | usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, | ||
464 | usb_rcvintpipe(dev->udev, | ||
465 | dev->interrupt_in_endpoint->bEndpointAddress), | ||
466 | dev->interrupt_in_buffer, | ||
467 | le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize), | ||
468 | adu_interrupt_in_callback, | ||
469 | dev, | ||
470 | dev->interrupt_in_endpoint->bInterval); | ||
471 | retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); | ||
472 | if (!retval) { | ||
473 | spin_unlock_irqrestore(&dev->buflock, flags); | ||
474 | dbg(2," %s : submitted OK", __FUNCTION__); | ||
475 | } else { | ||
476 | if (retval == -ENOMEM) { | ||
477 | retval = bytes_read ? bytes_read : -ENOMEM; | ||
478 | } | ||
479 | spin_unlock_irqrestore(&dev->buflock, flags); | ||
480 | dbg(2," %s : submit failed", __FUNCTION__); | ||
481 | goto exit; | ||
482 | } | ||
483 | } | ||
484 | |||
485 | /* we wait for I/O to complete */ | ||
486 | set_current_state(TASK_INTERRUPTIBLE); | ||
487 | add_wait_queue(&dev->read_wait, &wait); | ||
488 | if (!dev->read_urb_finished) | ||
489 | timeout = schedule_timeout(COMMAND_TIMEOUT); | ||
490 | else | ||
491 | set_current_state(TASK_RUNNING); | ||
492 | remove_wait_queue(&dev->read_wait, &wait); | ||
493 | |||
494 | if (timeout <= 0) { | ||
495 | dbg(2," %s : timeout", __FUNCTION__); | ||
496 | retval = bytes_read ? bytes_read : -ETIMEDOUT; | ||
497 | goto exit; | ||
498 | } | ||
499 | |||
500 | if (signal_pending(current)) { | ||
501 | dbg(2," %s : signal pending", __FUNCTION__); | ||
502 | retval = bytes_read ? bytes_read : -EINTR; | ||
503 | goto exit; | ||
504 | } | ||
505 | } | ||
506 | } | ||
507 | } | ||
508 | |||
509 | retval = bytes_read; | ||
510 | /* if the primary buffer is empty then use it */ | ||
511 | if (should_submit && !dev->interrupt_in_urb->status==-EINPROGRESS) { | ||
512 | usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, | ||
513 | usb_rcvintpipe(dev->udev, | ||
514 | dev->interrupt_in_endpoint->bEndpointAddress), | ||
515 | dev->interrupt_in_buffer, | ||
516 | le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize), | ||
517 | adu_interrupt_in_callback, | ||
518 | dev, | ||
519 | dev->interrupt_in_endpoint->bInterval); | ||
520 | /* dev->interrupt_in_urb->transfer_flags |= URB_ASYNC_UNLINK; */ | ||
521 | dev->read_urb_finished = 0; | ||
522 | usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); | ||
523 | /* we ignore failure */ | ||
524 | } | ||
525 | |||
526 | exit: | ||
527 | /* unlock the device */ | ||
528 | up(&dev->sem); | ||
529 | |||
530 | dbg(2," %s : leave, return value %d", __FUNCTION__, retval); | ||
531 | return retval; | ||
532 | } | ||
533 | |||
534 | static ssize_t adu_write(struct file *file, const __user char *buffer, | ||
535 | size_t count, loff_t *ppos) | ||
536 | { | ||
537 | struct adu_device *dev; | ||
538 | size_t bytes_written = 0; | ||
539 | size_t bytes_to_write; | ||
540 | size_t buffer_size; | ||
541 | int retval = 0; | ||
542 | int timeout = 0; | ||
543 | |||
544 | dbg(2," %s : enter, count = %Zd", __FUNCTION__, count); | ||
545 | |||
546 | dev = file->private_data; | ||
547 | |||
548 | /* lock this object */ | ||
549 | down_interruptible(&dev->sem); | ||
550 | |||
551 | /* verify that the device wasn't unplugged */ | ||
552 | if (dev->udev == NULL || dev->minor == 0) { | ||
553 | retval = -ENODEV; | ||
554 | err("No device or device unplugged %d", retval); | ||
555 | goto exit; | ||
556 | } | ||
557 | |||
558 | /* verify that we actually have some data to write */ | ||
559 | if (count == 0) { | ||
560 | dbg(1," %s : write request of 0 bytes", __FUNCTION__); | ||
561 | goto exit; | ||
562 | } | ||
563 | |||
564 | |||
565 | while (count > 0) { | ||
566 | if (dev->interrupt_out_urb->status == -EINPROGRESS) { | ||
567 | timeout = COMMAND_TIMEOUT; | ||
568 | |||
569 | while (timeout > 0) { | ||
570 | if (signal_pending(current)) { | ||
571 | dbg(1," %s : interrupted", __FUNCTION__); | ||
572 | retval = -EINTR; | ||
573 | goto exit; | ||
574 | } | ||
575 | up(&dev->sem); | ||
576 | timeout = interruptible_sleep_on_timeout(&dev->write_wait, timeout); | ||
577 | down_interruptible(&dev->sem); | ||
578 | if (timeout > 0) { | ||
579 | break; | ||
580 | } | ||
581 | dbg(1," %s : interrupted timeout: %d", __FUNCTION__, timeout); | ||
582 | } | ||
583 | |||
584 | |||
585 | dbg(1," %s : final timeout: %d", __FUNCTION__, timeout); | ||
586 | |||
587 | if (timeout == 0) { | ||
588 | dbg(1, "%s - command timed out.", __FUNCTION__); | ||
589 | retval = -ETIMEDOUT; | ||
590 | goto exit; | ||
591 | } | ||
592 | |||
593 | dbg(4," %s : in progress, count = %Zd", __FUNCTION__, count); | ||
594 | |||
595 | } else { | ||
596 | dbg(4," %s : sending, count = %Zd", __FUNCTION__, count); | ||
597 | |||
598 | /* write the data into interrupt_out_buffer from userspace */ | ||
599 | buffer_size = le16_to_cpu(dev->interrupt_out_endpoint->wMaxPacketSize); | ||
600 | bytes_to_write = count > buffer_size ? buffer_size : count; | ||
601 | dbg(4," %s : buffer_size = %Zd, count = %Zd, bytes_to_write = %Zd", | ||
602 | __FUNCTION__, buffer_size, count, bytes_to_write); | ||
603 | |||
604 | if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) { | ||
605 | retval = -EFAULT; | ||
606 | goto exit; | ||
607 | } | ||
608 | |||
609 | /* send off the urb */ | ||
610 | usb_fill_int_urb( | ||
611 | dev->interrupt_out_urb, | ||
612 | dev->udev, | ||
613 | usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress), | ||
614 | dev->interrupt_out_buffer, | ||
615 | bytes_to_write, | ||
616 | adu_interrupt_out_callback, | ||
617 | dev, | ||
618 | dev->interrupt_in_endpoint->bInterval); | ||
619 | /* dev->interrupt_in_urb->transfer_flags |= URB_ASYNC_UNLINK; */ | ||
620 | dev->interrupt_out_urb->actual_length = bytes_to_write; | ||
621 | retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); | ||
622 | if (retval < 0) { | ||
623 | err("Couldn't submit interrupt_out_urb %d", retval); | ||
624 | goto exit; | ||
625 | } | ||
626 | |||
627 | buffer += bytes_to_write; | ||
628 | count -= bytes_to_write; | ||
629 | |||
630 | bytes_written += bytes_to_write; | ||
631 | } | ||
632 | } | ||
633 | |||
634 | retval = bytes_written; | ||
635 | |||
636 | exit: | ||
637 | /* unlock the device */ | ||
638 | up(&dev->sem); | ||
639 | |||
640 | dbg(2," %s : leave, return value %d", __FUNCTION__, retval); | ||
641 | |||
642 | return retval; | ||
643 | } | ||
644 | |||
645 | /* file operations needed when we register this driver */ | ||
646 | static struct file_operations adu_fops = { | ||
647 | .owner = THIS_MODULE, | ||
648 | .read = adu_read, | ||
649 | .write = adu_write, | ||
650 | .open = adu_open, | ||
651 | .release = adu_release, | ||
652 | }; | ||
653 | |||
654 | /* | ||
655 | * usb class driver info in order to get a minor number from the usb core, | ||
656 | * and to have the device registered with devfs and the driver core | ||
657 | */ | ||
658 | static struct usb_class_driver adu_class = { | ||
659 | .name = "usb/adutux%d", | ||
660 | .fops = &adu_fops, | ||
661 | .minor_base = ADU_MINOR_BASE, | ||
662 | }; | ||
663 | |||
664 | /** | ||
665 | * adu_probe | ||
666 | * | ||
667 | * Called by the usb core when a new device is connected that it thinks | ||
668 | * this driver might be interested in. | ||
669 | */ | ||
670 | static int adu_probe(struct usb_interface *interface, | ||
671 | const struct usb_device_id *id) | ||
672 | { | ||
673 | struct usb_device *udev = interface_to_usbdev(interface); | ||
674 | struct adu_device *dev = NULL; | ||
675 | struct usb_host_interface *iface_desc; | ||
676 | struct usb_endpoint_descriptor *endpoint; | ||
677 | int retval = -ENODEV; | ||
678 | int in_end_size; | ||
679 | int out_end_size; | ||
680 | int i; | ||
681 | |||
682 | dbg(2," %s : enter", __FUNCTION__); | ||
683 | |||
684 | if (udev == NULL) { | ||
685 | dev_err(&interface->dev, "udev is NULL.\n"); | ||
686 | goto exit; | ||
687 | } | ||
688 | |||
689 | /* allocate memory for our device state and intialize it */ | ||
690 | dev = kzalloc(sizeof(struct adu_device), GFP_KERNEL); | ||
691 | if (dev == NULL) { | ||
692 | dev_err(&interface->dev, "Out of memory\n"); | ||
693 | retval = -ENOMEM; | ||
694 | goto exit; | ||
695 | } | ||
696 | |||
697 | init_MUTEX(&dev->sem); | ||
698 | spin_lock_init(&dev->buflock); | ||
699 | dev->udev = udev; | ||
700 | init_waitqueue_head(&dev->read_wait); | ||
701 | init_waitqueue_head(&dev->write_wait); | ||
702 | |||
703 | iface_desc = &interface->altsetting[0]; | ||
704 | |||
705 | /* set up the endpoint information */ | ||
706 | for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { | ||
707 | endpoint = &iface_desc->endpoint[i].desc; | ||
708 | |||
709 | if (usb_endpoint_is_int_in(endpoint)) | ||
710 | dev->interrupt_in_endpoint = endpoint; | ||
711 | |||
712 | if (usb_endpoint_is_int_out(endpoint)) | ||
713 | dev->interrupt_out_endpoint = endpoint; | ||
714 | } | ||
715 | if (dev->interrupt_in_endpoint == NULL) { | ||
716 | dev_err(&interface->dev, "interrupt in endpoint not found\n"); | ||
717 | goto error; | ||
718 | } | ||
719 | if (dev->interrupt_out_endpoint == NULL) { | ||
720 | dev_err(&interface->dev, "interrupt out endpoint not found\n"); | ||
721 | goto error; | ||
722 | } | ||
723 | |||
724 | in_end_size = le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); | ||
725 | out_end_size = le16_to_cpu(dev->interrupt_out_endpoint->wMaxPacketSize); | ||
726 | |||
727 | dev->read_buffer_primary = kmalloc((4 * in_end_size), GFP_KERNEL); | ||
728 | if (!dev->read_buffer_primary) { | ||
729 | dev_err(&interface->dev, "Couldn't allocate read_buffer_primary\n"); | ||
730 | retval = -ENOMEM; | ||
731 | goto error; | ||
732 | } | ||
733 | |||
734 | /* debug code prime the buffer */ | ||
735 | memset(dev->read_buffer_primary, 'a', in_end_size); | ||
736 | memset(dev->read_buffer_primary + in_end_size, 'b', in_end_size); | ||
737 | memset(dev->read_buffer_primary + (2 * in_end_size), 'c', in_end_size); | ||
738 | memset(dev->read_buffer_primary + (3 * in_end_size), 'd', in_end_size); | ||
739 | |||
740 | dev->read_buffer_secondary = kmalloc((4 * in_end_size), GFP_KERNEL); | ||
741 | if (!dev->read_buffer_secondary) { | ||
742 | dev_err(&interface->dev, "Couldn't allocate read_buffer_secondary\n"); | ||
743 | retval = -ENOMEM; | ||
744 | goto error; | ||
745 | } | ||
746 | |||
747 | /* debug code prime the buffer */ | ||
748 | memset(dev->read_buffer_secondary, 'e', in_end_size); | ||
749 | memset(dev->read_buffer_secondary + in_end_size, 'f', in_end_size); | ||
750 | memset(dev->read_buffer_secondary + (2 * in_end_size), 'g', in_end_size); | ||
751 | memset(dev->read_buffer_secondary + (3 * in_end_size), 'h', in_end_size); | ||
752 | |||
753 | dev->interrupt_in_buffer = kmalloc(in_end_size, GFP_KERNEL); | ||
754 | if (!dev->interrupt_in_buffer) { | ||
755 | dev_err(&interface->dev, "Couldn't allocate interrupt_in_buffer\n"); | ||
756 | goto error; | ||
757 | } | ||
758 | |||
759 | /* debug code prime the buffer */ | ||
760 | memset(dev->interrupt_in_buffer, 'i', in_end_size); | ||
761 | |||
762 | dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); | ||
763 | if (!dev->interrupt_in_urb) { | ||
764 | dev_err(&interface->dev, "Couldn't allocate interrupt_in_urb\n"); | ||
765 | goto error; | ||
766 | } | ||
767 | dev->interrupt_out_buffer = kmalloc(out_end_size, GFP_KERNEL); | ||
768 | if (!dev->interrupt_out_buffer) { | ||
769 | dev_err(&interface->dev, "Couldn't allocate interrupt_out_buffer\n"); | ||
770 | goto error; | ||
771 | } | ||
772 | dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); | ||
773 | if (!dev->interrupt_out_urb) { | ||
774 | dev_err(&interface->dev, "Couldn't allocate interrupt_out_urb\n"); | ||
775 | goto error; | ||
776 | } | ||
777 | |||
778 | if (!usb_string(udev, udev->descriptor.iSerialNumber, dev->serial_number, | ||
779 | sizeof(dev->serial_number))) { | ||
780 | dev_err(&interface->dev, "Could not retrieve serial number\n"); | ||
781 | goto error; | ||
782 | } | ||
783 | dbg(2," %s : serial_number=%s", __FUNCTION__, dev->serial_number); | ||
784 | |||
785 | /* we can register the device now, as it is ready */ | ||
786 | usb_set_intfdata(interface, dev); | ||
787 | |||
788 | retval = usb_register_dev(interface, &adu_class); | ||
789 | |||
790 | if (retval) { | ||
791 | /* something prevented us from registering this driver */ | ||
792 | dev_err(&interface->dev, "Not able to get a minor for this device.\n"); | ||
793 | usb_set_intfdata(interface, NULL); | ||
794 | goto error; | ||
795 | } | ||
796 | |||
797 | dev->minor = interface->minor; | ||
798 | |||
799 | /* let the user know what node this device is now attached to */ | ||
800 | dev_info(&interface->dev, "ADU%d %s now attached to /dev/usb/adutux%d", | ||
801 | udev->descriptor.idProduct, dev->serial_number, | ||
802 | (dev->minor - ADU_MINOR_BASE)); | ||
803 | exit: | ||
804 | dbg(2," %s : leave, return value %p (dev)", __FUNCTION__, dev); | ||
805 | |||
806 | return retval; | ||
807 | |||
808 | error: | ||
809 | adu_delete(dev); | ||
810 | return retval; | ||
811 | } | ||
812 | |||
813 | /** | ||
814 | * adu_disconnect | ||
815 | * | ||
816 | * Called by the usb core when the device is removed from the system. | ||
817 | */ | ||
818 | static void adu_disconnect(struct usb_interface *interface) | ||
819 | { | ||
820 | struct adu_device *dev; | ||
821 | int minor; | ||
822 | |||
823 | dbg(2," %s : enter", __FUNCTION__); | ||
824 | |||
825 | mutex_lock(&disconnect_mutex); /* not interruptible */ | ||
826 | |||
827 | dev = usb_get_intfdata(interface); | ||
828 | usb_set_intfdata(interface, NULL); | ||
829 | |||
830 | down(&dev->sem); /* not interruptible */ | ||
831 | |||
832 | minor = dev->minor; | ||
833 | |||
834 | /* give back our minor */ | ||
835 | usb_deregister_dev(interface, &adu_class); | ||
836 | dev->minor = 0; | ||
837 | |||
838 | /* if the device is not opened, then we clean up right now */ | ||
839 | dbg(2," %s : open count %d", __FUNCTION__, dev->open_count); | ||
840 | if (!dev->open_count) { | ||
841 | up(&dev->sem); | ||
842 | adu_delete(dev); | ||
843 | } else { | ||
844 | dev->udev = NULL; | ||
845 | up(&dev->sem); | ||
846 | } | ||
847 | |||
848 | mutex_unlock(&disconnect_mutex); | ||
849 | |||
850 | dev_info(&interface->dev, "ADU device adutux%d now disconnected", | ||
851 | (minor - ADU_MINOR_BASE)); | ||
852 | |||
853 | dbg(2," %s : leave", __FUNCTION__); | ||
854 | } | ||
855 | |||
856 | /* usb specific object needed to register this driver with the usb subsystem */ | ||
857 | static struct usb_driver adu_driver = { | ||
858 | .name = "adutux", | ||
859 | .probe = adu_probe, | ||
860 | .disconnect = adu_disconnect, | ||
861 | .id_table = device_table, | ||
862 | }; | ||
863 | |||
864 | static int __init adu_init(void) | ||
865 | { | ||
866 | int result; | ||
867 | |||
868 | dbg(2," %s : enter", __FUNCTION__); | ||
869 | |||
870 | /* register this driver with the USB subsystem */ | ||
871 | result = usb_register(&adu_driver); | ||
872 | if (result < 0) { | ||
873 | err("usb_register failed for the "__FILE__" driver. " | ||
874 | "Error number %d", result); | ||
875 | goto exit; | ||
876 | } | ||
877 | |||
878 | info("adutux " DRIVER_DESC " " DRIVER_VERSION); | ||
879 | info("adutux is an experimental driver. Use at your own risk"); | ||
880 | |||
881 | exit: | ||
882 | dbg(2," %s : leave, return value %d", __FUNCTION__, result); | ||
883 | |||
884 | return result; | ||
885 | } | ||
886 | |||
887 | static void __exit adu_exit(void) | ||
888 | { | ||
889 | dbg(2," %s : enter", __FUNCTION__); | ||
890 | /* deregister this driver with the USB subsystem */ | ||
891 | usb_deregister(&adu_driver); | ||
892 | dbg(2," %s : leave", __FUNCTION__); | ||
893 | } | ||
894 | |||
895 | module_init(adu_init); | ||
896 | module_exit(adu_exit); | ||
897 | |||
898 | MODULE_AUTHOR(DRIVER_AUTHOR); | ||
899 | MODULE_DESCRIPTION(DRIVER_DESC); | ||
900 | MODULE_LICENSE("GPL"); | ||