]>
Commit | Line | Data |
---|---|---|
8da3dc28 DT |
1 | /* |
2 | * Frontier Designs Alphatrack driver | |
3 | * | |
4 | * Copyright (C) 2007 Michael Taht (m@taht.net) | |
5 | * | |
6 | * Based on the usbled driver and ldusb drivers by | |
7 | * | |
8 | * Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.com) | |
9 | * Copyright (C) 2005 Michael Hund <mhund@ld-didactic.de> | |
10 | * | |
11 | * The ldusb driver was, in turn, derived from Lego USB Tower driver | |
12 | * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net> | |
13 | * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net> | |
14 | * | |
15 | * This program is free software; you can redistribute it and/or | |
16 | * modify it under the terms of the GNU General Public License as | |
17 | * published by the Free Software Foundation, version 2. | |
18 | * | |
19 | */ | |
20 | ||
21 | /** | |
22 | * This driver uses a ring buffer for time critical reading of | |
23 | * interrupt in reports and provides read and write methods for | |
24 | * raw interrupt reports. | |
25 | */ | |
26 | ||
27 | /* Note: this currently uses a dumb ringbuffer for reads and writes. | |
28 | * A more optimal driver would cache and kill off outstanding urbs that are | |
29 | * now invalid, and ignore ones that already were in the queue but valid | |
30 | * as we only have 30 commands for the alphatrack. In particular this is | |
31 | * key for getting lights to flash in time as otherwise many commands | |
32 | * can be buffered up before the light change makes it to the interface. | |
33 | */ | |
34 | ||
35 | #include <linux/kernel.h> | |
36 | #include <linux/errno.h> | |
37 | #include <linux/init.h> | |
38 | #include <linux/slab.h> | |
39 | #include <linux/module.h> | |
40 | #include <linux/kobject.h> | |
41 | #include <linux/mutex.h> | |
8da3dc28 | 42 | |
f6a0ccad | 43 | #include <linux/uaccess.h> |
8da3dc28 DT |
44 | #include <linux/input.h> |
45 | #include <linux/usb.h> | |
46 | #include <linux/poll.h> | |
47 | ||
8da3dc28 DT |
48 | #include "alphatrack.h" |
49 | ||
50 | #define VENDOR_ID 0x165b | |
51 | #define PRODUCT_ID 0xfad1 | |
52 | ||
53 | #ifdef CONFIG_USB_DYNAMIC_MINORS | |
54 | #define USB_ALPHATRACK_MINOR_BASE 0 | |
55 | #else | |
f6a0ccad | 56 | /* FIXME 176 - is another driver's minor - apply for that */ |
8da3dc28 DT |
57 | #define USB_ALPHATRACK_MINOR_BASE 176 |
58 | #endif | |
59 | ||
60 | /* table of devices that work with this driver */ | |
a457732b | 61 | static const struct usb_device_id usb_alphatrack_table[] = { |
f6a0ccad DT |
62 | {USB_DEVICE(VENDOR_ID, PRODUCT_ID)}, |
63 | {} /* Terminating entry */ | |
8da3dc28 DT |
64 | }; |
65 | ||
66 | MODULE_DEVICE_TABLE(usb, usb_alphatrack_table); | |
f6a0ccad | 67 | MODULE_VERSION("0.41"); |
8da3dc28 DT |
68 | MODULE_AUTHOR("Mike Taht <m@taht.net>"); |
69 | MODULE_DESCRIPTION("Alphatrack USB Driver"); | |
70 | MODULE_LICENSE("GPL"); | |
71 | MODULE_SUPPORTED_DEVICE("Frontier Designs Alphatrack Control Surface"); | |
72 | ||
73 | /* These aren't done yet */ | |
74 | ||
8da3dc28 DT |
75 | #define SUPPRESS_EXTRA_ONLINE_EVENTS 0 |
76 | #define BUFFERED_WRITES 0 | |
77 | #define SUPPRESS_EXTRA_OFFLINE_EVENTS 0 | |
78 | #define COMPRESS_FADER_EVENTS 0 | |
79 | ||
80 | #define BUFFERED_READS 1 | |
81 | #define RING_BUFFER_SIZE 512 | |
82 | #define WRITE_BUFFER_SIZE 34 | |
83 | #define ALPHATRACK_USB_TIMEOUT 10 | |
84 | #define OUTPUT_CMD_SIZE 8 | |
85 | #define INPUT_CMD_SIZE 12 | |
f6a0ccad | 86 | #define ALPHATRACK_DEBUG 0 |
8da3dc28 | 87 | |
f6a0ccad | 88 | static int debug = ALPHATRACK_DEBUG; |
8da3dc28 DT |
89 | |
90 | /* Use our own dbg macro */ | |
f6a0ccad DT |
91 | #define dbg_info(dev, format, arg...) do \ |
92 | { if (debug) dev_info(dev , format , ## arg); } while (0) | |
8da3dc28 | 93 | |
8da3dc28 DT |
94 | #define alphatrack_ocmd_info(dev, cmd, format, arg...) |
95 | ||
96 | #define alphatrack_icmd_info(dev, cmd, format, arg...) | |
97 | ||
8da3dc28 DT |
98 | /* Module parameters */ |
99 | ||
100 | module_param(debug, int, S_IRUGO | S_IWUSR); | |
101 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | |
102 | ||
103 | /* All interrupt in transfers are collected in a ring buffer to | |
104 | * avoid racing conditions and get better performance of the driver. | |
105 | */ | |
106 | ||
107 | static int ring_buffer_size = RING_BUFFER_SIZE; | |
108 | ||
f6a0ccad | 109 | module_param(ring_buffer_size, int, S_IRUGO); |
8da3dc28 DT |
110 | MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size"); |
111 | ||
112 | /* The write_buffer can one day contain more than one interrupt out transfer. | |
113 | */ | |
114 | ||
115 | static int write_buffer_size = WRITE_BUFFER_SIZE; | |
f6a0ccad | 116 | module_param(write_buffer_size, int, S_IRUGO); |
8da3dc28 DT |
117 | MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); |
118 | ||
119 | /* | |
120 | * Increase the interval for debugging purposes. | |
121 | * or set to 1 to use the standard interval from the endpoint descriptors. | |
122 | */ | |
123 | ||
124 | static int min_interrupt_in_interval = ALPHATRACK_USB_TIMEOUT; | |
125 | module_param(min_interrupt_in_interval, int, 0); | |
f6a0ccad DT |
126 | MODULE_PARM_DESC(min_interrupt_in_interval, |
127 | "Minimum interrupt in interval in ms"); | |
8da3dc28 DT |
128 | |
129 | static int min_interrupt_out_interval = ALPHATRACK_USB_TIMEOUT; | |
130 | module_param(min_interrupt_out_interval, int, 0); | |
f6a0ccad DT |
131 | MODULE_PARM_DESC(min_interrupt_out_interval, |
132 | "Minimum interrupt out interval in ms"); | |
8da3dc28 DT |
133 | |
134 | /* Structure to hold all of our device specific stuff */ | |
135 | ||
136 | struct usb_alphatrack { | |
3504e0c8 | 137 | struct mutex mtx; /* locks this structure */ |
f6a0ccad DT |
138 | struct usb_interface *intf; /* save off the usb interface pointer */ |
139 | int open_count; /* number of times this port has been opened */ | |
140 | ||
141 | /* make gcc happy */ | |
142 | struct alphatrack_icmd (*ring_buffer)[RING_BUFFER_SIZE]; | |
143 | struct alphatrack_ocmd (*write_buffer)[WRITE_BUFFER_SIZE]; | |
144 | unsigned int ring_head; | |
145 | unsigned int ring_tail; | |
146 | ||
147 | wait_queue_head_t read_wait; | |
148 | wait_queue_head_t write_wait; | |
149 | ||
150 | unsigned char *interrupt_in_buffer; | |
151 | unsigned char *oldi_buffer; | |
152 | struct usb_endpoint_descriptor *interrupt_in_endpoint; | |
153 | struct urb *interrupt_in_urb; | |
154 | int interrupt_in_interval; | |
155 | size_t interrupt_in_endpoint_size; | |
156 | int interrupt_in_running; | |
157 | int interrupt_in_done; | |
158 | ||
159 | char *interrupt_out_buffer; | |
160 | struct usb_endpoint_descriptor *interrupt_out_endpoint; | |
161 | struct urb *interrupt_out_urb; | |
162 | int interrupt_out_interval; | |
163 | size_t interrupt_out_endpoint_size; | |
164 | int interrupt_out_busy; | |
8da3dc28 DT |
165 | |
166 | atomic_t writes_pending; | |
f6a0ccad DT |
167 | int event; /* alternate interface to events */ |
168 | int fader; /* 10 bits */ | |
169 | int lights; /* 23 bits */ | |
170 | unsigned char dump_state; /* 0 if disabled 1 if enabled */ | |
171 | unsigned char enable; /* 0 if disabled 1 if enabled */ | |
172 | unsigned char offline; /* if the device is out of range or asleep */ | |
173 | unsigned char verbose; /* be verbose in error reporting */ | |
174 | unsigned char last_cmd[OUTPUT_CMD_SIZE]; | |
175 | unsigned char screen[32]; | |
8da3dc28 DT |
176 | }; |
177 | ||
178 | /* prevent races between open() and disconnect() */ | |
179 | static DEFINE_MUTEX(disconnect_mutex); | |
180 | ||
181 | /* forward declaration */ | |
182 | ||
183 | static struct usb_driver usb_alphatrack_driver; | |
184 | ||
8da3dc28 DT |
185 | /** |
186 | * usb_alphatrack_abort_transfers | |
187 | * aborts transfers and frees associated data structures | |
188 | */ | |
189 | static void usb_alphatrack_abort_transfers(struct usb_alphatrack *dev) | |
190 | { | |
191 | /* shutdown transfer */ | |
192 | if (dev->interrupt_in_running) { | |
193 | dev->interrupt_in_running = 0; | |
194 | if (dev->intf) | |
195 | usb_kill_urb(dev->interrupt_in_urb); | |
196 | } | |
197 | if (dev->interrupt_out_busy) | |
198 | if (dev->intf) | |
199 | usb_kill_urb(dev->interrupt_out_urb); | |
200 | } | |
201 | ||
8da3dc28 DT |
202 | /** |
203 | * usb_alphatrack_delete | |
204 | */ | |
205 | static void usb_alphatrack_delete(struct usb_alphatrack *dev) | |
206 | { | |
207 | usb_alphatrack_abort_transfers(dev); | |
208 | usb_free_urb(dev->interrupt_in_urb); | |
209 | usb_free_urb(dev->interrupt_out_urb); | |
210 | kfree(dev->ring_buffer); | |
211 | kfree(dev->interrupt_in_buffer); | |
212 | kfree(dev->interrupt_out_buffer); | |
f6a0ccad | 213 | kfree(dev); /* fixme oldi_buffer */ |
8da3dc28 DT |
214 | } |
215 | ||
216 | /** | |
217 | * usb_alphatrack_interrupt_in_callback | |
218 | */ | |
219 | ||
220 | static void usb_alphatrack_interrupt_in_callback(struct urb *urb) | |
221 | { | |
222 | struct usb_alphatrack *dev = urb->context; | |
223 | unsigned int next_ring_head; | |
224 | int retval = -1; | |
8da3dc28 DT |
225 | |
226 | if (urb->status) { | |
227 | if (urb->status == -ENOENT || | |
f6a0ccad | 228 | urb->status == -ECONNRESET || urb->status == -ESHUTDOWN) { |
8da3dc28 DT |
229 | goto exit; |
230 | } else { | |
f6a0ccad DT |
231 | dbg_info(&dev->intf->dev, |
232 | "%s: nonzero status received: %d\n", __func__, | |
233 | urb->status); | |
234 | goto resubmit; /* maybe we can recover */ | |
8da3dc28 DT |
235 | } |
236 | } | |
237 | ||
238 | if (urb->actual_length != INPUT_CMD_SIZE) { | |
239 | dev_warn(&dev->intf->dev, | |
f6a0ccad | 240 | "Urb length was %d bytes!!" |
1e1d25cb | 241 | "Do something intelligent\n", urb->actual_length); |
8da3dc28 | 242 | } else { |
f6a0ccad DT |
243 | alphatrack_ocmd_info(&dev->intf->dev, |
244 | &(*dev->ring_buffer)[dev->ring_tail].cmd, | |
245 | "%s", "bla"); | |
246 | if (memcmp | |
247 | (dev->interrupt_in_buffer, dev->oldi_buffer, | |
248 | INPUT_CMD_SIZE) == 0) { | |
249 | goto resubmit; | |
8da3dc28 | 250 | } |
f6a0ccad DT |
251 | memcpy(dev->oldi_buffer, dev->interrupt_in_buffer, |
252 | INPUT_CMD_SIZE); | |
8da3dc28 DT |
253 | |
254 | #if SUPPRESS_EXTRA_OFFLINE_EVENTS | |
f6a0ccad DT |
255 | if (dev->offline == 2 && dev->interrupt_in_buffer[1] == 0xff) |
256 | goto resubmit; | |
257 | if (dev->offline == 1 && dev->interrupt_in_buffer[1] == 0xff) { | |
258 | dev->offline = 2; | |
259 | goto resubmit; | |
260 | } | |
8da3dc28 | 261 | /* Always pass one offline event up the stack */ |
f6a0ccad DT |
262 | if (dev->offline > 0 && dev->interrupt_in_buffer[1] != 0xff) |
263 | dev->offline = 0; | |
264 | if (dev->offline == 0 && dev->interrupt_in_buffer[1] == 0xff) | |
265 | dev->offline = 1; | |
8da3dc28 | 266 | #endif |
f6a0ccad DT |
267 | dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", |
268 | __func__, dev->ring_head, dev->ring_tail); | |
269 | next_ring_head = (dev->ring_head + 1) % ring_buffer_size; | |
8da3dc28 DT |
270 | |
271 | if (next_ring_head != dev->ring_tail) { | |
272 | memcpy(&((*dev->ring_buffer)[dev->ring_head]), | |
f6a0ccad | 273 | dev->interrupt_in_buffer, urb->actual_length); |
8da3dc28 DT |
274 | dev->ring_head = next_ring_head; |
275 | retval = 0; | |
276 | memset(dev->interrupt_in_buffer, 0, urb->actual_length); | |
277 | } else { | |
278 | dev_warn(&dev->intf->dev, | |
279 | "Ring buffer overflow, %d bytes dropped\n", | |
280 | urb->actual_length); | |
281 | memset(dev->interrupt_in_buffer, 0, urb->actual_length); | |
282 | } | |
283 | } | |
284 | ||
285 | resubmit: | |
286 | /* resubmit if we're still running */ | |
287 | if (dev->interrupt_in_running && dev->intf) { | |
288 | retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); | |
289 | if (retval) | |
290 | dev_err(&dev->intf->dev, | |
291 | "usb_submit_urb failed (%d)\n", retval); | |
292 | } | |
293 | ||
294 | exit: | |
295 | dev->interrupt_in_done = 1; | |
296 | wake_up_interruptible(&dev->read_wait); | |
297 | } | |
298 | ||
299 | /** | |
300 | * usb_alphatrack_interrupt_out_callback | |
301 | */ | |
302 | static void usb_alphatrack_interrupt_out_callback(struct urb *urb) | |
303 | { | |
304 | struct usb_alphatrack *dev = urb->context; | |
305 | ||
306 | /* sync/async unlink faults aren't errors */ | |
307 | if (urb->status && !(urb->status == -ENOENT || | |
308 | urb->status == -ECONNRESET || | |
309 | urb->status == -ESHUTDOWN)) | |
310 | dbg_info(&dev->intf->dev, | |
311 | "%s - nonzero write interrupt status received: %d\n", | |
d599edca | 312 | __func__, urb->status); |
8da3dc28 DT |
313 | atomic_dec(&dev->writes_pending); |
314 | dev->interrupt_out_busy = 0; | |
315 | wake_up_interruptible(&dev->write_wait); | |
316 | } | |
317 | ||
318 | /** | |
319 | * usb_alphatrack_open | |
320 | */ | |
321 | static int usb_alphatrack_open(struct inode *inode, struct file *file) | |
322 | { | |
323 | struct usb_alphatrack *dev; | |
324 | int subminor; | |
325 | int retval = 0; | |
326 | struct usb_interface *interface; | |
327 | ||
328 | nonseekable_open(inode, file); | |
329 | subminor = iminor(inode); | |
330 | ||
331 | mutex_lock(&disconnect_mutex); | |
332 | ||
333 | interface = usb_find_interface(&usb_alphatrack_driver, subminor); | |
334 | ||
335 | if (!interface) { | |
336 | err("%s - error, can't find device for minor %d\n", | |
f6a0ccad | 337 | __func__, subminor); |
8da3dc28 DT |
338 | retval = -ENODEV; |
339 | goto unlock_disconnect_exit; | |
340 | } | |
341 | ||
342 | dev = usb_get_intfdata(interface); | |
343 | ||
344 | if (!dev) { | |
345 | retval = -ENODEV; | |
346 | goto unlock_disconnect_exit; | |
347 | } | |
348 | ||
349 | /* lock this device */ | |
3504e0c8 | 350 | if (mutex_lock_interruptible(&dev->mtx)) { |
8da3dc28 DT |
351 | retval = -ERESTARTSYS; |
352 | goto unlock_disconnect_exit; | |
353 | } | |
354 | ||
355 | /* allow opening only once */ | |
356 | if (dev->open_count) { | |
357 | retval = -EBUSY; | |
358 | goto unlock_exit; | |
359 | } | |
360 | dev->open_count = 1; | |
361 | ||
362 | /* initialize in direction */ | |
363 | dev->ring_head = 0; | |
364 | dev->ring_tail = 0; | |
365 | usb_fill_int_urb(dev->interrupt_in_urb, | |
366 | interface_to_usbdev(interface), | |
367 | usb_rcvintpipe(interface_to_usbdev(interface), | |
f6a0ccad DT |
368 | dev->interrupt_in_endpoint-> |
369 | bEndpointAddress), | |
8da3dc28 DT |
370 | dev->interrupt_in_buffer, |
371 | dev->interrupt_in_endpoint_size, | |
f6a0ccad | 372 | usb_alphatrack_interrupt_in_callback, dev, |
8da3dc28 DT |
373 | dev->interrupt_in_interval); |
374 | ||
375 | dev->interrupt_in_running = 1; | |
376 | dev->interrupt_in_done = 0; | |
377 | dev->enable = 1; | |
378 | dev->offline = 0; | |
379 | ||
380 | retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); | |
381 | if (retval) { | |
f6a0ccad DT |
382 | dev_err(&interface->dev, |
383 | "Couldn't submit interrupt_in_urb %d\n", retval); | |
8da3dc28 DT |
384 | dev->interrupt_in_running = 0; |
385 | dev->open_count = 0; | |
386 | goto unlock_exit; | |
387 | } | |
388 | ||
389 | /* save device in the file's private structure */ | |
390 | file->private_data = dev; | |
391 | ||
8da3dc28 | 392 | unlock_exit: |
3504e0c8 | 393 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
394 | |
395 | unlock_disconnect_exit: | |
396 | mutex_unlock(&disconnect_mutex); | |
397 | ||
398 | return retval; | |
399 | } | |
400 | ||
401 | /** | |
402 | * usb_alphatrack_release | |
403 | */ | |
404 | static int usb_alphatrack_release(struct inode *inode, struct file *file) | |
405 | { | |
406 | struct usb_alphatrack *dev; | |
407 | int retval = 0; | |
408 | ||
409 | dev = file->private_data; | |
410 | ||
411 | if (dev == NULL) { | |
412 | retval = -ENODEV; | |
413 | goto exit; | |
414 | } | |
415 | ||
3504e0c8 | 416 | if (mutex_lock_interruptible(&dev->mtx)) { |
8da3dc28 DT |
417 | retval = -ERESTARTSYS; |
418 | goto exit; | |
419 | } | |
420 | ||
421 | if (dev->open_count != 1) { | |
422 | retval = -ENODEV; | |
423 | goto unlock_exit; | |
424 | } | |
425 | ||
426 | if (dev->intf == NULL) { | |
427 | /* the device was unplugged before the file was released */ | |
3504e0c8 | 428 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
429 | /* unlock here as usb_alphatrack_delete frees dev */ |
430 | usb_alphatrack_delete(dev); | |
431 | retval = -ENODEV; | |
432 | goto exit; | |
433 | } | |
434 | ||
435 | /* wait until write transfer is finished */ | |
436 | if (dev->interrupt_out_busy) | |
f6a0ccad DT |
437 | wait_event_interruptible_timeout(dev->write_wait, |
438 | !dev->interrupt_out_busy, | |
439 | 2 * HZ); | |
8da3dc28 DT |
440 | usb_alphatrack_abort_transfers(dev); |
441 | dev->open_count = 0; | |
442 | ||
443 | unlock_exit: | |
3504e0c8 | 444 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
445 | |
446 | exit: | |
447 | return retval; | |
448 | } | |
449 | ||
450 | /** | |
451 | * usb_alphatrack_poll | |
452 | */ | |
f6a0ccad | 453 | static unsigned int usb_alphatrack_poll(struct file *file, poll_table * wait) |
8da3dc28 DT |
454 | { |
455 | struct usb_alphatrack *dev; | |
456 | unsigned int mask = 0; | |
457 | ||
458 | dev = file->private_data; | |
459 | ||
460 | poll_wait(file, &dev->read_wait, wait); | |
461 | poll_wait(file, &dev->write_wait, wait); | |
462 | ||
463 | if (dev->ring_head != dev->ring_tail) | |
464 | mask |= POLLIN | POLLRDNORM; | |
465 | if (!dev->interrupt_out_busy) | |
466 | mask |= POLLOUT | POLLWRNORM; | |
467 | ||
468 | return mask; | |
469 | } | |
470 | ||
471 | /** | |
472 | * usb_alphatrack_read | |
473 | */ | |
f6a0ccad DT |
474 | static ssize_t usb_alphatrack_read(struct file *file, char __user *buffer, |
475 | size_t count, loff_t *ppos) | |
8da3dc28 DT |
476 | { |
477 | struct usb_alphatrack *dev; | |
478 | int retval = 0; | |
479 | ||
480 | int c = 0; | |
481 | ||
482 | dev = file->private_data; | |
483 | ||
484 | /* verify that we actually have some data to read */ | |
485 | if (count == 0) | |
486 | goto exit; | |
487 | ||
488 | /* lock this object */ | |
3504e0c8 | 489 | if (mutex_lock_interruptible(&dev->mtx)) { |
8da3dc28 DT |
490 | retval = -ERESTARTSYS; |
491 | goto exit; | |
492 | } | |
493 | ||
494 | /* verify that the device wasn't unplugged */ | |
495 | if (dev->intf == NULL) { | |
496 | retval = -ENODEV; | |
497 | err("No device or device unplugged %d\n", retval); | |
498 | goto unlock_exit; | |
499 | } | |
500 | ||
501 | while (dev->ring_head == dev->ring_tail) { | |
f6a0ccad DT |
502 | if (file->f_flags & O_NONBLOCK) { |
503 | retval = -EAGAIN; | |
504 | goto unlock_exit; | |
505 | } | |
506 | dev->interrupt_in_done = 0; | |
507 | retval = | |
508 | wait_event_interruptible(dev->read_wait, | |
509 | dev->interrupt_in_done); | |
510 | if (retval < 0) | |
511 | goto unlock_exit; | |
512 | } | |
513 | ||
514 | alphatrack_ocmd_info(&dev->intf->dev, | |
515 | &(*dev->ring_buffer)[dev->ring_tail].cmd, "%s", | |
516 | ": copying to userspace"); | |
517 | ||
518 | c = 0; | |
519 | while ((c < count) && (dev->ring_tail != dev->ring_head)) { | |
520 | if (copy_to_user | |
521 | (&buffer[c], &(*dev->ring_buffer)[dev->ring_tail], | |
522 | INPUT_CMD_SIZE)) { | |
523 | retval = -EFAULT; | |
524 | goto unlock_exit; | |
525 | } | |
526 | dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; | |
527 | c += INPUT_CMD_SIZE; | |
528 | dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", | |
529 | __func__, dev->ring_head, dev->ring_tail); | |
530 | } | |
531 | retval = c; | |
8da3dc28 DT |
532 | |
533 | unlock_exit: | |
534 | /* unlock the device */ | |
3504e0c8 | 535 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
536 | |
537 | exit: | |
538 | return retval; | |
539 | } | |
540 | ||
541 | /** | |
542 | * usb_alphatrack_write | |
543 | */ | |
f6a0ccad DT |
544 | static ssize_t usb_alphatrack_write(struct file *file, |
545 | const char __user *buffer, size_t count, | |
546 | loff_t *ppos) | |
8da3dc28 DT |
547 | { |
548 | struct usb_alphatrack *dev; | |
549 | size_t bytes_to_write; | |
550 | int retval = 0; | |
551 | ||
552 | dev = file->private_data; | |
553 | ||
554 | /* verify that we actually have some data to write */ | |
555 | if (count == 0) | |
556 | goto exit; | |
557 | ||
558 | /* lock this object */ | |
3504e0c8 | 559 | if (mutex_lock_interruptible(&dev->mtx)) { |
8da3dc28 DT |
560 | retval = -ERESTARTSYS; |
561 | goto exit; | |
562 | } | |
563 | ||
564 | /* verify that the device wasn't unplugged */ | |
565 | if (dev->intf == NULL) { | |
566 | retval = -ENODEV; | |
567 | err("No device or device unplugged %d\n", retval); | |
568 | goto unlock_exit; | |
569 | } | |
570 | ||
571 | /* wait until previous transfer is finished */ | |
572 | if (dev->interrupt_out_busy) { | |
573 | if (file->f_flags & O_NONBLOCK) { | |
574 | retval = -EAGAIN; | |
575 | goto unlock_exit; | |
576 | } | |
f6a0ccad DT |
577 | retval = |
578 | wait_event_interruptible(dev->write_wait, | |
579 | !dev->interrupt_out_busy); | |
580 | if (retval < 0) | |
8da3dc28 | 581 | goto unlock_exit; |
8da3dc28 DT |
582 | } |
583 | ||
584 | /* write the data into interrupt_out_buffer from userspace */ | |
f6a0ccad DT |
585 | /* FIXME - if you write more than 12 bytes this breaks */ |
586 | bytes_to_write = | |
587 | min(count, write_buffer_size * dev->interrupt_out_endpoint_size); | |
8da3dc28 | 588 | if (bytes_to_write < count) |
f6a0ccad DT |
589 | dev_warn(&dev->intf->dev, |
590 | "Write buffer overflow, %zd bytes dropped\n", | |
591 | count - bytes_to_write); | |
8da3dc28 | 592 | |
f6a0ccad DT |
593 | dbg_info(&dev->intf->dev, "%s: count = %zd, bytes_to_write = %zd\n", |
594 | __func__, count, bytes_to_write); | |
8da3dc28 DT |
595 | |
596 | if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { | |
597 | retval = -EFAULT; | |
598 | goto unlock_exit; | |
599 | } | |
600 | ||
601 | if (dev->interrupt_out_endpoint == NULL) { | |
1e1d25cb | 602 | err("Endpoint should not be be null!\n"); |
8da3dc28 DT |
603 | goto unlock_exit; |
604 | } | |
605 | ||
606 | /* send off the urb */ | |
607 | usb_fill_int_urb(dev->interrupt_out_urb, | |
608 | interface_to_usbdev(dev->intf), | |
609 | usb_sndintpipe(interface_to_usbdev(dev->intf), | |
f6a0ccad DT |
610 | dev->interrupt_out_endpoint-> |
611 | bEndpointAddress), | |
612 | dev->interrupt_out_buffer, bytes_to_write, | |
613 | usb_alphatrack_interrupt_out_callback, dev, | |
8da3dc28 DT |
614 | dev->interrupt_out_interval); |
615 | dev->interrupt_out_busy = 1; | |
616 | atomic_inc(&dev->writes_pending); | |
617 | wmb(); | |
618 | ||
619 | retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); | |
620 | if (retval) { | |
621 | dev->interrupt_out_busy = 0; | |
622 | err("Couldn't submit interrupt_out_urb %d\n", retval); | |
623 | atomic_dec(&dev->writes_pending); | |
624 | goto unlock_exit; | |
625 | } | |
626 | retval = bytes_to_write; | |
627 | ||
628 | unlock_exit: | |
629 | /* unlock the device */ | |
3504e0c8 | 630 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
631 | |
632 | exit: | |
633 | return retval; | |
634 | } | |
635 | ||
636 | /* file operations needed when we register this driver */ | |
637 | static const struct file_operations usb_alphatrack_fops = { | |
f6a0ccad DT |
638 | .owner = THIS_MODULE, |
639 | .read = usb_alphatrack_read, | |
640 | .write = usb_alphatrack_write, | |
641 | .open = usb_alphatrack_open, | |
642 | .release = usb_alphatrack_release, | |
643 | .poll = usb_alphatrack_poll, | |
6038f373 | 644 | .llseek = no_llseek, |
8da3dc28 DT |
645 | }; |
646 | ||
647 | /* | |
648 | * usb class driver info in order to get a minor number from the usb core, | |
649 | * and to have the device registered with the driver core | |
650 | */ | |
651 | ||
652 | static struct usb_class_driver usb_alphatrack_class = { | |
f6a0ccad DT |
653 | .name = "alphatrack%d", |
654 | .fops = &usb_alphatrack_fops, | |
655 | .minor_base = USB_ALPHATRACK_MINOR_BASE, | |
8da3dc28 DT |
656 | }; |
657 | ||
8da3dc28 DT |
658 | /** |
659 | * usb_alphatrack_probe | |
660 | * | |
661 | * Called by the usb core when a new device is connected that it thinks | |
662 | * this driver might be interested in. | |
663 | */ | |
f6a0ccad DT |
664 | static int usb_alphatrack_probe(struct usb_interface *intf, |
665 | const struct usb_device_id *id) | |
8da3dc28 DT |
666 | { |
667 | struct usb_device *udev = interface_to_usbdev(intf); | |
668 | struct usb_alphatrack *dev = NULL; | |
669 | struct usb_host_interface *iface_desc; | |
670 | struct usb_endpoint_descriptor *endpoint; | |
671 | int i; | |
672 | int true_size; | |
673 | int retval = -ENOMEM; | |
674 | ||
9b0131cb | 675 | /* allocate memory for our device state and initialize it */ |
8da3dc28 DT |
676 | |
677 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | |
678 | if (dev == NULL) { | |
679 | dev_err(&intf->dev, "Out of memory\n"); | |
680 | goto exit; | |
681 | } | |
3504e0c8 | 682 | mutex_init(&dev->mtx); |
8da3dc28 DT |
683 | dev->intf = intf; |
684 | init_waitqueue_head(&dev->read_wait); | |
685 | init_waitqueue_head(&dev->write_wait); | |
686 | ||
687 | iface_desc = intf->cur_altsetting; | |
688 | ||
689 | /* set up the endpoint information */ | |
690 | for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { | |
691 | endpoint = &iface_desc->endpoint[i].desc; | |
692 | ||
693 | if (usb_endpoint_is_int_in(endpoint)) | |
694 | dev->interrupt_in_endpoint = endpoint; | |
695 | ||
696 | if (usb_endpoint_is_int_out(endpoint)) | |
697 | dev->interrupt_out_endpoint = endpoint; | |
698 | } | |
699 | if (dev->interrupt_in_endpoint == NULL) { | |
700 | dev_err(&intf->dev, "Interrupt in endpoint not found\n"); | |
701 | goto error; | |
702 | } | |
703 | if (dev->interrupt_out_endpoint == NULL) | |
f6a0ccad DT |
704 | dev_warn(&intf->dev, |
705 | "Interrupt out endpoint not found" | |
706 | "(using control endpoint instead)\n"); | |
8da3dc28 | 707 | |
f6a0ccad DT |
708 | dev->interrupt_in_endpoint_size = |
709 | le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); | |
8da3dc28 DT |
710 | |
711 | if (dev->interrupt_in_endpoint_size != 64) | |
f6a0ccad | 712 | dev_warn(&intf->dev, "Interrupt in endpoint size is not 64!\n"); |
8da3dc28 | 713 | |
f6a0ccad DT |
714 | if (ring_buffer_size == 0) |
715 | ring_buffer_size = RING_BUFFER_SIZE; | |
8da3dc28 | 716 | |
f6a0ccad | 717 | true_size = min(ring_buffer_size, RING_BUFFER_SIZE); |
8da3dc28 | 718 | |
f6a0ccad DT |
719 | /* FIXME - there are more usb_alloc routines for dma correctness. |
720 | Needed? */ | |
721 | dev->ring_buffer = | |
722 | kmalloc((true_size * sizeof(struct alphatrack_icmd)), GFP_KERNEL); | |
8da3dc28 DT |
723 | |
724 | if (!dev->ring_buffer) { | |
f6a0ccad DT |
725 | dev_err(&intf->dev, |
726 | "Couldn't allocate input ring_buffer of size %d\n", | |
727 | true_size); | |
8da3dc28 DT |
728 | goto error; |
729 | } | |
730 | ||
f6a0ccad DT |
731 | dev->interrupt_in_buffer = |
732 | kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); | |
8da3dc28 DT |
733 | |
734 | if (!dev->interrupt_in_buffer) { | |
735 | dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); | |
736 | goto error; | |
737 | } | |
738 | dev->oldi_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); | |
739 | if (!dev->oldi_buffer) { | |
740 | dev_err(&intf->dev, "Couldn't allocate old buffer\n"); | |
741 | goto error; | |
742 | } | |
743 | dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); | |
744 | if (!dev->interrupt_in_urb) { | |
745 | dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); | |
746 | goto error; | |
747 | } | |
748 | ||
f6a0ccad DT |
749 | dev->interrupt_out_endpoint_size = |
750 | dev->interrupt_out_endpoint ? le16_to_cpu(dev-> | |
751 | interrupt_out_endpoint-> | |
752 | wMaxPacketSize) : udev-> | |
753 | descriptor.bMaxPacketSize0; | |
8da3dc28 | 754 | |
f6a0ccad DT |
755 | if (dev->interrupt_out_endpoint_size != 64) |
756 | dev_warn(&intf->dev, | |
757 | "Interrupt out endpoint size is not 64!)\n"); | |
8da3dc28 | 758 | |
f6a0ccad DT |
759 | if (write_buffer_size == 0) |
760 | write_buffer_size = WRITE_BUFFER_SIZE; | |
761 | true_size = min(write_buffer_size, WRITE_BUFFER_SIZE); | |
8da3dc28 | 762 | |
f6a0ccad DT |
763 | dev->interrupt_out_buffer = |
764 | kmalloc(true_size * dev->interrupt_out_endpoint_size, GFP_KERNEL); | |
8da3dc28 DT |
765 | |
766 | if (!dev->interrupt_out_buffer) { | |
767 | dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); | |
768 | goto error; | |
769 | } | |
770 | ||
f6a0ccad DT |
771 | dev->write_buffer = |
772 | kmalloc(sizeof(struct alphatrack_ocmd) * true_size, GFP_KERNEL); | |
8da3dc28 DT |
773 | |
774 | if (!dev->write_buffer) { | |
1e1d25cb | 775 | dev_err(&intf->dev, "Couldn't allocate write_buffer\n"); |
8da3dc28 DT |
776 | goto error; |
777 | } | |
778 | ||
779 | dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); | |
780 | if (!dev->interrupt_out_urb) { | |
781 | dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); | |
782 | goto error; | |
783 | } | |
f6a0ccad DT |
784 | dev->interrupt_in_interval = |
785 | min_interrupt_in_interval > | |
786 | dev->interrupt_in_endpoint-> | |
787 | bInterval ? min_interrupt_in_interval : dev->interrupt_in_endpoint-> | |
788 | bInterval; | |
8da3dc28 | 789 | if (dev->interrupt_out_endpoint) |
f6a0ccad DT |
790 | dev->interrupt_out_interval = |
791 | min_interrupt_out_interval > | |
792 | dev->interrupt_out_endpoint-> | |
793 | bInterval ? min_interrupt_out_interval : dev-> | |
794 | interrupt_out_endpoint->bInterval; | |
8da3dc28 DT |
795 | |
796 | /* we can register the device now, as it is ready */ | |
797 | usb_set_intfdata(intf, dev); | |
798 | ||
f6a0ccad | 799 | atomic_set(&dev->writes_pending, 0); |
8da3dc28 DT |
800 | retval = usb_register_dev(intf, &usb_alphatrack_class); |
801 | if (retval) { | |
802 | /* something prevented us from registering this driver */ | |
f6a0ccad DT |
803 | dev_err(&intf->dev, |
804 | "Not able to get a minor for this device.\n"); | |
8da3dc28 DT |
805 | usb_set_intfdata(intf, NULL); |
806 | goto error; | |
807 | } | |
808 | ||
809 | /* let the user know what node this device is now attached to */ | |
f6a0ccad DT |
810 | dev_info(&intf->dev, |
811 | "Alphatrack Device #%d now attached to major %d minor %d\n", | |
812 | (intf->minor - USB_ALPHATRACK_MINOR_BASE), USB_MAJOR, | |
813 | intf->minor); | |
8da3dc28 | 814 | |
8da3dc28 DT |
815 | exit: |
816 | return retval; | |
817 | ||
818 | error: | |
819 | usb_alphatrack_delete(dev); | |
820 | ||
821 | return retval; | |
822 | } | |
823 | ||
824 | /** | |
825 | * usb_alphatrack_disconnect | |
826 | * | |
827 | * Called by the usb core when the device is removed from the system. | |
828 | */ | |
829 | static void usb_alphatrack_disconnect(struct usb_interface *intf) | |
830 | { | |
831 | struct usb_alphatrack *dev; | |
832 | int minor; | |
833 | ||
834 | mutex_lock(&disconnect_mutex); | |
835 | ||
836 | dev = usb_get_intfdata(intf); | |
837 | usb_set_intfdata(intf, NULL); | |
838 | ||
3504e0c8 | 839 | mutex_lock(&dev->mtx); |
8da3dc28 DT |
840 | |
841 | minor = intf->minor; | |
842 | ||
843 | /* give back our minor */ | |
844 | usb_deregister_dev(intf, &usb_alphatrack_class); | |
845 | ||
846 | /* if the device is not opened, then we clean up right now */ | |
847 | if (!dev->open_count) { | |
3504e0c8 | 848 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
849 | usb_alphatrack_delete(dev); |
850 | } else { | |
851 | dev->intf = NULL; | |
3504e0c8 | 852 | mutex_unlock(&dev->mtx); |
8da3dc28 DT |
853 | } |
854 | ||
f6a0ccad | 855 | atomic_set(&dev->writes_pending, 0); |
8da3dc28 DT |
856 | mutex_unlock(&disconnect_mutex); |
857 | ||
858 | dev_info(&intf->dev, "Alphatrack Surface #%d now disconnected\n", | |
859 | (minor - USB_ALPHATRACK_MINOR_BASE)); | |
860 | } | |
861 | ||
862 | /* usb specific object needed to register this driver with the usb subsystem */ | |
863 | static struct usb_driver usb_alphatrack_driver = { | |
f6a0ccad DT |
864 | .name = "alphatrack", |
865 | .probe = usb_alphatrack_probe, | |
866 | .disconnect = usb_alphatrack_disconnect, | |
867 | .id_table = usb_alphatrack_table, | |
8da3dc28 DT |
868 | }; |
869 | ||
870 | /** | |
871 | * usb_alphatrack_init | |
872 | */ | |
873 | static int __init usb_alphatrack_init(void) | |
874 | { | |
875 | int retval; | |
876 | ||
877 | /* register this driver with the USB subsystem */ | |
878 | retval = usb_register(&usb_alphatrack_driver); | |
879 | if (retval) | |
f6a0ccad DT |
880 | err("usb_register failed for the " __FILE__ |
881 | " driver. Error number %d\n", retval); | |
8da3dc28 DT |
882 | |
883 | return retval; | |
884 | } | |
885 | ||
886 | /** | |
887 | * usb_alphatrack_exit | |
888 | */ | |
889 | static void __exit usb_alphatrack_exit(void) | |
890 | { | |
891 | /* deregister this driver with the USB subsystem */ | |
892 | usb_deregister(&usb_alphatrack_driver); | |
893 | } | |
894 | ||
895 | module_init(usb_alphatrack_init); | |
896 | module_exit(usb_alphatrack_exit); |