usb: gadget: Add Uevent to notify userspace
authorBadhri Jagan Sridharan <Badhri@google.com>
Mon, 15 Dec 2014 18:44:47 +0000 (10:44 -0800)
committerJohn Stultz <john.stultz@linaro.org>
Tue, 16 Feb 2016 21:52:07 +0000 (13:52 -0800)
Android userspace UsbDeviceManager relies on the
uevents generated by the composition driver to
generate user notifications. This CL adds uevents
to be generated whenever USB changes its state
i.e. connected, disconnected, configured.

This CL also intercepts the setup requests from
the usb_core anb routes it to the specific
usb function if required.

Signed-off-by: Badhri Jagan Sridharan <Badhri@google.com>
Change-Id: Ib3d3a78255a532f7449dac286f776c2966caf8c1

drivers/usb/gadget/Kconfig
drivers/usb/gadget/configfs.c

index fc7b02bfcf09c7b5f223df9183493fb0fbbf761e..6977b224218ce6c32e602e3e08c6e9f6ba8181b1 100644 (file)
@@ -391,6 +391,14 @@ config USB_CONFIGFS_F_PTP
        help
          USB gadget PTP support
 
+config USB_CONFIGFS_UEVENT
+       boolean "Uevent notification of Gadget state"
+       depends on USB_CONFIGFS
+       help
+         Enable uevent notifications to userspace when the gadget
+         state changes. The gadget can be in any of the following
+         three states: "CONNECTED/DISCONNECTED/CONFIGURED"
+
 config USB_CONFIGFS_F_UAC1
        bool "Audio Class 1.0"
        depends on USB_CONFIGFS
index 163d305e1200bb07a73357d7b5824d97b362b924..8c527eb2289c832eaee1dc44eb0500b9ff3f8207 100644 (file)
@@ -9,6 +9,20 @@
 #include "u_f.h"
 #include "u_os_desc.h"
 
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+#include <linux/platform_device.h>
+#include <linux/kdev_t.h>
+#include <linux/usb/ch9.h>
+#include "u_fs.h"
+
+#ifdef CONFIG_USB_CONFIGFS_F_ACC
+extern int acc_ctrlrequest(struct usb_composite_dev *cdev,
+                               const struct usb_ctrlrequest *ctrl);
+void acc_disconnect(void);
+#endif
+static struct class *android_class;
+#endif
+
 int check_user_usb_string(const char *name,
                struct usb_gadget_strings *stringtab_dev)
 {
@@ -62,6 +76,12 @@ struct gadget_info {
        bool use_os_desc;
        char b_vendor_code;
        char qw_sign[OS_STRING_QW_SIGN_LEN];
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+       bool connected;
+       bool sw_connected;
+       struct work_struct work;
+       struct device *dev;
+#endif
 };
 
 static inline struct gadget_info *to_gadget_info(struct config_item *item)
@@ -266,7 +286,7 @@ static ssize_t gadget_dev_desc_UDC_store(struct config_item *item,
 
        mutex_lock(&gi->lock);
 
-       if (!strlen(name)) {
+       if (!strlen(name) || strcmp(name, "none") == 0) {
                ret = unregister_gadget(gi);
                if (ret)
                        goto err;
@@ -1373,6 +1393,57 @@ err_comp_cleanup:
        return ret;
 }
 
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static void android_work(struct work_struct *data)
+{
+       struct gadget_info *gi = container_of(data, struct gadget_info, work);
+       struct usb_composite_dev *cdev = &gi->cdev;
+       char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL };
+       char *connected[2]    = { "USB_STATE=CONNECTED", NULL };
+       char *configured[2]   = { "USB_STATE=CONFIGURED", NULL };
+       /* 0-connected 1-configured 2-disconnected*/
+       bool status[3] = { false, false, false };
+       unsigned long flags;
+       bool uevent_sent = false;
+
+       spin_lock_irqsave(&cdev->lock, flags);
+       if (cdev->config)
+               status[1] = true;
+
+       if (gi->connected != gi->sw_connected) {
+               if (gi->connected)
+                       status[0] = true;
+               else
+                       status[2] = true;
+               gi->sw_connected = gi->connected;
+       }
+       spin_unlock_irqrestore(&cdev->lock, flags);
+
+       if (status[0]) {
+               kobject_uevent_env(&gi->dev->kobj, KOBJ_CHANGE, connected);
+               pr_info("%s: sent uevent %s\n", __func__, connected[0]);
+               uevent_sent = true;
+       }
+
+       if (status[1]) {
+               kobject_uevent_env(&gi->dev->kobj, KOBJ_CHANGE, configured);
+               pr_info("%s: sent uevent %s\n", __func__, configured[0]);
+               uevent_sent = true;
+       }
+
+       if (status[2]) {
+               kobject_uevent_env(&gi->dev->kobj, KOBJ_CHANGE, disconnected);
+               pr_info("%s: sent uevent %s\n", __func__, disconnected[0]);
+               uevent_sent = true;
+       }
+
+       if (!uevent_sent) {
+               pr_info("%s: did not send uevent (%d %d %p)\n", __func__,
+                       gi->connected, gi->sw_connected, cdev->config);
+       }
+}
+#endif
+
 static void configfs_composite_unbind(struct usb_gadget *gadget)
 {
        struct usb_composite_dev        *cdev;
@@ -1392,14 +1463,78 @@ static void configfs_composite_unbind(struct usb_gadget *gadget)
        set_gadget_data(gadget, NULL);
 }
 
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static int android_setup(struct usb_gadget *gadget,
+                       const struct usb_ctrlrequest *c)
+{
+       struct usb_composite_dev *cdev = get_gadget_data(gadget);
+       unsigned long flags;
+       struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
+       int value = -EOPNOTSUPP;
+       struct usb_function_instance *fi;
+
+       spin_lock_irqsave(&cdev->lock, flags);
+       if (!gi->connected) {
+               gi->connected = 1;
+               schedule_work(&gi->work);
+       }
+       spin_unlock_irqrestore(&cdev->lock, flags);
+       list_for_each_entry(fi, &gi->available_func, cfs_list) {
+               if (fi != NULL && fi->f != NULL && fi->f->setup != NULL) {
+                       value = fi->f->setup(fi->f, c);
+                       if (value >= 0)
+                               break;
+               }
+       }
+
+#ifdef CONFIG_USB_CONFIGFS_F_ACC
+       if (value < 0)
+               value = acc_ctrlrequest(cdev, c);
+#endif
+
+       if (value < 0)
+               value = composite_setup(gadget, c);
+
+       spin_lock_irqsave(&cdev->lock, flags);
+       if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
+                                               cdev->config) {
+               schedule_work(&gi->work);
+       }
+       spin_unlock_irqrestore(&cdev->lock, flags);
+
+       return value;
+}
+
+static void android_disconnect(struct usb_gadget *gadget)
+{
+       struct usb_composite_dev        *cdev = get_gadget_data(gadget);
+       struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
+
+       /* accessory HID support can be active while the
+               accessory function is not actually enabled,
+               so we need to inform it when we are disconnected.
+       */
+
+#ifdef CONFIG_USB_CONFIGFS_F_ACC
+       acc_disconnect();
+#endif
+       gi->connected = 0;
+       schedule_work(&gi->work);
+       composite_disconnect(gadget);
+}
+#endif
+
 static const struct usb_gadget_driver configfs_driver_template = {
        .bind           = configfs_composite_bind,
        .unbind         = configfs_composite_unbind,
-
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+       .setup          = android_setup,
+       .disconnect     = android_disconnect,
+#else
        .setup          = composite_setup,
        .reset          = composite_disconnect,
        .disconnect     = composite_disconnect,
-
+#endif
        .suspend        = composite_suspend,
        .resume         = composite_resume,
 
@@ -1455,6 +1590,12 @@ static struct config_group *gadgets_make(
        gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
        gi->composite.name = gi->composite.gadget_driver.function;
 
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+       INIT_WORK(&gi->work, android_work);
+       gi->dev = device_create(android_class, NULL,
+                               MKDEV(0, 0), NULL, "android0");
+#endif
+
        if (!gi->composite.gadget_driver.function)
                goto err;
 
@@ -1506,6 +1647,13 @@ static int __init gadget_cfs_init(void)
        config_group_init(&gadget_subsys.su_group);
 
        ret = configfs_register_subsystem(&gadget_subsys);
+
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+       android_class = class_create(THIS_MODULE, "android_usb");
+       if (IS_ERR(android_class))
+               return PTR_ERR(android_class);
+#endif
+
        return ret;
 }
 module_init(gadget_cfs_init);