David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Frontier Designs Tranzport 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 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 21 | /* |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 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 17 commands for the tranzport. 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. |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 33 | */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 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/mutex.h> |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 41 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 42 | #include <linux/uaccess.h> |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 43 | #include <linux/input.h> |
| 44 | #include <linux/usb.h> |
| 45 | #include <linux/poll.h> |
| 46 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 47 | /* Define these values to match your devices */ |
| 48 | #define VENDOR_ID 0x165b |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 49 | #define PRODUCT_ID 0x8101 |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 50 | |
| 51 | #ifdef CONFIG_USB_DYNAMIC_MINORS |
| 52 | #define USB_TRANZPORT_MINOR_BASE 0 |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 53 | #else /* FIXME 177- is the another driver's minor - apply for a minor soon */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 54 | #define USB_TRANZPORT_MINOR_BASE 177 |
| 55 | #endif |
| 56 | |
| 57 | /* table of devices that work with this driver */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 58 | static struct usb_device_id usb_tranzport_table[] = { |
| 59 | {USB_DEVICE(VENDOR_ID, PRODUCT_ID)}, |
| 60 | {} /* Terminating entry */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 61 | }; |
| 62 | |
| 63 | MODULE_DEVICE_TABLE(usb, usb_tranzport_table); |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 64 | MODULE_VERSION("0.35"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 65 | MODULE_AUTHOR("Mike Taht <m@taht.net>"); |
| 66 | MODULE_DESCRIPTION("Tranzport USB Driver"); |
| 67 | MODULE_LICENSE("GPL"); |
| 68 | MODULE_SUPPORTED_DEVICE("Frontier Designs Tranzport Control Surface"); |
| 69 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 70 | #define SUPPRESS_EXTRA_OFFLINE_EVENTS 1 |
| 71 | #define COMPRESS_WHEEL_EVENTS 1 |
| 72 | #define BUFFERED_READS 1 |
| 73 | #define RING_BUFFER_SIZE 1000 |
| 74 | #define WRITE_BUFFER_SIZE 34 |
| 75 | #define TRANZPORT_USB_TIMEOUT 10 |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 76 | #define TRANZPORT_DEBUG 0 |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 77 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 78 | static int debug = TRANZPORT_DEBUG; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 79 | |
| 80 | /* Use our own dbg macro */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 81 | #define dbg_info(dev, format, arg...) do \ |
| 82 | { if (debug) dev_info(dev , format , ## arg); } while (0) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 83 | |
| 84 | /* Module parameters */ |
| 85 | |
| 86 | module_param(debug, int, S_IRUGO | S_IWUSR); |
| 87 | MODULE_PARM_DESC(debug, "Debug enabled or not"); |
| 88 | |
| 89 | /* All interrupt in transfers are collected in a ring buffer to |
| 90 | * avoid racing conditions and get better performance of the driver. |
| 91 | */ |
| 92 | |
| 93 | static int ring_buffer_size = RING_BUFFER_SIZE; |
| 94 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 95 | module_param(ring_buffer_size, int, S_IRUGO); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 96 | MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); |
| 97 | |
| 98 | /* The write_buffer can one day contain more than one interrupt out transfer. |
| 99 | */ |
| 100 | static int write_buffer_size = WRITE_BUFFER_SIZE; |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 101 | module_param(write_buffer_size, int, S_IRUGO); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 102 | MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); |
| 103 | |
| 104 | /* |
| 105 | * Increase the interval for debugging purposes. |
| 106 | * or set to 1 to use the standard interval from the endpoint descriptors. |
| 107 | */ |
| 108 | |
| 109 | static int min_interrupt_in_interval = TRANZPORT_USB_TIMEOUT; |
| 110 | module_param(min_interrupt_in_interval, int, 0); |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 111 | MODULE_PARM_DESC(min_interrupt_in_interval, |
| 112 | "Minimum interrupt in interval in ms"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 113 | |
| 114 | static int min_interrupt_out_interval = TRANZPORT_USB_TIMEOUT; |
| 115 | module_param(min_interrupt_out_interval, int, 0); |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 116 | MODULE_PARM_DESC(min_interrupt_out_interval, |
| 117 | "Minimum interrupt out interval in ms"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 118 | |
| 119 | struct tranzport_cmd { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 120 | unsigned char cmd[8]; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 121 | }; |
| 122 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 123 | /* Structure to hold all of our device specific stuff */ |
| 124 | |
| 125 | struct usb_tranzport { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 126 | struct semaphore sem; /* locks this structure */ |
| 127 | struct usb_interface *intf; /* save off the usb interface pointer */ |
| 128 | int open_count; /* number of times this port opened */ |
| 129 | struct tranzport_cmd (*ring_buffer)[RING_BUFFER_SIZE]; |
| 130 | unsigned int ring_head; |
| 131 | unsigned int ring_tail; |
| 132 | wait_queue_head_t read_wait; |
| 133 | wait_queue_head_t write_wait; |
| 134 | unsigned char *interrupt_in_buffer; |
| 135 | struct usb_endpoint_descriptor *interrupt_in_endpoint; |
| 136 | struct urb *interrupt_in_urb; |
| 137 | int interrupt_in_interval; |
| 138 | size_t interrupt_in_endpoint_size; |
| 139 | int interrupt_in_running; |
| 140 | int interrupt_in_done; |
| 141 | char *interrupt_out_buffer; |
| 142 | struct usb_endpoint_descriptor *interrupt_out_endpoint; |
| 143 | struct urb *interrupt_out_urb; |
| 144 | int interrupt_out_interval; |
| 145 | size_t interrupt_out_endpoint_size; |
| 146 | int interrupt_out_busy; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 147 | |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 148 | /* Sysfs support */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 149 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 150 | unsigned char enable; /* 0 if disabled 1 if enabled */ |
| 151 | unsigned char offline; /* if the device is out of range or asleep */ |
| 152 | unsigned char compress_wheel; /* flag to compress wheel events */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 153 | }; |
| 154 | |
| 155 | /* prevent races between open() and disconnect() */ |
| 156 | static DEFINE_MUTEX(disconnect_mutex); |
| 157 | |
| 158 | static struct usb_driver usb_tranzport_driver; |
| 159 | |
| 160 | /** |
| 161 | * usb_tranzport_abort_transfers |
| 162 | * aborts transfers and frees associated data structures |
| 163 | */ |
| 164 | static void usb_tranzport_abort_transfers(struct usb_tranzport *dev) |
| 165 | { |
| 166 | /* shutdown transfer */ |
| 167 | if (dev->interrupt_in_running) { |
| 168 | dev->interrupt_in_running = 0; |
| 169 | if (dev->intf) |
| 170 | usb_kill_urb(dev->interrupt_in_urb); |
| 171 | } |
| 172 | if (dev->interrupt_out_busy) |
| 173 | if (dev->intf) |
| 174 | usb_kill_urb(dev->interrupt_out_urb); |
| 175 | } |
| 176 | |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 177 | #define show_int(value) \ |
| 178 | static ssize_t show_##value(struct device *dev, \ |
| 179 | struct device_attribute *attr, char *buf) \ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 180 | { \ |
| 181 | struct usb_interface *intf = to_usb_interface(dev); \ |
| 182 | struct usb_tranzport *t = usb_get_intfdata(intf); \ |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 183 | return sprintf(buf, "%d\n", t->value); \ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 184 | } \ |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 185 | static DEVICE_ATTR(value, S_IRUGO, show_##value, NULL); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 186 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 187 | #define show_set_int(value) \ |
| 188 | static ssize_t show_##value(struct device *dev, \ |
| 189 | struct device_attribute *attr, char *buf) \ |
| 190 | { \ |
| 191 | struct usb_interface *intf = to_usb_interface(dev); \ |
| 192 | struct usb_tranzport *t = usb_get_intfdata(intf); \ |
| 193 | return sprintf(buf, "%d\n", t->value); \ |
| 194 | } \ |
| 195 | static ssize_t set_##value(struct device *dev, \ |
| 196 | struct device_attribute *attr, \ |
| 197 | const char *buf, size_t count) \ |
| 198 | { \ |
| 199 | struct usb_interface *intf = to_usb_interface(dev); \ |
| 200 | struct usb_tranzport *t = usb_get_intfdata(intf); \ |
| 201 | int temp = simple_strtoul(buf, NULL, 10); \ |
| 202 | t->value = temp; \ |
| 203 | return count; \ |
| 204 | } \ |
| 205 | static DEVICE_ATTR(value, S_IWUGO | S_IRUGO, show_##value, set_##value); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 206 | |
David Täht | dab8c35 | 2009-01-20 08:33:25 -0600 | [diff] [blame] | 207 | show_int(enable); |
| 208 | show_int(offline); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 209 | show_set_int(compress_wheel); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 210 | |
| 211 | /** |
| 212 | * usb_tranzport_delete |
| 213 | */ |
| 214 | static void usb_tranzport_delete(struct usb_tranzport *dev) |
| 215 | { |
| 216 | usb_tranzport_abort_transfers(dev); |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 217 | if (dev->intf != NULL) { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 218 | device_remove_file(&dev->intf->dev, &dev_attr_enable); |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 219 | device_remove_file(&dev->intf->dev, &dev_attr_offline); |
| 220 | device_remove_file(&dev->intf->dev, &dev_attr_compress_wheel); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | /* free data structures */ |
| 224 | usb_free_urb(dev->interrupt_in_urb); |
| 225 | usb_free_urb(dev->interrupt_out_urb); |
| 226 | kfree(dev->ring_buffer); |
| 227 | kfree(dev->interrupt_in_buffer); |
| 228 | kfree(dev->interrupt_out_buffer); |
| 229 | kfree(dev); |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * usb_tranzport_interrupt_in_callback |
| 234 | */ |
| 235 | |
| 236 | static void usb_tranzport_interrupt_in_callback(struct urb *urb) |
| 237 | { |
| 238 | struct usb_tranzport *dev = urb->context; |
| 239 | unsigned int next_ring_head; |
| 240 | int retval = -1; |
| 241 | |
| 242 | if (urb->status) { |
| 243 | if (urb->status == -ENOENT || |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 244 | urb->status == -ECONNRESET || |
| 245 | urb->status == -ESHUTDOWN) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 246 | goto exit; |
| 247 | } else { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 248 | dbg_info(&dev->intf->dev, |
| 249 | "%s: nonzero status received: %d\n", |
Harvey Harrison | d599edc | 2009-01-07 14:31:57 -0800 | [diff] [blame] | 250 | __func__, urb->status); |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 251 | goto resubmit; /* maybe we can recover */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 252 | } |
| 253 | } |
| 254 | |
| 255 | if (urb->actual_length != 8) { |
| 256 | dev_warn(&dev->intf->dev, |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 257 | "Urb length was %d bytes!!" |
| 258 | "Do something intelligent \n", |
| 259 | urb->actual_length); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 260 | } else { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 261 | dbg_info(&dev->intf->dev, |
| 262 | "%s: received: %02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 263 | __func__, dev->interrupt_in_buffer[0], |
| 264 | dev->interrupt_in_buffer[1], |
| 265 | dev->interrupt_in_buffer[2], |
| 266 | dev->interrupt_in_buffer[3], |
| 267 | dev->interrupt_in_buffer[4], |
| 268 | dev->interrupt_in_buffer[5], |
| 269 | dev->interrupt_in_buffer[6], |
| 270 | dev->interrupt_in_buffer[7]); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 271 | #if SUPPRESS_EXTRA_OFFLINE_EVENTS |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 272 | if (dev->offline == 2 && dev->interrupt_in_buffer[1] == 0xff) |
| 273 | goto resubmit; |
| 274 | if (dev->offline == 1 && dev->interrupt_in_buffer[1] == 0xff) { |
| 275 | dev->offline = 2; |
| 276 | goto resubmit; |
| 277 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 278 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 279 | /* Always pass one offline event up the stack */ |
| 280 | if (dev->offline > 0 && dev->interrupt_in_buffer[1] != 0xff) |
| 281 | dev->offline = 0; |
| 282 | if (dev->offline == 0 && dev->interrupt_in_buffer[1] == 0xff) |
| 283 | dev->offline = 1; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 284 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 285 | #endif /* SUPPRESS_EXTRA_OFFLINE_EVENTS */ |
| 286 | dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", |
| 287 | __func__, dev->ring_head, dev->ring_tail); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 288 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 289 | next_ring_head = (dev->ring_head + 1) % ring_buffer_size; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 290 | |
| 291 | if (next_ring_head != dev->ring_tail) { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 292 | memcpy(&((*dev->ring_buffer)[dev->ring_head]), |
| 293 | dev->interrupt_in_buffer, urb->actual_length); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 294 | dev->ring_head = next_ring_head; |
| 295 | retval = 0; |
| 296 | memset(dev->interrupt_in_buffer, 0, urb->actual_length); |
| 297 | } else { |
| 298 | dev_warn(&dev->intf->dev, |
| 299 | "Ring buffer overflow, %d bytes dropped\n", |
| 300 | urb->actual_length); |
| 301 | memset(dev->interrupt_in_buffer, 0, urb->actual_length); |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | resubmit: |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 306 | /* resubmit if we're still running */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 307 | if (dev->interrupt_in_running && dev->intf) { |
| 308 | retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); |
| 309 | if (retval) |
| 310 | dev_err(&dev->intf->dev, |
| 311 | "usb_submit_urb failed (%d)\n", retval); |
| 312 | } |
| 313 | |
| 314 | exit: |
| 315 | dev->interrupt_in_done = 1; |
| 316 | wake_up_interruptible(&dev->read_wait); |
| 317 | } |
| 318 | |
| 319 | /** |
| 320 | * usb_tranzport_interrupt_out_callback |
| 321 | */ |
| 322 | static void usb_tranzport_interrupt_out_callback(struct urb *urb) |
| 323 | { |
| 324 | struct usb_tranzport *dev = urb->context; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 325 | /* sync/async unlink faults aren't errors */ |
| 326 | if (urb->status && !(urb->status == -ENOENT || |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 327 | urb->status == -ECONNRESET || |
| 328 | urb->status == -ESHUTDOWN)) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 329 | dbg_info(&dev->intf->dev, |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 330 | "%s - nonzero write interrupt status received: %d\n", |
| 331 | __func__, urb->status); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 332 | |
| 333 | dev->interrupt_out_busy = 0; |
| 334 | wake_up_interruptible(&dev->write_wait); |
| 335 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 336 | /** |
| 337 | * usb_tranzport_open |
| 338 | */ |
| 339 | static int usb_tranzport_open(struct inode *inode, struct file *file) |
| 340 | { |
| 341 | struct usb_tranzport *dev; |
| 342 | int subminor; |
| 343 | int retval = 0; |
| 344 | struct usb_interface *interface; |
| 345 | |
| 346 | nonseekable_open(inode, file); |
| 347 | subminor = iminor(inode); |
| 348 | |
| 349 | mutex_lock(&disconnect_mutex); |
| 350 | |
| 351 | interface = usb_find_interface(&usb_tranzport_driver, subminor); |
| 352 | |
| 353 | if (!interface) { |
| 354 | err("%s - error, can't find device for minor %d\n", |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 355 | __func__, subminor); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 356 | retval = -ENODEV; |
| 357 | goto unlock_disconnect_exit; |
| 358 | } |
| 359 | |
| 360 | dev = usb_get_intfdata(interface); |
| 361 | |
| 362 | if (!dev) { |
| 363 | retval = -ENODEV; |
| 364 | goto unlock_disconnect_exit; |
| 365 | } |
| 366 | |
| 367 | /* lock this device */ |
| 368 | if (down_interruptible(&dev->sem)) { |
| 369 | retval = -ERESTARTSYS; |
| 370 | goto unlock_disconnect_exit; |
| 371 | } |
| 372 | |
| 373 | /* allow opening only once */ |
| 374 | if (dev->open_count) { |
| 375 | retval = -EBUSY; |
| 376 | goto unlock_exit; |
| 377 | } |
| 378 | dev->open_count = 1; |
| 379 | |
| 380 | /* initialize in direction */ |
| 381 | dev->ring_head = 0; |
| 382 | dev->ring_tail = 0; |
| 383 | usb_fill_int_urb(dev->interrupt_in_urb, |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 384 | interface_to_usbdev(interface), |
| 385 | usb_rcvintpipe(interface_to_usbdev(interface), |
| 386 | dev->interrupt_in_endpoint-> |
| 387 | bEndpointAddress), |
| 388 | dev->interrupt_in_buffer, |
| 389 | dev->interrupt_in_endpoint_size, |
| 390 | usb_tranzport_interrupt_in_callback, dev, |
| 391 | dev->interrupt_in_interval); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 392 | |
| 393 | dev->interrupt_in_running = 1; |
| 394 | dev->interrupt_in_done = 0; |
| 395 | dev->enable = 1; |
| 396 | dev->offline = 0; |
| 397 | dev->compress_wheel = 1; |
| 398 | |
| 399 | retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); |
| 400 | if (retval) { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 401 | dev_err(&interface->dev, |
| 402 | "Couldn't submit interrupt_in_urb %d\n", retval); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 403 | dev->interrupt_in_running = 0; |
| 404 | dev->open_count = 0; |
| 405 | goto unlock_exit; |
| 406 | } |
| 407 | |
| 408 | /* save device in the file's private structure */ |
| 409 | file->private_data = dev; |
| 410 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 411 | unlock_exit: |
| 412 | up(&dev->sem); |
| 413 | |
| 414 | unlock_disconnect_exit: |
| 415 | mutex_unlock(&disconnect_mutex); |
| 416 | |
| 417 | return retval; |
| 418 | } |
| 419 | |
| 420 | /** |
| 421 | * usb_tranzport_release |
| 422 | */ |
| 423 | static int usb_tranzport_release(struct inode *inode, struct file *file) |
| 424 | { |
| 425 | struct usb_tranzport *dev; |
| 426 | int retval = 0; |
| 427 | |
| 428 | dev = file->private_data; |
| 429 | |
| 430 | if (dev == NULL) { |
| 431 | retval = -ENODEV; |
| 432 | goto exit; |
| 433 | } |
| 434 | |
| 435 | if (down_interruptible(&dev->sem)) { |
| 436 | retval = -ERESTARTSYS; |
| 437 | goto exit; |
| 438 | } |
| 439 | |
| 440 | if (dev->open_count != 1) { |
| 441 | retval = -ENODEV; |
| 442 | goto unlock_exit; |
| 443 | } |
| 444 | |
| 445 | if (dev->intf == NULL) { |
| 446 | /* the device was unplugged before the file was released */ |
| 447 | up(&dev->sem); |
| 448 | /* unlock here as usb_tranzport_delete frees dev */ |
| 449 | usb_tranzport_delete(dev); |
| 450 | retval = -ENODEV; |
| 451 | goto exit; |
| 452 | } |
| 453 | |
| 454 | /* wait until write transfer is finished */ |
| 455 | if (dev->interrupt_out_busy) |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 456 | wait_event_interruptible_timeout(dev->write_wait, |
| 457 | !dev->interrupt_out_busy, |
| 458 | 2 * HZ); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 459 | usb_tranzport_abort_transfers(dev); |
| 460 | dev->open_count = 0; |
| 461 | |
| 462 | unlock_exit: |
| 463 | up(&dev->sem); |
| 464 | |
| 465 | exit: |
| 466 | return retval; |
| 467 | } |
| 468 | |
| 469 | /** |
| 470 | * usb_tranzport_poll |
| 471 | */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 472 | static unsigned int usb_tranzport_poll(struct file *file, poll_table * wait) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 473 | { |
| 474 | struct usb_tranzport *dev; |
| 475 | unsigned int mask = 0; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 476 | dev = file->private_data; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 477 | poll_wait(file, &dev->read_wait, wait); |
| 478 | poll_wait(file, &dev->write_wait, wait); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 479 | if (dev->ring_head != dev->ring_tail) |
| 480 | mask |= POLLIN | POLLRDNORM; |
| 481 | if (!dev->interrupt_out_busy) |
| 482 | mask |= POLLOUT | POLLWRNORM; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 483 | return mask; |
| 484 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 485 | /** |
| 486 | * usb_tranzport_read |
| 487 | */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 488 | |
| 489 | static ssize_t usb_tranzport_read(struct file *file, char __user *buffer, |
| 490 | size_t count, loff_t *ppos) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 491 | { |
| 492 | struct usb_tranzport *dev; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 493 | int retval = 0; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 494 | #if BUFFERED_READS |
| 495 | int c = 0; |
| 496 | #endif |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 497 | #if COMPRESS_WHEEL_EVENTS |
| 498 | signed char oldwheel; |
| 499 | signed char newwheel; |
| 500 | int cancompress = 1; |
| 501 | int next_tail; |
| 502 | #endif |
| 503 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 504 | /* do I have such a thing as a null event? */ |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 505 | |
| 506 | dev = file->private_data; |
| 507 | |
| 508 | /* verify that we actually have some data to read */ |
| 509 | if (count == 0) |
| 510 | goto exit; |
| 511 | |
| 512 | /* lock this object */ |
| 513 | if (down_interruptible(&dev->sem)) { |
| 514 | retval = -ERESTARTSYS; |
| 515 | goto exit; |
| 516 | } |
| 517 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 518 | /* verify that the device wasn't unplugged */ if (dev->intf == NULL) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 519 | retval = -ENODEV; |
| 520 | err("No device or device unplugged %d\n", retval); |
| 521 | goto unlock_exit; |
| 522 | } |
| 523 | |
| 524 | while (dev->ring_head == dev->ring_tail) { |
| 525 | |
| 526 | if (file->f_flags & O_NONBLOCK) { |
| 527 | retval = -EAGAIN; |
| 528 | goto unlock_exit; |
| 529 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 530 | /* tiny race - FIXME: make atomic? */ |
| 531 | /* atomic_cmp_exchange(&dev->interrupt_in_done,0,0); */ |
| 532 | dev->interrupt_in_done = 0; |
| 533 | retval = wait_event_interruptible(dev->read_wait, |
| 534 | dev->interrupt_in_done); |
| 535 | if (retval < 0) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 536 | goto unlock_exit; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 537 | } |
| 538 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 539 | dbg_info(&dev->intf->dev, |
| 540 | "%s: copying to userspace: " |
| 541 | "%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 542 | __func__, |
| 543 | (*dev->ring_buffer)[dev->ring_tail].cmd[0], |
| 544 | (*dev->ring_buffer)[dev->ring_tail].cmd[1], |
| 545 | (*dev->ring_buffer)[dev->ring_tail].cmd[2], |
| 546 | (*dev->ring_buffer)[dev->ring_tail].cmd[3], |
| 547 | (*dev->ring_buffer)[dev->ring_tail].cmd[4], |
| 548 | (*dev->ring_buffer)[dev->ring_tail].cmd[5], |
| 549 | (*dev->ring_buffer)[dev->ring_tail].cmd[6], |
| 550 | (*dev->ring_buffer)[dev->ring_tail].cmd[7]); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 551 | |
| 552 | #if BUFFERED_READS |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 553 | c = 0; |
| 554 | while ((c < count) && (dev->ring_tail != dev->ring_head)) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 555 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 556 | #if COMPRESS_WHEEL_EVENTS |
| 557 | next_tail = (dev->ring_tail+1) % ring_buffer_size; |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 558 | if (dev->compress_wheel) |
| 559 | cancompress = 1; |
| 560 | while (dev->ring_head != next_tail && cancompress == 1) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 561 | newwheel = (*dev->ring_buffer)[next_tail].cmd[6]; |
| 562 | oldwheel = (*dev->ring_buffer)[dev->ring_tail].cmd[6]; |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 563 | /* if both are wheel events, and |
| 564 | no buttons have changes (FIXME, do I have to check?), |
| 565 | and we are the same sign, we can compress +- 7F |
| 566 | */ |
| 567 | dbg_info(&dev->intf->dev, |
| 568 | "%s: trying to compress: " |
| 569 | "%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 570 | __func__, |
| 571 | (*dev->ring_buffer)[dev->ring_tail].cmd[0], |
| 572 | (*dev->ring_buffer)[dev->ring_tail].cmd[1], |
| 573 | (*dev->ring_buffer)[dev->ring_tail].cmd[2], |
| 574 | (*dev->ring_buffer)[dev->ring_tail].cmd[3], |
| 575 | (*dev->ring_buffer)[dev->ring_tail].cmd[4], |
| 576 | (*dev->ring_buffer)[dev->ring_tail].cmd[5], |
| 577 | (*dev->ring_buffer)[dev->ring_tail].cmd[6], |
| 578 | (*dev->ring_buffer)[dev->ring_tail].cmd[7]); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 579 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 580 | if (((*dev->ring_buffer)[dev->ring_tail].cmd[6] != 0 && |
| 581 | (*dev->ring_buffer)[next_tail].cmd[6] != 0) && |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 582 | ((newwheel > 0 && oldwheel > 0) || |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 583 | (newwheel < 0 && oldwheel < 0)) && |
| 584 | ((*dev->ring_buffer)[dev->ring_tail].cmd[2] == |
| 585 | (*dev->ring_buffer)[next_tail].cmd[2]) && |
| 586 | ((*dev->ring_buffer)[dev->ring_tail].cmd[3] == |
| 587 | (*dev->ring_buffer)[next_tail].cmd[3]) && |
| 588 | ((*dev->ring_buffer)[dev->ring_tail].cmd[4] == |
| 589 | (*dev->ring_buffer)[next_tail].cmd[4]) && |
| 590 | ((*dev->ring_buffer)[dev->ring_tail].cmd[5] == |
| 591 | (*dev->ring_buffer)[next_tail].cmd[5])) { |
| 592 | dbg_info(&dev->intf->dev, |
| 593 | "%s: should compress: " |
| 594 | "%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 595 | __func__, |
| 596 | (*dev->ring_buffer)[dev->ring_tail]. |
| 597 | cmd[0], |
| 598 | (*dev->ring_buffer)[dev->ring_tail]. |
| 599 | cmd[1], |
| 600 | (*dev->ring_buffer)[dev->ring_tail]. |
| 601 | cmd[2], |
| 602 | (*dev->ring_buffer)[dev->ring_tail]. |
| 603 | cmd[3], |
| 604 | (*dev->ring_buffer)[dev->ring_tail]. |
| 605 | cmd[4], |
| 606 | (*dev->ring_buffer)[dev->ring_tail]. |
| 607 | cmd[5], |
| 608 | (*dev->ring_buffer)[dev->ring_tail]. |
| 609 | cmd[6], |
| 610 | (*dev->ring_buffer)[dev->ring_tail]. |
| 611 | cmd[7]); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 612 | newwheel += oldwheel; |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 613 | if (oldwheel > 0 && !(newwheel > 0)) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 614 | newwheel = 0x7f; |
| 615 | cancompress = 0; |
| 616 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 617 | if (oldwheel < 0 && !(newwheel < 0)) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 618 | newwheel = 0x80; |
| 619 | cancompress = 0; |
| 620 | } |
| 621 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 622 | (*dev->ring_buffer)[next_tail].cmd[6] = |
| 623 | newwheel; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 624 | dev->ring_tail = next_tail; |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 625 | next_tail = |
| 626 | (dev->ring_tail + 1) % ring_buffer_size; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 627 | } else { |
| 628 | cancompress = 0; |
| 629 | } |
| 630 | } |
| 631 | #endif /* COMPRESS_WHEEL_EVENTS */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 632 | if (copy_to_user( |
| 633 | &buffer[c], |
| 634 | &(*dev->ring_buffer)[dev->ring_tail], 8)) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 635 | retval = -EFAULT; |
| 636 | goto unlock_exit; |
| 637 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 638 | dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; |
| 639 | c += 8; |
| 640 | dbg_info(&dev->intf->dev, |
| 641 | "%s: head, tail are %x, %x\n", |
| 642 | __func__, dev->ring_head, dev->ring_tail); |
| 643 | } |
| 644 | retval = c; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 645 | |
| 646 | #else |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 647 | /* if (copy_to_user(buffer, &(*dev->ring_buffer)[dev->ring_tail], 8)) { */ |
| 648 | retval = -EFAULT; |
| 649 | goto unlock_exit; |
| 650 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 651 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 652 | dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; |
| 653 | dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", |
| 654 | __func__, dev->ring_head, dev->ring_tail); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 655 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 656 | retval = 8; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 657 | #endif /* BUFFERED_READS */ |
| 658 | |
| 659 | unlock_exit: |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 660 | /* unlock the device */ |
| 661 | up(&dev->sem); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 662 | |
| 663 | exit: |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 664 | return retval; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 665 | } |
| 666 | |
| 667 | /** |
| 668 | * usb_tranzport_write |
| 669 | */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 670 | static ssize_t usb_tranzport_write(struct file *file, |
| 671 | const char __user *buffer, size_t count, |
| 672 | loff_t *ppos) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 673 | { |
| 674 | struct usb_tranzport *dev; |
| 675 | size_t bytes_to_write; |
| 676 | int retval = 0; |
| 677 | |
| 678 | dev = file->private_data; |
| 679 | |
| 680 | /* verify that we actually have some data to write */ |
| 681 | if (count == 0) |
| 682 | goto exit; |
| 683 | |
| 684 | /* lock this object */ |
| 685 | if (down_interruptible(&dev->sem)) { |
| 686 | retval = -ERESTARTSYS; |
| 687 | goto exit; |
| 688 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 689 | /* verify that the device wasn't unplugged */ |
| 690 | if (dev->intf == NULL) { |
| 691 | retval = -ENODEV; |
| 692 | err("No device or device unplugged %d\n", retval); |
| 693 | goto unlock_exit; |
| 694 | } |
| 695 | |
| 696 | /* wait until previous transfer is finished */ |
| 697 | if (dev->interrupt_out_busy) { |
| 698 | if (file->f_flags & O_NONBLOCK) { |
| 699 | retval = -EAGAIN; |
| 700 | goto unlock_exit; |
| 701 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 702 | retval = wait_event_interruptible(dev->write_wait, |
| 703 | !dev->interrupt_out_busy); |
| 704 | if (retval < 0) |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 705 | goto unlock_exit; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 706 | } |
| 707 | |
| 708 | /* write the data into interrupt_out_buffer from userspace */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 709 | bytes_to_write = min(count, |
| 710 | write_buffer_size * |
| 711 | dev->interrupt_out_endpoint_size); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 712 | if (bytes_to_write < count) |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 713 | dev_warn(&dev->intf->dev, |
| 714 | "Write buffer overflow, %zd bytes dropped\n", |
| 715 | count - bytes_to_write); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 716 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 717 | dbg_info(&dev->intf->dev, |
| 718 | "%s: count = %zd, bytes_to_write = %zd\n", __func__, |
| 719 | count, bytes_to_write); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 720 | |
| 721 | if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { |
| 722 | retval = -EFAULT; |
| 723 | goto unlock_exit; |
| 724 | } |
| 725 | |
| 726 | if (dev->interrupt_out_endpoint == NULL) { |
| 727 | err("Endpoint should not be be null! \n"); |
| 728 | goto unlock_exit; |
| 729 | } |
| 730 | |
| 731 | /* send off the urb */ |
| 732 | usb_fill_int_urb(dev->interrupt_out_urb, |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 733 | interface_to_usbdev(dev->intf), |
| 734 | usb_sndintpipe(interface_to_usbdev(dev->intf), |
| 735 | dev->interrupt_out_endpoint-> |
| 736 | bEndpointAddress), |
| 737 | dev->interrupt_out_buffer, bytes_to_write, |
| 738 | usb_tranzport_interrupt_out_callback, dev, |
| 739 | dev->interrupt_out_interval); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 740 | |
| 741 | dev->interrupt_out_busy = 1; |
| 742 | wmb(); |
| 743 | |
| 744 | retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); |
| 745 | if (retval) { |
| 746 | dev->interrupt_out_busy = 0; |
| 747 | err("Couldn't submit interrupt_out_urb %d\n", retval); |
| 748 | goto unlock_exit; |
| 749 | } |
| 750 | retval = bytes_to_write; |
| 751 | |
| 752 | unlock_exit: |
| 753 | /* unlock the device */ |
| 754 | up(&dev->sem); |
| 755 | |
| 756 | exit: |
| 757 | return retval; |
| 758 | } |
| 759 | |
| 760 | /* file operations needed when we register this driver */ |
| 761 | static const struct file_operations usb_tranzport_fops = { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 762 | .owner = THIS_MODULE, |
| 763 | .read = usb_tranzport_read, |
| 764 | .write = usb_tranzport_write, |
| 765 | .open = usb_tranzport_open, |
| 766 | .release = usb_tranzport_release, |
| 767 | .poll = usb_tranzport_poll, |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 768 | }; |
| 769 | |
| 770 | /* |
| 771 | * usb class driver info in order to get a minor number from the usb core, |
| 772 | * and to have the device registered with the driver core |
| 773 | */ |
| 774 | static struct usb_class_driver usb_tranzport_class = { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 775 | .name = "tranzport%d", |
| 776 | .fops = &usb_tranzport_fops, |
| 777 | .minor_base = USB_TRANZPORT_MINOR_BASE, |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 778 | }; |
| 779 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 780 | /** |
| 781 | * usb_tranzport_probe |
| 782 | * |
| 783 | * Called by the usb core when a new device is connected that it thinks |
| 784 | * this driver might be interested in. |
| 785 | */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 786 | static int usb_tranzport_probe(struct usb_interface *intf, |
| 787 | const struct usb_device_id *id) { |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 788 | struct usb_device *udev = interface_to_usbdev(intf); |
| 789 | struct usb_tranzport *dev = NULL; |
| 790 | struct usb_host_interface *iface_desc; |
| 791 | struct usb_endpoint_descriptor *endpoint; |
| 792 | int i; |
| 793 | int true_size; |
| 794 | int retval = -ENOMEM; |
| 795 | |
| 796 | /* allocate memory for our device state and intialize it */ |
| 797 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 798 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 799 | if (dev == NULL) { |
| 800 | dev_err(&intf->dev, "Out of memory\n"); |
| 801 | goto exit; |
| 802 | } |
| 803 | init_MUTEX(&dev->sem); |
| 804 | dev->intf = intf; |
| 805 | init_waitqueue_head(&dev->read_wait); |
| 806 | init_waitqueue_head(&dev->write_wait); |
| 807 | |
| 808 | iface_desc = intf->cur_altsetting; |
| 809 | |
| 810 | /* set up the endpoint information */ |
| 811 | for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { |
| 812 | endpoint = &iface_desc->endpoint[i].desc; |
| 813 | |
| 814 | if (usb_endpoint_is_int_in(endpoint)) |
| 815 | dev->interrupt_in_endpoint = endpoint; |
| 816 | |
| 817 | if (usb_endpoint_is_int_out(endpoint)) |
| 818 | dev->interrupt_out_endpoint = endpoint; |
| 819 | } |
| 820 | if (dev->interrupt_in_endpoint == NULL) { |
| 821 | dev_err(&intf->dev, "Interrupt in endpoint not found\n"); |
| 822 | goto error; |
| 823 | } |
| 824 | if (dev->interrupt_out_endpoint == NULL) |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 825 | dev_warn(&intf->dev, |
| 826 | "Interrupt out endpoint not found" |
| 827 | "(using control endpoint instead)\n"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 828 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 829 | dev->interrupt_in_endpoint_size = |
| 830 | le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 831 | |
| 832 | if (dev->interrupt_in_endpoint_size != 8) |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 833 | dev_warn(&intf->dev, "Interrupt in endpoint size is not 8!\n"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 834 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 835 | if (ring_buffer_size == 0) |
| 836 | ring_buffer_size = RING_BUFFER_SIZE; |
| 837 | true_size = min(ring_buffer_size, RING_BUFFER_SIZE); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 838 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 839 | /* FIXME - there are more usb_alloc routines for dma correctness. |
| 840 | Needed? */ |
| 841 | |
| 842 | dev->ring_buffer = |
| 843 | kmalloc((true_size * sizeof(struct tranzport_cmd)) + 8, GFP_KERNEL); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 844 | |
| 845 | if (!dev->ring_buffer) { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 846 | dev_err(&intf->dev, |
| 847 | "Couldn't allocate ring_buffer size %d\n", true_size); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 848 | goto error; |
| 849 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 850 | dev->interrupt_in_buffer = |
| 851 | kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 852 | if (!dev->interrupt_in_buffer) { |
| 853 | dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); |
| 854 | goto error; |
| 855 | } |
| 856 | dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); |
| 857 | if (!dev->interrupt_in_urb) { |
| 858 | dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); |
| 859 | goto error; |
| 860 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 861 | dev->interrupt_out_endpoint_size = |
| 862 | dev->interrupt_out_endpoint ? |
| 863 | le16_to_cpu(dev->interrupt_out_endpoint->wMaxPacketSize) : |
| 864 | udev->descriptor.bMaxPacketSize0; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 865 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 866 | if (dev->interrupt_out_endpoint_size != 8) |
| 867 | dev_warn(&intf->dev, |
| 868 | "Interrupt out endpoint size is not 8!)\n"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 869 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 870 | dev->interrupt_out_buffer = |
| 871 | kmalloc(write_buffer_size * dev->interrupt_out_endpoint_size, |
| 872 | GFP_KERNEL); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 873 | if (!dev->interrupt_out_buffer) { |
| 874 | dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); |
| 875 | goto error; |
| 876 | } |
| 877 | dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); |
| 878 | if (!dev->interrupt_out_urb) { |
| 879 | dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); |
| 880 | goto error; |
| 881 | } |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 882 | dev->interrupt_in_interval = |
| 883 | min_interrupt_in_interval > |
| 884 | dev->interrupt_in_endpoint->bInterval ? min_interrupt_in_interval |
| 885 | : dev->interrupt_in_endpoint->bInterval; |
| 886 | |
| 887 | if (dev->interrupt_out_endpoint) { |
| 888 | dev->interrupt_out_interval = |
| 889 | min_interrupt_out_interval > |
| 890 | dev->interrupt_out_endpoint->bInterval ? |
| 891 | min_interrupt_out_interval : |
| 892 | dev->interrupt_out_endpoint->bInterval; |
| 893 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 894 | |
| 895 | /* we can register the device now, as it is ready */ |
| 896 | usb_set_intfdata(intf, dev); |
| 897 | |
| 898 | retval = usb_register_dev(intf, &usb_tranzport_class); |
| 899 | if (retval) { |
| 900 | /* something prevented us from registering this driver */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 901 | dev_err(&intf->dev, |
| 902 | "Not able to get a minor for this device.\n"); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 903 | usb_set_intfdata(intf, NULL); |
| 904 | goto error; |
| 905 | } |
| 906 | |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 907 | retval = device_create_file(&intf->dev, &dev_attr_compress_wheel); |
| 908 | if (retval) |
| 909 | goto error; |
| 910 | retval = device_create_file(&intf->dev, &dev_attr_enable); |
| 911 | if (retval) |
| 912 | goto error; |
| 913 | retval = device_create_file(&intf->dev, &dev_attr_offline); |
| 914 | if (retval) |
| 915 | goto error; |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 916 | |
| 917 | /* let the user know what node this device is now attached to */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 918 | dev_info(&intf->dev, |
| 919 | "Tranzport Device #%d now attached to major %d minor %d\n", |
| 920 | (intf->minor - USB_TRANZPORT_MINOR_BASE), USB_MAJOR, |
| 921 | intf->minor); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 922 | |
| 923 | exit: |
| 924 | return retval; |
| 925 | |
| 926 | error: |
| 927 | usb_tranzport_delete(dev); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 928 | return retval; |
| 929 | } |
| 930 | |
| 931 | /** |
| 932 | * usb_tranzport_disconnect |
| 933 | * |
| 934 | * Called by the usb core when the device is removed from the system. |
| 935 | */ |
| 936 | static void usb_tranzport_disconnect(struct usb_interface *intf) |
| 937 | { |
| 938 | struct usb_tranzport *dev; |
| 939 | int minor; |
| 940 | mutex_lock(&disconnect_mutex); |
| 941 | dev = usb_get_intfdata(intf); |
| 942 | usb_set_intfdata(intf, NULL); |
| 943 | down(&dev->sem); |
| 944 | minor = intf->minor; |
| 945 | /* give back our minor */ |
| 946 | usb_deregister_dev(intf, &usb_tranzport_class); |
| 947 | |
| 948 | /* if the device is not opened, then we clean up right now */ |
| 949 | if (!dev->open_count) { |
| 950 | up(&dev->sem); |
| 951 | usb_tranzport_delete(dev); |
| 952 | } else { |
| 953 | dev->intf = NULL; |
| 954 | up(&dev->sem); |
| 955 | } |
| 956 | |
| 957 | mutex_unlock(&disconnect_mutex); |
| 958 | |
| 959 | dev_info(&intf->dev, "Tranzport Surface #%d now disconnected\n", |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 960 | (minor - USB_TRANZPORT_MINOR_BASE)); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 961 | } |
| 962 | |
| 963 | /* usb specific object needed to register this driver with the usb subsystem */ |
| 964 | static struct usb_driver usb_tranzport_driver = { |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 965 | .name = "tranzport", |
| 966 | .probe = usb_tranzport_probe, |
| 967 | .disconnect = usb_tranzport_disconnect, |
| 968 | .id_table = usb_tranzport_table, |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 969 | }; |
| 970 | |
| 971 | /** |
| 972 | * usb_tranzport_init |
| 973 | */ |
| 974 | static int __init usb_tranzport_init(void) |
| 975 | { |
| 976 | int retval; |
| 977 | |
| 978 | /* register this driver with the USB subsystem */ |
| 979 | retval = usb_register(&usb_tranzport_driver); |
| 980 | if (retval) |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 981 | err("usb_register failed for the " __FILE__ |
| 982 | " driver. Error number %d\n", retval); |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 983 | return retval; |
| 984 | } |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 985 | /** |
| 986 | * usb_tranzport_exit |
| 987 | */ |
David Täht | 7c68d6b | 2009-01-20 08:33:20 -0600 | [diff] [blame] | 988 | |
David Taht | 8da3dc2 | 2008-12-17 17:13:45 -0800 | [diff] [blame] | 989 | static void __exit usb_tranzport_exit(void) |
| 990 | { |
| 991 | /* deregister this driver with the USB subsystem */ |
| 992 | usb_deregister(&usb_tranzport_driver); |
| 993 | } |
| 994 | |
| 995 | module_init(usb_tranzport_init); |
| 996 | module_exit(usb_tranzport_exit); |