@@ -16,6 +16,7 @@
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/usb/g_hid.h>
+#include <linux/uhid.h>
#include "u_f.h"
#include "u_hid.h"
@@ -27,6 +28,11 @@ static struct class *hidg_class;
static DEFINE_IDA(hidg_ida);
static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */
+struct report_entry {
+ struct uhid_set_report_req report_data;
+ struct list_head node;
+};
+
/*-------------------------------------------------------------------------*/
/* HID gadget struct */
@@ -71,6 +77,10 @@ struct f_hidg {
wait_queue_head_t write_queue;
struct usb_request *req;
+ /* hid report list */
+ spinlock_t report_spinlock;
+ struct list_head report_list;
+
int minor;
struct cdev cdev;
struct usb_function func;
@@ -553,6 +563,136 @@ static int f_hidg_open(struct inode *inode, struct file *fd)
return 0;
}
+static bool f_hidg_param_valid(struct report_entry *entry)
+{
+
+ if (entry->report_data.size > UHID_DATA_MAX)
+ return false;
+
+ switch (entry->report_data.rtype) {
+ case UHID_FEATURE_REPORT:
+ case UHID_OUTPUT_REPORT:
+ case UHID_INPUT_REPORT:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static struct report_entry *f_hidg_search_for_report(struct f_hidg *hidg, u8 rnum, u8 rtype)
+{
+ struct list_head *ptr;
+ struct report_entry *entry;
+
+ list_for_each(ptr, &hidg->report_list) {
+ entry = list_entry(ptr, struct report_entry, node);
+ if (entry->report_data.rnum == rnum &&
+ entry->report_data.rtype == rtype) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+static long f_hidg_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct f_hidg *hidg = file->private_data;
+ struct report_entry *entry;
+ struct report_entry *ptr;
+ unsigned long flags;
+ struct uhid_get_report_req report;
+ u16 size;
+
+ switch (cmd) {
+ case GADGET_ADD_REPORT_STATUS:
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ if (copy_from_user(&entry->report_data, (struct uhid_set_report_req *)arg,
+ sizeof(struct uhid_set_report_req))) {
+ kfree(entry);
+ return -EFAULT;
+ }
+
+ if (f_hidg_param_valid(entry) == false) {
+ kfree(entry);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ ptr = f_hidg_search_for_report(hidg, entry->report_data.rnum,
+ entry->report_data.rtype);
+ if (ptr) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ kfree(ptr);
+ return -EEXIST;
+ }
+ list_add_tail(&entry->node, &hidg->report_list);
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ break;
+ case GADGET_REMOVE_REPORT_STATUS:
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ if (list_empty(&hidg->report_list)) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+
+ if (copy_from_user(&report, (struct uhid_get_report_req *)arg, sizeof(report)))
+ return -EFAULT;
+
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+
+ ptr = f_hidg_search_for_report(hidg, report.rnum, report.rtype);
+ if (ptr) {
+ list_del(&ptr->node);
+ } else {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ kfree(ptr);
+ break;
+ case GADGET_UPDATE_REPORT_STATUS:
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ if (list_empty(&hidg->report_list)) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+
+ if (copy_from_user(&report, (struct uhid_get_report_req *)arg, sizeof(report)))
+ return -EFAULT;
+
+ if (copy_from_user(&size, (void __user *)(arg + sizeof(report)), sizeof(size)))
+ return -EFAULT;
+
+ if (size > UHID_DATA_MAX)
+ return -EINVAL;
+
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+
+ ptr = f_hidg_search_for_report(hidg, report.rnum, report.rtype);
+ if (!ptr) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -ENODATA;
+ }
+
+ if (copy_from_user(&ptr->report_data, (struct uhid_set_report_req *)arg,
+ sizeof(struct uhid_set_report_req))) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ return -EFAULT;
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ break;
+ }
+ return 0;
+}
+
/*-------------------------------------------------------------------------*/
/* usb_function */
@@ -634,6 +774,8 @@ static int hidg_setup(struct usb_function *f,
struct f_hidg *hidg = func_to_hidg(f);
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = cdev->req;
+ struct report_entry *entry;
+ unsigned long flags;
int status = 0;
__u16 value, length;
@@ -649,9 +791,25 @@ static int hidg_setup(struct usb_function *f,
| HID_REQ_GET_REPORT):
VDBG(cdev, "get_report\n");
- /* send an empty report */
length = min_t(unsigned, length, hidg->report_length);
- memset(req->buf, 0x0, length);
+ spin_lock_irqsave(&hidg->report_spinlock, flags);
+ if (list_empty(&hidg->report_list)) {
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
+ memset(req->buf, 0x0, length);
+ goto respond;
+ }
+
+ entry = f_hidg_search_for_report(hidg, value & 0xf,
+ value >> 8);
+
+ /* send a report */
+ if (entry) {
+ length = min_t(unsigned, length, entry->report_data.size);
+ memcpy(req->buf, entry->report_data.data, length);
+ } else {
+ memset(req->buf, 0x0, length);
+ }
+ spin_unlock_irqrestore(&hidg->report_spinlock, flags);
goto respond;
break;
@@ -893,6 +1051,7 @@ static const struct file_operations f_hidg_fops = {
.owner = THIS_MODULE,
.open = f_hidg_open,
.release = f_hidg_release,
+ .unlocked_ioctl = f_hidg_ioctl,
.write = f_hidg_write,
.read = f_hidg_read,
.poll = f_hidg_poll,
@@ -997,6 +1156,9 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
init_waitqueue_head(&hidg->read_queue);
INIT_LIST_HEAD(&hidg->completed_out_req);
+ spin_lock_init(&hidg->report_spinlock);
+ INIT_LIST_HEAD(&hidg->report_list);
+
/* create char device */
cdev_init(&hidg->cdev, &f_hidg_fops);
dev = MKDEV(major, hidg->minor);
similarity index 72%
rename from include/linux/usb/g_hid.h
rename to include/uapi/linux/usb/g_hid.h
@@ -22,6 +22,8 @@
#ifndef __LINUX_USB_G_HID_H
#define __LINUX_USB_G_HID_H
+#include <linux/uhid.h>
+
struct hidg_func_descriptor {
unsigned char subclass;
unsigned char protocol;
@@ -30,4 +32,12 @@ struct hidg_func_descriptor {
unsigned char report_desc[];
};
+/* The 'g' code is also used by gadgetfs and printer ioctl requests.
+ * Don't add any colliding codes to either driver, and keep
+ * them in unique ranges (size 0x40 for now).
+ */
+#define GADGET_ADD_REPORT_STATUS _IOWR('g', 0x41, struct uhid_set_report_req)
+#define GADGET_REMOVE_REPORT_STATUS _IOWR('g', 0x42, struct uhid_get_report_req)
+#define GADGET_UPDATE_REPORT_STATUS _IOWR('g', 0x43, struct uhid_set_report_req)
+
#endif /* __LINUX_USB_G_HID_H */