diff options
Diffstat (limited to 'drivers/hid/hidraw.c')
-rw-r--r-- | drivers/hid/hidraw.c | 176 |
1 files changed, 134 insertions, 42 deletions
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index a3866b5c0c43..c79578b5a788 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c | |||
@@ -19,6 +19,8 @@ | |||
19 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | 19 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
23 | |||
22 | #include <linux/fs.h> | 24 | #include <linux/fs.h> |
23 | #include <linux/module.h> | 25 | #include <linux/module.h> |
24 | #include <linux/errno.h> | 26 | #include <linux/errno.h> |
@@ -32,7 +34,6 @@ | |||
32 | #include <linux/hid.h> | 34 | #include <linux/hid.h> |
33 | #include <linux/mutex.h> | 35 | #include <linux/mutex.h> |
34 | #include <linux/sched.h> | 36 | #include <linux/sched.h> |
35 | #include <linux/smp_lock.h> | ||
36 | 37 | ||
37 | #include <linux/hidraw.h> | 38 | #include <linux/hidraw.h> |
38 | 39 | ||
@@ -90,7 +91,7 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, | |||
90 | ret = -EFAULT; | 91 | ret = -EFAULT; |
91 | goto out; | 92 | goto out; |
92 | } | 93 | } |
93 | ret += len; | 94 | ret = len; |
94 | 95 | ||
95 | kfree(list->buffer[list->tail].value); | 96 | kfree(list->buffer[list->tail].value); |
96 | list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); | 97 | list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); |
@@ -100,16 +101,15 @@ out: | |||
100 | return ret; | 101 | return ret; |
101 | } | 102 | } |
102 | 103 | ||
103 | /* the first byte is expected to be a report number */ | 104 | /* 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 | * This function is to be called with the minors_lock mutex held */ |
106 | static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type) | ||
105 | { | 107 | { |
106 | unsigned int minor = iminor(file->f_path.dentry->d_inode); | 108 | unsigned int minor = iminor(file->f_path.dentry->d_inode); |
107 | struct hid_device *dev; | 109 | struct hid_device *dev; |
108 | __u8 *buf; | 110 | __u8 *buf; |
109 | int ret = 0; | 111 | int ret = 0; |
110 | 112 | ||
111 | mutex_lock(&minors_lock); | ||
112 | |||
113 | if (!hidraw_table[minor]) { | 113 | if (!hidraw_table[minor]) { |
114 | ret = -ENODEV; | 114 | ret = -ENODEV; |
115 | goto out; | 115 | goto out; |
@@ -123,6 +123,70 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t | |||
123 | } | 123 | } |
124 | 124 | ||
125 | if (count > HID_MAX_BUFFER_SIZE) { | 125 | if (count > HID_MAX_BUFFER_SIZE) { |
126 | hid_warn(dev, "pid %d passed too large report\n", | ||
127 | task_pid_nr(current)); | ||
128 | ret = -EINVAL; | ||
129 | goto out; | ||
130 | } | ||
131 | |||
132 | if (count < 2) { | ||
133 | hid_warn(dev, "pid %d passed too short report\n", | ||
134 | task_pid_nr(current)); | ||
135 | ret = -EINVAL; | ||
136 | goto out; | ||
137 | } | ||
138 | |||
139 | buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); | ||
140 | if (!buf) { | ||
141 | ret = -ENOMEM; | ||
142 | goto out; | ||
143 | } | ||
144 | |||
145 | if (copy_from_user(buf, buffer, count)) { | ||
146 | ret = -EFAULT; | ||
147 | goto out_free; | ||
148 | } | ||
149 | |||
150 | ret = dev->hid_output_raw_report(dev, buf, count, report_type); | ||
151 | out_free: | ||
152 | kfree(buf); | ||
153 | out: | ||
154 | return ret; | ||
155 | } | ||
156 | |||
157 | /* the first byte is expected to be a report number */ | ||
158 | static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) | ||
159 | { | ||
160 | ssize_t ret; | ||
161 | mutex_lock(&minors_lock); | ||
162 | ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT); | ||
163 | mutex_unlock(&minors_lock); | ||
164 | return ret; | ||
165 | } | ||
166 | |||
167 | |||
168 | /* This function performs a Get_Report transfer over the control endpoint | ||
169 | * per section 7.2.1 of the HID specification, version 1.1. The first byte | ||
170 | * of buffer is the report number to request, or 0x0 if the defice does not | ||
171 | * use numbered reports. The report_type parameter can be HID_FEATURE_REPORT | ||
172 | * or HID_INPUT_REPORT. This function is to be called with the minors_lock | ||
173 | * mutex held. */ | ||
174 | static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type) | ||
175 | { | ||
176 | unsigned int minor = iminor(file->f_path.dentry->d_inode); | ||
177 | struct hid_device *dev; | ||
178 | __u8 *buf; | ||
179 | int ret = 0, len; | ||
180 | unsigned char report_number; | ||
181 | |||
182 | dev = hidraw_table[minor]->hid; | ||
183 | |||
184 | if (!dev->hid_get_raw_report) { | ||
185 | ret = -ENODEV; | ||
186 | goto out; | ||
187 | } | ||
188 | |||
189 | if (count > HID_MAX_BUFFER_SIZE) { | ||
126 | printk(KERN_WARNING "hidraw: pid %d passed too large report\n", | 190 | printk(KERN_WARNING "hidraw: pid %d passed too large report\n", |
127 | task_pid_nr(current)); | 191 | task_pid_nr(current)); |
128 | ret = -EINVAL; | 192 | ret = -EINVAL; |
@@ -142,16 +206,30 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t | |||
142 | goto out; | 206 | goto out; |
143 | } | 207 | } |
144 | 208 | ||
145 | if (copy_from_user(buf, buffer, count)) { | 209 | /* Read the first byte from the user. This is the report number, |
210 | * which is passed to dev->hid_get_raw_report(). */ | ||
211 | if (copy_from_user(&report_number, buffer, 1)) { | ||
212 | ret = -EFAULT; | ||
213 | goto out_free; | ||
214 | } | ||
215 | |||
216 | ret = dev->hid_get_raw_report(dev, report_number, buf, count, report_type); | ||
217 | |||
218 | if (ret < 0) | ||
219 | goto out_free; | ||
220 | |||
221 | len = (ret < count) ? ret : count; | ||
222 | |||
223 | if (copy_to_user(buffer, buf, len)) { | ||
146 | ret = -EFAULT; | 224 | ret = -EFAULT; |
147 | goto out_free; | 225 | goto out_free; |
148 | } | 226 | } |
149 | 227 | ||
150 | ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT); | 228 | ret = len; |
229 | |||
151 | out_free: | 230 | out_free: |
152 | kfree(buf); | 231 | kfree(buf); |
153 | out: | 232 | out: |
154 | mutex_unlock(&minors_lock); | ||
155 | return ret; | 233 | return ret; |
156 | } | 234 | } |
157 | 235 | ||
@@ -193,15 +271,13 @@ static int hidraw_open(struct inode *inode, struct file *file) | |||
193 | 271 | ||
194 | dev = hidraw_table[minor]; | 272 | dev = hidraw_table[minor]; |
195 | if (!dev->open++) { | 273 | if (!dev->open++) { |
196 | if (dev->hid->ll_driver->power) { | 274 | err = hid_hw_power(dev->hid, PM_HINT_FULLON); |
197 | err = dev->hid->ll_driver->power(dev->hid, PM_HINT_FULLON); | 275 | if (err < 0) |
198 | if (err < 0) | 276 | goto out_unlock; |
199 | goto out_unlock; | 277 | |
200 | } | 278 | err = hid_hw_open(dev->hid); |
201 | err = dev->hid->ll_driver->open(dev->hid); | ||
202 | if (err < 0) { | 279 | if (err < 0) { |
203 | if (dev->hid->ll_driver->power) | 280 | hid_hw_power(dev->hid, PM_HINT_NORMAL); |
204 | dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL); | ||
205 | dev->open--; | 281 | dev->open--; |
206 | } | 282 | } |
207 | } | 283 | } |
@@ -218,25 +294,30 @@ static int hidraw_release(struct inode * inode, struct file * file) | |||
218 | unsigned int minor = iminor(inode); | 294 | unsigned int minor = iminor(inode); |
219 | struct hidraw *dev; | 295 | struct hidraw *dev; |
220 | struct hidraw_list *list = file->private_data; | 296 | struct hidraw_list *list = file->private_data; |
297 | int ret; | ||
221 | 298 | ||
222 | if (!hidraw_table[minor]) | 299 | mutex_lock(&minors_lock); |
223 | return -ENODEV; | 300 | if (!hidraw_table[minor]) { |
301 | ret = -ENODEV; | ||
302 | goto unlock; | ||
303 | } | ||
224 | 304 | ||
225 | list_del(&list->node); | 305 | list_del(&list->node); |
226 | dev = hidraw_table[minor]; | 306 | dev = hidraw_table[minor]; |
227 | if (!--dev->open) { | 307 | if (!--dev->open) { |
228 | if (list->hidraw->exist) { | 308 | if (list->hidraw->exist) { |
229 | if (dev->hid->ll_driver->power) | 309 | hid_hw_power(dev->hid, PM_HINT_NORMAL); |
230 | dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL); | 310 | hid_hw_close(dev->hid); |
231 | dev->hid->ll_driver->close(dev->hid); | ||
232 | } else { | 311 | } else { |
233 | kfree(list->hidraw); | 312 | kfree(list->hidraw); |
234 | } | 313 | } |
235 | } | 314 | } |
236 | |||
237 | kfree(list); | 315 | kfree(list); |
316 | ret = 0; | ||
317 | unlock: | ||
318 | mutex_unlock(&minors_lock); | ||
238 | 319 | ||
239 | return 0; | 320 | return ret; |
240 | } | 321 | } |
241 | 322 | ||
242 | static long hidraw_ioctl(struct file *file, unsigned int cmd, | 323 | static long hidraw_ioctl(struct file *file, unsigned int cmd, |
@@ -291,18 +372,30 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, | |||
291 | default: | 372 | default: |
292 | { | 373 | { |
293 | struct hid_device *hid = dev->hid; | 374 | struct hid_device *hid = dev->hid; |
294 | if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) { | 375 | if (_IOC_TYPE(cmd) != 'H') { |
376 | ret = -EINVAL; | ||
377 | break; | ||
378 | } | ||
379 | |||
380 | if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) { | ||
381 | int len = _IOC_SIZE(cmd); | ||
382 | ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); | ||
383 | break; | ||
384 | } | ||
385 | if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) { | ||
386 | int len = _IOC_SIZE(cmd); | ||
387 | ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); | ||
388 | break; | ||
389 | } | ||
390 | |||
391 | /* Begin Read-only ioctls. */ | ||
392 | if (_IOC_DIR(cmd) != _IOC_READ) { | ||
295 | ret = -EINVAL; | 393 | ret = -EINVAL; |
296 | break; | 394 | break; |
297 | } | 395 | } |
298 | 396 | ||
299 | if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { | 397 | if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { |
300 | int len; | 398 | int len = strlen(hid->name) + 1; |
301 | if (!hid->name) { | ||
302 | ret = 0; | ||
303 | break; | ||
304 | } | ||
305 | len = strlen(hid->name) + 1; | ||
306 | if (len > _IOC_SIZE(cmd)) | 399 | if (len > _IOC_SIZE(cmd)) |
307 | len = _IOC_SIZE(cmd); | 400 | len = _IOC_SIZE(cmd); |
308 | ret = copy_to_user(user_arg, hid->name, len) ? | 401 | ret = copy_to_user(user_arg, hid->name, len) ? |
@@ -311,19 +404,14 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, | |||
311 | } | 404 | } |
312 | 405 | ||
313 | if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { | 406 | if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { |
314 | int len; | 407 | int len = strlen(hid->phys) + 1; |
315 | if (!hid->phys) { | ||
316 | ret = 0; | ||
317 | break; | ||
318 | } | ||
319 | len = strlen(hid->phys) + 1; | ||
320 | if (len > _IOC_SIZE(cmd)) | 408 | if (len > _IOC_SIZE(cmd)) |
321 | len = _IOC_SIZE(cmd); | 409 | len = _IOC_SIZE(cmd); |
322 | ret = copy_to_user(user_arg, hid->phys, len) ? | 410 | ret = copy_to_user(user_arg, hid->phys, len) ? |
323 | -EFAULT : len; | 411 | -EFAULT : len; |
324 | break; | 412 | break; |
325 | } | 413 | } |
326 | } | 414 | } |
327 | 415 | ||
328 | ret = -ENOTTY; | 416 | ret = -ENOTTY; |
329 | } | 417 | } |
@@ -340,6 +428,10 @@ static const struct file_operations hidraw_ops = { | |||
340 | .open = hidraw_open, | 428 | .open = hidraw_open, |
341 | .release = hidraw_release, | 429 | .release = hidraw_release, |
342 | .unlocked_ioctl = hidraw_ioctl, | 430 | .unlocked_ioctl = hidraw_ioctl, |
431 | #ifdef CONFIG_COMPAT | ||
432 | .compat_ioctl = hidraw_ioctl, | ||
433 | #endif | ||
434 | .llseek = noop_llseek, | ||
343 | }; | 435 | }; |
344 | 436 | ||
345 | void hidraw_report_event(struct hid_device *hid, u8 *data, int len) | 437 | void hidraw_report_event(struct hid_device *hid, u8 *data, int len) |
@@ -420,14 +512,14 @@ void hidraw_disconnect(struct hid_device *hid) | |||
420 | 512 | ||
421 | hidraw->exist = 0; | 513 | hidraw->exist = 0; |
422 | 514 | ||
515 | device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); | ||
516 | |||
423 | mutex_lock(&minors_lock); | 517 | mutex_lock(&minors_lock); |
424 | hidraw_table[hidraw->minor] = NULL; | 518 | hidraw_table[hidraw->minor] = NULL; |
425 | mutex_unlock(&minors_lock); | 519 | mutex_unlock(&minors_lock); |
426 | 520 | ||
427 | device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); | ||
428 | |||
429 | if (hidraw->open) { | 521 | if (hidraw->open) { |
430 | hid->ll_driver->close(hid); | 522 | hid_hw_close(hid); |
431 | wake_up_interruptible(&hidraw->wait); | 523 | wake_up_interruptible(&hidraw->wait); |
432 | } else { | 524 | } else { |
433 | kfree(hidraw); | 525 | kfree(hidraw); |
@@ -446,7 +538,7 @@ int __init hidraw_init(void) | |||
446 | hidraw_major = MAJOR(dev_id); | 538 | hidraw_major = MAJOR(dev_id); |
447 | 539 | ||
448 | if (result < 0) { | 540 | if (result < 0) { |
449 | printk(KERN_WARNING "hidraw: can't get major number\n"); | 541 | pr_warn("can't get major number\n"); |
450 | result = 0; | 542 | result = 0; |
451 | goto out; | 543 | goto out; |
452 | } | 544 | } |