diff options
Diffstat (limited to 'drivers/hid/hidraw.c')
| -rw-r--r-- | drivers/hid/hidraw.c | 224 | 
1 files changed, 122 insertions, 102 deletions
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index c887f48756f4..bbd6f23bce78 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -394,27 +394,15 @@ static int hidraw_revoke(struct hidraw_list *list)  	return 0;  } -static long hidraw_ioctl(struct file *file, unsigned int cmd, -							unsigned long arg) +static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, +				    void __user *arg)  { -	struct inode *inode = file_inode(file); -	unsigned int minor = iminor(inode); -	long ret = 0; -	struct hidraw *dev; -	struct hidraw_list *list = file->private_data; -	void __user *user_arg = (void __user*) arg; - -	down_read(&minors_rwsem); -	dev = hidraw_table[minor]; -	if (!dev || !dev->exist || hidraw_is_revoked(list)) { -		ret = -ENODEV; -		goto out; -	} +	struct hid_device *hid = dev->hid;  	switch (cmd) {  		case HIDIOCGRDESCSIZE: -			if (put_user(dev->hid->rsize, (int __user *)arg)) -				ret = -EFAULT; +			if (put_user(hid->rsize, (int __user *)arg)) +				return -EFAULT;  			break;  		case HIDIOCGRDESC: @@ -422,113 +410,145 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,  				__u32 len;  				if (get_user(len, (int __user *)arg)) -					ret = -EFAULT; -				else if (len > HID_MAX_DESCRIPTOR_SIZE - 1) -					ret = -EINVAL; -				else if (copy_to_user(user_arg + offsetof( -					struct hidraw_report_descriptor, -					value[0]), -					dev->hid->rdesc, -					min(dev->hid->rsize, len))) -					ret = -EFAULT; +					return -EFAULT; + +				if (len > HID_MAX_DESCRIPTOR_SIZE - 1) +					return -EINVAL; + +				if (copy_to_user(arg + offsetof( +				    struct hidraw_report_descriptor, +				    value[0]), +				    hid->rdesc, +				    min(hid->rsize, len))) +					return -EFAULT; +  				break;  			}  		case HIDIOCGRAWINFO:  			{  				struct hidraw_devinfo dinfo; -				dinfo.bustype = dev->hid->bus; -				dinfo.vendor = dev->hid->vendor; -				dinfo.product = dev->hid->product; -				if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) -					ret = -EFAULT; +				dinfo.bustype = hid->bus; +				dinfo.vendor = hid->vendor; +				dinfo.product = hid->product; +				if (copy_to_user(arg, &dinfo, sizeof(dinfo))) +					return -EFAULT;  				break;  			}  		case HIDIOCREVOKE:  			{ -				if (user_arg) -					ret = -EINVAL; -				else -					ret = hidraw_revoke(list); -				break; +				struct hidraw_list *list = file->private_data; + +				if (arg) +					return -EINVAL; + +				return hidraw_revoke(list);  			}  		default: -			{ -				struct hid_device *hid = dev->hid; -				if (_IOC_TYPE(cmd) != 'H') { -					ret = -EINVAL; -					break; -				} +			/* +			 * None of the above ioctls can return -EAGAIN, so +			 * use it as a marker that we need to check variable +			 * length ioctls. +			 */ +			return -EAGAIN; +	} -				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; -				} +	return 0; +} -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) { -					int len = _IOC_SIZE(cmd); -					ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); -					break; -				} -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) { -					int len = _IOC_SIZE(cmd); -					ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); -					break; -				} +static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, +					  void __user *user_arg) +{ +	int len = _IOC_SIZE(cmd); + +	switch (cmd & ~IOCSIZE_MASK) { +	case HIDIOCSFEATURE(0): +		return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); +	case HIDIOCGFEATURE(0): +		return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); +	case HIDIOCSINPUT(0): +		return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); +	case HIDIOCGINPUT(0): +		return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); +	case HIDIOCSOUTPUT(0): +		return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); +	case HIDIOCGOUTPUT(0): +		return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); +	} -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) { -					int len = _IOC_SIZE(cmd); -					ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); -					break; -				} -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) { -					int len = _IOC_SIZE(cmd); -					ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); -					break; -				} +	return -EINVAL; +} -				/* Begin Read-only ioctls. */ -				if (_IOC_DIR(cmd) != _IOC_READ) { -					ret = -EINVAL; -					break; -				} +static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, +					  void __user *user_arg) +{ +	struct hid_device *hid = dev->hid; +	int len = _IOC_SIZE(cmd); +	int field_len; + +	switch (cmd & ~IOCSIZE_MASK) { +	case HIDIOCGRAWNAME(0): +		field_len = strlen(hid->name) + 1; +		if (len > field_len) +			len = field_len; +		return copy_to_user(user_arg, hid->name, len) ?  -EFAULT : len; +	case HIDIOCGRAWPHYS(0): +		field_len = strlen(hid->phys) + 1; +		if (len > field_len) +			len = field_len; +		return copy_to_user(user_arg, hid->phys, len) ?  -EFAULT : len; +	case HIDIOCGRAWUNIQ(0): +		field_len = strlen(hid->uniq) + 1; +		if (len > field_len) +			len = field_len; +		return copy_to_user(user_arg, hid->uniq, len) ?  -EFAULT : len; +	} -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { -					int len = strlen(hid->name) + 1; -					if (len > _IOC_SIZE(cmd)) -						len = _IOC_SIZE(cmd); -					ret = copy_to_user(user_arg, hid->name, len) ? -						-EFAULT : len; -					break; -				} +	return -EINVAL; +} -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { -					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; -				} +static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ +	struct inode *inode = file_inode(file); +	unsigned int minor = iminor(inode); +	struct hidraw *dev; +	struct hidraw_list *list = file->private_data; +	void __user *user_arg = (void __user *)arg; +	int ret; -				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) { -					int len = strlen(hid->uniq) + 1; -					if (len > _IOC_SIZE(cmd)) -						len = _IOC_SIZE(cmd); -					ret = copy_to_user(user_arg, hid->uniq, len) ? -						-EFAULT : len; -					break; -				} -			} +	down_read(&minors_rwsem); +	dev = hidraw_table[minor]; +	if (!dev || !dev->exist || hidraw_is_revoked(list)) { +		ret = -ENODEV; +		goto out; +	} + +	if (_IOC_TYPE(cmd) != 'H') { +		ret = -EINVAL; +		goto out; +	} +	if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) {  		ret = -ENOTTY; +		goto out;  	} + +	ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg); +	if (ret != -EAGAIN) +		goto out; + +	switch (_IOC_DIR(cmd)) { +	case (_IOC_READ | _IOC_WRITE): +		ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg); +		break; +	case _IOC_READ: +		ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg); +		break; +	default: +		/* Any other IOC_DIR is wrong */ +		ret = -EINVAL; +	} +  out:  	up_read(&minors_rwsem);  	return ret;  | 
