| 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: NOUPSTREAM: 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. |
| |
| [CPNOTE: 19/07/21] Lee: Should be re-written to use upstream APIs - pinged Badhri |
| |
| 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> |
| Signed-off-by: Lee Jones <joneslee@google.com> |
| --- |
| drivers/usb/gadget/Kconfig | 8 ++ |
| drivers/usb/gadget/configfs.c | 253 +++++++++++++++++++++++++++++++++- |
| include/linux/usb/composite.h | 1 + |
| 3 files changed, 260 insertions(+), 2 deletions(-) |
| |
| diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig |
| --- 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 |
| --- a/drivers/usb/gadget/configfs.c |
| +++ b/drivers/usb/gadget/configfs.c |
| @@ -10,6 +10,32 @@ |
| #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; |
| +static struct device *android_device; |
| +static int index; |
| +static int gadget_index; |
| + |
| +struct device *create_function_device(char *name) |
| +{ |
| + if (android_device && !IS_ERR(android_device)) |
| + return device_create(android_class, android_device, |
| + MKDEV(0, index++), NULL, name); |
| + else |
| + return ERR_PTR(-EINVAL); |
| +} |
| +EXPORT_SYMBOL_GPL(create_function_device); |
| +#endif |
| + |
| int check_user_usb_string(const char *name, |
| struct usb_gadget_strings *stringtab_dev) |
| { |
| @@ -51,6 +77,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) |
| @@ -273,7 +305,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; |
| @@ -1417,6 +1449,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; |
| @@ -1444,6 +1527,50 @@ 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; |
| +} |
| + |
| +#else // CONFIG_USB_CONFIGFS_UEVENT |
| + |
| static int configfs_composite_setup(struct usb_gadget *gadget, |
| const struct usb_ctrlrequest *ctrl) |
| { |
| @@ -1469,6 +1596,8 @@ static int configfs_composite_setup(struct usb_gadget *gadget, |
| return ret; |
| } |
| |
| +#endif // CONFIG_USB_CONFIGFS_UEVENT |
| + |
| static void configfs_composite_disconnect(struct usb_gadget *gadget) |
| { |
| struct usb_composite_dev *cdev; |
| @@ -1479,6 +1608,14 @@ static void configfs_composite_disconnect(struct usb_gadget *gadget) |
| if (!cdev) |
| return; |
| |
| +#ifdef CONFIG_USB_CONFIGFS_F_ACC |
| + /* |
| + * 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(); |
| +#endif |
| gi = container_of(cdev, struct gadget_info, cdev); |
| spin_lock_irqsave(&gi->spinlock, flags); |
| cdev = get_gadget_data(gadget); |
| @@ -1487,6 +1624,10 @@ static void configfs_composite_disconnect(struct usb_gadget *gadget) |
| return; |
| } |
| |
| +#ifdef CONFIG_USB_CONFIGFS_UEVENT |
| + gi->connected = 0; |
| + schedule_work(&gi->work); |
| +#endif |
| composite_disconnect(gadget); |
| spin_unlock_irqrestore(&gi->spinlock, flags); |
| } |
| @@ -1561,10 +1702,13 @@ 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, |
| +#else |
| .setup = configfs_composite_setup, |
| +#endif |
| .reset = configfs_composite_reset, |
| .disconnect = configfs_composite_disconnect, |
| - |
| .suspend = configfs_composite_suspend, |
| .resume = configfs_composite_resume, |
| |
| @@ -1576,6 +1720,91 @@ static const struct usb_gadget_driver configfs_driver_template = { |
| .match_existing_only = 1, |
| }; |
| |
| +#ifdef CONFIG_USB_CONFIGFS_UEVENT |
| +static ssize_t state_show(struct device *pdev, struct device_attribute *attr, |
| + char *buf) |
| +{ |
| + struct gadget_info *dev = dev_get_drvdata(pdev); |
| + struct usb_composite_dev *cdev; |
| + char *state = "DISCONNECTED"; |
| + unsigned long flags; |
| + |
| + if (!dev) |
| + goto out; |
| + |
| + cdev = &dev->cdev; |
| + |
| + if (!cdev) |
| + goto out; |
| + |
| + spin_lock_irqsave(&cdev->lock, flags); |
| + if (cdev->config) |
| + state = "CONFIGURED"; |
| + else if (dev->connected) |
| + state = "CONNECTED"; |
| + spin_unlock_irqrestore(&cdev->lock, flags); |
| +out: |
| + return sprintf(buf, "%s\n", state); |
| +} |
| + |
| +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); |
| + |
| +static struct device_attribute *android_usb_attributes[] = { |
| + &dev_attr_state, |
| + NULL |
| +}; |
| + |
| +static int android_device_create(struct gadget_info *gi) |
| +{ |
| + struct device_attribute **attrs; |
| + struct device_attribute *attr; |
| + |
| + INIT_WORK(&gi->work, android_work); |
| + gi->dev = device_create(android_class, NULL, |
| + MKDEV(0, 0), NULL, "android%d", gadget_index++); |
| + if (IS_ERR(gi->dev)) |
| + return PTR_ERR(gi->dev); |
| + |
| + dev_set_drvdata(gi->dev, gi); |
| + if (!android_device) |
| + android_device = gi->dev; |
| + |
| + attrs = android_usb_attributes; |
| + while ((attr = *attrs++)) { |
| + int err; |
| + |
| + err = device_create_file(gi->dev, attr); |
| + if (err) { |
| + device_destroy(gi->dev->class, |
| + gi->dev->devt); |
| + return err; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static void android_device_destroy(struct gadget_info *gi) |
| +{ |
| + struct device_attribute **attrs; |
| + struct device_attribute *attr; |
| + |
| + attrs = android_usb_attributes; |
| + while ((attr = *attrs++)) |
| + device_remove_file(gi->dev, attr); |
| + device_destroy(gi->dev->class, gi->dev->devt); |
| +} |
| +#else |
| +static inline int android_device_create(struct gadget_info *gi) |
| +{ |
| + return 0; |
| +} |
| + |
| +static inline void android_device_destroy(struct gadget_info *gi) |
| +{ |
| +} |
| +#endif |
| + |
| static struct config_group *gadgets_make( |
| struct config_group *group, |
| const char *name) |
| @@ -1628,7 +1857,11 @@ static struct config_group *gadgets_make( |
| if (!gi->composite.gadget_driver.function) |
| goto err; |
| |
| + if (android_device_create(gi) < 0) |
| + goto err; |
| + |
| return &gi->group; |
| + |
| err: |
| kfree(gi); |
| return ERR_PTR(-ENOMEM); |
| @@ -1636,7 +1869,11 @@ static struct config_group *gadgets_make( |
| |
| static void gadgets_drop(struct config_group *group, struct config_item *item) |
| { |
| + struct gadget_info *gi; |
| + |
| + gi = container_of(to_config_group(item), struct gadget_info, group); |
| config_item_put(item); |
| + android_device_destroy(gi); |
| } |
| |
| static struct configfs_group_operations gadgets_ops = { |
| @@ -1676,6 +1913,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); |
| @@ -1683,5 +1927,10 @@ module_init(gadget_cfs_init); |
| static void __exit gadget_cfs_exit(void) |
| { |
| configfs_unregister_subsystem(&gadget_subsys); |
| +#ifdef CONFIG_USB_CONFIGFS_UEVENT |
| + if (!IS_ERR(android_class)) |
| + class_destroy(android_class); |
| +#endif |
| + |
| } |
| module_exit(gadget_cfs_exit); |
| diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h |
| --- a/include/linux/usb/composite.h |
| +++ b/include/linux/usb/composite.h |
| @@ -578,6 +578,7 @@ struct usb_function_instance { |
| struct config_group group; |
| struct list_head cfs_list; |
| struct usb_function_driver *fd; |
| + struct usb_function *f; |
| int (*set_inst_name)(struct usb_function_instance *inst, |
| const char *name); |
| void (*free_func_inst)(struct usb_function_instance *inst); |