162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2001 Paul Stewart 462306a36Sopenharmony_ci * Copyright (c) 2001 Vojtech Pavlik 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * HID char devices, giving access to raw HID device events. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci/* 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Should you need to contact me, the author, you can do so either by 1262306a36Sopenharmony_ci * e-mail - mail your message to Paul Stewart <stewart@wetlogic.net> 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/poll.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/sched/signal.h> 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/init.h> 2062306a36Sopenharmony_ci#include <linux/input.h> 2162306a36Sopenharmony_ci#include <linux/usb.h> 2262306a36Sopenharmony_ci#include <linux/hid.h> 2362306a36Sopenharmony_ci#include <linux/hiddev.h> 2462306a36Sopenharmony_ci#include <linux/compat.h> 2562306a36Sopenharmony_ci#include <linux/vmalloc.h> 2662306a36Sopenharmony_ci#include <linux/nospec.h> 2762306a36Sopenharmony_ci#include "usbhid.h" 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#ifdef CONFIG_USB_DYNAMIC_MINORS 3062306a36Sopenharmony_ci#define HIDDEV_MINOR_BASE 0 3162306a36Sopenharmony_ci#define HIDDEV_MINORS 256 3262306a36Sopenharmony_ci#else 3362306a36Sopenharmony_ci#define HIDDEV_MINOR_BASE 96 3462306a36Sopenharmony_ci#define HIDDEV_MINORS 16 3562306a36Sopenharmony_ci#endif 3662306a36Sopenharmony_ci#define HIDDEV_BUFFER_SIZE 2048 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct hiddev_list { 3962306a36Sopenharmony_ci struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE]; 4062306a36Sopenharmony_ci int head; 4162306a36Sopenharmony_ci int tail; 4262306a36Sopenharmony_ci unsigned flags; 4362306a36Sopenharmony_ci struct fasync_struct *fasync; 4462306a36Sopenharmony_ci struct hiddev *hiddev; 4562306a36Sopenharmony_ci struct list_head node; 4662306a36Sopenharmony_ci struct mutex thread_lock; 4762306a36Sopenharmony_ci}; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* 5062306a36Sopenharmony_ci * Find a report, given the report's type and ID. The ID can be specified 5162306a36Sopenharmony_ci * indirectly by REPORT_ID_FIRST (which returns the first report of the given 5262306a36Sopenharmony_ci * type) or by (REPORT_ID_NEXT | old_id), which returns the next report of the 5362306a36Sopenharmony_ci * given type which follows old_id. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_cistatic struct hid_report * 5662306a36Sopenharmony_cihiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci unsigned int flags = rinfo->report_id & ~HID_REPORT_ID_MASK; 5962306a36Sopenharmony_ci unsigned int rid = rinfo->report_id & HID_REPORT_ID_MASK; 6062306a36Sopenharmony_ci struct hid_report_enum *report_enum; 6162306a36Sopenharmony_ci struct hid_report *report; 6262306a36Sopenharmony_ci struct list_head *list; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if (rinfo->report_type < HID_REPORT_TYPE_MIN || 6562306a36Sopenharmony_ci rinfo->report_type > HID_REPORT_TYPE_MAX) 6662306a36Sopenharmony_ci return NULL; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci report_enum = hid->report_enum + 6962306a36Sopenharmony_ci (rinfo->report_type - HID_REPORT_TYPE_MIN); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci switch (flags) { 7262306a36Sopenharmony_ci case 0: /* Nothing to do -- report_id is already set correctly */ 7362306a36Sopenharmony_ci break; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci case HID_REPORT_ID_FIRST: 7662306a36Sopenharmony_ci if (list_empty(&report_enum->report_list)) 7762306a36Sopenharmony_ci return NULL; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci list = report_enum->report_list.next; 8062306a36Sopenharmony_ci report = list_entry(list, struct hid_report, list); 8162306a36Sopenharmony_ci rinfo->report_id = report->id; 8262306a36Sopenharmony_ci break; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci case HID_REPORT_ID_NEXT: 8562306a36Sopenharmony_ci report = report_enum->report_id_hash[rid]; 8662306a36Sopenharmony_ci if (!report) 8762306a36Sopenharmony_ci return NULL; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci list = report->list.next; 9062306a36Sopenharmony_ci if (list == &report_enum->report_list) 9162306a36Sopenharmony_ci return NULL; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci report = list_entry(list, struct hid_report, list); 9462306a36Sopenharmony_ci rinfo->report_id = report->id; 9562306a36Sopenharmony_ci break; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci default: 9862306a36Sopenharmony_ci return NULL; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return report_enum->report_id_hash[rinfo->report_id]; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* 10562306a36Sopenharmony_ci * Perform an exhaustive search of the report table for a usage, given its 10662306a36Sopenharmony_ci * type and usage id. 10762306a36Sopenharmony_ci */ 10862306a36Sopenharmony_cistatic struct hid_field * 10962306a36Sopenharmony_cihiddev_lookup_usage(struct hid_device *hid, struct hiddev_usage_ref *uref) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci int i, j; 11262306a36Sopenharmony_ci struct hid_report *report; 11362306a36Sopenharmony_ci struct hid_report_enum *report_enum; 11462306a36Sopenharmony_ci struct hid_field *field; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (uref->report_type < HID_REPORT_TYPE_MIN || 11762306a36Sopenharmony_ci uref->report_type > HID_REPORT_TYPE_MAX) 11862306a36Sopenharmony_ci return NULL; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci report_enum = hid->report_enum + 12162306a36Sopenharmony_ci (uref->report_type - HID_REPORT_TYPE_MIN); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci list_for_each_entry(report, &report_enum->report_list, list) { 12462306a36Sopenharmony_ci for (i = 0; i < report->maxfield; i++) { 12562306a36Sopenharmony_ci field = report->field[i]; 12662306a36Sopenharmony_ci for (j = 0; j < field->maxusage; j++) { 12762306a36Sopenharmony_ci if (field->usage[j].hid == uref->usage_code) { 12862306a36Sopenharmony_ci uref->report_id = report->id; 12962306a36Sopenharmony_ci uref->field_index = i; 13062306a36Sopenharmony_ci uref->usage_index = j; 13162306a36Sopenharmony_ci return field; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return NULL; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic void hiddev_send_event(struct hid_device *hid, 14162306a36Sopenharmony_ci struct hiddev_usage_ref *uref) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci struct hiddev *hiddev = hid->hiddev; 14462306a36Sopenharmony_ci struct hiddev_list *list; 14562306a36Sopenharmony_ci unsigned long flags; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci spin_lock_irqsave(&hiddev->list_lock, flags); 14862306a36Sopenharmony_ci list_for_each_entry(list, &hiddev->list, node) { 14962306a36Sopenharmony_ci if (uref->field_index != HID_FIELD_INDEX_NONE || 15062306a36Sopenharmony_ci (list->flags & HIDDEV_FLAG_REPORT) != 0) { 15162306a36Sopenharmony_ci list->buffer[list->head] = *uref; 15262306a36Sopenharmony_ci list->head = (list->head + 1) & 15362306a36Sopenharmony_ci (HIDDEV_BUFFER_SIZE - 1); 15462306a36Sopenharmony_ci kill_fasync(&list->fasync, SIGIO, POLL_IN); 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci spin_unlock_irqrestore(&hiddev->list_lock, flags); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci wake_up_interruptible(&hiddev->wait); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/* 16362306a36Sopenharmony_ci * This is where hid.c calls into hiddev to pass an event that occurred over 16462306a36Sopenharmony_ci * the interrupt pipe 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_civoid hiddev_hid_event(struct hid_device *hid, struct hid_field *field, 16762306a36Sopenharmony_ci struct hid_usage *usage, __s32 value) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci unsigned type = field->report_type; 17062306a36Sopenharmony_ci struct hiddev_usage_ref uref; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci uref.report_type = 17362306a36Sopenharmony_ci (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : 17462306a36Sopenharmony_ci ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : 17562306a36Sopenharmony_ci ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0)); 17662306a36Sopenharmony_ci uref.report_id = field->report->id; 17762306a36Sopenharmony_ci uref.field_index = field->index; 17862306a36Sopenharmony_ci uref.usage_index = (usage - field->usage); 17962306a36Sopenharmony_ci uref.usage_code = usage->hid; 18062306a36Sopenharmony_ci uref.value = value; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci hiddev_send_event(hid, &uref); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(hiddev_hid_event); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_civoid hiddev_report_event(struct hid_device *hid, struct hid_report *report) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci unsigned type = report->type; 18962306a36Sopenharmony_ci struct hiddev_usage_ref uref; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci memset(&uref, 0, sizeof(uref)); 19262306a36Sopenharmony_ci uref.report_type = 19362306a36Sopenharmony_ci (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT : 19462306a36Sopenharmony_ci ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : 19562306a36Sopenharmony_ci ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0)); 19662306a36Sopenharmony_ci uref.report_id = report->id; 19762306a36Sopenharmony_ci uref.field_index = HID_FIELD_INDEX_NONE; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci hiddev_send_event(hid, &uref); 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci/* 20362306a36Sopenharmony_ci * fasync file op 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_cistatic int hiddev_fasync(int fd, struct file *file, int on) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct hiddev_list *list = file->private_data; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return fasync_helper(fd, file, on, &list->fasync); 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci/* 21462306a36Sopenharmony_ci * release file op 21562306a36Sopenharmony_ci */ 21662306a36Sopenharmony_cistatic int hiddev_release(struct inode * inode, struct file * file) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct hiddev_list *list = file->private_data; 21962306a36Sopenharmony_ci unsigned long flags; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci spin_lock_irqsave(&list->hiddev->list_lock, flags); 22262306a36Sopenharmony_ci list_del(&list->node); 22362306a36Sopenharmony_ci spin_unlock_irqrestore(&list->hiddev->list_lock, flags); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci mutex_lock(&list->hiddev->existancelock); 22662306a36Sopenharmony_ci if (!--list->hiddev->open) { 22762306a36Sopenharmony_ci if (list->hiddev->exist) { 22862306a36Sopenharmony_ci hid_hw_close(list->hiddev->hid); 22962306a36Sopenharmony_ci hid_hw_power(list->hiddev->hid, PM_HINT_NORMAL); 23062306a36Sopenharmony_ci } else { 23162306a36Sopenharmony_ci mutex_unlock(&list->hiddev->existancelock); 23262306a36Sopenharmony_ci kfree(list->hiddev); 23362306a36Sopenharmony_ci vfree(list); 23462306a36Sopenharmony_ci return 0; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci mutex_unlock(&list->hiddev->existancelock); 23962306a36Sopenharmony_ci vfree(list); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic int __hiddev_open(struct hiddev *hiddev, struct file *file) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci struct hiddev_list *list; 24762306a36Sopenharmony_ci int error; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci lockdep_assert_held(&hiddev->existancelock); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci list = vzalloc(sizeof(*list)); 25262306a36Sopenharmony_ci if (!list) 25362306a36Sopenharmony_ci return -ENOMEM; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci mutex_init(&list->thread_lock); 25662306a36Sopenharmony_ci list->hiddev = hiddev; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (!hiddev->open++) { 25962306a36Sopenharmony_ci error = hid_hw_power(hiddev->hid, PM_HINT_FULLON); 26062306a36Sopenharmony_ci if (error < 0) 26162306a36Sopenharmony_ci goto err_drop_count; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci error = hid_hw_open(hiddev->hid); 26462306a36Sopenharmony_ci if (error < 0) 26562306a36Sopenharmony_ci goto err_normal_power; 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci spin_lock_irq(&hiddev->list_lock); 26962306a36Sopenharmony_ci list_add_tail(&list->node, &hiddev->list); 27062306a36Sopenharmony_ci spin_unlock_irq(&hiddev->list_lock); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci file->private_data = list; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci return 0; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cierr_normal_power: 27762306a36Sopenharmony_ci hid_hw_power(hiddev->hid, PM_HINT_NORMAL); 27862306a36Sopenharmony_cierr_drop_count: 27962306a36Sopenharmony_ci hiddev->open--; 28062306a36Sopenharmony_ci vfree(list); 28162306a36Sopenharmony_ci return error; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci/* 28562306a36Sopenharmony_ci * open file op 28662306a36Sopenharmony_ci */ 28762306a36Sopenharmony_cistatic int hiddev_open(struct inode *inode, struct file *file) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct usb_interface *intf; 29062306a36Sopenharmony_ci struct hid_device *hid; 29162306a36Sopenharmony_ci struct hiddev *hiddev; 29262306a36Sopenharmony_ci int res; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci intf = usbhid_find_interface(iminor(inode)); 29562306a36Sopenharmony_ci if (!intf) 29662306a36Sopenharmony_ci return -ENODEV; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci hid = usb_get_intfdata(intf); 29962306a36Sopenharmony_ci hiddev = hid->hiddev; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci mutex_lock(&hiddev->existancelock); 30262306a36Sopenharmony_ci res = hiddev->exist ? __hiddev_open(hiddev, file) : -ENODEV; 30362306a36Sopenharmony_ci mutex_unlock(&hiddev->existancelock); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci return res; 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci/* 30962306a36Sopenharmony_ci * "write" file op 31062306a36Sopenharmony_ci */ 31162306a36Sopenharmony_cistatic ssize_t hiddev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci return -EINVAL; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci/* 31762306a36Sopenharmony_ci * "read" file op 31862306a36Sopenharmony_ci */ 31962306a36Sopenharmony_cistatic ssize_t hiddev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci DEFINE_WAIT(wait); 32262306a36Sopenharmony_ci struct hiddev_list *list = file->private_data; 32362306a36Sopenharmony_ci int event_size; 32462306a36Sopenharmony_ci int retval; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ? 32762306a36Sopenharmony_ci sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (count < event_size) 33062306a36Sopenharmony_ci return 0; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci /* lock against other threads */ 33362306a36Sopenharmony_ci retval = mutex_lock_interruptible(&list->thread_lock); 33462306a36Sopenharmony_ci if (retval) 33562306a36Sopenharmony_ci return -ERESTARTSYS; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci while (retval == 0) { 33862306a36Sopenharmony_ci if (list->head == list->tail) { 33962306a36Sopenharmony_ci prepare_to_wait(&list->hiddev->wait, &wait, TASK_INTERRUPTIBLE); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci while (list->head == list->tail) { 34262306a36Sopenharmony_ci if (signal_pending(current)) { 34362306a36Sopenharmony_ci retval = -ERESTARTSYS; 34462306a36Sopenharmony_ci break; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci if (!list->hiddev->exist) { 34762306a36Sopenharmony_ci retval = -EIO; 34862306a36Sopenharmony_ci break; 34962306a36Sopenharmony_ci } 35062306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) { 35162306a36Sopenharmony_ci retval = -EAGAIN; 35262306a36Sopenharmony_ci break; 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci /* let O_NONBLOCK tasks run */ 35662306a36Sopenharmony_ci mutex_unlock(&list->thread_lock); 35762306a36Sopenharmony_ci schedule(); 35862306a36Sopenharmony_ci if (mutex_lock_interruptible(&list->thread_lock)) { 35962306a36Sopenharmony_ci finish_wait(&list->hiddev->wait, &wait); 36062306a36Sopenharmony_ci return -EINTR; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 36362306a36Sopenharmony_ci } 36462306a36Sopenharmony_ci finish_wait(&list->hiddev->wait, &wait); 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci if (retval) { 36962306a36Sopenharmony_ci mutex_unlock(&list->thread_lock); 37062306a36Sopenharmony_ci return retval; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci while (list->head != list->tail && 37562306a36Sopenharmony_ci retval + event_size <= count) { 37662306a36Sopenharmony_ci if ((list->flags & HIDDEV_FLAG_UREF) == 0) { 37762306a36Sopenharmony_ci if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE) { 37862306a36Sopenharmony_ci struct hiddev_event event; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci event.hid = list->buffer[list->tail].usage_code; 38162306a36Sopenharmony_ci event.value = list->buffer[list->tail].value; 38262306a36Sopenharmony_ci if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) { 38362306a36Sopenharmony_ci mutex_unlock(&list->thread_lock); 38462306a36Sopenharmony_ci return -EFAULT; 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci retval += sizeof(struct hiddev_event); 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci } else { 38962306a36Sopenharmony_ci if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE || 39062306a36Sopenharmony_ci (list->flags & HIDDEV_FLAG_REPORT) != 0) { 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) { 39362306a36Sopenharmony_ci mutex_unlock(&list->thread_lock); 39462306a36Sopenharmony_ci return -EFAULT; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci retval += sizeof(struct hiddev_usage_ref); 39762306a36Sopenharmony_ci } 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1); 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci } 40362306a36Sopenharmony_ci mutex_unlock(&list->thread_lock); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci return retval; 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci/* 40962306a36Sopenharmony_ci * "poll" file op 41062306a36Sopenharmony_ci * No kernel lock - fine 41162306a36Sopenharmony_ci */ 41262306a36Sopenharmony_cistatic __poll_t hiddev_poll(struct file *file, poll_table *wait) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci struct hiddev_list *list = file->private_data; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci poll_wait(file, &list->hiddev->wait, wait); 41762306a36Sopenharmony_ci if (list->head != list->tail) 41862306a36Sopenharmony_ci return EPOLLIN | EPOLLRDNORM | EPOLLOUT; 41962306a36Sopenharmony_ci if (!list->hiddev->exist) 42062306a36Sopenharmony_ci return EPOLLERR | EPOLLHUP; 42162306a36Sopenharmony_ci return 0; 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci/* 42562306a36Sopenharmony_ci * "ioctl" file op 42662306a36Sopenharmony_ci */ 42762306a36Sopenharmony_cistatic noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd, void __user *user_arg) 42862306a36Sopenharmony_ci{ 42962306a36Sopenharmony_ci struct hid_device *hid = hiddev->hid; 43062306a36Sopenharmony_ci struct hiddev_report_info rinfo; 43162306a36Sopenharmony_ci struct hiddev_usage_ref_multi *uref_multi = NULL; 43262306a36Sopenharmony_ci struct hiddev_usage_ref *uref; 43362306a36Sopenharmony_ci struct hid_report *report; 43462306a36Sopenharmony_ci struct hid_field *field; 43562306a36Sopenharmony_ci int i; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL); 43862306a36Sopenharmony_ci if (!uref_multi) 43962306a36Sopenharmony_ci return -ENOMEM; 44062306a36Sopenharmony_ci uref = &uref_multi->uref; 44162306a36Sopenharmony_ci if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) { 44262306a36Sopenharmony_ci if (copy_from_user(uref_multi, user_arg, 44362306a36Sopenharmony_ci sizeof(*uref_multi))) 44462306a36Sopenharmony_ci goto fault; 44562306a36Sopenharmony_ci } else { 44662306a36Sopenharmony_ci if (copy_from_user(uref, user_arg, sizeof(*uref))) 44762306a36Sopenharmony_ci goto fault; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci switch (cmd) { 45162306a36Sopenharmony_ci case HIDIOCGUCODE: 45262306a36Sopenharmony_ci rinfo.report_type = uref->report_type; 45362306a36Sopenharmony_ci rinfo.report_id = uref->report_id; 45462306a36Sopenharmony_ci if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) 45562306a36Sopenharmony_ci goto inval; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci if (uref->field_index >= report->maxfield) 45862306a36Sopenharmony_ci goto inval; 45962306a36Sopenharmony_ci uref->field_index = array_index_nospec(uref->field_index, 46062306a36Sopenharmony_ci report->maxfield); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci field = report->field[uref->field_index]; 46362306a36Sopenharmony_ci if (uref->usage_index >= field->maxusage) 46462306a36Sopenharmony_ci goto inval; 46562306a36Sopenharmony_ci uref->usage_index = array_index_nospec(uref->usage_index, 46662306a36Sopenharmony_ci field->maxusage); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci uref->usage_code = field->usage[uref->usage_index].hid; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci if (copy_to_user(user_arg, uref, sizeof(*uref))) 47162306a36Sopenharmony_ci goto fault; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci goto goodreturn; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci default: 47662306a36Sopenharmony_ci if (cmd != HIDIOCGUSAGE && 47762306a36Sopenharmony_ci cmd != HIDIOCGUSAGES && 47862306a36Sopenharmony_ci uref->report_type == HID_REPORT_TYPE_INPUT) 47962306a36Sopenharmony_ci goto inval; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci if (uref->report_id == HID_REPORT_ID_UNKNOWN) { 48262306a36Sopenharmony_ci field = hiddev_lookup_usage(hid, uref); 48362306a36Sopenharmony_ci if (field == NULL) 48462306a36Sopenharmony_ci goto inval; 48562306a36Sopenharmony_ci } else { 48662306a36Sopenharmony_ci rinfo.report_type = uref->report_type; 48762306a36Sopenharmony_ci rinfo.report_id = uref->report_id; 48862306a36Sopenharmony_ci if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) 48962306a36Sopenharmony_ci goto inval; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci if (uref->field_index >= report->maxfield) 49262306a36Sopenharmony_ci goto inval; 49362306a36Sopenharmony_ci uref->field_index = array_index_nospec(uref->field_index, 49462306a36Sopenharmony_ci report->maxfield); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci field = report->field[uref->field_index]; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci if (cmd == HIDIOCGCOLLECTIONINDEX) { 49962306a36Sopenharmony_ci if (uref->usage_index >= field->maxusage) 50062306a36Sopenharmony_ci goto inval; 50162306a36Sopenharmony_ci uref->usage_index = 50262306a36Sopenharmony_ci array_index_nospec(uref->usage_index, 50362306a36Sopenharmony_ci field->maxusage); 50462306a36Sopenharmony_ci } else if (uref->usage_index >= field->report_count) 50562306a36Sopenharmony_ci goto inval; 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) { 50962306a36Sopenharmony_ci if (uref_multi->num_values > HID_MAX_MULTI_USAGES || 51062306a36Sopenharmony_ci uref->usage_index + uref_multi->num_values > 51162306a36Sopenharmony_ci field->report_count) 51262306a36Sopenharmony_ci goto inval; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci uref->usage_index = 51562306a36Sopenharmony_ci array_index_nospec(uref->usage_index, 51662306a36Sopenharmony_ci field->report_count - 51762306a36Sopenharmony_ci uref_multi->num_values); 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci switch (cmd) { 52162306a36Sopenharmony_ci case HIDIOCGUSAGE: 52262306a36Sopenharmony_ci if (uref->usage_index >= field->report_count) 52362306a36Sopenharmony_ci goto inval; 52462306a36Sopenharmony_ci uref->value = field->value[uref->usage_index]; 52562306a36Sopenharmony_ci if (copy_to_user(user_arg, uref, sizeof(*uref))) 52662306a36Sopenharmony_ci goto fault; 52762306a36Sopenharmony_ci goto goodreturn; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci case HIDIOCSUSAGE: 53062306a36Sopenharmony_ci if (uref->usage_index >= field->report_count) 53162306a36Sopenharmony_ci goto inval; 53262306a36Sopenharmony_ci field->value[uref->usage_index] = uref->value; 53362306a36Sopenharmony_ci goto goodreturn; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci case HIDIOCGCOLLECTIONINDEX: 53662306a36Sopenharmony_ci i = field->usage[uref->usage_index].collection_index; 53762306a36Sopenharmony_ci kfree(uref_multi); 53862306a36Sopenharmony_ci return i; 53962306a36Sopenharmony_ci case HIDIOCGUSAGES: 54062306a36Sopenharmony_ci for (i = 0; i < uref_multi->num_values; i++) 54162306a36Sopenharmony_ci uref_multi->values[i] = 54262306a36Sopenharmony_ci field->value[uref->usage_index + i]; 54362306a36Sopenharmony_ci if (copy_to_user(user_arg, uref_multi, 54462306a36Sopenharmony_ci sizeof(*uref_multi))) 54562306a36Sopenharmony_ci goto fault; 54662306a36Sopenharmony_ci goto goodreturn; 54762306a36Sopenharmony_ci case HIDIOCSUSAGES: 54862306a36Sopenharmony_ci for (i = 0; i < uref_multi->num_values; i++) 54962306a36Sopenharmony_ci field->value[uref->usage_index + i] = 55062306a36Sopenharmony_ci uref_multi->values[i]; 55162306a36Sopenharmony_ci goto goodreturn; 55262306a36Sopenharmony_ci } 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cigoodreturn: 55562306a36Sopenharmony_ci kfree(uref_multi); 55662306a36Sopenharmony_ci return 0; 55762306a36Sopenharmony_cifault: 55862306a36Sopenharmony_ci kfree(uref_multi); 55962306a36Sopenharmony_ci return -EFAULT; 56062306a36Sopenharmony_ciinval: 56162306a36Sopenharmony_ci kfree(uref_multi); 56262306a36Sopenharmony_ci return -EINVAL; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci} 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_cistatic noinline int hiddev_ioctl_string(struct hiddev *hiddev, unsigned int cmd, void __user *user_arg) 56762306a36Sopenharmony_ci{ 56862306a36Sopenharmony_ci struct hid_device *hid = hiddev->hid; 56962306a36Sopenharmony_ci struct usb_device *dev = hid_to_usb_dev(hid); 57062306a36Sopenharmony_ci int idx, len; 57162306a36Sopenharmony_ci char *buf; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci if (get_user(idx, (int __user *)user_arg)) 57462306a36Sopenharmony_ci return -EFAULT; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL) 57762306a36Sopenharmony_ci return -ENOMEM; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) { 58062306a36Sopenharmony_ci kfree(buf); 58162306a36Sopenharmony_ci return -EINVAL; 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci if (copy_to_user(user_arg+sizeof(int), buf, len+1)) { 58562306a36Sopenharmony_ci kfree(buf); 58662306a36Sopenharmony_ci return -EFAULT; 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci kfree(buf); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci return len; 59262306a36Sopenharmony_ci} 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_cistatic long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 59562306a36Sopenharmony_ci{ 59662306a36Sopenharmony_ci struct hiddev_list *list = file->private_data; 59762306a36Sopenharmony_ci struct hiddev *hiddev = list->hiddev; 59862306a36Sopenharmony_ci struct hid_device *hid; 59962306a36Sopenharmony_ci struct hiddev_collection_info cinfo; 60062306a36Sopenharmony_ci struct hiddev_report_info rinfo; 60162306a36Sopenharmony_ci struct hiddev_field_info finfo; 60262306a36Sopenharmony_ci struct hiddev_devinfo dinfo; 60362306a36Sopenharmony_ci struct hid_report *report; 60462306a36Sopenharmony_ci struct hid_field *field; 60562306a36Sopenharmony_ci void __user *user_arg = (void __user *)arg; 60662306a36Sopenharmony_ci int i, r = -EINVAL; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci /* Called without BKL by compat methods so no BKL taken */ 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci mutex_lock(&hiddev->existancelock); 61162306a36Sopenharmony_ci if (!hiddev->exist) { 61262306a36Sopenharmony_ci r = -ENODEV; 61362306a36Sopenharmony_ci goto ret_unlock; 61462306a36Sopenharmony_ci } 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci hid = hiddev->hid; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci switch (cmd) { 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci case HIDIOCGVERSION: 62162306a36Sopenharmony_ci r = put_user(HID_VERSION, (int __user *)arg) ? 62262306a36Sopenharmony_ci -EFAULT : 0; 62362306a36Sopenharmony_ci break; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci case HIDIOCAPPLICATION: 62662306a36Sopenharmony_ci if (arg >= hid->maxapplication) 62762306a36Sopenharmony_ci break; 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci for (i = 0; i < hid->maxcollection; i++) 63062306a36Sopenharmony_ci if (hid->collection[i].type == 63162306a36Sopenharmony_ci HID_COLLECTION_APPLICATION && arg-- == 0) 63262306a36Sopenharmony_ci break; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci if (i < hid->maxcollection) 63562306a36Sopenharmony_ci r = hid->collection[i].usage; 63662306a36Sopenharmony_ci break; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci case HIDIOCGDEVINFO: 63962306a36Sopenharmony_ci { 64062306a36Sopenharmony_ci struct usb_device *dev = hid_to_usb_dev(hid); 64162306a36Sopenharmony_ci struct usbhid_device *usbhid = hid->driver_data; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci memset(&dinfo, 0, sizeof(dinfo)); 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci dinfo.bustype = BUS_USB; 64662306a36Sopenharmony_ci dinfo.busnum = dev->bus->busnum; 64762306a36Sopenharmony_ci dinfo.devnum = dev->devnum; 64862306a36Sopenharmony_ci dinfo.ifnum = usbhid->ifnum; 64962306a36Sopenharmony_ci dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor); 65062306a36Sopenharmony_ci dinfo.product = le16_to_cpu(dev->descriptor.idProduct); 65162306a36Sopenharmony_ci dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice); 65262306a36Sopenharmony_ci dinfo.num_applications = hid->maxapplication; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci r = copy_to_user(user_arg, &dinfo, sizeof(dinfo)) ? 65562306a36Sopenharmony_ci -EFAULT : 0; 65662306a36Sopenharmony_ci break; 65762306a36Sopenharmony_ci } 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci case HIDIOCGFLAG: 66062306a36Sopenharmony_ci r = put_user(list->flags, (int __user *)arg) ? 66162306a36Sopenharmony_ci -EFAULT : 0; 66262306a36Sopenharmony_ci break; 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci case HIDIOCSFLAG: 66562306a36Sopenharmony_ci { 66662306a36Sopenharmony_ci int newflags; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci if (get_user(newflags, (int __user *)arg)) { 66962306a36Sopenharmony_ci r = -EFAULT; 67062306a36Sopenharmony_ci break; 67162306a36Sopenharmony_ci } 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci if ((newflags & ~HIDDEV_FLAGS) != 0 || 67462306a36Sopenharmony_ci ((newflags & HIDDEV_FLAG_REPORT) != 0 && 67562306a36Sopenharmony_ci (newflags & HIDDEV_FLAG_UREF) == 0)) 67662306a36Sopenharmony_ci break; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci list->flags = newflags; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci r = 0; 68162306a36Sopenharmony_ci break; 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci case HIDIOCGSTRING: 68562306a36Sopenharmony_ci r = hiddev_ioctl_string(hiddev, cmd, user_arg); 68662306a36Sopenharmony_ci break; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci case HIDIOCINITREPORT: 68962306a36Sopenharmony_ci usbhid_init_reports(hid); 69062306a36Sopenharmony_ci hiddev->initialized = true; 69162306a36Sopenharmony_ci r = 0; 69262306a36Sopenharmony_ci break; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci case HIDIOCGREPORT: 69562306a36Sopenharmony_ci if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) { 69662306a36Sopenharmony_ci r = -EFAULT; 69762306a36Sopenharmony_ci break; 69862306a36Sopenharmony_ci } 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT) 70162306a36Sopenharmony_ci break; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci report = hiddev_lookup_report(hid, &rinfo); 70462306a36Sopenharmony_ci if (report == NULL) 70562306a36Sopenharmony_ci break; 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci hid_hw_request(hid, report, HID_REQ_GET_REPORT); 70862306a36Sopenharmony_ci hid_hw_wait(hid); 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci r = 0; 71162306a36Sopenharmony_ci break; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci case HIDIOCSREPORT: 71462306a36Sopenharmony_ci if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) { 71562306a36Sopenharmony_ci r = -EFAULT; 71662306a36Sopenharmony_ci break; 71762306a36Sopenharmony_ci } 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci if (rinfo.report_type == HID_REPORT_TYPE_INPUT) 72062306a36Sopenharmony_ci break; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci report = hiddev_lookup_report(hid, &rinfo); 72362306a36Sopenharmony_ci if (report == NULL) 72462306a36Sopenharmony_ci break; 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci hid_hw_request(hid, report, HID_REQ_SET_REPORT); 72762306a36Sopenharmony_ci hid_hw_wait(hid); 72862306a36Sopenharmony_ci 72962306a36Sopenharmony_ci r = 0; 73062306a36Sopenharmony_ci break; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci case HIDIOCGREPORTINFO: 73362306a36Sopenharmony_ci if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) { 73462306a36Sopenharmony_ci r = -EFAULT; 73562306a36Sopenharmony_ci break; 73662306a36Sopenharmony_ci } 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci report = hiddev_lookup_report(hid, &rinfo); 73962306a36Sopenharmony_ci if (report == NULL) 74062306a36Sopenharmony_ci break; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci rinfo.num_fields = report->maxfield; 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci r = copy_to_user(user_arg, &rinfo, sizeof(rinfo)) ? 74562306a36Sopenharmony_ci -EFAULT : 0; 74662306a36Sopenharmony_ci break; 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci case HIDIOCGFIELDINFO: 74962306a36Sopenharmony_ci if (copy_from_user(&finfo, user_arg, sizeof(finfo))) { 75062306a36Sopenharmony_ci r = -EFAULT; 75162306a36Sopenharmony_ci break; 75262306a36Sopenharmony_ci } 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci rinfo.report_type = finfo.report_type; 75562306a36Sopenharmony_ci rinfo.report_id = finfo.report_id; 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci report = hiddev_lookup_report(hid, &rinfo); 75862306a36Sopenharmony_ci if (report == NULL) 75962306a36Sopenharmony_ci break; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci if (finfo.field_index >= report->maxfield) 76262306a36Sopenharmony_ci break; 76362306a36Sopenharmony_ci finfo.field_index = array_index_nospec(finfo.field_index, 76462306a36Sopenharmony_ci report->maxfield); 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci field = report->field[finfo.field_index]; 76762306a36Sopenharmony_ci memset(&finfo, 0, sizeof(finfo)); 76862306a36Sopenharmony_ci finfo.report_type = rinfo.report_type; 76962306a36Sopenharmony_ci finfo.report_id = rinfo.report_id; 77062306a36Sopenharmony_ci finfo.field_index = field->report_count - 1; 77162306a36Sopenharmony_ci finfo.maxusage = field->maxusage; 77262306a36Sopenharmony_ci finfo.flags = field->flags; 77362306a36Sopenharmony_ci finfo.physical = field->physical; 77462306a36Sopenharmony_ci finfo.logical = field->logical; 77562306a36Sopenharmony_ci finfo.application = field->application; 77662306a36Sopenharmony_ci finfo.logical_minimum = field->logical_minimum; 77762306a36Sopenharmony_ci finfo.logical_maximum = field->logical_maximum; 77862306a36Sopenharmony_ci finfo.physical_minimum = field->physical_minimum; 77962306a36Sopenharmony_ci finfo.physical_maximum = field->physical_maximum; 78062306a36Sopenharmony_ci finfo.unit_exponent = field->unit_exponent; 78162306a36Sopenharmony_ci finfo.unit = field->unit; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci r = copy_to_user(user_arg, &finfo, sizeof(finfo)) ? 78462306a36Sopenharmony_ci -EFAULT : 0; 78562306a36Sopenharmony_ci break; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci case HIDIOCGUCODE: 78862306a36Sopenharmony_ci case HIDIOCGUSAGE: 78962306a36Sopenharmony_ci case HIDIOCSUSAGE: 79062306a36Sopenharmony_ci case HIDIOCGUSAGES: 79162306a36Sopenharmony_ci case HIDIOCSUSAGES: 79262306a36Sopenharmony_ci case HIDIOCGCOLLECTIONINDEX: 79362306a36Sopenharmony_ci if (!hiddev->initialized) { 79462306a36Sopenharmony_ci usbhid_init_reports(hid); 79562306a36Sopenharmony_ci hiddev->initialized = true; 79662306a36Sopenharmony_ci } 79762306a36Sopenharmony_ci r = hiddev_ioctl_usage(hiddev, cmd, user_arg); 79862306a36Sopenharmony_ci break; 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci case HIDIOCGCOLLECTIONINFO: 80162306a36Sopenharmony_ci if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) { 80262306a36Sopenharmony_ci r = -EFAULT; 80362306a36Sopenharmony_ci break; 80462306a36Sopenharmony_ci } 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci if (cinfo.index >= hid->maxcollection) 80762306a36Sopenharmony_ci break; 80862306a36Sopenharmony_ci cinfo.index = array_index_nospec(cinfo.index, 80962306a36Sopenharmony_ci hid->maxcollection); 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci cinfo.type = hid->collection[cinfo.index].type; 81262306a36Sopenharmony_ci cinfo.usage = hid->collection[cinfo.index].usage; 81362306a36Sopenharmony_ci cinfo.level = hid->collection[cinfo.index].level; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci r = copy_to_user(user_arg, &cinfo, sizeof(cinfo)) ? 81662306a36Sopenharmony_ci -EFAULT : 0; 81762306a36Sopenharmony_ci break; 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci default: 82062306a36Sopenharmony_ci if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) 82162306a36Sopenharmony_ci break; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) { 82462306a36Sopenharmony_ci int len = strlen(hid->name) + 1; 82562306a36Sopenharmony_ci if (len > _IOC_SIZE(cmd)) 82662306a36Sopenharmony_ci len = _IOC_SIZE(cmd); 82762306a36Sopenharmony_ci r = copy_to_user(user_arg, hid->name, len) ? 82862306a36Sopenharmony_ci -EFAULT : len; 82962306a36Sopenharmony_ci break; 83062306a36Sopenharmony_ci } 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) { 83362306a36Sopenharmony_ci int len = strlen(hid->phys) + 1; 83462306a36Sopenharmony_ci if (len > _IOC_SIZE(cmd)) 83562306a36Sopenharmony_ci len = _IOC_SIZE(cmd); 83662306a36Sopenharmony_ci r = copy_to_user(user_arg, hid->phys, len) ? 83762306a36Sopenharmony_ci -EFAULT : len; 83862306a36Sopenharmony_ci break; 83962306a36Sopenharmony_ci } 84062306a36Sopenharmony_ci } 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ciret_unlock: 84362306a36Sopenharmony_ci mutex_unlock(&hiddev->existancelock); 84462306a36Sopenharmony_ci return r; 84562306a36Sopenharmony_ci} 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_cistatic const struct file_operations hiddev_fops = { 84862306a36Sopenharmony_ci .owner = THIS_MODULE, 84962306a36Sopenharmony_ci .read = hiddev_read, 85062306a36Sopenharmony_ci .write = hiddev_write, 85162306a36Sopenharmony_ci .poll = hiddev_poll, 85262306a36Sopenharmony_ci .open = hiddev_open, 85362306a36Sopenharmony_ci .release = hiddev_release, 85462306a36Sopenharmony_ci .unlocked_ioctl = hiddev_ioctl, 85562306a36Sopenharmony_ci .fasync = hiddev_fasync, 85662306a36Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 85762306a36Sopenharmony_ci .llseek = noop_llseek, 85862306a36Sopenharmony_ci}; 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_cistatic char *hiddev_devnode(const struct device *dev, umode_t *mode) 86162306a36Sopenharmony_ci{ 86262306a36Sopenharmony_ci return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev)); 86362306a36Sopenharmony_ci} 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_cistatic struct usb_class_driver hiddev_class = { 86662306a36Sopenharmony_ci .name = "hiddev%d", 86762306a36Sopenharmony_ci .devnode = hiddev_devnode, 86862306a36Sopenharmony_ci .fops = &hiddev_fops, 86962306a36Sopenharmony_ci .minor_base = HIDDEV_MINOR_BASE, 87062306a36Sopenharmony_ci}; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci/* 87362306a36Sopenharmony_ci * This is where hid.c calls us to connect a hid device to the hiddev driver 87462306a36Sopenharmony_ci */ 87562306a36Sopenharmony_ciint hiddev_connect(struct hid_device *hid, unsigned int force) 87662306a36Sopenharmony_ci{ 87762306a36Sopenharmony_ci struct hiddev *hiddev; 87862306a36Sopenharmony_ci struct usbhid_device *usbhid = hid->driver_data; 87962306a36Sopenharmony_ci int retval; 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci if (!force) { 88262306a36Sopenharmony_ci unsigned int i; 88362306a36Sopenharmony_ci for (i = 0; i < hid->maxcollection; i++) 88462306a36Sopenharmony_ci if (hid->collection[i].type == 88562306a36Sopenharmony_ci HID_COLLECTION_APPLICATION && 88662306a36Sopenharmony_ci !IS_INPUT_APPLICATION(hid->collection[i].usage)) 88762306a36Sopenharmony_ci break; 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci if (i == hid->maxcollection) 89062306a36Sopenharmony_ci return -EINVAL; 89162306a36Sopenharmony_ci } 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ci if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL))) 89462306a36Sopenharmony_ci return -ENOMEM; 89562306a36Sopenharmony_ci 89662306a36Sopenharmony_ci init_waitqueue_head(&hiddev->wait); 89762306a36Sopenharmony_ci INIT_LIST_HEAD(&hiddev->list); 89862306a36Sopenharmony_ci spin_lock_init(&hiddev->list_lock); 89962306a36Sopenharmony_ci mutex_init(&hiddev->existancelock); 90062306a36Sopenharmony_ci hid->hiddev = hiddev; 90162306a36Sopenharmony_ci hiddev->hid = hid; 90262306a36Sopenharmony_ci hiddev->exist = 1; 90362306a36Sopenharmony_ci retval = usb_register_dev(usbhid->intf, &hiddev_class); 90462306a36Sopenharmony_ci if (retval) { 90562306a36Sopenharmony_ci hid_err(hid, "Not able to get a minor for this device\n"); 90662306a36Sopenharmony_ci hid->hiddev = NULL; 90762306a36Sopenharmony_ci kfree(hiddev); 90862306a36Sopenharmony_ci return retval; 90962306a36Sopenharmony_ci } 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ci /* 91262306a36Sopenharmony_ci * If HID_QUIRK_NO_INIT_REPORTS is set, make sure we don't initialize 91362306a36Sopenharmony_ci * the reports. 91462306a36Sopenharmony_ci */ 91562306a36Sopenharmony_ci hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS; 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci hiddev->minor = usbhid->intf->minor; 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci return 0; 92062306a36Sopenharmony_ci} 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci/* 92362306a36Sopenharmony_ci * This is where hid.c calls us to disconnect a hiddev device from the 92462306a36Sopenharmony_ci * corresponding hid device (usually because the usb device has disconnected) 92562306a36Sopenharmony_ci */ 92662306a36Sopenharmony_cistatic struct usb_class_driver hiddev_class; 92762306a36Sopenharmony_civoid hiddev_disconnect(struct hid_device *hid) 92862306a36Sopenharmony_ci{ 92962306a36Sopenharmony_ci struct hiddev *hiddev = hid->hiddev; 93062306a36Sopenharmony_ci struct usbhid_device *usbhid = hid->driver_data; 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci usb_deregister_dev(usbhid->intf, &hiddev_class); 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci mutex_lock(&hiddev->existancelock); 93562306a36Sopenharmony_ci hiddev->exist = 0; 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci if (hiddev->open) { 93862306a36Sopenharmony_ci hid_hw_close(hiddev->hid); 93962306a36Sopenharmony_ci wake_up_interruptible(&hiddev->wait); 94062306a36Sopenharmony_ci mutex_unlock(&hiddev->existancelock); 94162306a36Sopenharmony_ci } else { 94262306a36Sopenharmony_ci mutex_unlock(&hiddev->existancelock); 94362306a36Sopenharmony_ci kfree(hiddev); 94462306a36Sopenharmony_ci } 94562306a36Sopenharmony_ci} 946