Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a77196fc authored by Badhri Jagan Sridharan's avatar Badhri Jagan Sridharan Committed by Alistair Strachan
Browse files

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: default avatarBadhri 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: default avatarAmit Pundir <amit.pundir@linaro.org>
[astrachan: Folded change 5c899c9fd75d ("ANDROID: usb: gadget: configfs:
            fix null ptr in android_disconnect") into this patch]
Signed-off-by: default avatarAlistair Strachan <astrachan@google.com>
parent 2794c17f
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -229,6 +229,14 @@ config USB_CONFIGFS
	  appropriate symbolic links.
	  For more information see Documentation/usb/gadget_configfs.txt.

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
+163 −3
Original line number Diff line number Diff line
@@ -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)
{
@@ -61,6 +74,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 +285,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;
@@ -1372,6 +1391,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;
@@ -1391,14 +1461,91 @@ 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);

	/* 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);
}
#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,
	.reset          = android_disconnect,
	.disconnect     = android_disconnect,
#else
	.setup          = composite_setup,
	.reset          = composite_disconnect,
	.disconnect     = composite_disconnect,

#endif
	.suspend	= composite_suspend,
	.resume		= composite_resume,

@@ -1458,6 +1605,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;

@@ -1509,6 +1662,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);