diff options
Diffstat (limited to 'drivers/hid/hidraw.c')
| -rw-r--r-- | drivers/hid/hidraw.c | 326 | 
1 files changed, 236 insertions, 90 deletions
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 8a4b32dca9f..9c2d7c23f29 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -6,7 +6,7 @@   * to work on raw hid events as they want to, and avoids a need to   * use a transport-specific userspace libhid/libusb libraries.   * - *  Copyright (c) 2007 Jiri Kosina + *  Copyright (c) 2007-2014 Jiri Kosina   */  /* @@ -19,6 +19,8 @@   * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/fs.h>  #include <linux/module.h>  #include <linux/errno.h> @@ -32,7 +34,6 @@  #include <linux/hid.h>  #include <linux/mutex.h>  #include <linux/sched.h> -#include <linux/smp_lock.h>  #include <linux/hidraw.h> @@ -56,10 +57,6 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,  			set_current_state(TASK_INTERRUPTIBLE);  			while (list->head == list->tail) { -				if (file->f_flags & O_NONBLOCK) { -					ret = -EAGAIN; -					break; -				}  				if (signal_pending(current)) {  					ret = -ERESTARTSYS;  					break; @@ -68,6 +65,10 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,  					ret = -EIO;  					break;  				} +				if (file->f_flags & O_NONBLOCK) { +					ret = -EAGAIN; +					break; +				}  				/* allow O_NONBLOCK to work well from other threads */  				mutex_unlock(&list->read_mutex); @@ -86,13 +87,16 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,  		len = list->buffer[list->tail].len > count ?  			count : list->buffer[list->tail].len; -		if (copy_to_user(buffer, list->buffer[list->tail].value, len)) { -			ret = -EFAULT; -			goto out; +		if (list->buffer[list->tail].value) { +			if (copy_to_user(buffer, list->buffer[list->tail].value, len)) { +				ret = -EFAULT; +				goto out; +			} +			ret = len;  		} -		ret += len;  		kfree(list->buffer[list->tail].value); +		list->buffer[list->tail].value = NULL;  		list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);  	}  out: @@ -100,24 +104,102 @@ out:  	return ret;  } -/* the first byte is expected to be a report number */ -static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +/* + * The first byte of the report buffer is expected to be a report number. + * + * This function is to be called with the minors_lock mutex held. + */ +static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type)  { -	unsigned int minor = iminor(file->f_path.dentry->d_inode); +	unsigned int minor = iminor(file_inode(file));  	struct hid_device *dev;  	__u8 *buf;  	int ret = 0; -	mutex_lock(&minors_lock); - -	if (!hidraw_table[minor]) { +	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {  		ret = -ENODEV;  		goto out;  	}  	dev = hidraw_table[minor]->hid; -	if (!dev->hid_output_raw_report) { + +	if (count > HID_MAX_BUFFER_SIZE) { +		hid_warn(dev, "pid %d passed too large report\n", +			 task_pid_nr(current)); +		ret = -EINVAL; +		goto out; +	} + +	if (count < 2) { +		hid_warn(dev, "pid %d passed too short report\n", +			 task_pid_nr(current)); +		ret = -EINVAL; +		goto out; +	} + +	buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); +	if (!buf) { +		ret = -ENOMEM; +		goto out; +	} + +	if (copy_from_user(buf, buffer, count)) { +		ret = -EFAULT; +		goto out_free; +	} + +	if ((report_type == HID_OUTPUT_REPORT) && +	    !(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) { +		ret = hid_hw_output_report(dev, buf, count); +		/* +		 * compatibility with old implementation of USB-HID and I2C-HID: +		 * if the device does not support receiving output reports, +		 * on an interrupt endpoint, fallback to SET_REPORT HID command. +		 */ +		if (ret != -ENOSYS) +			goto out_free; +	} + +	ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type, +				HID_REQ_SET_REPORT); + +out_free: +	kfree(buf); +out: +	return ret; +} + +static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ +	ssize_t ret; +	mutex_lock(&minors_lock); +	ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT); +	mutex_unlock(&minors_lock); +	return ret; +} + + +/* + * This function performs a Get_Report transfer over the control endpoint + * per section 7.2.1 of the HID specification, version 1.1.  The first byte + * of buffer is the report number to request, or 0x0 if the defice does not + * use numbered reports. The report_type parameter can be HID_FEATURE_REPORT + * or HID_INPUT_REPORT. + * + * This function is to be called with the minors_lock mutex held. + */ +static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type) +{ +	unsigned int minor = iminor(file_inode(file)); +	struct hid_device *dev; +	__u8 *buf; +	int ret = 0, len; +	unsigned char report_number; + +	dev = hidraw_table[minor]->hid; + +	if (!dev->ll_driver->raw_request) {  		ret = -ENODEV;  		goto out;  	} @@ -142,16 +224,33 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t  		goto out;  	} -	if (copy_from_user(buf, buffer, count)) { +	/* +	 * Read the first byte from the user. This is the report number, +	 * which is passed to hid_hw_raw_request(). +	 */ +	if (copy_from_user(&report_number, buffer, 1)) {  		ret = -EFAULT;  		goto out_free;  	} -	ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT); +	ret = hid_hw_raw_request(dev, report_number, buf, count, report_type, +				 HID_REQ_GET_REPORT); + +	if (ret < 0) +		goto out_free; + +	len = (ret < count) ? ret : count; + +	if (copy_to_user(buffer, buf, len)) { +		ret = -EFAULT; +		goto out_free; +	} + +	ret = len; +  out_free:  	kfree(buf);  out: -	mutex_unlock(&minors_lock);  	return ret;  } @@ -172,6 +271,7 @@ static int hidraw_open(struct inode *inode, struct file *file)  	unsigned int minor = iminor(inode);  	struct hidraw *dev;  	struct hidraw_list *list; +	unsigned long flags;  	int err = 0;  	if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { @@ -180,75 +280,97 @@ static int hidraw_open(struct inode *inode, struct file *file)  	}  	mutex_lock(&minors_lock); -	if (!hidraw_table[minor]) { -		kfree(list); +	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {  		err = -ENODEV;  		goto out_unlock;  	} -	list->hidraw = hidraw_table[minor]; -	mutex_init(&list->read_mutex); -	list_add_tail(&list->node, &hidraw_table[minor]->list); -	file->private_data = list; -  	dev = hidraw_table[minor];  	if (!dev->open++) { -		if (dev->hid->ll_driver->power) { -			err = dev->hid->ll_driver->power(dev->hid, PM_HINT_FULLON); -			if (err < 0) -				goto out_unlock; +		err = hid_hw_power(dev->hid, PM_HINT_FULLON); +		if (err < 0) { +			dev->open--; +			goto out_unlock;  		} -		err = dev->hid->ll_driver->open(dev->hid); + +		err = hid_hw_open(dev->hid);  		if (err < 0) { -			if (dev->hid->ll_driver->power) -				dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL); +			hid_hw_power(dev->hid, PM_HINT_NORMAL);  			dev->open--; +			goto out_unlock;  		}  	} +	list->hidraw = hidraw_table[minor]; +	mutex_init(&list->read_mutex); +	spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); +	list_add_tail(&list->node, &hidraw_table[minor]->list); +	spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); +	file->private_data = list;  out_unlock:  	mutex_unlock(&minors_lock);  out: +	if (err < 0) +		kfree(list);  	return err;  } +static int hidraw_fasync(int fd, struct file *file, int on) +{ +	struct hidraw_list *list = file->private_data; + +	return fasync_helper(fd, file, on, &list->fasync); +} + +static void drop_ref(struct hidraw *hidraw, int exists_bit) +{ +	if (exists_bit) { +		hidraw->exist = 0; +		if (hidraw->open) { +			hid_hw_close(hidraw->hid); +			wake_up_interruptible(&hidraw->wait); +		} +		device_destroy(hidraw_class, +			       MKDEV(hidraw_major, hidraw->minor)); +	} else { +		--hidraw->open; +	} +	if (!hidraw->open) { +		if (!hidraw->exist) { +			hidraw_table[hidraw->minor] = NULL; +			kfree(hidraw); +		} else { +			/* close device for last reader */ +			hid_hw_power(hidraw->hid, PM_HINT_NORMAL); +			hid_hw_close(hidraw->hid); +		} +	} +} +  static int hidraw_release(struct inode * inode, struct file * file)  {  	unsigned int minor = iminor(inode); -	struct hidraw *dev;  	struct hidraw_list *list = file->private_data; -	int ret; +	unsigned long flags;  	mutex_lock(&minors_lock); -	if (!hidraw_table[minor]) { -		ret = -ENODEV; -		goto unlock; -	} +	spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);  	list_del(&list->node); -	dev = hidraw_table[minor]; -	if (!--dev->open) { -		if (list->hidraw->exist) { -			if (dev->hid->ll_driver->power) -				dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL); -			dev->hid->ll_driver->close(dev->hid); -		} else { -			kfree(list->hidraw); -		} -	} +	spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);  	kfree(list); -	ret = 0; -unlock: -	mutex_unlock(&minors_lock); -	return ret; +	drop_ref(hidraw_table[minor], 0); + +	mutex_unlock(&minors_lock); +	return 0;  }  static long hidraw_ioctl(struct file *file, unsigned int cmd,  							unsigned long arg)  { -	struct inode *inode = file->f_path.dentry->d_inode; +	struct inode *inode = file_inode(file);  	unsigned int minor = iminor(inode);  	long ret = 0;  	struct hidraw *dev; @@ -297,18 +419,30 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,  		default:  			{  				struct hid_device *hid = dev->hid; -				if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) { +				if (_IOC_TYPE(cmd) != 'H') { +					ret = -EINVAL; +					break; +				} + +				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) { +					int len = _IOC_SIZE(cmd); +					ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); +					break; +				} +				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) { +					int len = _IOC_SIZE(cmd); +					ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); +					break; +				} + +				/* Begin Read-only ioctls. */ +				if (_IOC_DIR(cmd) != _IOC_READ) {  					ret = -EINVAL;  					break;  				}  				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { -					int len; -					if (!hid->name) { -						ret = 0; -						break; -					} -					len = strlen(hid->name) + 1; +					int len = strlen(hid->name) + 1;  					if (len > _IOC_SIZE(cmd))  						len = _IOC_SIZE(cmd);  					ret = copy_to_user(user_arg, hid->name, len) ? @@ -317,19 +451,14 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,  				}  				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { -					int len; -					if (!hid->phys) { -						ret = 0; -						break; -					} -					len = strlen(hid->phys) + 1; +					int len = strlen(hid->phys) + 1;  					if (len > _IOC_SIZE(cmd))  						len = _IOC_SIZE(cmd);  					ret = copy_to_user(user_arg, hid->phys, len) ?  						-EFAULT : len;  					break;  				} -		} +			}  		ret = -ENOTTY;  	} @@ -346,22 +475,39 @@ static const struct file_operations hidraw_ops = {  	.open =         hidraw_open,  	.release =      hidraw_release,  	.unlocked_ioctl = hidraw_ioctl, +	.fasync =	hidraw_fasync, +#ifdef CONFIG_COMPAT +	.compat_ioctl   = hidraw_ioctl, +#endif  	.llseek =	noop_llseek,  }; -void hidraw_report_event(struct hid_device *hid, u8 *data, int len) +int hidraw_report_event(struct hid_device *hid, u8 *data, int len)  {  	struct hidraw *dev = hid->hidraw;  	struct hidraw_list *list; +	int ret = 0; +	unsigned long flags; +	spin_lock_irqsave(&dev->list_lock, flags);  	list_for_each_entry(list, &dev->list, node) { -		list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC); +		int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); + +		if (new_head == list->tail) +			continue; + +		if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) { +			ret = -ENOMEM; +			break; +		}  		list->buffer[list->head].len = len; -		list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); +		list->head = new_head;  		kill_fasync(&list->fasync, SIGIO, POLL_IN);  	} +	spin_unlock_irqrestore(&dev->list_lock, flags);  	wake_up_interruptible(&dev->wait); +	return ret;  }  EXPORT_SYMBOL_GPL(hidraw_report_event); @@ -370,7 +516,7 @@ int hidraw_connect(struct hid_device *hid)  	int minor, result;  	struct hidraw *dev; -	/* we accept any HID device, no matter the applications */ +	/* we accept any HID device, all applications */  	dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL);  	if (!dev) @@ -405,8 +551,8 @@ int hidraw_connect(struct hid_device *hid)  		goto out;  	} -	mutex_unlock(&minors_lock);  	init_waitqueue_head(&dev->wait); +	spin_lock_init(&dev->list_lock);  	INIT_LIST_HEAD(&dev->list);  	dev->hid = hid; @@ -415,6 +561,7 @@ int hidraw_connect(struct hid_device *hid)  	dev->exist = 1;  	hid->hidraw = dev; +	mutex_unlock(&minors_lock);  out:  	return result; @@ -425,20 +572,11 @@ void hidraw_disconnect(struct hid_device *hid)  {  	struct hidraw *hidraw = hid->hidraw; -	hidraw->exist = 0; -  	mutex_lock(&minors_lock); -	hidraw_table[hidraw->minor] = NULL; -	mutex_unlock(&minors_lock); -	device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); +	drop_ref(hidraw, 1); -	if (hidraw->open) { -		hid->ll_driver->close(hid); -		wake_up_interruptible(&hidraw->wait); -	} else { -		kfree(hidraw); -	} +	mutex_unlock(&minors_lock);  }  EXPORT_SYMBOL_GPL(hidraw_disconnect); @@ -453,22 +591,30 @@ int __init hidraw_init(void)  	hidraw_major = MAJOR(dev_id);  	if (result < 0) { -		printk(KERN_WARNING "hidraw: can't get major number\n"); -		result = 0; +		pr_warn("can't get major number\n");  		goto out;  	}  	hidraw_class = class_create(THIS_MODULE, "hidraw");  	if (IS_ERR(hidraw_class)) {  		result = PTR_ERR(hidraw_class); -		unregister_chrdev(hidraw_major, "hidraw"); -		goto out; +		goto error_cdev;  	}          cdev_init(&hidraw_cdev, &hidraw_ops); -        cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); +	result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); +	if (result < 0) +		goto error_class; + +	printk(KERN_INFO "hidraw: raw HID events driver (C) Jiri Kosina\n");  out:  	return result; + +error_class: +	class_destroy(hidraw_class); +error_cdev: +	unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); +	goto out;  }  void hidraw_exit(void)  | 
