| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Badhri Jagan Sridharan <Badhri@google.com> |
| Date: Mon, 15 Dec 2014 10:44:47 -0800 |
| Subject: ANDROID: usb: gadget: configfs: Add Uevent to notify userspace |
| |
| 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. |
| |
| Bug: 68755607 |
| Bug: 120441124 |
| Change-Id: Ib3d3a78255a532f7449dac286f776c2966caf8c1 |
| [badhri: Migrate to using udc uevents from upstream sysfs.] |
| Signed-off-by: Badhri Jagan Sridharan <badhri@google.com> |
| [AmitP: Folded following android-4.9 commit changes into this patch |
| 9214c899f730 ("ANDROID: usb: gadget: configfs: handle gadget reset request for android")] |
| Signed-off-by: Amit Pundir <amit.pundir@linaro.org> |
| [adelva: Folded change 5c899c9fd75d ("ANDROID: usb: gadget: configfs: |
| fix null ptr in android_disconnect") into this patch] |
| Signed-off-by: Alistair Delva <adelva@google.com> |
| [maennich: Folded change 43e98b082f9e ("ANDROID: usb: gadget: configfs: |
| fix compiler warning") into this patch] |
| Signed-off-by: Matthias Maennich <maennich@google.com> |
| --- |
| drivers/usb/gadget/Kconfig | 8 ++ |
| drivers/usb/gadget/configfs.c | 168 +++++++++++++++++++++++++++++++++- |
| 2 files changed, 174 insertions(+), 2 deletions(-) |
| |
| diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig |
| index 02ff850278b1..f5e82dcac583 100644 |
| --- a/drivers/usb/gadget/Kconfig |
| +++ b/drivers/usb/gadget/Kconfig |
| @@ -230,6 +230,14 @@ config USB_CONFIGFS |
| appropriate symbolic links. |
| For more information see Documentation/usb/gadget_configfs.rst. |
| |
| +config USB_CONFIGFS_UEVENT |
| + bool "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_SERIAL |
| bool "Generic serial bulk in/out" |
| depends on USB_CONFIGFS |
| diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c |
| index ab9ac48a751a..51007d9adf0c 100644 |
| --- a/drivers/usb/gadget/configfs.c |
| +++ b/drivers/usb/gadget/configfs.c |
| @@ -10,6 +10,19 @@ |
| #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> |
| + |
| +#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) |
| { |
| @@ -63,6 +76,12 @@ struct gadget_info { |
| char qw_sign[OS_STRING_QW_SIGN_LEN]; |
| spinlock_t spinlock; |
| bool unbind; |
| +#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) |
| @@ -268,7 +287,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; |
| @@ -1375,6 +1394,57 @@ static int configfs_composite_bind(struct usb_gadget *gadget, |
| 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; |
| @@ -1400,6 +1470,80 @@ static void configfs_composite_unbind(struct usb_gadget *gadget) |
| spin_unlock_irqrestore(&gi->spinlock, flags); |
| } |
| |
| +#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); |
| + |
| + /* FIXME: There's a race between usb_gadget_udc_stop() which is likely |
| + * to set the gadget driver to NULL in the udc driver and this drivers |
| + * gadget disconnect fn which likely checks for the gadget driver to |
| + * be a null ptr. It happens that unbind (doing set_gadget_data(NULL)) |
| + * is called before the gadget driver is set to NULL and the udc driver |
| + * calls disconnect fn which results in cdev being a null ptr. |
| + */ |
| + if (cdev == NULL) { |
| + WARN(1, "%s: gadget driver already disconnected\n", __func__); |
| + return; |
| + } |
| + |
| + /* 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); |
| +} |
| + |
| +#else // CONFIG_USB_CONFIGFS_UEVENT |
| + |
| static int configfs_composite_setup(struct usb_gadget *gadget, |
| const struct usb_ctrlrequest *ctrl) |
| { |
| @@ -1447,6 +1591,8 @@ static void configfs_composite_disconnect(struct usb_gadget *gadget) |
| spin_unlock_irqrestore(&gi->spinlock, flags); |
| } |
| |
| +#endif // CONFIG_USB_CONFIGFS_UEVENT |
| + |
| static void configfs_composite_suspend(struct usb_gadget *gadget) |
| { |
| struct usb_composite_dev *cdev; |
| @@ -1495,10 +1641,15 @@ 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, |
| + .reset = android_disconnect, |
| + .disconnect = android_disconnect, |
| +#else |
| .setup = configfs_composite_setup, |
| .reset = configfs_composite_disconnect, |
| .disconnect = configfs_composite_disconnect, |
| - |
| +#endif |
| .suspend = configfs_composite_suspend, |
| .resume = configfs_composite_resume, |
| |
| @@ -1559,6 +1710,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; |
| |
| @@ -1610,6 +1767,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); |