patch-2.4.22 linux-2.4.22/drivers/usb/hiddev.c

Next file: linux-2.4.22/drivers/usb/host/Config.in
Previous file: linux-2.4.22/drivers/usb/hid.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.21/drivers/usb/hiddev.c linux-2.4.22/drivers/usb/hiddev.c
@@ -50,9 +50,10 @@
 };
 
 struct hiddev_list {
-	struct hiddev_event buffer[HIDDEV_BUFFER_SIZE];
+	struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE];
 	int head;
 	int tail;
+	unsigned flags;
 	struct fasync_struct *fasync;
 	struct hiddev *hiddev;
 	struct hiddev_list *next;
@@ -70,7 +71,8 @@
 static struct hid_report *
 hiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo)
 {
-	struct hid_report_enum *report_enum;
+	unsigned flags = rinfo->report_id & ~HID_REPORT_ID_MASK;
+	struct hid_report_enum *report_enum = NULL;
 	struct list_head *list;
 
 	if (rinfo->report_type < HID_REPORT_TYPE_MIN ||
@@ -78,27 +80,29 @@
 
 	report_enum = hid->report_enum +
 		(rinfo->report_type - HID_REPORT_TYPE_MIN);
-	if ((rinfo->report_id & ~HID_REPORT_ID_MASK) != 0) {
-		switch (rinfo->report_id & ~HID_REPORT_ID_MASK) {
-		case HID_REPORT_ID_FIRST:
-			list = report_enum->report_list.next;
-			if (list == &report_enum->report_list) return NULL;
-			rinfo->report_id = ((struct hid_report *) list)->id;
-			break;
-
-		case HID_REPORT_ID_NEXT:
-			list = (struct list_head *)
-				report_enum->report_id_hash[rinfo->report_id &
-							   HID_REPORT_ID_MASK];
-			if (list == NULL) return NULL;
-			list = list->next;
-			if (list == &report_enum->report_list) return NULL;
-			rinfo->report_id = ((struct hid_report *) list)->id;
-			break;
-
-		default:
-			return NULL;
-		}
+	      
+	switch (flags) {
+	case 0: /* Nothing to do -- report_id is already set correctly */
+		break;
+
+	case HID_REPORT_ID_FIRST:
+		list = report_enum->report_list.next;
+		if (list == &report_enum->report_list) return NULL;
+		rinfo->report_id = ((struct hid_report *) list)->id;
+		break;
+		
+	case HID_REPORT_ID_NEXT:
+		list = (struct list_head *)
+			report_enum->report_id_hash[rinfo->report_id &
+						    HID_REPORT_ID_MASK];
+		if (list == NULL) return NULL;
+		list = list->next;
+		if (list == &report_enum->report_list) return NULL;
+		rinfo->report_id = ((struct hid_report *) list)->id;
+		break;
+		
+	default:
+		return NULL;
 	}
 
 	return report_enum->report_id_hash[rinfo->report_id];
@@ -142,21 +146,20 @@
 	return NULL;
 }
 
-/*
- * This is where hid.c calls into hiddev to pass an event that occurred over
- * the interrupt pipe
- */
-void hiddev_hid_event(struct hid_device *hid, unsigned int usage, int value)
+static void hiddev_send_event(struct hid_device *hid,
+			      struct hiddev_usage_ref *uref)
 {
 	struct hiddev *hiddev = hid->hiddev;
 	struct hiddev_list *list = hiddev->list;
 
 	while (list) {
-		list->buffer[list->head].hid = usage;
-		list->buffer[list->head].value = value;
-		list->head = (list->head + 1) & (HIDDEV_BUFFER_SIZE - 1);
-
-		kill_fasync(&list->fasync, SIGIO, POLL_IN);
+		if (uref->field_index != HID_FIELD_INDEX_NONE ||
+		    (list->flags & HIDDEV_FLAG_REPORT) != 0) {
+			list->buffer[list->head] = *uref;
+			list->head = (list->head + 1) & 
+				(HIDDEV_BUFFER_SIZE - 1);
+			kill_fasync(&list->fasync, SIGIO, POLL_IN);
+		}
 
 		list = list->next;
 	}
@@ -165,6 +168,46 @@
 }
 
 /*
+ * This is where hid.c calls into hiddev to pass an event that occurred over
+ * the interrupt pipe
+ */
+void hiddev_hid_event(struct hid_device *hid, struct hid_field *field,
+		      struct hid_usage *usage, __s32 value)
+{
+	unsigned type = field->report_type;
+	struct hiddev_usage_ref uref;
+
+	uref.report_type = 
+	  (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
+	  ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : 
+	   ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE:0));
+	uref.report_id = field->report->id;
+	uref.field_index = field->index;
+	uref.usage_index = (usage - field->usage);
+	uref.usage_code = usage->hid;
+	uref.value = value;
+
+	hiddev_send_event(hid, &uref);
+}
+
+
+void hiddev_report_event(struct hid_device *hid, struct hid_report *report)
+{
+	unsigned type = report->type;
+	struct hiddev_usage_ref uref;
+
+	memset(&uref, 0, sizeof(uref));
+	uref.report_type = 
+	  (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
+	  ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT : 
+	   ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE:0));
+	uref.report_id = report->id;
+	uref.field_index = HID_FIELD_INDEX_NONE;
+
+	hiddev_send_event(hid, &uref);
+}
+
+/*
  * fasync file op
  */
 static int hiddev_fasync(int fd, struct file *file, int on)
@@ -193,7 +236,6 @@
 	struct hiddev_list *list = file->private_data;
 	struct hiddev_list **listptr;
 
-	lock_kernel();
 	listptr = &list->hiddev->list;
 	hiddev_fasync(-1, file, 0);
 
@@ -209,7 +251,6 @@
 	}
 
 	kfree(list);
-	unlock_kernel();
 
 	return 0;
 }
@@ -259,43 +300,66 @@
 {
 	DECLARE_WAITQUEUE(wait, current);
 	struct hiddev_list *list = file->private_data;
+	int event_size;
 	int retval = 0;
 
-	if (list->head == list->tail) {
-
-		add_wait_queue(&list->hiddev->wait, &wait);
-		set_current_state(TASK_INTERRUPTIBLE);
+	event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ?
+		sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event);
 
-		while (list->head == list->tail) {
+	if (count < event_size) return 0;
 
-			if (file->f_flags & O_NONBLOCK) {
-				retval = -EAGAIN;
-				break;
-			}
-			if (signal_pending(current)) {
-				retval = -ERESTARTSYS;
-				break;
-			}
-			if (!list->hiddev->exist) {
-				retval = -EIO;
-				break;
+	while (retval == 0) {
+		if (list->head == list->tail) {
+			add_wait_queue(&list->hiddev->wait, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+			
+			while (list->head == list->tail) {
+				if (file->f_flags & O_NONBLOCK) {
+					retval = -EAGAIN;
+					break;
+				}
+				if (signal_pending(current)) {
+					retval = -ERESTARTSYS;
+					break;
+				}
+				if (!list->hiddev->exist) {
+					retval = -EIO;
+					break;
+				}
+				
+				schedule();
 			}
 
-			schedule();
+			set_current_state(TASK_RUNNING);
+			remove_wait_queue(&list->hiddev->wait, &wait);
 		}
 
-		set_current_state(TASK_RUNNING);
-		remove_wait_queue(&list->hiddev->wait, &wait);
-	}
+		if (retval)
+			return retval;
 
-	if (retval)
-		return retval;
+		while (list->head != list->tail && 
+		       retval + event_size <= count) {
+			if ((list->flags & HIDDEV_FLAG_UREF) == 0) {
+				if (list->buffer[list->tail].field_index !=
+				    HID_FIELD_INDEX_NONE) {
+					struct hiddev_event event;
+					event.hid = list->buffer[list->tail].usage_code;
+					event.value = list->buffer[list->tail].value;
+					if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event)))
+						return -EFAULT;
+					retval += sizeof(struct hiddev_event);
+				}
+			} else {
+				if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE ||
+				    (list->flags & HIDDEV_FLAG_REPORT) != 0) {
+					if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref)))
+						return -EFAULT;
+					retval += sizeof(struct hiddev_usage_ref);
+				}
+			}
+			list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1);
+		}
 
-	while (list->head != list->tail && retval + sizeof(struct hiddev_event) <= count) {
-		if (copy_to_user(buffer + retval, list->buffer + list->tail,
-				 sizeof(struct hiddev_event))) return -EFAULT;
-		list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1);
-		retval += sizeof(struct hiddev_event);
 	}
 
 	return retval;
@@ -329,10 +393,14 @@
 	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 uref;
+	struct hiddev_devinfo dinfo;
 	struct hid_report *report;
 	struct hid_field *field;
+	int i;
 
 	if (!hiddev->exist) return -EIO;
 
@@ -344,11 +412,18 @@
 	case HIDIOCAPPLICATION:
 		if (arg < 0 || arg >= hid->maxapplication)
 			return -EINVAL;
-		return hid->application[arg];
+
+		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:
-	{
-		struct hiddev_devinfo dinfo;
 		dinfo.bustype = BUS_USB;
 		dinfo.busnum = dev->bus->busnum;
 		dinfo.devnum = dev->devnum;
@@ -358,7 +433,25 @@
 		dinfo.version = dev->descriptor.bcdDevice;
 		dinfo.num_applications = hid->maxapplication;
 		return copy_to_user((void *) arg, &dinfo, sizeof(dinfo));
-	}
+
+	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:
 		{
@@ -387,7 +480,6 @@
 		}
 
 	case HIDIOCINITREPORT:
-
 		hid_init_reports(hid);
 
 		return 0;
@@ -432,8 +524,6 @@
 		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;
@@ -462,7 +552,6 @@
 		finfo.unit = field->unit;
 
 		return copy_to_user((void *) arg, &finfo, sizeof(finfo));
-	}
 
 	case HIDIOCGUCODE:
 		if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
@@ -485,9 +574,15 @@
 		return copy_to_user((void *) arg, &uref, sizeof(uref));
 
 	case HIDIOCGUSAGE:
+	case HIDIOCSUSAGE:
+	case HIDIOCGCOLLECTIONINDEX:
 		if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
 			return -EFAULT;
 
+		if (cmd == HIDIOCSUSAGE &&
+		    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)
@@ -506,38 +601,32 @@
 				return -EINVAL;
 		}
 
-		uref.value = field->value[uref.usage_index];
+		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;
 
-		return copy_to_user((void *) arg, &uref, sizeof(uref));
+		case HIDIOCGCOLLECTIONINDEX:
+			return field->usage[uref.usage_index].collection_index;
+		}
+		break;
 
-	case HIDIOCSUSAGE:
-		if (copy_from_user(&uref, (void *) arg, sizeof(uref)))
+	case HIDIOCGCOLLECTIONINFO:
+		if (copy_from_user(&cinfo, (void *) arg, sizeof(cinfo)))
 			return -EFAULT;
 
-		if (uref.report_type == HID_REPORT_TYPE_INPUT)
+		if (cinfo.index >= hid->maxcollection)
 			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;
+		cinfo.type = hid->collection[cinfo.index].type;
+		cinfo.usage = hid->collection[cinfo.index].usage;
+		cinfo.level = hid->collection[cinfo.index].level;
 
-		return 0;
+		return copy_to_user((void *) arg, &cinfo, sizeof(cinfo));
 
 	default:
 
@@ -576,12 +665,18 @@
 	int minor, i;
 	char devfs_name[16];
 
-	for (i = 0; i < hid->maxapplication; i++)
-		if (!IS_INPUT_APPLICATION(hid->application[i]))
-			break;
 
-	if (i == hid->maxapplication)
-		return -1;
+
+	if ((hid->quirks & HID_QUIRK_HIDDEV) == 0) {
+		for (i = 0; i < hid->maxcollection; i++)
+			if (hid->collection[i].type == 
+			    HID_COLLECTION_APPLICATION &&
+			    !IS_INPUT_APPLICATION(hid->collection[i].usage))
+				break;
+
+		if (i == hid->maxcollection)
+			return -1;
+	}
 
 	for (minor = 0; minor < HIDDEV_MINORS && hiddev_table[minor]; minor++);
 	if (minor == HIDDEV_MINORS) {

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)