diff options
Diffstat (limited to 'drivers/hid/usbhid/hiddev.c')
| -rw-r--r-- | drivers/hid/usbhid/hiddev.c | 281 | 
1 files changed, 147 insertions, 134 deletions
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index fedd88df9a1..2f1ddca6f2e 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -29,12 +29,12 @@  #include <linux/slab.h>  #include <linux/module.h>  #include <linux/init.h> -#include <linux/smp_lock.h>  #include <linux/input.h>  #include <linux/usb.h>  #include <linux/hid.h>  #include <linux/hiddev.h>  #include <linux/compat.h> +#include <linux/vmalloc.h>  #include "usbhid.h"  #ifdef CONFIG_USB_DYNAMIC_MINORS @@ -243,16 +243,21 @@ static int hiddev_release(struct inode * inode, struct file * file)  	list_del(&list->node);  	spin_unlock_irqrestore(&list->hiddev->list_lock, flags); +	mutex_lock(&list->hiddev->existancelock);  	if (!--list->hiddev->open) {  		if (list->hiddev->exist) {  			usbhid_close(list->hiddev->hid);  			usbhid_put_power(list->hiddev->hid);  		} else { +			mutex_unlock(&list->hiddev->existancelock);  			kfree(list->hiddev); +			vfree(list); +			return 0;  		}  	} -	kfree(list); +	mutex_unlock(&list->hiddev->existancelock); +	vfree(list);  	return 0;  } @@ -274,7 +279,7 @@ static int hiddev_open(struct inode *inode, struct file *file)  	hid = usb_get_intfdata(intf);  	hiddev = hid->hiddev; -	if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL))) +	if (!(list = vzalloc(sizeof(struct hiddev_list))))  		return -ENOMEM;  	mutex_init(&list->thread_lock);  	list->hiddev = hiddev; @@ -301,20 +306,24 @@ static int hiddev_open(struct inode *inode, struct file *file)  	list_add_tail(&list->node, &hiddev->list);  	spin_unlock_irq(&list->hiddev->list_lock); +	mutex_lock(&hiddev->existancelock);  	if (!list->hiddev->open++)  		if (list->hiddev->exist) {  			struct hid_device *hid = hiddev->hid;  			res = usbhid_get_power(hid);  			if (res < 0) {  				res = -EIO; -				goto bail; +				goto bail_unlock;  			}  			usbhid_open(hid);  		} +	mutex_unlock(&hiddev->existancelock);  	return 0; +bail_unlock: +	mutex_unlock(&hiddev->existancelock);  bail:  	file->private_data = NULL; -	kfree(list); +	vfree(list);  	return res;  } @@ -352,10 +361,6 @@ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t coun  			prepare_to_wait(&list->hiddev->wait, &wait, TASK_INTERRUPTIBLE);  			while (list->head == list->tail) { -				if (file->f_flags & O_NONBLOCK) { -					retval = -EAGAIN; -					break; -				}  				if (signal_pending(current)) {  					retval = -ERESTARTSYS;  					break; @@ -364,12 +369,18 @@ static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t coun  					retval = -EIO;  					break;  				} +				if (file->f_flags & O_NONBLOCK) { +					retval = -EAGAIN; +					break; +				}  				/* let O_NONBLOCK tasks run */  				mutex_unlock(&list->thread_lock);  				schedule(); -				if (mutex_lock_interruptible(&list->thread_lock)) +				if (mutex_lock_interruptible(&list->thread_lock)) { +					finish_wait(&list->hiddev->wait, &wait);  					return -EINTR; +				}  				set_current_state(TASK_INTERRUPTIBLE);  			}  			finish_wait(&list->hiddev->wait, &wait); @@ -510,7 +521,7 @@ static noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd,  				 (uref_multi->num_values > HID_MAX_MULTI_USAGES ||  				  uref->usage_index + uref_multi->num_values > field->report_count))  				goto inval; -			} +		}  		switch (cmd) {  		case HIDIOCGUSAGE: @@ -586,163 +597,170 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  {  	struct hiddev_list *list = file->private_data;  	struct hiddev *hiddev = list->hiddev; -	struct hid_device *hid = hiddev->hid; -	struct usb_device *dev; +	struct hid_device *hid;  	struct hiddev_collection_info cinfo;  	struct hiddev_report_info rinfo;  	struct hiddev_field_info finfo;  	struct hiddev_devinfo dinfo;  	struct hid_report *report;  	struct hid_field *field; -	struct usbhid_device *usbhid = hid->driver_data;  	void __user *user_arg = (void __user *)arg; -	int i, r; -	 +	int i, r = -EINVAL; +  	/* Called without BKL by compat methods so no BKL taken */ -	/* FIXME: Who or what stop this racing with a disconnect ?? */ -	if (!hiddev->exist || !hid) -		return -EIO; +	mutex_lock(&hiddev->existancelock); +	if (!hiddev->exist) { +		r = -ENODEV; +		goto ret_unlock; +	} -	dev = hid_to_usb_dev(hid); +	hid = hiddev->hid;  	switch (cmd) {  	case HIDIOCGVERSION: -		return put_user(HID_VERSION, (int __user *)arg); +		r = put_user(HID_VERSION, (int __user *)arg) ? +			-EFAULT : 0; +		break;  	case HIDIOCAPPLICATION: -		if (arg < 0 || arg >= hid->maxapplication) -			return -EINVAL; +		if (arg >= hid->maxapplication) +			break;  		for (i = 0; i < hid->maxcollection; i++)  			if (hid->collection[i].type ==  			    HID_COLLECTION_APPLICATION && arg-- == 0)  				break; -		if (i == hid->maxcollection) -			return -EINVAL; - -		return hid->collection[i].usage; +		if (i < hid->maxcollection) +			r = hid->collection[i].usage; +		break;  	case HIDIOCGDEVINFO: -		dinfo.bustype = BUS_USB; -		dinfo.busnum = dev->bus->busnum; -		dinfo.devnum = dev->devnum; -		dinfo.ifnum = usbhid->ifnum; -		dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor); -		dinfo.product = le16_to_cpu(dev->descriptor.idProduct); -		dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice); -		dinfo.num_applications = hid->maxapplication; -		if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) -			return -EFAULT; - -		return 0; +		{ +			struct usb_device *dev = hid_to_usb_dev(hid); +			struct usbhid_device *usbhid = hid->driver_data; + +			memset(&dinfo, 0, sizeof(dinfo)); + +			dinfo.bustype = BUS_USB; +			dinfo.busnum = dev->bus->busnum; +			dinfo.devnum = dev->devnum; +			dinfo.ifnum = usbhid->ifnum; +			dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor); +			dinfo.product = le16_to_cpu(dev->descriptor.idProduct); +			dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice); +			dinfo.num_applications = hid->maxapplication; + +			r = copy_to_user(user_arg, &dinfo, sizeof(dinfo)) ? +				-EFAULT : 0; +			break; +		}  	case HIDIOCGFLAG: -		if (put_user(list->flags, (int __user *)arg)) -			return -EFAULT; - -		return 0; +		r = put_user(list->flags, (int __user *)arg) ? +			-EFAULT : 0; +		break;  	case HIDIOCSFLAG:  		{  			int newflags; -			if (get_user(newflags, (int __user *)arg)) -				return -EFAULT; + +			if (get_user(newflags, (int __user *)arg)) { +				r = -EFAULT; +				break; +			}  			if ((newflags & ~HIDDEV_FLAGS) != 0 ||  			    ((newflags & HIDDEV_FLAG_REPORT) != 0 &&  			     (newflags & HIDDEV_FLAG_UREF) == 0)) -				return -EINVAL; +				break;  			list->flags = newflags; -			return 0; +			r = 0; +			break;  		}  	case HIDIOCGSTRING: -		mutex_lock(&hiddev->existancelock); -		if (hiddev->exist) -			r = hiddev_ioctl_string(hiddev, cmd, user_arg); -		else -			r = -ENODEV; -		mutex_unlock(&hiddev->existancelock); -		return r; +		r = hiddev_ioctl_string(hiddev, cmd, user_arg); +		break;  	case HIDIOCINITREPORT: -		mutex_lock(&hiddev->existancelock); -		if (!hiddev->exist) { -			mutex_unlock(&hiddev->existancelock); -			return -ENODEV; -		}  		usbhid_init_reports(hid); -		mutex_unlock(&hiddev->existancelock); - -		return 0; +		r = 0; +		break;  	case HIDIOCGREPORT: -		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) -			return -EFAULT; +		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) { +			r = -EFAULT; +			break; +		}  		if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT) -			return -EINVAL; +			break; -		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) -			return -EINVAL; +		report = hiddev_lookup_report(hid, &rinfo); +		if (report == NULL) +			break; -		mutex_lock(&hiddev->existancelock); -		if (hiddev->exist) { -			usbhid_submit_report(hid, report, USB_DIR_IN); -			usbhid_wait_io(hid); -		} -		mutex_unlock(&hiddev->existancelock); +		hid_hw_request(hid, report, HID_REQ_GET_REPORT); +		hid_hw_wait(hid); -		return 0; +		r = 0; +		break;  	case HIDIOCSREPORT: -		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) -			return -EFAULT; +		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) { +			r = -EFAULT; +			break; +		}  		if (rinfo.report_type == HID_REPORT_TYPE_INPUT) -			return -EINVAL; +			break; -		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) -			return -EINVAL; +		report = hiddev_lookup_report(hid, &rinfo); +		if (report == NULL) +			break; -		mutex_lock(&hiddev->existancelock); -		if (hiddev->exist) { -			usbhid_submit_report(hid, report, USB_DIR_OUT); -			usbhid_wait_io(hid); -		} -		mutex_unlock(&hiddev->existancelock); +		hid_hw_request(hid, report, HID_REQ_SET_REPORT); +		hid_hw_wait(hid); -		return 0; +		r = 0; +		break;  	case HIDIOCGREPORTINFO: -		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) -			return -EFAULT; +		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) { +			r = -EFAULT; +			break; +		} -		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) -			return -EINVAL; +		report = hiddev_lookup_report(hid, &rinfo); +		if (report == NULL) +			break;  		rinfo.num_fields = report->maxfield; -		if (copy_to_user(user_arg, &rinfo, sizeof(rinfo))) -			return -EFAULT; - -		return 0; +		r = copy_to_user(user_arg, &rinfo, sizeof(rinfo)) ? +			-EFAULT : 0; +		break;  	case HIDIOCGFIELDINFO: -		if (copy_from_user(&finfo, user_arg, sizeof(finfo))) -			return -EFAULT; +		if (copy_from_user(&finfo, user_arg, sizeof(finfo))) { +			r = -EFAULT; +			break; +		} +  		rinfo.report_type = finfo.report_type;  		rinfo.report_id = finfo.report_id; -		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL) -			return -EINVAL; + +		report = hiddev_lookup_report(hid, &rinfo); +		if (report == NULL) +			break;  		if (finfo.field_index >= report->maxfield) -			return -EINVAL; +			break;  		field = report->field[finfo.field_index];  		memset(&finfo, 0, sizeof(finfo)); @@ -761,10 +779,9 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  		finfo.unit_exponent = field->unit_exponent;  		finfo.unit = field->unit; -		if (copy_to_user(user_arg, &finfo, sizeof(finfo))) -			return -EFAULT; - -		return 0; +		r = copy_to_user(user_arg, &finfo, sizeof(finfo)) ? +			-EFAULT : 0; +		break;  	case HIDIOCGUCODE:  		/* fall through */ @@ -773,57 +790,52 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)  	case HIDIOCGUSAGES:  	case HIDIOCSUSAGES:  	case HIDIOCGCOLLECTIONINDEX: -		mutex_lock(&hiddev->existancelock); -		if (hiddev->exist) -			r = hiddev_ioctl_usage(hiddev, cmd, user_arg); -		else -			r = -ENODEV; -		mutex_unlock(&hiddev->existancelock); -		return r; +		r = hiddev_ioctl_usage(hiddev, cmd, user_arg); +		break;  	case HIDIOCGCOLLECTIONINFO: -		if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) -			return -EFAULT; +		if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) { +			r = -EFAULT; +			break; +		}  		if (cinfo.index >= hid->maxcollection) -			return -EINVAL; +			break;  		cinfo.type = hid->collection[cinfo.index].type;  		cinfo.usage = hid->collection[cinfo.index].usage;  		cinfo.level = hid->collection[cinfo.index].level; -		if (copy_to_user(user_arg, &cinfo, sizeof(cinfo))) -			return -EFAULT; -		return 0; +		r = copy_to_user(user_arg, &cinfo, sizeof(cinfo)) ? +			-EFAULT : 0; +		break;  	default: -  		if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) -			return -EINVAL; +			break;  		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) { -			int len; -			if (!hid->name) -				return 0; -			len = strlen(hid->name) + 1; +			int len = strlen(hid->name) + 1;  			if (len > _IOC_SIZE(cmd))  				 len = _IOC_SIZE(cmd); -			return copy_to_user(user_arg, hid->name, len) ? +			r = copy_to_user(user_arg, hid->name, len) ?  				-EFAULT : len; +			break;  		}  		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) { -			int len; -			if (!hid->phys) -				return 0; -			len = strlen(hid->phys) + 1; +			int len = strlen(hid->phys) + 1;  			if (len > _IOC_SIZE(cmd))  				len = _IOC_SIZE(cmd); -			return copy_to_user(user_arg, hid->phys, len) ? +			r = copy_to_user(user_arg, hid->phys, len) ?  				-EFAULT : len; +			break;  		}  	} -	return -EINVAL; + +ret_unlock: +	mutex_unlock(&hiddev->existancelock); +	return r;  }  #ifdef CONFIG_COMPAT @@ -848,7 +860,7 @@ static const struct file_operations hiddev_fops = {  	.llseek		= noop_llseek,  }; -static char *hiddev_devnode(struct device *dev, mode_t *mode) +static char *hiddev_devnode(struct device *dev, umode_t *mode)  {  	return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));  } @@ -893,7 +905,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)  	hiddev->exist = 1;  	retval = usb_register_dev(usbhid->intf, &hiddev_class);  	if (retval) { -		err_hid("Not able to get a minor for this device."); +		hid_err(hid, "Not able to get a minor for this device\n");  		hid->hiddev = NULL;  		kfree(hiddev);  		return -1; @@ -911,16 +923,17 @@ void hiddev_disconnect(struct hid_device *hid)  	struct hiddev *hiddev = hid->hiddev;  	struct usbhid_device *usbhid = hid->driver_data; +	usb_deregister_dev(usbhid->intf, &hiddev_class); +  	mutex_lock(&hiddev->existancelock);  	hiddev->exist = 0; -	mutex_unlock(&hiddev->existancelock); - -	usb_deregister_dev(usbhid->intf, &hiddev_class);  	if (hiddev->open) { +		mutex_unlock(&hiddev->existancelock);  		usbhid_close(hiddev->hid);  		wake_up_interruptible(&hiddev->wait);  	} else { +		mutex_unlock(&hiddev->existancelock);  		kfree(hiddev);  	}  }  | 
