/*
 * "ioctl" file op
 */
static int hiddev_ioctl(struct inode *inode, 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 = hid->dev;
	struct hiddev_collection_info cinfo;
	struct hiddev_report_info rinfo;
	struct hiddev_field_info finfo;
	struct hiddev_usage_ref_multi uref_multi;
	struct hiddev_usage_ref *uref = &uref_multi.uref;
	struct hiddev_devinfo dinfo;
	struct hid_report *report;
	struct hid_field *field;
	int i;

	if (!hiddev->exist) return -EIO;

	switch (cmd) {

	case HIDIOCGVERSION:
		return put_user(HID_VERSION, (int *) arg);

	case HIDIOCAPPLICATION:
		if (arg < 0 || arg >= hid->maxapplication)
			return -EINVAL;

		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;

	case HIDIOCGDEVINFO:
		dinfo.bustype = BUS_USB;
		dinfo.busnum = dev->bus->busnum;
		dinfo.devnum = dev->devnum;
		dinfo.ifnum = hid->ifnum;
		dinfo.vendor = dev->descriptor.idVendor;
		dinfo.product = dev->descriptor.idProduct;
		dinfo.version = dev->descriptor.bcdDevice;
		dinfo.num_applications = hid->maxapplication;
		if (copy_to_user((void *) arg, &dinfo, sizeof(dinfo)))
			return -EFAULT;
		return 0;

	case HIDIOCGFLAG:
		return put_user(list->flags, (int *) arg);

	case HIDIOCSFLAG:
		{
			int newflags;
			if (get_user(newflags, (int *) arg))
				return -EFAULT;

			if ((newflags & ~HIDDEV_FLAGS) != 0 ||
			    ((newflags & HIDDEV_FLAG_REPORT) != 0 &&
			     (newflags & HIDDEV_FLAG_UREF) == 0))
				return -EINVAL;

			list->flags = newflags;

			return 0;
		}

	case HIDIOCGSTRING:
		{
			int idx, len;
			char *buf;

			if (get_user(idx, (int *) arg))
				return -EFAULT;

			if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL)
				return -ENOMEM;

			if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) {
				kfree(buf);
				return -EINVAL;
			}

			if (copy_to_user((void *) (arg+sizeof(int)), buf, len+1)) {
				kfree(buf);
				return -EFAULT;
			}

			kfree(buf);

			return len;
		}

	case HIDIOCINITREPORT:
		hid_init_reports(hid);

		return 0;

	case HIDIOCGREPORT:
		if (copy_from_user(&rinfo, (void *) arg, sizeof(rinfo)))
			return -EFAULT;

		if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT)
			return -EINVAL;

		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		hid_read_report(hid, report);

		return 0;

	case HIDIOCSREPORT:
		if (copy_from_user(&rinfo, (void *) arg, sizeof(rinfo)))
			return -EFAULT;

		if (rinfo.report_type == HID_REPORT_TYPE_INPUT)
			return -EINVAL;

		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		hid_write_report(hid, report);

		return 0;

	case HIDIOCGREPORTINFO:
		if (copy_from_user(&rinfo, (void *) arg, sizeof(rinfo)))
			return -EFAULT;

		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		rinfo.num_fields = report->maxfield;

		if (copy_to_user((void *) arg, &rinfo, sizeof(rinfo)))
			return -EFAULT;
		return 0;

	case HIDIOCGFIELDINFO:
		if (copy_from_user(&finfo, (void *) arg, sizeof(finfo)))
			return -EFAULT;
		rinfo.report_type = finfo.report_type;
		rinfo.report_id = finfo.report_id;
		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		if (finfo.field_index >= report->maxfield)
			return -EINVAL;

		field = report->field[finfo.field_index];
		memset(&finfo, 0, sizeof(finfo));
		finfo.report_type = rinfo.report_type;
		finfo.report_id = rinfo.report_id;
		finfo.field_index = field->report_count - 1;
		finfo.maxusage = field->maxusage;
		finfo.flags = field->flags;
		finfo.physical = field->physical;
		finfo.logical = field->logical;
		finfo.application = field->application;
		finfo.logical_minimum = field->logical_minimum;
		finfo.logical_maximum = field->logical_maximum;
		finfo.physical_minimum = field->physical_minimum;
		finfo.physical_maximum = field->physical_maximum;
		finfo.unit_exponent = field->unit_exponent;
		finfo.unit = field->unit;

		if (copy_to_user((void *) arg, &finfo, sizeof(finfo)))
			return -EFAULT;
		return 0;

	case HIDIOCGUCODE:
		if (copy_from_user(uref, (void *) arg, sizeof(*uref)))
			return -EFAULT;

		rinfo.report_type = uref->report_type;
		rinfo.report_id = uref->report_id;
		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		if (uref->field_index >= report->maxfield)
			return -EINVAL;

		field = report->field[uref->field_index];
		if (uref->usage_index >= field->maxusage)
			return -EINVAL;

		uref->usage_code = field->usage[uref->usage_index].hid;

		if (copy_to_user((void *) arg, uref, sizeof(*uref)))
			return -EFAULT;
		return 0;

	case HIDIOCGUSAGE:
	case HIDIOCSUSAGE:
	case HIDIOCGUSAGES:
	case HIDIOCSUSAGES:
	case HIDIOCGCOLLECTIONINDEX:
		if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) {
			if (copy_from_user(&uref_multi, (void *) arg, 
					   sizeof(uref_multi)))
				return -EFAULT;
		} else {
			if (copy_from_user(uref, (void *) arg, sizeof(*uref)))
				return -EFAULT;
		}

		if (cmd != HIDIOCGUSAGE && 
		    cmd != HIDIOCGUSAGES &&
		    uref->report_type == HID_REPORT_TYPE_INPUT)
			return -EINVAL;

		if (uref->report_id == HID_REPORT_ID_UNKNOWN) {
			field = hiddev_lookup_usage(hid, uref);
			if (field == NULL)
				return -EINVAL;
		} else {
			rinfo.report_type = uref->report_type;
			rinfo.report_id = uref->report_id;
			if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
				return -EINVAL;

			if (uref->field_index >= report->maxfield)
				return -EINVAL;

			field = report->field[uref->field_index];
			if (uref->usage_index >= field->maxusage)
				return -EINVAL;

			if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) {
				if (uref_multi.num_values >= HID_MAX_USAGES || 
				    uref->usage_index >= field->maxusage || 
				   (uref->usage_index + uref_multi.num_values) >= field->maxusage)
					return -EINVAL;
			}
		}

		switch (cmd) {
		case HIDIOCGUSAGE:
			uref->value = field->value[uref->usage_index];
			return copy_to_user((void *) arg, uref, sizeof(*uref));

		case HIDIOCSUSAGE:
			field->value[uref->usage_index] = uref->value;
			return 0;

		case HIDIOCGCOLLECTIONINDEX:
			return field->usage[uref->usage_index].collection_index;
		case HIDIOCGUSAGES:
			for (i = 0; i < uref_multi.num_values; i++)
				uref_multi.values[i] = 
				    field->value[uref->usage_index + i];
			if (copy_to_user((void *) arg, &uref_multi, 
					 sizeof(uref_multi)))
				return -EFAULT;
			return 0;
		case HIDIOCSUSAGES:
			for (i = 0; i < uref_multi.num_values; i++)
				field->value[uref->usage_index + i] = 
				    uref_multi.values[i];
			return 0;
		}
		break;

	case HIDIOCGCOLLECTIONINFO:
		if (copy_from_user(&cinfo, (void *) arg, sizeof(cinfo)))
			return -EFAULT;

		if (cinfo.index >= hid->maxcollection)
			return -EINVAL;

		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((void *) arg, &cinfo, sizeof(cinfo)))
			return -EFAULT;
		return 0;

	default:

		if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ)
			return -EINVAL;

		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) {
			int len;
			if (!hid->name) return 0;
			len = strlen(hid->name) + 1;
			if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd);
			return copy_to_user((char *) arg, hid->name, len) ?
				-EFAULT : len;
		}
	}
	return -EINVAL;
}
Beispiel #2
0
/*
 * "ioctl" file op
 */
static int hiddev_ioctl(struct inode *inode, 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 = hid->dev;
	struct hiddev_report_info rinfo;
	struct hiddev_usage_ref uref;
	struct hid_report *report;
	struct hid_field *field;

	if (!hiddev->exist) return -EIO;

	switch (cmd) {

	case HIDIOCGVERSION:
		return put_user(HID_VERSION, (int *) arg);

	case HIDIOCAPPLICATION:
		if (arg < 0 || arg >= hid->maxapplication)
			return -EINVAL;
		return hid->application[arg];

	case HIDIOCGDEVINFO:
	{
		struct hiddev_devinfo dinfo;
		dinfo.bustype = BUS_USB;
		dinfo.busnum = dev->bus->busnum;
		dinfo.devnum = dev->devnum;
		dinfo.ifnum = hid->ifnum;
		dinfo.vendor = dev->descriptor.idVendor;
		dinfo.product = dev->descriptor.idProduct;
		dinfo.version = dev->descriptor.bcdDevice;
		dinfo.num_applications = hid->maxapplication;
		return copy_to_user((void *) arg, &dinfo, sizeof(dinfo));
	}

	case HIDIOCGSTRING:
		{
			int idx, len;
			char *buf;

			if (get_user(idx, (int *) arg))
				return -EFAULT;

			if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL)
				return -ENOMEM;

			if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) {
				kfree(buf);
				return -EINVAL;
			}

			if (copy_to_user((void *) (arg+sizeof(int)), buf, len+1)) {
				kfree(buf);
				return -EFAULT;
			}

			kfree(buf);

			return len;
		}

	case HIDIOCINITREPORT:

		hid_init_reports(hid);

		return 0;

	case HIDIOCGREPORT:
		if (copy_from_user(&rinfo, (void *) arg, sizeof(rinfo)))
			return -EFAULT;

		if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT)
			return -EINVAL;

		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		hid_read_report(hid, report);

		return 0;

	case HIDIOCSREPORT:
		if (copy_from_user(&rinfo, (void *) arg, sizeof(rinfo)))
			return -EFAULT;

		if (rinfo.report_type == HID_REPORT_TYPE_INPUT)
			return -EINVAL;

		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		hid_write_report(hid, report);

		return 0;

	case HIDIOCGREPORTINFO:
		if (copy_from_user(&rinfo, (void *) arg, sizeof(rinfo)))
			return -EFAULT;

		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		rinfo.num_fields = report->maxfield;

		return copy_to_user((void *) arg, &rinfo, sizeof(rinfo));

	case HIDIOCGFIELDINFO:
	{
		struct hiddev_field_info finfo;
		if (copy_from_user(&finfo, (void *) arg, sizeof(finfo)))
			return -EFAULT;
		rinfo.report_type = finfo.report_type;
		rinfo.report_id = finfo.report_id;
		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		if (finfo.field_index >= report->maxfield)
			return -EINVAL;

		field = report->field[finfo.field_index];
		memset(&finfo, 0, sizeof(finfo));
		finfo.report_type = rinfo.report_type;
		finfo.report_id = rinfo.report_id;
		finfo.field_index = field->report_count - 1;
		finfo.maxusage = field->maxusage;
		finfo.flags = field->flags;
		finfo.physical = field->physical;
		finfo.logical = field->logical;
		finfo.application = field->application;
		finfo.logical_minimum = field->logical_minimum;
		finfo.logical_maximum = field->logical_maximum;
		finfo.physical_minimum = field->physical_minimum;
		finfo.physical_maximum = field->physical_maximum;
		finfo.unit_exponent = field->unit_exponent;
		finfo.unit = field->unit;

		return copy_to_user((void *) arg, &finfo, sizeof(finfo));
	}

	case HIDIOCGUCODE:
		if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
			return -EFAULT;

		rinfo.report_type = uref.report_type;
		rinfo.report_id = uref.report_id;
		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
			return -EINVAL;

		if (uref.field_index >= report->maxfield)
			return -EINVAL;

		field = report->field[uref.field_index];
		if (uref.usage_index >= field->maxusage)
			return -EINVAL;

		uref.usage_code = field->usage[uref.usage_index].hid;

		return copy_to_user((void *) arg, &uref, sizeof(uref));

	case HIDIOCGUSAGE:
		if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
			return -EFAULT;

		if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
			field = hiddev_lookup_usage(hid, &uref);
			if (field == NULL)
				return -EINVAL;
		} else {
			rinfo.report_type = uref.report_type;
			rinfo.report_id = uref.report_id;
			if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
				return -EINVAL;

			if (uref.field_index >= report->maxfield)
				return -EINVAL;

			field = report->field[uref.field_index];
			if (uref.usage_index >= field->maxusage)
				return -EINVAL;
		}

		uref.value = field->value[uref.usage_index];

		return copy_to_user((void *) arg, &uref, sizeof(uref));

	case HIDIOCSUSAGE:
		if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
			return -EFAULT;

		if (uref.report_type == HID_REPORT_TYPE_INPUT)
			return -EINVAL;

		if (uref.report_id == HID_REPORT_ID_UNKNOWN) {
			field = hiddev_lookup_usage(hid, &uref);
			if (field == NULL)
				return -EINVAL;
		} else {
			rinfo.report_type = uref.report_type;
			rinfo.report_id = uref.report_id;
			if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
				return -EINVAL;

			if (uref.field_index >= report->maxfield)
				return -EINVAL;

			field = report->field[uref.field_index];
			if (uref.usage_index >= field->maxusage)
				return -EINVAL;
		}

		field->value[uref.usage_index] = uref.value;

		return 0;

	default:

		if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ)
			return -EINVAL;

		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) {
			int len;
			if (!hid->name) return 0;
			len = strlen(hid->name) + 1;
			if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd);
			return copy_to_user((char *) arg, hid->name, len) ?
				-EFAULT : len;
		}
	}
	return -EINVAL;
}