162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HID raw devices, giving access to raw HID events.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * In comparison to hiddev, this device does not process the
662306a36Sopenharmony_ci * hid events at all (no parsing, no lookups). This lets applications
762306a36Sopenharmony_ci * to work on raw hid events as they want to, and avoids a need to
862306a36Sopenharmony_ci * use a transport-specific userspace libhid/libusb libraries.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci *  Copyright (c) 2007-2014 Jiri Kosina
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/fs.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/errno.h>
1962306a36Sopenharmony_ci#include <linux/kernel.h>
2062306a36Sopenharmony_ci#include <linux/init.h>
2162306a36Sopenharmony_ci#include <linux/cdev.h>
2262306a36Sopenharmony_ci#include <linux/poll.h>
2362306a36Sopenharmony_ci#include <linux/device.h>
2462306a36Sopenharmony_ci#include <linux/major.h>
2562306a36Sopenharmony_ci#include <linux/slab.h>
2662306a36Sopenharmony_ci#include <linux/hid.h>
2762306a36Sopenharmony_ci#include <linux/mutex.h>
2862306a36Sopenharmony_ci#include <linux/sched/signal.h>
2962306a36Sopenharmony_ci#include <linux/string.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include <linux/hidraw.h>
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int hidraw_major;
3462306a36Sopenharmony_cistatic struct cdev hidraw_cdev;
3562306a36Sopenharmony_cistatic const struct class hidraw_class = {
3662306a36Sopenharmony_ci	.name = "hidraw",
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_cistatic struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES];
3962306a36Sopenharmony_cistatic DECLARE_RWSEM(minors_rwsem);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct hidraw_list *list = file->private_data;
4462306a36Sopenharmony_ci	int ret = 0, len;
4562306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	mutex_lock(&list->read_mutex);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	while (ret == 0) {
5062306a36Sopenharmony_ci		if (list->head == list->tail) {
5162306a36Sopenharmony_ci			add_wait_queue(&list->hidraw->wait, &wait);
5262306a36Sopenharmony_ci			set_current_state(TASK_INTERRUPTIBLE);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci			while (list->head == list->tail) {
5562306a36Sopenharmony_ci				if (signal_pending(current)) {
5662306a36Sopenharmony_ci					ret = -ERESTARTSYS;
5762306a36Sopenharmony_ci					break;
5862306a36Sopenharmony_ci				}
5962306a36Sopenharmony_ci				if (!list->hidraw->exist) {
6062306a36Sopenharmony_ci					ret = -EIO;
6162306a36Sopenharmony_ci					break;
6262306a36Sopenharmony_ci				}
6362306a36Sopenharmony_ci				if (file->f_flags & O_NONBLOCK) {
6462306a36Sopenharmony_ci					ret = -EAGAIN;
6562306a36Sopenharmony_ci					break;
6662306a36Sopenharmony_ci				}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci				/* allow O_NONBLOCK to work well from other threads */
6962306a36Sopenharmony_ci				mutex_unlock(&list->read_mutex);
7062306a36Sopenharmony_ci				schedule();
7162306a36Sopenharmony_ci				mutex_lock(&list->read_mutex);
7262306a36Sopenharmony_ci				set_current_state(TASK_INTERRUPTIBLE);
7362306a36Sopenharmony_ci			}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci			set_current_state(TASK_RUNNING);
7662306a36Sopenharmony_ci			remove_wait_queue(&list->hidraw->wait, &wait);
7762306a36Sopenharmony_ci		}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		if (ret)
8062306a36Sopenharmony_ci			goto out;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		len = list->buffer[list->tail].len > count ?
8362306a36Sopenharmony_ci			count : list->buffer[list->tail].len;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		if (list->buffer[list->tail].value) {
8662306a36Sopenharmony_ci			if (copy_to_user(buffer, list->buffer[list->tail].value, len)) {
8762306a36Sopenharmony_ci				ret = -EFAULT;
8862306a36Sopenharmony_ci				goto out;
8962306a36Sopenharmony_ci			}
9062306a36Sopenharmony_ci			ret = len;
9162306a36Sopenharmony_ci		}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		kfree(list->buffer[list->tail].value);
9462306a36Sopenharmony_ci		list->buffer[list->tail].value = NULL;
9562306a36Sopenharmony_ci		list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ciout:
9862306a36Sopenharmony_ci	mutex_unlock(&list->read_mutex);
9962306a36Sopenharmony_ci	return ret;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/*
10362306a36Sopenharmony_ci * The first byte of the report buffer is expected to be a report number.
10462306a36Sopenharmony_ci */
10562306a36Sopenharmony_cistatic ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	unsigned int minor = iminor(file_inode(file));
10862306a36Sopenharmony_ci	struct hid_device *dev;
10962306a36Sopenharmony_ci	__u8 *buf;
11062306a36Sopenharmony_ci	int ret = 0;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	lockdep_assert_held(&minors_rwsem);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
11562306a36Sopenharmony_ci		ret = -ENODEV;
11662306a36Sopenharmony_ci		goto out;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	dev = hidraw_table[minor]->hid;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	if (count > HID_MAX_BUFFER_SIZE) {
12262306a36Sopenharmony_ci		hid_warn(dev, "pid %d passed too large report\n",
12362306a36Sopenharmony_ci			 task_pid_nr(current));
12462306a36Sopenharmony_ci		ret = -EINVAL;
12562306a36Sopenharmony_ci		goto out;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (count < 2) {
12962306a36Sopenharmony_ci		hid_warn(dev, "pid %d passed too short report\n",
13062306a36Sopenharmony_ci			 task_pid_nr(current));
13162306a36Sopenharmony_ci		ret = -EINVAL;
13262306a36Sopenharmony_ci		goto out;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	buf = memdup_user(buffer, count);
13662306a36Sopenharmony_ci	if (IS_ERR(buf)) {
13762306a36Sopenharmony_ci		ret = PTR_ERR(buf);
13862306a36Sopenharmony_ci		goto out;
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	if ((report_type == HID_OUTPUT_REPORT) &&
14262306a36Sopenharmony_ci	    !(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) {
14362306a36Sopenharmony_ci		ret = hid_hw_output_report(dev, buf, count);
14462306a36Sopenharmony_ci		/*
14562306a36Sopenharmony_ci		 * compatibility with old implementation of USB-HID and I2C-HID:
14662306a36Sopenharmony_ci		 * if the device does not support receiving output reports,
14762306a36Sopenharmony_ci		 * on an interrupt endpoint, fallback to SET_REPORT HID command.
14862306a36Sopenharmony_ci		 */
14962306a36Sopenharmony_ci		if (ret != -ENOSYS)
15062306a36Sopenharmony_ci			goto out_free;
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type,
15462306a36Sopenharmony_ci				HID_REQ_SET_REPORT);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ciout_free:
15762306a36Sopenharmony_ci	kfree(buf);
15862306a36Sopenharmony_ciout:
15962306a36Sopenharmony_ci	return ret;
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	ssize_t ret;
16562306a36Sopenharmony_ci	down_read(&minors_rwsem);
16662306a36Sopenharmony_ci	ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
16762306a36Sopenharmony_ci	up_read(&minors_rwsem);
16862306a36Sopenharmony_ci	return ret;
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci/*
17362306a36Sopenharmony_ci * This function performs a Get_Report transfer over the control endpoint
17462306a36Sopenharmony_ci * per section 7.2.1 of the HID specification, version 1.1.  The first byte
17562306a36Sopenharmony_ci * of buffer is the report number to request, or 0x0 if the device does not
17662306a36Sopenharmony_ci * use numbered reports. The report_type parameter can be HID_FEATURE_REPORT
17762306a36Sopenharmony_ci * or HID_INPUT_REPORT.
17862306a36Sopenharmony_ci */
17962306a36Sopenharmony_cistatic ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	unsigned int minor = iminor(file_inode(file));
18262306a36Sopenharmony_ci	struct hid_device *dev;
18362306a36Sopenharmony_ci	__u8 *buf;
18462306a36Sopenharmony_ci	int ret = 0, len;
18562306a36Sopenharmony_ci	unsigned char report_number;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	lockdep_assert_held(&minors_rwsem);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
19062306a36Sopenharmony_ci		ret = -ENODEV;
19162306a36Sopenharmony_ci		goto out;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	dev = hidraw_table[minor]->hid;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (!dev->ll_driver->raw_request) {
19762306a36Sopenharmony_ci		ret = -ENODEV;
19862306a36Sopenharmony_ci		goto out;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	if (count > HID_MAX_BUFFER_SIZE) {
20262306a36Sopenharmony_ci		hid_warn(dev, "pid %d passed too large report\n",
20362306a36Sopenharmony_ci			task_pid_nr(current));
20462306a36Sopenharmony_ci		ret = -EINVAL;
20562306a36Sopenharmony_ci		goto out;
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (count < 2) {
20962306a36Sopenharmony_ci		hid_warn(dev, "pid %d passed too short report\n",
21062306a36Sopenharmony_ci			task_pid_nr(current));
21162306a36Sopenharmony_ci		ret = -EINVAL;
21262306a36Sopenharmony_ci		goto out;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	buf = kmalloc(count, GFP_KERNEL);
21662306a36Sopenharmony_ci	if (!buf) {
21762306a36Sopenharmony_ci		ret = -ENOMEM;
21862306a36Sopenharmony_ci		goto out;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/*
22262306a36Sopenharmony_ci	 * Read the first byte from the user. This is the report number,
22362306a36Sopenharmony_ci	 * which is passed to hid_hw_raw_request().
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	if (copy_from_user(&report_number, buffer, 1)) {
22662306a36Sopenharmony_ci		ret = -EFAULT;
22762306a36Sopenharmony_ci		goto out_free;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ret = hid_hw_raw_request(dev, report_number, buf, count, report_type,
23162306a36Sopenharmony_ci				 HID_REQ_GET_REPORT);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	if (ret < 0)
23462306a36Sopenharmony_ci		goto out_free;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	len = (ret < count) ? ret : count;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	if (copy_to_user(buffer, buf, len)) {
23962306a36Sopenharmony_ci		ret = -EFAULT;
24062306a36Sopenharmony_ci		goto out_free;
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	ret = len;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ciout_free:
24662306a36Sopenharmony_ci	kfree(buf);
24762306a36Sopenharmony_ciout:
24862306a36Sopenharmony_ci	return ret;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic __poll_t hidraw_poll(struct file *file, poll_table *wait)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	struct hidraw_list *list = file->private_data;
25462306a36Sopenharmony_ci	__poll_t mask = EPOLLOUT | EPOLLWRNORM; /* hidraw is always writable */
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	poll_wait(file, &list->hidraw->wait, wait);
25762306a36Sopenharmony_ci	if (list->head != list->tail)
25862306a36Sopenharmony_ci		mask |= EPOLLIN | EPOLLRDNORM;
25962306a36Sopenharmony_ci	if (!list->hidraw->exist)
26062306a36Sopenharmony_ci		mask |= EPOLLERR | EPOLLHUP;
26162306a36Sopenharmony_ci	return mask;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic int hidraw_open(struct inode *inode, struct file *file)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	unsigned int minor = iminor(inode);
26762306a36Sopenharmony_ci	struct hidraw *dev;
26862306a36Sopenharmony_ci	struct hidraw_list *list;
26962306a36Sopenharmony_ci	unsigned long flags;
27062306a36Sopenharmony_ci	int err = 0;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) {
27362306a36Sopenharmony_ci		err = -ENOMEM;
27462306a36Sopenharmony_ci		goto out;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	/*
27862306a36Sopenharmony_ci	 * Technically not writing to the hidraw_table but a write lock is
27962306a36Sopenharmony_ci	 * required to protect the device refcount. This is symmetrical to
28062306a36Sopenharmony_ci	 * hidraw_release().
28162306a36Sopenharmony_ci	 */
28262306a36Sopenharmony_ci	down_write(&minors_rwsem);
28362306a36Sopenharmony_ci	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
28462306a36Sopenharmony_ci		err = -ENODEV;
28562306a36Sopenharmony_ci		goto out_unlock;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	dev = hidraw_table[minor];
28962306a36Sopenharmony_ci	if (!dev->open++) {
29062306a36Sopenharmony_ci		err = hid_hw_power(dev->hid, PM_HINT_FULLON);
29162306a36Sopenharmony_ci		if (err < 0) {
29262306a36Sopenharmony_ci			dev->open--;
29362306a36Sopenharmony_ci			goto out_unlock;
29462306a36Sopenharmony_ci		}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci		err = hid_hw_open(dev->hid);
29762306a36Sopenharmony_ci		if (err < 0) {
29862306a36Sopenharmony_ci			hid_hw_power(dev->hid, PM_HINT_NORMAL);
29962306a36Sopenharmony_ci			dev->open--;
30062306a36Sopenharmony_ci			goto out_unlock;
30162306a36Sopenharmony_ci		}
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	list->hidraw = hidraw_table[minor];
30562306a36Sopenharmony_ci	mutex_init(&list->read_mutex);
30662306a36Sopenharmony_ci	spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
30762306a36Sopenharmony_ci	list_add_tail(&list->node, &hidraw_table[minor]->list);
30862306a36Sopenharmony_ci	spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
30962306a36Sopenharmony_ci	file->private_data = list;
31062306a36Sopenharmony_ciout_unlock:
31162306a36Sopenharmony_ci	up_write(&minors_rwsem);
31262306a36Sopenharmony_ciout:
31362306a36Sopenharmony_ci	if (err < 0)
31462306a36Sopenharmony_ci		kfree(list);
31562306a36Sopenharmony_ci	return err;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic int hidraw_fasync(int fd, struct file *file, int on)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	struct hidraw_list *list = file->private_data;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return fasync_helper(fd, file, on, &list->fasync);
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic void drop_ref(struct hidraw *hidraw, int exists_bit)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	if (exists_bit) {
32962306a36Sopenharmony_ci		hidraw->exist = 0;
33062306a36Sopenharmony_ci		if (hidraw->open) {
33162306a36Sopenharmony_ci			hid_hw_close(hidraw->hid);
33262306a36Sopenharmony_ci			wake_up_interruptible(&hidraw->wait);
33362306a36Sopenharmony_ci		}
33462306a36Sopenharmony_ci		device_destroy(&hidraw_class,
33562306a36Sopenharmony_ci			       MKDEV(hidraw_major, hidraw->minor));
33662306a36Sopenharmony_ci	} else {
33762306a36Sopenharmony_ci		--hidraw->open;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci	if (!hidraw->open) {
34062306a36Sopenharmony_ci		if (!hidraw->exist) {
34162306a36Sopenharmony_ci			hidraw_table[hidraw->minor] = NULL;
34262306a36Sopenharmony_ci			kfree(hidraw);
34362306a36Sopenharmony_ci		} else {
34462306a36Sopenharmony_ci			/* close device for last reader */
34562306a36Sopenharmony_ci			hid_hw_close(hidraw->hid);
34662306a36Sopenharmony_ci			hid_hw_power(hidraw->hid, PM_HINT_NORMAL);
34762306a36Sopenharmony_ci		}
34862306a36Sopenharmony_ci	}
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic int hidraw_release(struct inode * inode, struct file * file)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	unsigned int minor = iminor(inode);
35462306a36Sopenharmony_ci	struct hidraw_list *list = file->private_data;
35562306a36Sopenharmony_ci	unsigned long flags;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	down_write(&minors_rwsem);
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
36062306a36Sopenharmony_ci	while (list->tail != list->head) {
36162306a36Sopenharmony_ci		kfree(list->buffer[list->tail].value);
36262306a36Sopenharmony_ci		list->buffer[list->tail].value = NULL;
36362306a36Sopenharmony_ci		list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
36462306a36Sopenharmony_ci	}
36562306a36Sopenharmony_ci	list_del(&list->node);
36662306a36Sopenharmony_ci	spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
36762306a36Sopenharmony_ci	kfree(list);
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	drop_ref(hidraw_table[minor], 0);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	up_write(&minors_rwsem);
37262306a36Sopenharmony_ci	return 0;
37362306a36Sopenharmony_ci}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_cistatic long hidraw_ioctl(struct file *file, unsigned int cmd,
37662306a36Sopenharmony_ci							unsigned long arg)
37762306a36Sopenharmony_ci{
37862306a36Sopenharmony_ci	struct inode *inode = file_inode(file);
37962306a36Sopenharmony_ci	unsigned int minor = iminor(inode);
38062306a36Sopenharmony_ci	long ret = 0;
38162306a36Sopenharmony_ci	struct hidraw *dev;
38262306a36Sopenharmony_ci	void __user *user_arg = (void __user*) arg;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	down_read(&minors_rwsem);
38562306a36Sopenharmony_ci	dev = hidraw_table[minor];
38662306a36Sopenharmony_ci	if (!dev || !dev->exist) {
38762306a36Sopenharmony_ci		ret = -ENODEV;
38862306a36Sopenharmony_ci		goto out;
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	switch (cmd) {
39262306a36Sopenharmony_ci		case HIDIOCGRDESCSIZE:
39362306a36Sopenharmony_ci			if (put_user(dev->hid->rsize, (int __user *)arg))
39462306a36Sopenharmony_ci				ret = -EFAULT;
39562306a36Sopenharmony_ci			break;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci		case HIDIOCGRDESC:
39862306a36Sopenharmony_ci			{
39962306a36Sopenharmony_ci				__u32 len;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci				if (get_user(len, (int __user *)arg))
40262306a36Sopenharmony_ci					ret = -EFAULT;
40362306a36Sopenharmony_ci				else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
40462306a36Sopenharmony_ci					ret = -EINVAL;
40562306a36Sopenharmony_ci				else if (copy_to_user(user_arg + offsetof(
40662306a36Sopenharmony_ci					struct hidraw_report_descriptor,
40762306a36Sopenharmony_ci					value[0]),
40862306a36Sopenharmony_ci					dev->hid->rdesc,
40962306a36Sopenharmony_ci					min(dev->hid->rsize, len)))
41062306a36Sopenharmony_ci					ret = -EFAULT;
41162306a36Sopenharmony_ci				break;
41262306a36Sopenharmony_ci			}
41362306a36Sopenharmony_ci		case HIDIOCGRAWINFO:
41462306a36Sopenharmony_ci			{
41562306a36Sopenharmony_ci				struct hidraw_devinfo dinfo;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci				dinfo.bustype = dev->hid->bus;
41862306a36Sopenharmony_ci				dinfo.vendor = dev->hid->vendor;
41962306a36Sopenharmony_ci				dinfo.product = dev->hid->product;
42062306a36Sopenharmony_ci				if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
42162306a36Sopenharmony_ci					ret = -EFAULT;
42262306a36Sopenharmony_ci				break;
42362306a36Sopenharmony_ci			}
42462306a36Sopenharmony_ci		default:
42562306a36Sopenharmony_ci			{
42662306a36Sopenharmony_ci				struct hid_device *hid = dev->hid;
42762306a36Sopenharmony_ci				if (_IOC_TYPE(cmd) != 'H') {
42862306a36Sopenharmony_ci					ret = -EINVAL;
42962306a36Sopenharmony_ci					break;
43062306a36Sopenharmony_ci				}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
43362306a36Sopenharmony_ci					int len = _IOC_SIZE(cmd);
43462306a36Sopenharmony_ci					ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
43562306a36Sopenharmony_ci					break;
43662306a36Sopenharmony_ci				}
43762306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
43862306a36Sopenharmony_ci					int len = _IOC_SIZE(cmd);
43962306a36Sopenharmony_ci					ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
44062306a36Sopenharmony_ci					break;
44162306a36Sopenharmony_ci				}
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
44462306a36Sopenharmony_ci					int len = _IOC_SIZE(cmd);
44562306a36Sopenharmony_ci					ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
44662306a36Sopenharmony_ci					break;
44762306a36Sopenharmony_ci				}
44862306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
44962306a36Sopenharmony_ci					int len = _IOC_SIZE(cmd);
45062306a36Sopenharmony_ci					ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
45162306a36Sopenharmony_ci					break;
45262306a36Sopenharmony_ci				}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
45562306a36Sopenharmony_ci					int len = _IOC_SIZE(cmd);
45662306a36Sopenharmony_ci					ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
45762306a36Sopenharmony_ci					break;
45862306a36Sopenharmony_ci				}
45962306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
46062306a36Sopenharmony_ci					int len = _IOC_SIZE(cmd);
46162306a36Sopenharmony_ci					ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
46262306a36Sopenharmony_ci					break;
46362306a36Sopenharmony_ci				}
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci				/* Begin Read-only ioctls. */
46662306a36Sopenharmony_ci				if (_IOC_DIR(cmd) != _IOC_READ) {
46762306a36Sopenharmony_ci					ret = -EINVAL;
46862306a36Sopenharmony_ci					break;
46962306a36Sopenharmony_ci				}
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
47262306a36Sopenharmony_ci					int len = strlen(hid->name) + 1;
47362306a36Sopenharmony_ci					if (len > _IOC_SIZE(cmd))
47462306a36Sopenharmony_ci						len = _IOC_SIZE(cmd);
47562306a36Sopenharmony_ci					ret = copy_to_user(user_arg, hid->name, len) ?
47662306a36Sopenharmony_ci						-EFAULT : len;
47762306a36Sopenharmony_ci					break;
47862306a36Sopenharmony_ci				}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
48162306a36Sopenharmony_ci					int len = strlen(hid->phys) + 1;
48262306a36Sopenharmony_ci					if (len > _IOC_SIZE(cmd))
48362306a36Sopenharmony_ci						len = _IOC_SIZE(cmd);
48462306a36Sopenharmony_ci					ret = copy_to_user(user_arg, hid->phys, len) ?
48562306a36Sopenharmony_ci						-EFAULT : len;
48662306a36Sopenharmony_ci					break;
48762306a36Sopenharmony_ci				}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
49062306a36Sopenharmony_ci					int len = strlen(hid->uniq) + 1;
49162306a36Sopenharmony_ci					if (len > _IOC_SIZE(cmd))
49262306a36Sopenharmony_ci						len = _IOC_SIZE(cmd);
49362306a36Sopenharmony_ci					ret = copy_to_user(user_arg, hid->uniq, len) ?
49462306a36Sopenharmony_ci						-EFAULT : len;
49562306a36Sopenharmony_ci					break;
49662306a36Sopenharmony_ci				}
49762306a36Sopenharmony_ci			}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci		ret = -ENOTTY;
50062306a36Sopenharmony_ci	}
50162306a36Sopenharmony_ciout:
50262306a36Sopenharmony_ci	up_read(&minors_rwsem);
50362306a36Sopenharmony_ci	return ret;
50462306a36Sopenharmony_ci}
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_cistatic const struct file_operations hidraw_ops = {
50762306a36Sopenharmony_ci	.owner =        THIS_MODULE,
50862306a36Sopenharmony_ci	.read =         hidraw_read,
50962306a36Sopenharmony_ci	.write =        hidraw_write,
51062306a36Sopenharmony_ci	.poll =         hidraw_poll,
51162306a36Sopenharmony_ci	.open =         hidraw_open,
51262306a36Sopenharmony_ci	.release =      hidraw_release,
51362306a36Sopenharmony_ci	.unlocked_ioctl = hidraw_ioctl,
51462306a36Sopenharmony_ci	.fasync =	hidraw_fasync,
51562306a36Sopenharmony_ci	.compat_ioctl   = compat_ptr_ioctl,
51662306a36Sopenharmony_ci	.llseek =	noop_llseek,
51762306a36Sopenharmony_ci};
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ciint hidraw_report_event(struct hid_device *hid, u8 *data, int len)
52062306a36Sopenharmony_ci{
52162306a36Sopenharmony_ci	struct hidraw *dev = hid->hidraw;
52262306a36Sopenharmony_ci	struct hidraw_list *list;
52362306a36Sopenharmony_ci	int ret = 0;
52462306a36Sopenharmony_ci	unsigned long flags;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	spin_lock_irqsave(&dev->list_lock, flags);
52762306a36Sopenharmony_ci	list_for_each_entry(list, &dev->list, node) {
52862306a36Sopenharmony_ci		int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci		if (new_head == list->tail)
53162306a36Sopenharmony_ci			continue;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci		if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
53462306a36Sopenharmony_ci			ret = -ENOMEM;
53562306a36Sopenharmony_ci			break;
53662306a36Sopenharmony_ci		}
53762306a36Sopenharmony_ci		list->buffer[list->head].len = len;
53862306a36Sopenharmony_ci		list->head = new_head;
53962306a36Sopenharmony_ci		kill_fasync(&list->fasync, SIGIO, POLL_IN);
54062306a36Sopenharmony_ci	}
54162306a36Sopenharmony_ci	spin_unlock_irqrestore(&dev->list_lock, flags);
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	wake_up_interruptible(&dev->wait);
54462306a36Sopenharmony_ci	return ret;
54562306a36Sopenharmony_ci}
54662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(hidraw_report_event);
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ciint hidraw_connect(struct hid_device *hid)
54962306a36Sopenharmony_ci{
55062306a36Sopenharmony_ci	int minor, result;
55162306a36Sopenharmony_ci	struct hidraw *dev;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	/* we accept any HID device, all applications */
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL);
55662306a36Sopenharmony_ci	if (!dev)
55762306a36Sopenharmony_ci		return -ENOMEM;
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	result = -EINVAL;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	down_write(&minors_rwsem);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) {
56462306a36Sopenharmony_ci		if (hidraw_table[minor])
56562306a36Sopenharmony_ci			continue;
56662306a36Sopenharmony_ci		hidraw_table[minor] = dev;
56762306a36Sopenharmony_ci		result = 0;
56862306a36Sopenharmony_ci		break;
56962306a36Sopenharmony_ci	}
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	if (result) {
57262306a36Sopenharmony_ci		up_write(&minors_rwsem);
57362306a36Sopenharmony_ci		kfree(dev);
57462306a36Sopenharmony_ci		goto out;
57562306a36Sopenharmony_ci	}
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	dev->dev = device_create(&hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),
57862306a36Sopenharmony_ci				 NULL, "%s%d", "hidraw", minor);
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	if (IS_ERR(dev->dev)) {
58162306a36Sopenharmony_ci		hidraw_table[minor] = NULL;
58262306a36Sopenharmony_ci		up_write(&minors_rwsem);
58362306a36Sopenharmony_ci		result = PTR_ERR(dev->dev);
58462306a36Sopenharmony_ci		kfree(dev);
58562306a36Sopenharmony_ci		goto out;
58662306a36Sopenharmony_ci	}
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci	init_waitqueue_head(&dev->wait);
58962306a36Sopenharmony_ci	spin_lock_init(&dev->list_lock);
59062306a36Sopenharmony_ci	INIT_LIST_HEAD(&dev->list);
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	dev->hid = hid;
59362306a36Sopenharmony_ci	dev->minor = minor;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	dev->exist = 1;
59662306a36Sopenharmony_ci	hid->hidraw = dev;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	up_write(&minors_rwsem);
59962306a36Sopenharmony_ciout:
60062306a36Sopenharmony_ci	return result;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci}
60362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(hidraw_connect);
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_civoid hidraw_disconnect(struct hid_device *hid)
60662306a36Sopenharmony_ci{
60762306a36Sopenharmony_ci	struct hidraw *hidraw = hid->hidraw;
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	down_write(&minors_rwsem);
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	drop_ref(hidraw, 1);
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	up_write(&minors_rwsem);
61462306a36Sopenharmony_ci}
61562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(hidraw_disconnect);
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ciint __init hidraw_init(void)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	int result;
62062306a36Sopenharmony_ci	dev_t dev_id;
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR,
62362306a36Sopenharmony_ci			HIDRAW_MAX_DEVICES, "hidraw");
62462306a36Sopenharmony_ci	if (result < 0) {
62562306a36Sopenharmony_ci		pr_warn("can't get major number\n");
62662306a36Sopenharmony_ci		goto out;
62762306a36Sopenharmony_ci	}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	hidraw_major = MAJOR(dev_id);
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	result = class_register(&hidraw_class);
63262306a36Sopenharmony_ci	if (result)
63362306a36Sopenharmony_ci		goto error_cdev;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci        cdev_init(&hidraw_cdev, &hidraw_ops);
63662306a36Sopenharmony_ci	result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES);
63762306a36Sopenharmony_ci	if (result < 0)
63862306a36Sopenharmony_ci		goto error_class;
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	pr_info("raw HID events driver (C) Jiri Kosina\n");
64162306a36Sopenharmony_ciout:
64262306a36Sopenharmony_ci	return result;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_cierror_class:
64562306a36Sopenharmony_ci	class_unregister(&hidraw_class);
64662306a36Sopenharmony_cierror_cdev:
64762306a36Sopenharmony_ci	unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
64862306a36Sopenharmony_ci	goto out;
64962306a36Sopenharmony_ci}
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_civoid hidraw_exit(void)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	dev_t dev_id = MKDEV(hidraw_major, 0);
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	cdev_del(&hidraw_cdev);
65662306a36Sopenharmony_ci	class_unregister(&hidraw_class);
65762306a36Sopenharmony_ci	unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci}
660