aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid/hid-roccat.c
diff options
context:
space:
mode:
authorStefan Achatz <erazor_de@users.sourceforge.net>2010-05-19 12:55:16 -0400
committerJiri Kosina <jkosina@suse.cz>2010-05-25 03:57:03 -0400
commit206f5f2fcb5ff5bb0c60f9e9189937f3ca03e378 (patch)
tree9699132bf54da1b36560208892a4067bd889a992 /drivers/hid/hid-roccat.c
parentc2fd1a4ebf9127c280d227acb635eb1df213439c (diff)
HID: roccat: propagate special events of roccat hardware to userspace
Module roccat is a char device used to report special events of roccat hardware to userland. These events include requests for on-screen-display of profile or dpi settings or requests for execution of macro sequences that are not stored in device. The information in these events depends on hid device implementation and contains data that is not available in a single hid event or else hidraw could have been used. It is inspired by hidraw, but uses only one circular buffer for all readers. The device is as generic as possible so that the functionality is usable by all (kone and upcomming) roccat device drivers. Signed-off-by: Stefan Achatz <erazor_de@users.sourceforge.net> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/hid-roccat.c')
-rw-r--r--drivers/hid/hid-roccat.c428
1 files changed, 428 insertions, 0 deletions
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
new file mode 100644
index 000000000000..e05d48edb66f
--- /dev/null
+++ b/drivers/hid/hid-roccat.c
@@ -0,0 +1,428 @@
1/*
2 * Roccat driver for Linux
3 *
4 * Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net>
5 */
6
7/*
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2 of the License, or (at your option)
11 * any later version.
12 */
13
14/*
15 * Module roccat is a char device used to report special events of roccat
16 * hardware to userland. These events include requests for on-screen-display of
17 * profile or dpi settings or requests for execution of macro sequences that are
18 * not stored in device. The information in these events depends on hid device
19 * implementation and contains data that is not available in a single hid event
20 * or else hidraw could have been used.
21 * It is inspired by hidraw, but uses only one circular buffer for all readers.
22 */
23
24#include <linux/cdev.h>
25#include <linux/poll.h>
26#include <linux/sched.h>
27
28#include "hid-roccat.h"
29
30#define ROCCAT_FIRST_MINOR 0
31#define ROCCAT_MAX_DEVICES 8
32
33/* should be a power of 2 for performance reason */
34#define ROCCAT_CBUF_SIZE 16
35
36struct roccat_report {
37 uint8_t *value;
38 int len;
39};
40
41struct roccat_device {
42 unsigned int minor;
43 int open;
44 int exist;
45 wait_queue_head_t wait;
46 struct device *dev;
47 struct hid_device *hid;
48 struct list_head readers;
49 /* protects modifications of readers list */
50 struct mutex readers_lock;
51
52 /*
53 * circular_buffer has one writer and multiple readers with their own
54 * read pointers
55 */
56 struct roccat_report cbuf[ROCCAT_CBUF_SIZE];
57 int cbuf_end;
58 struct mutex cbuf_lock;
59};
60
61struct roccat_reader {
62 struct list_head node;
63 struct roccat_device *device;
64 int cbuf_start;
65};
66
67static int roccat_major;
68static struct class *roccat_class;
69static struct cdev roccat_cdev;
70
71static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
72/* protects modifications of devices array */
73static DEFINE_MUTEX(devices_lock);
74
75static ssize_t roccat_read(struct file *file, char __user *buffer,
76 size_t count, loff_t *ppos)
77{
78 struct roccat_reader *reader = file->private_data;
79 struct roccat_device *device = reader->device;
80 struct roccat_report *report;
81 ssize_t retval = 0, len;
82 DECLARE_WAITQUEUE(wait, current);
83
84 mutex_lock(&device->cbuf_lock);
85
86 /* no data? */
87 if (reader->cbuf_start == device->cbuf_end) {
88 add_wait_queue(&device->wait, &wait);
89 set_current_state(TASK_INTERRUPTIBLE);
90
91 /* wait for data */
92 while (reader->cbuf_start == device->cbuf_end) {
93 if (file->f_flags & O_NONBLOCK) {
94 retval = -EAGAIN;
95 break;
96 }
97 if (signal_pending(current)) {
98 retval = -ERESTARTSYS;
99 break;
100 }
101 if (!device->exist) {
102 retval = -EIO;
103 break;
104 }
105
106 mutex_unlock(&device->cbuf_lock);
107 schedule();
108 mutex_lock(&device->cbuf_lock);
109 set_current_state(TASK_INTERRUPTIBLE);
110 }
111
112 set_current_state(TASK_RUNNING);
113 remove_wait_queue(&device->wait, &wait);
114 }
115
116 /* here we either have data or a reason to return if retval is set */
117 if (retval)
118 goto exit_unlock;
119
120 report = &device->cbuf[reader->cbuf_start];
121 /*
122 * If report is larger than requested amount of data, rest of report
123 * is lost!
124 */
125 len = report->len > count ? count : report->len;
126
127 if (copy_to_user(buffer, report->value, len)) {
128 retval = -EFAULT;
129 goto exit_unlock;
130 }
131 retval += len;
132 reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE;
133
134exit_unlock:
135 mutex_unlock(&device->cbuf_lock);
136 return retval;
137}
138
139static unsigned int roccat_poll(struct file *file, poll_table *wait)
140{
141 struct roccat_reader *reader = file->private_data;
142 poll_wait(file, &reader->device->wait, wait);
143 if (reader->cbuf_start != reader->device->cbuf_end)
144 return POLLIN | POLLRDNORM;
145 if (!reader->device->exist)
146 return POLLERR | POLLHUP;
147 return 0;
148}
149
150static int roccat_open(struct inode *inode, struct file *file)
151{
152 unsigned int minor = iminor(inode);
153 struct roccat_reader *reader;
154 struct roccat_device *device;
155 int error = 0;
156
157 reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL);
158 if (!reader)
159 return -ENOMEM;
160
161 mutex_lock(&devices_lock);
162
163 device = devices[minor];
164
165 mutex_lock(&device->readers_lock);
166
167 if (!device) {
168 printk(KERN_EMERG "roccat device with minor %d doesn't exist\n",
169 minor);
170 error = -ENODEV;
171 goto exit_unlock;
172 }
173
174 if (!device->open++) {
175 /* power on device on adding first reader */
176 if (device->hid->ll_driver->power) {
177 error = device->hid->ll_driver->power(device->hid,
178 PM_HINT_FULLON);
179 if (error < 0) {
180 --device->open;
181 goto exit_unlock;
182 }
183 }
184 error = device->hid->ll_driver->open(device->hid);
185 if (error < 0) {
186 if (device->hid->ll_driver->power)
187 device->hid->ll_driver->power(device->hid,
188 PM_HINT_NORMAL);
189 --device->open;
190 goto exit_unlock;
191 }
192 }
193
194 reader->device = device;
195 /* new reader doesn't get old events */
196 reader->cbuf_start = device->cbuf_end;
197
198 list_add_tail(&reader->node, &device->readers);
199 file->private_data = reader;
200
201exit_unlock:
202 mutex_unlock(&device->readers_lock);
203 mutex_unlock(&devices_lock);
204 return error;
205}
206
207static int roccat_release(struct inode *inode, struct file *file)
208{
209 unsigned int minor = iminor(inode);
210 struct roccat_reader *reader = file->private_data;
211 struct roccat_device *device;
212
213 mutex_lock(&devices_lock);
214
215 device = devices[minor];
216 if (!device) {
217 mutex_unlock(&devices_lock);
218 printk(KERN_EMERG "roccat device with minor %d doesn't exist\n",
219 minor);
220 return -ENODEV;
221 }
222
223 mutex_lock(&device->readers_lock);
224 list_del(&reader->node);
225 mutex_unlock(&device->readers_lock);
226 kfree(reader);
227
228 if (!--device->open) {
229 /* removing last reader */
230 if (device->exist) {
231 if (device->hid->ll_driver->power)
232 device->hid->ll_driver->power(device->hid,
233 PM_HINT_NORMAL);
234 device->hid->ll_driver->close(device->hid);
235 } else {
236 kfree(device);
237 }
238 }
239
240 mutex_unlock(&devices_lock);
241
242 return 0;
243}
244
245/*
246 * roccat_report_event() - output data to readers
247 * @minor: minor device number returned by roccat_connect()
248 * @data: pointer to data
249 * @len: size of data
250 *
251 * Return value is zero on success, a negative error code on failure.
252 *
253 * This is called from interrupt handler.
254 */
255int roccat_report_event(int minor, u8 const *data, int len)
256{
257 struct roccat_device *device;
258 struct roccat_reader *reader;
259 struct roccat_report *report;
260 uint8_t *new_value;
261
262 new_value = kmemdup(data, len, GFP_ATOMIC);
263 if (!new_value)
264 return -ENOMEM;
265
266 device = devices[minor];
267
268 report = &device->cbuf[device->cbuf_end];
269
270 /* passing NULL is safe */
271 kfree(report->value);
272
273 report->value = new_value;
274 report->len = len;
275 device->cbuf_end = (device->cbuf_end + 1) % ROCCAT_CBUF_SIZE;
276
277 list_for_each_entry(reader, &device->readers, node) {
278 /*
279 * As we already inserted one element, the buffer can't be
280 * empty. If start and end are equal, buffer is full and we
281 * increase start, so that slow reader misses one event, but
282 * gets the newer ones in the right order.
283 */
284 if (reader->cbuf_start == device->cbuf_end)
285 reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE;
286 }
287
288 wake_up_interruptible(&device->wait);
289 return 0;
290}
291EXPORT_SYMBOL_GPL(roccat_report_event);
292
293/*
294 * roccat_connect() - create a char device for special event output
295 * @hid: the hid device the char device should be connected to.
296 *
297 * Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on
298 * success, a negative error code on failure.
299 */
300int roccat_connect(struct hid_device *hid)
301{
302 unsigned int minor;
303 struct roccat_device *device;
304 int temp;
305
306 device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL);
307 if (!device)
308 return -ENOMEM;
309
310 mutex_lock(&devices_lock);
311
312 for (minor = 0; minor < ROCCAT_MAX_DEVICES; ++minor) {
313 if (devices[minor])
314 continue;
315 break;
316 }
317
318 if (minor < ROCCAT_MAX_DEVICES) {
319 devices[minor] = device;
320 } else {
321 mutex_unlock(&devices_lock);
322 kfree(device);
323 return -EINVAL;
324 }
325
326 device->dev = device_create(roccat_class, &hid->dev,
327 MKDEV(roccat_major, minor), NULL,
328 "%s%s%d", "roccat", hid->driver->name, minor);
329
330 if (IS_ERR(device->dev)) {
331 devices[minor] = NULL;
332 mutex_unlock(&devices_lock);
333 temp = PTR_ERR(device->dev);
334 kfree(device);
335 return temp;
336 }
337
338 mutex_unlock(&devices_lock);
339
340 init_waitqueue_head(&device->wait);
341 INIT_LIST_HEAD(&device->readers);
342 mutex_init(&device->readers_lock);
343 mutex_init(&device->cbuf_lock);
344 device->minor = minor;
345 device->hid = hid;
346 device->exist = 1;
347 device->cbuf_end = 0;
348
349 return minor;
350}
351EXPORT_SYMBOL_GPL(roccat_connect);
352
353/* roccat_disconnect() - remove char device from hid device
354 * @minor: the minor device number returned by roccat_connect()
355 */
356void roccat_disconnect(int minor)
357{
358 struct roccat_device *device;
359
360 mutex_lock(&devices_lock);
361 device = devices[minor];
362 devices[minor] = NULL;
363 mutex_unlock(&devices_lock);
364
365 device->exist = 0; /* TODO exist maybe not needed */
366
367 device_destroy(roccat_class, MKDEV(roccat_major, minor));
368
369 if (device->open) {
370 device->hid->ll_driver->close(device->hid);
371 wake_up_interruptible(&device->wait);
372 } else {
373 kfree(device);
374 }
375}
376EXPORT_SYMBOL_GPL(roccat_disconnect);
377
378static const struct file_operations roccat_ops = {
379 .owner = THIS_MODULE,
380 .read = roccat_read,
381 .poll = roccat_poll,
382 .open = roccat_open,
383 .release = roccat_release,
384};
385
386static int __init roccat_init(void)
387{
388 int retval;
389 dev_t dev_id;
390
391 retval = alloc_chrdev_region(&dev_id, ROCCAT_FIRST_MINOR,
392 ROCCAT_MAX_DEVICES, "roccat");
393
394 roccat_major = MAJOR(dev_id);
395
396 if (retval < 0) {
397 printk(KERN_WARNING "roccat: can't get major number\n");
398 return retval;
399 }
400
401 roccat_class = class_create(THIS_MODULE, "roccat");
402 if (IS_ERR(roccat_class)) {
403 retval = PTR_ERR(roccat_class);
404 unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES);
405 return retval;
406 }
407
408 cdev_init(&roccat_cdev, &roccat_ops);
409 cdev_add(&roccat_cdev, dev_id, ROCCAT_MAX_DEVICES);
410
411 return 0;
412}
413
414static void __exit roccat_exit(void)
415{
416 dev_t dev_id = MKDEV(roccat_major, 0);
417
418 cdev_del(&roccat_cdev);
419 class_destroy(roccat_class);
420 unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES);
421}
422
423module_init(roccat_init);
424module_exit(roccat_exit);
425
426MODULE_AUTHOR("Stefan Achatz");
427MODULE_DESCRIPTION("USB Roccat char device");
428MODULE_LICENSE("GPL v2");