diff options
Diffstat (limited to 'drivers/hid/hidraw.c')
-rw-r--r-- | drivers/hid/hidraw.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c new file mode 100644 index 000000000000..20ebba1032b8 --- /dev/null +++ b/drivers/hid/hidraw.c | |||
@@ -0,0 +1,395 @@ | |||
1 | /* | ||
2 | * HID raw devices, giving access to raw HID events. | ||
3 | * | ||
4 | * In comparison to hiddev, this device does not process the | ||
5 | * hid events at all (no parsing, no lookups). This lets applications | ||
6 | * to work on raw hid events as they want to, and avoids a need to | ||
7 | * use a transport-specific userspace libhid/libusb libraries. | ||
8 | * | ||
9 | * Copyright (c) 2007 Jiri Kosina | ||
10 | */ | ||
11 | |||
12 | /* | ||
13 | * This program is free software; you can redistribute it and/or modify it | ||
14 | * under the terms and conditions of the GNU General Public License, | ||
15 | * version 2, as published by the Free Software Foundation. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License along with | ||
18 | * this program; if not, write to the Free Software Foundation, Inc., | ||
19 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
20 | */ | ||
21 | |||
22 | #include <linux/fs.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/errno.h> | ||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/cdev.h> | ||
28 | #include <linux/poll.h> | ||
29 | #include <linux/device.h> | ||
30 | #include <linux/major.h> | ||
31 | #include <linux/hid.h> | ||
32 | #include <linux/mutex.h> | ||
33 | |||
34 | #include <linux/hidraw.h> | ||
35 | |||
36 | static int hidraw_major; | ||
37 | static struct cdev hidraw_cdev; | ||
38 | static struct class *hidraw_class; | ||
39 | static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES]; | ||
40 | static DEFINE_SPINLOCK(minors_lock); | ||
41 | |||
42 | static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) | ||
43 | { | ||
44 | struct hidraw_list *list = file->private_data; | ||
45 | int ret = 0, len; | ||
46 | char *report; | ||
47 | DECLARE_WAITQUEUE(wait, current); | ||
48 | |||
49 | while (ret == 0) { | ||
50 | |||
51 | mutex_lock(&list->read_mutex); | ||
52 | |||
53 | if (list->head == list->tail) { | ||
54 | add_wait_queue(&list->hidraw->wait, &wait); | ||
55 | set_current_state(TASK_INTERRUPTIBLE); | ||
56 | |||
57 | while (list->head == list->tail) { | ||
58 | if (file->f_flags & O_NONBLOCK) { | ||
59 | ret = -EAGAIN; | ||
60 | break; | ||
61 | } | ||
62 | if (signal_pending(current)) { | ||
63 | ret = -ERESTARTSYS; | ||
64 | break; | ||
65 | } | ||
66 | if (!list->hidraw->exist) { | ||
67 | ret = -EIO; | ||
68 | break; | ||
69 | } | ||
70 | |||
71 | /* allow O_NONBLOCK to work well from other threads */ | ||
72 | mutex_unlock(&list->read_mutex); | ||
73 | schedule(); | ||
74 | mutex_lock(&list->read_mutex); | ||
75 | set_current_state(TASK_INTERRUPTIBLE); | ||
76 | } | ||
77 | |||
78 | set_current_state(TASK_RUNNING); | ||
79 | remove_wait_queue(&list->hidraw->wait, &wait); | ||
80 | } | ||
81 | |||
82 | if (ret) | ||
83 | goto out; | ||
84 | |||
85 | report = list->buffer[list->tail].value; | ||
86 | len = list->buffer[list->tail].len > count ? | ||
87 | count : list->buffer[list->tail].len; | ||
88 | |||
89 | if (copy_to_user(buffer, list->buffer[list->tail].value, len)) { | ||
90 | ret = -EFAULT; | ||
91 | goto out; | ||
92 | } | ||
93 | ret += len; | ||
94 | |||
95 | kfree(list->buffer[list->tail].value); | ||
96 | list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); | ||
97 | } | ||
98 | out: | ||
99 | mutex_unlock(&list->read_mutex); | ||
100 | return ret; | ||
101 | } | ||
102 | |||
103 | /* the first byte is expected to be a report number */ | ||
104 | static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) | ||
105 | { | ||
106 | unsigned int minor = iminor(file->f_path.dentry->d_inode); | ||
107 | struct hid_device *dev = hidraw_table[minor]->hid; | ||
108 | __u8 *buf; | ||
109 | int ret = 0; | ||
110 | |||
111 | if (!dev->hid_output_raw_report) | ||
112 | return -ENODEV; | ||
113 | |||
114 | if (count > HID_MIN_BUFFER_SIZE) { | ||
115 | printk(KERN_WARNING "hidraw: pid %d passed too large report\n", | ||
116 | current->pid); | ||
117 | return -EINVAL; | ||
118 | } | ||
119 | |||
120 | if (count < 2) { | ||
121 | printk(KERN_WARNING "hidraw: pid %d passed too short report\n", | ||
122 | current->pid); | ||
123 | return -EINVAL; | ||
124 | } | ||
125 | |||
126 | buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); | ||
127 | if (!buf) | ||
128 | return -ENOMEM; | ||
129 | |||
130 | if (copy_from_user(buf, buffer, count)) { | ||
131 | ret = -EFAULT; | ||
132 | goto out; | ||
133 | } | ||
134 | |||
135 | ret = dev->hid_output_raw_report(dev, buf, count); | ||
136 | out: | ||
137 | kfree(buf); | ||
138 | return ret; | ||
139 | } | ||
140 | |||
141 | static unsigned int hidraw_poll(struct file *file, poll_table *wait) | ||
142 | { | ||
143 | struct hidraw_list *list = file->private_data; | ||
144 | |||
145 | poll_wait(file, &list->hidraw->wait, wait); | ||
146 | if (list->head != list->tail) | ||
147 | return POLLIN | POLLRDNORM; | ||
148 | if (!list->hidraw->exist) | ||
149 | return POLLERR | POLLHUP; | ||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int hidraw_open(struct inode *inode, struct file *file) | ||
154 | { | ||
155 | unsigned int minor = iminor(inode); | ||
156 | struct hidraw *dev; | ||
157 | struct hidraw_list *list; | ||
158 | int err = 0; | ||
159 | |||
160 | if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { | ||
161 | err = -ENOMEM; | ||
162 | goto out; | ||
163 | } | ||
164 | |||
165 | spin_lock(&minors_lock); | ||
166 | if (!hidraw_table[minor]) { | ||
167 | printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", | ||
168 | minor); | ||
169 | kfree(list); | ||
170 | err = -ENODEV; | ||
171 | goto out_unlock; | ||
172 | } | ||
173 | |||
174 | list->hidraw = hidraw_table[minor]; | ||
175 | mutex_init(&list->read_mutex); | ||
176 | list_add_tail(&list->node, &hidraw_table[minor]->list); | ||
177 | file->private_data = list; | ||
178 | |||
179 | dev = hidraw_table[minor]; | ||
180 | if (!dev->open++) | ||
181 | dev->hid->hid_open(dev->hid); | ||
182 | |||
183 | out_unlock: | ||
184 | spin_unlock(&minors_lock); | ||
185 | out: | ||
186 | return err; | ||
187 | |||
188 | } | ||
189 | |||
190 | static int hidraw_release(struct inode * inode, struct file * file) | ||
191 | { | ||
192 | unsigned int minor = iminor(inode); | ||
193 | struct hidraw *dev; | ||
194 | struct hidraw_list *list = file->private_data; | ||
195 | |||
196 | if (!hidraw_table[minor]) { | ||
197 | printk(KERN_EMERG "hidraw device with minor %d doesn't exist\n", | ||
198 | minor); | ||
199 | return -ENODEV; | ||
200 | } | ||
201 | |||
202 | list_del(&list->node); | ||
203 | dev = hidraw_table[minor]; | ||
204 | if (!dev->open--) { | ||
205 | if (list->hidraw->exist) | ||
206 | dev->hid->hid_close(dev->hid); | ||
207 | else | ||
208 | kfree(list->hidraw); | ||
209 | } | ||
210 | |||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static int hidraw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | ||
215 | { | ||
216 | unsigned int minor = iminor(inode); | ||
217 | struct hidraw *dev = hidraw_table[minor]; | ||
218 | void __user *user_arg = (void __user*) arg; | ||
219 | |||
220 | switch (cmd) { | ||
221 | case HIDIOCGRDESCSIZE: | ||
222 | if (put_user(dev->hid->rsize, (int __user *)arg)) | ||
223 | return -EFAULT; | ||
224 | return 0; | ||
225 | |||
226 | case HIDIOCGRDESC: | ||
227 | { | ||
228 | __u32 len; | ||
229 | |||
230 | if (get_user(len, (int __user *)arg)) | ||
231 | return -EFAULT; | ||
232 | if (copy_to_user(*((__u8 **)(user_arg + | ||
233 | sizeof(__u32))), | ||
234 | dev->hid->rdesc, len)) | ||
235 | return -EFAULT; | ||
236 | return 0; | ||
237 | } | ||
238 | case HIDIOCGRAWINFO: | ||
239 | { | ||
240 | struct hidraw_devinfo dinfo; | ||
241 | |||
242 | dinfo.bustype = dev->hid->bus; | ||
243 | dinfo.vendor = dev->hid->vendor; | ||
244 | dinfo.product = dev->hid->product; | ||
245 | if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) | ||
246 | return -EFAULT; | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | default: | ||
251 | printk(KERN_EMERG "hidraw: unsupported ioctl() %x\n", | ||
252 | cmd); | ||
253 | } | ||
254 | return -EINVAL; | ||
255 | } | ||
256 | |||
257 | static const struct file_operations hidraw_ops = { | ||
258 | .owner = THIS_MODULE, | ||
259 | .read = hidraw_read, | ||
260 | .write = hidraw_write, | ||
261 | .poll = hidraw_poll, | ||
262 | .open = hidraw_open, | ||
263 | .release = hidraw_release, | ||
264 | .ioctl = hidraw_ioctl, | ||
265 | }; | ||
266 | |||
267 | void hidraw_report_event(struct hid_device *hid, u8 *data, int len) | ||
268 | { | ||
269 | struct hidraw *dev = hid->hidraw; | ||
270 | struct hidraw_list *list; | ||
271 | |||
272 | list_for_each_entry(list, &dev->list, node) { | ||
273 | list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC); | ||
274 | list->buffer[list->head].len = len; | ||
275 | list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); | ||
276 | kill_fasync(&list->fasync, SIGIO, POLL_IN); | ||
277 | } | ||
278 | |||
279 | wake_up_interruptible(&dev->wait); | ||
280 | } | ||
281 | EXPORT_SYMBOL_GPL(hidraw_report_event); | ||
282 | |||
283 | int hidraw_connect(struct hid_device *hid) | ||
284 | { | ||
285 | int minor, result = -EINVAL; | ||
286 | struct hidraw *dev; | ||
287 | |||
288 | /* TODO currently we accept any HID device. This should later | ||
289 | * probably be fixed to accept only those devices which provide | ||
290 | * non-input applications | ||
291 | */ | ||
292 | |||
293 | if (!(dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL))) | ||
294 | return -1; | ||
295 | |||
296 | spin_lock(&minors_lock); | ||
297 | |||
298 | for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) { | ||
299 | if (hidraw_table[minor]) | ||
300 | continue; | ||
301 | hidraw_table[minor] = dev; | ||
302 | result = 0; | ||
303 | break; | ||
304 | } | ||
305 | |||
306 | spin_unlock(&minors_lock); | ||
307 | |||
308 | if (result) | ||
309 | goto out; | ||
310 | |||
311 | dev->dev = device_create(hidraw_class, NULL, MKDEV(hidraw_major, minor), | ||
312 | "%s%d", "hidraw", minor); | ||
313 | |||
314 | if (IS_ERR(dev->dev)) { | ||
315 | spin_lock(&minors_lock); | ||
316 | hidraw_table[minor] = NULL; | ||
317 | spin_unlock(&minors_lock); | ||
318 | result = PTR_ERR(dev->dev); | ||
319 | goto out; | ||
320 | } | ||
321 | |||
322 | init_waitqueue_head(&dev->wait); | ||
323 | INIT_LIST_HEAD(&dev->list); | ||
324 | |||
325 | dev->hid = hid; | ||
326 | dev->minor = minor; | ||
327 | |||
328 | dev->exist = 1; | ||
329 | hid->hidraw = dev; | ||
330 | |||
331 | out: | ||
332 | return result; | ||
333 | |||
334 | } | ||
335 | EXPORT_SYMBOL_GPL(hidraw_connect); | ||
336 | |||
337 | void hidraw_disconnect(struct hid_device *hid) | ||
338 | { | ||
339 | struct hidraw *hidraw = hid->hidraw; | ||
340 | |||
341 | hidraw->exist = 0; | ||
342 | |||
343 | spin_lock(&minors_lock); | ||
344 | hidraw_table[hidraw->minor] = NULL; | ||
345 | spin_unlock(&minors_lock); | ||
346 | |||
347 | device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); | ||
348 | |||
349 | if (hidraw->open) { | ||
350 | hid->hid_close(hid); | ||
351 | wake_up_interruptible(&hidraw->wait); | ||
352 | } else { | ||
353 | kfree(hidraw); | ||
354 | } | ||
355 | } | ||
356 | EXPORT_SYMBOL_GPL(hidraw_disconnect); | ||
357 | |||
358 | int __init hidraw_init(void) | ||
359 | { | ||
360 | int result; | ||
361 | dev_t dev_id; | ||
362 | |||
363 | result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR, | ||
364 | HIDRAW_MAX_DEVICES, "hidraw"); | ||
365 | |||
366 | hidraw_major = MAJOR(dev_id); | ||
367 | |||
368 | if (result < 0) { | ||
369 | printk(KERN_WARNING "hidraw: can't get major number\n"); | ||
370 | result = 0; | ||
371 | goto out; | ||
372 | } | ||
373 | |||
374 | hidraw_class = class_create(THIS_MODULE, "hidraw"); | ||
375 | if (IS_ERR(hidraw_class)) { | ||
376 | result = PTR_ERR(hidraw_class); | ||
377 | unregister_chrdev(hidraw_major, "hidraw"); | ||
378 | goto out; | ||
379 | } | ||
380 | |||
381 | cdev_init(&hidraw_cdev, &hidraw_ops); | ||
382 | cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); | ||
383 | out: | ||
384 | return result; | ||
385 | } | ||
386 | |||
387 | void __exit hidraw_exit(void) | ||
388 | { | ||
389 | dev_t dev_id = MKDEV(hidraw_major, 0); | ||
390 | |||
391 | cdev_del(&hidraw_cdev); | ||
392 | class_destroy(hidraw_class); | ||
393 | unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); | ||
394 | |||
395 | } | ||