USB: gadget: f_accessory: Add support for HID input devices
Rakesh Bodla [Tue, 14 Aug 2012 12:06:15 +0000 (17:06 +0530)]
Bug 1026047

Signed-off-by: Mike Lockwood <lockwood@google.com>
(cherry picked from commit 7a3d01af4f04950a86a18f5a439f1802d8093650)

Conflicts:

drivers/usb/gadget/f_accessory.c

Change-Id: I30bc02a1bcf636967d56370db83578f49f0e7514
Signed-off-by: Rakesh Bodla <rbodla@nvidia.com>
Reviewed-on: http://git-master/r/123342
Reviewed-by: Venkat Moganty <vmoganty@nvidia.com>

drivers/usb/gadget/android.c
drivers/usb/gadget/f_accessory.c
include/linux/usb/f_accessory.h

index 4805670..e23557e 100644 (file)
@@ -1113,6 +1113,11 @@ static void android_disconnect(struct usb_gadget *gadget)
        unsigned long flags;
 
        composite_disconnect(gadget);
+       /* accessory HID support can be active while the
+          accessory function is not actually enabled,
+          so we need to inform it when we are disconnected.
+        */
+       acc_disconnect();
 
        spin_lock_irqsave(&cdev->lock, flags);
        dev->connected = 0;
index bb6bee1..d248ef9 100644 (file)
@@ -33,6 +33,8 @@
 #include <linux/device.h>
 #include <linux/miscdevice.h>
 
+#include <linux/hid.h>
+#include <linux/hiddev.h>
 #include <linux/usb.h>
 #include <linux/usb/ch9.h>
 #include <linux/usb/f_accessory.h>
 #define TX_REQ_MAX 4
 #define RX_REQ_MAX 2
 
+struct acc_hid_dev {
+       struct list_head        list;
+       struct hid_device *hid;
+       struct acc_dev *dev;
+       /* accessory defined ID */
+       int id;
+       /* HID report descriptor */
+       u8 *report_desc;
+       /* length of HID report descriptor */
+       int report_desc_len;
+       /* number of bytes of report_desc we have received so far */
+       int report_desc_offset;
+};
+
 struct acc_dev {
        struct usb_function function;
        struct usb_composite_dev *cdev;
@@ -89,7 +105,21 @@ struct acc_dev {
        wait_queue_head_t write_wq;
        struct usb_request *rx_req[RX_REQ_MAX];
        int rx_done;
-       struct delayed_work work;
+
+       /* delayed work for handling ACCESSORY_START */
+       struct delayed_work start_work;
+
+       /* worker for registering and unregistering hid devices */
+       struct work_struct hid_work;
+
+       /* list of active HID devices */
+       struct list_head        hid_list;
+
+       /* list of new HID devices to register */
+       struct list_head        new_hid_list;
+
+       /* list of dead HID devices to unregister */
+       struct list_head        dead_hid_list;
 };
 
 static struct usb_interface_descriptor acc_interface_desc = {
@@ -298,6 +328,160 @@ static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req)
        }
 }
 
+static void acc_complete_set_hid_report_desc(struct usb_ep *ep,
+               struct usb_request *req)
+{
+       struct acc_hid_dev *hid = req->context;
+       struct acc_dev *dev = hid->dev;
+       int length = req->actual;
+
+       if (req->status != 0) {
+               pr_err("acc_complete_set_hid_report_desc, err %d\n",
+                       req->status);
+               return;
+       }
+
+       memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length);
+       hid->report_desc_offset += length;
+       if (hid->report_desc_offset == hid->report_desc_len) {
+               /* After we have received the entire report descriptor
+                * we schedule work to initialize the HID device
+                */
+               schedule_work(&dev->hid_work);
+       }
+}
+
+static void acc_complete_send_hid_event(struct usb_ep *ep,
+               struct usb_request *req)
+{
+       struct acc_hid_dev *hid = req->context;
+       int length = req->actual;
+
+       if (req->status != 0) {
+               pr_err("acc_complete_send_hid_event, err %d\n", req->status);
+               return;
+       }
+
+       hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1);
+}
+
+static int acc_hid_parse(struct hid_device *hid)
+{
+       struct acc_hid_dev *hdev = hid->driver_data;
+
+       hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len);
+       return 0;
+}
+
+static int acc_hid_start(struct hid_device *hid)
+{
+       return 0;
+}
+
+static void acc_hid_stop(struct hid_device *hid)
+{
+}
+
+static int acc_hid_open(struct hid_device *hid)
+{
+       return 0;
+}
+
+static void acc_hid_close(struct hid_device *hid)
+{
+}
+
+static struct hid_ll_driver acc_hid_ll_driver = {
+       .parse = acc_hid_parse,
+       .start = acc_hid_start,
+       .stop = acc_hid_stop,
+       .open = acc_hid_open,
+       .close = acc_hid_close,
+};
+
+static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev,
+               int id, int desc_len)
+{
+       struct acc_hid_dev *hdev;
+
+       hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC);
+       if (!hdev)
+               return NULL;
+       hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC);
+       if (!hdev->report_desc) {
+               kfree(hdev);
+               return NULL;
+       }
+       hdev->dev = dev;
+       hdev->id = id;
+       hdev->report_desc_len = desc_len;
+
+       return hdev;
+}
+
+static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id)
+{
+       struct acc_hid_dev *hid;
+
+       list_for_each_entry(hid, list, list) {
+               if (hid->id == id)
+                       return hid;
+       }
+       return NULL;
+}
+
+static int acc_register_hid(struct acc_dev *dev, int id, int desc_length)
+{
+       struct acc_hid_dev *hid;
+       unsigned long flags;
+
+       /* report descriptor length must be > 0 */
+       if (desc_length <= 0)
+               return -EINVAL;
+
+       spin_lock_irqsave(&dev->lock, flags);
+       /* replace HID if one already exists with this ID */
+       hid = acc_hid_get(&dev->hid_list, id);
+       if (!hid)
+               hid = acc_hid_get(&dev->new_hid_list, id);
+       if (hid)
+               list_move(&hid->list, &dev->dead_hid_list);
+
+       hid = acc_hid_new(dev, id, desc_length);
+       if (!hid) {
+               spin_unlock_irqrestore(&dev->lock, flags);
+               return -ENOMEM;
+       }
+
+       list_add(&hid->list, &dev->new_hid_list);
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       /* schedule work to register the HID device */
+       schedule_work(&dev->hid_work);
+       return 0;
+}
+
+static int acc_unregister_hid(struct acc_dev *dev, int id)
+{
+       struct acc_hid_dev *hid;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->lock, flags);
+       hid = acc_hid_get(&dev->hid_list, id);
+       if (!hid)
+               hid = acc_hid_get(&dev->new_hid_list, id);
+       if (!hid) {
+               spin_unlock_irqrestore(&dev->lock, flags);
+               return -EINVAL;
+       }
+
+       list_move(&hid->list, &dev->dead_hid_list);
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       schedule_work(&dev->hid_work);
+       return 0;
+}
+
 static int create_bulk_endpoints(struct acc_dev *dev,
                                struct usb_endpoint_descriptor *in_desc,
                                struct usb_endpoint_descriptor *out_desc)
@@ -355,7 +539,7 @@ static int create_bulk_endpoints(struct acc_dev *dev,
        return 0;
 
 fail:
-       printk(KERN_ERR "acc_bind() could not allocate requests\n");
+       pr_err("acc_bind() could not allocate requests\n");
        while ((req = req_get(dev, &dev->tx_idle)))
                acc_request_free(req, dev->ep_in);
        for (i = 0; i < RX_REQ_MAX; i++)
@@ -544,7 +728,7 @@ static int acc_release(struct inode *ip, struct file *fp)
        return 0;
 }
 
-/* file operations for /dev/acc_usb */
+/* file operations for /dev/usb_accessory */
 static const struct file_operations acc_fops = {
        .owner = THIS_MODULE,
        .read = acc_read,
@@ -554,23 +738,47 @@ static const struct file_operations acc_fops = {
        .release = acc_release,
 };
 
+static int acc_hid_probe(struct hid_device *hdev,
+               const struct hid_device_id *id)
+{
+       int ret;
+
+       ret = hid_parse(hdev);
+       if (ret)
+               return ret;
+       return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+}
+
 static struct miscdevice acc_device = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "usb_accessory",
        .fops = &acc_fops,
 };
 
+static const struct hid_device_id acc_hid_table[] = {
+       { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) },
+       { }
+};
+
+static struct hid_driver acc_hid_driver = {
+       .name = "USB accessory",
+       .id_table = acc_hid_table,
+       .probe = acc_hid_probe,
+};
 
 static int acc_ctrlrequest(struct usb_composite_dev *cdev,
                                const struct usb_ctrlrequest *ctrl)
 {
        struct acc_dev  *dev = _acc_dev;
        int     value = -EOPNOTSUPP;
+       struct acc_hid_dev *hid;
+       int offset;
        u8 b_requestType = ctrl->bRequestType;
        u8 b_request = ctrl->bRequest;
        u16     w_index = le16_to_cpu(ctrl->wIndex);
        u16     w_value = le16_to_cpu(ctrl->wValue);
        u16     w_length = le16_to_cpu(ctrl->wLength);
+       unsigned long flags;
 
 /*
        printk(KERN_INFO "acc_ctrlrequest "
@@ -583,7 +791,7 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev,
                if (b_request == ACCESSORY_START) {
                        dev->start_requested = 1;
                        schedule_delayed_work(
-                               &dev->work, msecs_to_jiffies(10));
+                               &dev->start_work, msecs_to_jiffies(10));
                        value = 0;
                } else if (b_request == ACCESSORY_SEND_STRING) {
                        dev->string_index = w_index;
@@ -594,13 +802,45 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev,
                                w_index == 0 && w_length == 0) {
                        dev->audio_mode = w_value;
                        value = 0;
+               } else if (b_request == ACCESSORY_REGISTER_HID) {
+                       value = acc_register_hid(dev, w_value, w_index);
+               } else if (b_request == ACCESSORY_UNREGISTER_HID) {
+                       value = acc_unregister_hid(dev, w_value);
+               } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) {
+                       spin_lock_irqsave(&dev->lock, flags);
+                       hid = acc_hid_get(&dev->new_hid_list, w_value);
+                       spin_unlock_irqrestore(&dev->lock, flags);
+                       if (!hid) {
+                               value = -EINVAL;
+                               goto err;
+                       }
+                       offset = w_index;
+                       if (offset != hid->report_desc_offset
+                               || offset + w_length > hid->report_desc_len) {
+                               value = -EINVAL;
+                               goto err;
+                       }
+                       cdev->req->context = hid;
+                       cdev->req->complete = acc_complete_set_hid_report_desc;
+                       value = w_length;
+               } else if (b_request == ACCESSORY_SEND_HID_EVENT) {
+                       spin_lock_irqsave(&dev->lock, flags);
+                       hid = acc_hid_get(&dev->hid_list, w_value);
+                       spin_unlock_irqrestore(&dev->lock, flags);
+                       if (!hid) {
+                               value = -EINVAL;
+                               goto err;
+                       }
+                       cdev->req->context = hid;
+                       cdev->req->complete = acc_complete_send_hid_event;
+                       value = w_length;
                }
        } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) {
                if (b_request == ACCESSORY_GET_PROTOCOL) {
                        *((u16 *)cdev->req->buf) = PROTOCOL_VERSION;
                        value = sizeof(u16);
 
-                       /* clear any strings left over from a previous session */
+                       /* clear strings left over from a previous session */
                        memset(dev->manufacturer, 0, sizeof(dev->manufacturer));
                        memset(dev->model, 0, sizeof(dev->model));
                        memset(dev->description, 0, sizeof(dev->description));
@@ -621,6 +861,7 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev,
                                __func__);
        }
 
+err:
        if (value == -EOPNOTSUPP)
                VDBG(cdev,
                        "unknown class-specific control req "
@@ -640,6 +881,10 @@ acc_function_bind(struct usb_configuration *c, struct usb_function *f)
 
        DBG(cdev, "acc_function_bind dev: %p\n", dev);
 
+       ret = hid_register_driver(&acc_hid_driver);
+       if (ret)
+               return ret;
+
        dev->start_requested = 0;
 
        /* allocate interface ID(s) */
@@ -669,6 +914,36 @@ acc_function_bind(struct usb_configuration *c, struct usb_function *f)
 }
 
 static void
+kill_all_hid_devices(struct acc_dev *dev)
+{
+       struct acc_hid_dev *hid;
+       struct list_head *entry, *temp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dev->lock, flags);
+       list_for_each_safe(entry, temp, &dev->hid_list) {
+               hid = list_entry(entry, struct acc_hid_dev, list);
+               list_del(&hid->list);
+               list_add(&hid->list, &dev->dead_hid_list);
+       }
+       list_for_each_safe(entry, temp, &dev->new_hid_list) {
+               hid = list_entry(entry, struct acc_hid_dev, list);
+               list_del(&hid->list);
+               list_add(&hid->list, &dev->dead_hid_list);
+       }
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       schedule_work(&dev->hid_work);
+}
+
+static void
+acc_hid_unbind(struct acc_dev *dev)
+{
+       hid_unregister_driver(&acc_hid_driver);
+       kill_all_hid_devices(dev);
+}
+
+static void
 acc_function_unbind(struct usb_configuration *c, struct usb_function *f)
 {
        struct acc_dev  *dev = func_to_dev(f);
@@ -679,14 +954,104 @@ acc_function_unbind(struct usb_configuration *c, struct usb_function *f)
                acc_request_free(req, dev->ep_in);
        for (i = 0; i < RX_REQ_MAX; i++)
                acc_request_free(dev->rx_req[i], dev->ep_out);
+
+       acc_hid_unbind(dev);
 }
 
-static void acc_work(struct work_struct *data)
+static void acc_start_work(struct work_struct *data)
 {
        char *envp[2] = { "ACCESSORY=START", NULL };
        kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp);
 }
 
+static int acc_hid_init(struct acc_hid_dev *hdev)
+{
+       struct hid_device *hid;
+       int ret;
+
+       hid = hid_allocate_device();
+       if (IS_ERR(hid))
+               return PTR_ERR(hid);
+
+       hid->ll_driver = &acc_hid_ll_driver;
+       hid->dev.parent = acc_device.this_device;
+
+       hid->bus = BUS_USB;
+       hid->vendor = HID_ANY_ID;
+       hid->product = HID_ANY_ID;
+       hid->driver_data = hdev;
+       ret = hid_add_device(hid);
+       if (ret) {
+               pr_err("can't add hid device: %d\n", ret);
+               hid_destroy_device(hid);
+               return ret;
+       }
+
+       hdev->hid = hid;
+       return 0;
+}
+
+static void acc_hid_delete(struct acc_hid_dev *hid)
+{
+       kfree(hid->report_desc);
+       kfree(hid);
+}
+
+static void acc_hid_work(struct work_struct *data)
+{
+       struct acc_dev *dev = _acc_dev;
+       struct list_head        *entry, *temp;
+       struct acc_hid_dev *hid;
+       struct list_head        new_list, dead_list;
+       unsigned long flags;
+
+       INIT_LIST_HEAD(&new_list);
+
+       spin_lock_irqsave(&dev->lock, flags);
+
+       /* copy hids that are ready for initialization to new_list */
+       list_for_each_safe(entry, temp, &dev->new_hid_list) {
+               hid = list_entry(entry, struct acc_hid_dev, list);
+               if (hid->report_desc_offset == hid->report_desc_len)
+                       list_move(&hid->list, &new_list);
+       }
+
+       if (list_empty(&dev->dead_hid_list)) {
+               INIT_LIST_HEAD(&dead_list);
+       } else {
+               /* move all of dev->dead_hid_list to dead_list */
+               dead_list.prev = dev->dead_hid_list.prev;
+               dead_list.next = dev->dead_hid_list.next;
+               dead_list.next->prev = &dead_list;
+               dead_list.prev->next = &dead_list;
+               INIT_LIST_HEAD(&dev->dead_hid_list);
+       }
+
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       /* register new HID devices */
+       list_for_each_safe(entry, temp, &new_list) {
+               hid = list_entry(entry, struct acc_hid_dev, list);
+               if (acc_hid_init(hid)) {
+                       pr_err("can't add HID device %p\n", hid);
+                       acc_hid_delete(hid);
+               } else {
+                       spin_lock_irqsave(&dev->lock, flags);
+                       list_move(&hid->list, &dev->hid_list);
+                       spin_unlock_irqrestore(&dev->lock, flags);
+               }
+       }
+
+       /* remove dead HID devices */
+       list_for_each_safe(entry, temp, &dead_list) {
+               hid = list_entry(entry, struct acc_hid_dev, list);
+               list_del(&hid->list);
+               if (hid->hid)
+                       hid_destroy_device(hid->hid);
+               acc_hid_delete(hid);
+       }
+}
+
 static int acc_function_set_alt(struct usb_function *f,
                unsigned intf, unsigned alt)
 {
@@ -772,7 +1137,11 @@ static int acc_setup(void)
        init_waitqueue_head(&dev->write_wq);
        atomic_set(&dev->open_excl, 0);
        INIT_LIST_HEAD(&dev->tx_idle);
-       INIT_DELAYED_WORK(&dev->work, acc_work);
+       INIT_LIST_HEAD(&dev->hid_list);
+       INIT_LIST_HEAD(&dev->new_hid_list);
+       INIT_LIST_HEAD(&dev->dead_hid_list);
+       INIT_DELAYED_WORK(&dev->start_work, acc_start_work);
+       INIT_WORK(&dev->hid_work, acc_hid_work);
 
        /* _acc_dev must be set before calling usb_gadget_register_driver */
        _acc_dev = dev;
@@ -785,10 +1154,16 @@ static int acc_setup(void)
 
 err:
        kfree(dev);
-       printk(KERN_ERR "USB accessory gadget driver failed to initialize\n");
+       pr_err("USB accessory gadget driver failed to initialize\n");
        return ret;
 }
 
+static void acc_disconnect(void)
+{
+       /* unregister all HID devices if USB is disconnected */
+       kill_all_hid_devices(_acc_dev);
+}
+
 static void acc_cleanup(void)
 {
        misc_deregister(&acc_device);
index ddb2fd0..61ebe0a 100644 (file)
@@ -44,7 +44,7 @@
  *     index:          0
  *     data            version number (16 bits little endian)
  *                     1 for original accessory support
- *                     2 adds device to host audio support
+ *                     2 adds HID and device to host audio support
  */
 #define ACCESSORY_GET_PROTOCOL  51
 
  */
 #define ACCESSORY_START         53
 
+/* Control request for registering a HID device.
+ * Upon registering, a unique ID is sent by the accessory in the
+ * value parameter. This ID will be used for future commands for
+ * the device
+ *
+ *     requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
+ *     request:        ACCESSORY_REGISTER_HID_DEVICE
+ *     value:          Accessory assigned ID for the HID device
+ *     index:          total length of the HID report descriptor
+ *     data            none
+ */
+#define ACCESSORY_REGISTER_HID         54
+
+/* Control request for unregistering a HID device.
+ *
+ *     requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
+ *     request:        ACCESSORY_REGISTER_HID
+ *     value:          Accessory assigned ID for the HID device
+ *     index:          0
+ *     data            none
+ */
+#define ACCESSORY_UNREGISTER_HID         55
+
+/* Control request for sending the HID report descriptor.
+ * If the HID descriptor is longer than the endpoint zero max packet size,
+ * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC
+ * commands. The data for the descriptor must be sent sequentially
+ * if multiple packets are needed.
+ *
+ *     requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
+ *     request:        ACCESSORY_SET_HID_REPORT_DESC
+ *     value:          Accessory assigned ID for the HID device
+ *     index:          offset of data in descriptor
+ *                      (needed when HID descriptor is too big for one packet)
+ *     data            the HID report descriptor
+ */
+#define ACCESSORY_SET_HID_REPORT_DESC         56
+
+/* Control request for sending HID events.
+ *
+ *     requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
+ *     request:        ACCESSORY_SEND_HID_EVENT
+ *     value:          Accessory assigned ID for the HID device
+ *     index:          0
+ *     data            the HID report for the event
+ */
+#define ACCESSORY_SEND_HID_EVENT         57
+
 /* Control request for setting the audio mode.
  *
  *     requestType:    USB_DIR_OUT | USB_TYPE_VENDOR