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

Commit d6c40da2 authored by Danny Segal's avatar Danny Segal Committed by Hemant Kumar
Browse files

usb: gadget: add infrastructure support for super-speed function suspend



The USB 3.0 specification defines a new 'Function Suspend' feature.
This feature enables the USB host to put inactive composite device
functions in a suspended state even when the device itself is not
suspended. This patch extends the existing framework of USB gadget
to properly support the 'Function Resume' and 'Function Remote Wakeup'
related features.

Signed-off-by: default avatarDanny Segal <dsegal@codeaurora.org>
Signed-off-by: default avatarMayank Rana <mrana@codeaurora.org>

[jackp@codeaurora.org: also squashed the following commits
usb: gadget: Fix missing function wakeup notification sending on BAM wakeup
usb: gadget: Fix race condition between function wakeup and bus resume
usb: gadget: Resolve recursive spinlock during remote wakeup
usb: composite: don't print the invalid function wakeup failure messages
dwc3: gadget: Replace polling mechanism to go into U0 state
usb: gadget: f_mbim: Queue notification request upon function resume
USB: composite: Hold spinlock before calling usb_func_wakeup_int() API
usb: gadget: Invalidate interface id upon adding function to config
usb: gadget: Fix bug is queuing req upon function suspend ]

Change-Id: I51713eac557eabc7b465d161377c09d4b6afa152
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
Signed-off-by: default avatarHemant Kumar <hemantk@codeaurora.org>
parent 19d6c4da
Loading
Loading
Loading
Loading
+130 −2
Original line number Diff line number Diff line
@@ -266,6 +266,7 @@ int usb_add_function(struct usb_configuration *config,
		goto done;

	function->config = config;
	function->intf_id = -EINVAL;
	list_add_tail(&function->list, &config->functions);

	if (function->bind_deactivated) {
@@ -419,6 +420,8 @@ int usb_interface_id(struct usb_configuration *config,

	if (id < MAX_CONFIG_INTERFACES) {
		config->interface[id] = function;
		if (function->intf_id < 0)
			function->intf_id = id;
		config->next_interface_id = id + 1;
		return id;
	}
@@ -426,6 +429,101 @@ int usb_interface_id(struct usb_configuration *config,
}
EXPORT_SYMBOL_GPL(usb_interface_id);

static int usb_func_wakeup_int(struct usb_function *func)
{
	int ret;
	struct usb_gadget *gadget;

	pr_debug("%s - %s function wakeup\n",
		__func__, func->name ? func->name : "");

	if (!func || !func->config || !func->config->cdev ||
		!func->config->cdev->gadget)
		return -EINVAL;

	gadget = func->config->cdev->gadget;
	if ((gadget->speed != USB_SPEED_SUPER) || !func->func_wakeup_allowed) {
		DBG(func->config->cdev,
			"Function Wakeup is not possible. speed=%u, func_wakeup_allowed=%u\n",
			gadget->speed,
			func->func_wakeup_allowed);

		return -ENOTSUPP;
	}

	ret = usb_gadget_func_wakeup(gadget, func->intf_id);

	return ret;
}

int usb_func_wakeup(struct usb_function *func)
{
	int ret;
	unsigned long flags;

	pr_debug("%s function wakeup\n",
		func->name ? func->name : "");

	spin_lock_irqsave(&func->config->cdev->lock, flags);
	ret = usb_func_wakeup_int(func);
	if (ret == -EAGAIN) {
		DBG(func->config->cdev,
			"Function wakeup for %s could not complete due to suspend state. Delayed until after bus resume.\n",
			func->name ? func->name : "");
		ret = 0;
	} else if (ret < 0 && ret != -ENOTSUPP) {
		ERROR(func->config->cdev,
			"Failed to wake function %s from suspend state. ret=%d. Canceling USB request.\n",
			func->name ? func->name : "", ret);
	}

	spin_unlock_irqrestore(&func->config->cdev->lock, flags);
	return ret;
}
EXPORT_SYMBOL(usb_func_wakeup);

int usb_func_ep_queue(struct usb_function *func, struct usb_ep *ep,
			       struct usb_request *req, gfp_t gfp_flags)
{
	int ret;
	struct usb_gadget *gadget;

	if (!func || !func->config || !func->config->cdev ||
			!func->config->cdev->gadget || !ep || !req) {
		ret = -EINVAL;
		goto done;
	}

	pr_debug("Function %s queueing new data into ep %u\n",
		func->name ? func->name : "", ep->address);

	gadget = func->config->cdev->gadget;
	if (func->func_is_suspended && func->func_wakeup_allowed) {
		ret = usb_gadget_func_wakeup(gadget, func->intf_id);
		if (ret == -EAGAIN) {
			pr_debug("bus suspended func wakeup for %s delayed until bus resume.\n",
				func->name ? func->name : "");
		} else if (ret < 0 && ret != -ENOTSUPP) {
			pr_err("Failed to wake function %s from suspend state. ret=%d.\n",
				func->name ? func->name : "", ret);
		}
		goto done;
	}

	if (!func->func_is_suspended)
		ret = 0;

	if (func->func_is_suspended && !func->func_wakeup_allowed) {
		ret = -ENOTSUPP;
		goto done;
	}

	ret = usb_ep_queue(ep, req, gfp_flags);
done:
	return ret;
}
EXPORT_SYMBOL(usb_func_ep_queue);

static u8 encode_bMaxPower(enum usb_device_speed speed,
		struct usb_configuration *c)
{
@@ -743,6 +841,11 @@ static void reset_config(struct usb_composite_dev *cdev)
		if (f->disable)
			f->disable(f);

		/* USB 3.0 addition */
		f->func_is_suspended = false;
		f->func_wakeup_allowed = false;
		f->func_wakeup_pending = false;

		bitmap_zero(f->endpoints, 32);
	}
	cdev->config = NULL;
@@ -1800,8 +1903,13 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
			if (!f)
				break;
			value = 0;
			if (f->func_suspend)
				value = f->func_suspend(f, w_index >> 8);
			if (f->func_suspend) {
				const u8 suspend_opt = w_index >> 8;

				value = f->func_suspend(f, suspend_opt);
				DBG(cdev, "%s function: FUNCTION_SUSPEND(%u)",
					f->name ? f->name : "", suspend_opt);
			}
			if (value < 0) {
				ERROR(cdev,
				      "func_suspend() returned error %d\n",
@@ -2265,6 +2373,8 @@ void composite_resume(struct usb_gadget *gadget)
	struct usb_composite_dev	*cdev = get_gadget_data(gadget);
	struct usb_function		*f;
	u16				maxpower;
	int				ret;
	unsigned long			flags;

	/* REVISIT:  should we have config level
	 * suspend/resume callbacks?
@@ -2272,8 +2382,25 @@ void composite_resume(struct usb_gadget *gadget)
	DBG(cdev, "resume\n");
	if (cdev->driver->resume)
		cdev->driver->resume(cdev);

	spin_lock_irqsave(&cdev->lock, flags);
	if (cdev->config) {
		list_for_each_entry(f, &cdev->config->functions, list) {
			ret = usb_func_wakeup_int(f);
			if (ret) {
				if (ret == -EAGAIN) {
					ERROR(f->config->cdev,
						"Function wakeup for %s could not complete due to suspend state.\n",
						f->name ? f->name : "");
					break;
				} else if (ret != -ENOTSUPP) {
					ERROR(f->config->cdev,
						"Failed to wake function %s from suspend state. ret=%d. Canceling USB request.\n",
						f->name ? f->name : "",
						ret);
				}
			}

			if (f->resume)
				f->resume(f);
		}
@@ -2284,6 +2411,7 @@ void composite_resume(struct usb_gadget *gadget)
			maxpower : CONFIG_USB_GADGET_VBUS_DRAW);
	}

	spin_unlock_irqrestore(&cdev->lock, flags);
	cdev->suspended = 0;
}

+21 −0
Original line number Diff line number Diff line
@@ -477,6 +477,27 @@ int usb_gadget_wakeup(struct usb_gadget *gadget)
}
EXPORT_SYMBOL_GPL(usb_gadget_wakeup);

/**
 * usb_gadget_func_wakeup - send a function remote wakeup up notification
 * to the host connected to this gadget
 * @gadget: controller used to wake up the host
 * @interface_id: the interface which triggered the remote wakeup event
 *
 * Returns zero on success. Otherwise, negative error code is returned.
 */
int usb_gadget_func_wakeup(struct usb_gadget *gadget,
	int interface_id)
{
	if (gadget->speed != USB_SPEED_SUPER)
		return -EOPNOTSUPP;

	if (!gadget->ops->func_wakeup)
		return -EOPNOTSUPP;

	return gadget->ops->func_wakeup(gadget, interface_id);
}
EXPORT_SYMBOL(usb_gadget_func_wakeup);

/**
 * usb_gadget_set_selfpowered - sets the device selfpowered feature.
 * @gadget:the device being declared as self-powered
+16 −1
Original line number Diff line number Diff line
@@ -118,6 +118,7 @@ struct usb_os_desc_table {
/**
 * struct usb_function - describes one function of a configuration
 * @name: For diagnostics, identifies the function.
 * @intf_id: Interface ID
 * @strings: tables of strings, keyed by identifiers assigned during bind()
 *	and by language IDs provided in control requests
 * @fs_descriptors: Table of full (or low) speed descriptors, using interface and
@@ -162,7 +163,14 @@ struct usb_os_desc_table {
 * @get_status: Returns function status as a reply to
 *	GetStatus() request when the recipient is Interface.
 * @func_suspend: callback to be called when
 *	SetFeature(FUNCTION_SUSPEND) is reseived
 *	SetFeature(FUNCTION_SUSPEND) is received
 * @func_is_suspended: Tells whether the function is currently in
 *	Function Suspend state (used in Super Speed mode only).
 * @func_wakeup_allowed: Tells whether Function Remote Wakeup has been allowed
 *	by the USB host (used in Super Speed mode only).
 * @func_wakeup_pending: Marks that the function has issued a Function Wakeup
 *	while the USB bus was suspended and therefore a Function Wakeup
 *	notification needs to be sent once the USB bus is resumed.
 *
 * A single USB function uses one or more interfaces, and should in most
 * cases support operation at both full and high speeds.  Each function is
@@ -190,6 +198,7 @@ struct usb_os_desc_table {

struct usb_function {
	const char			*name;
	int				intf_id;
	struct usb_gadget_strings	**strings;
	struct usb_descriptor_header	**fs_descriptors;
	struct usb_descriptor_header	**hs_descriptors;
@@ -233,6 +242,9 @@ struct usb_function {
	int			(*get_status)(struct usb_function *);
	int			(*func_suspend)(struct usb_function *,
						u8 suspend_opt);
	unsigned		func_is_suspended:1;
	unsigned		func_wakeup_allowed:1;
	unsigned		func_wakeup_pending:1;
	/* private: */
	/* internals */
	struct list_head		list;
@@ -248,6 +260,9 @@ int usb_function_deactivate(struct usb_function *);
int usb_function_activate(struct usb_function *);

int usb_interface_id(struct usb_configuration *, struct usb_function *);
int usb_func_wakeup(struct usb_function *func);

int usb_get_func_interface_id(struct usb_function *func);

int config_ep_by_speed(struct usb_gadget *g, struct usb_function *f,
			struct usb_ep *_ep);
+22 −0
Original line number Diff line number Diff line
@@ -301,6 +301,7 @@ struct usb_udc;
struct usb_gadget_ops {
	int	(*get_frame)(struct usb_gadget *);
	int	(*wakeup)(struct usb_gadget *);
	int	(*func_wakeup)(struct usb_gadget *g, int interface_id);
	int	(*set_selfpowered) (struct usb_gadget *, int is_selfpowered);
	int	(*vbus_session) (struct usb_gadget *, int is_active);
	int	(*vbus_draw) (struct usb_gadget *, unsigned mA);
@@ -553,6 +554,7 @@ static inline int gadget_is_otg(struct usb_gadget *g)
#if IS_ENABLED(CONFIG_USB_GADGET)
int usb_gadget_frame_number(struct usb_gadget *gadget);
int usb_gadget_wakeup(struct usb_gadget *gadget);
int usb_gadget_func_wakeup(struct usb_gadget *gadget, int interface_id);
int usb_gadget_set_selfpowered(struct usb_gadget *gadget);
int usb_gadget_clear_selfpowered(struct usb_gadget *gadget);
int usb_gadget_vbus_connect(struct usb_gadget *gadget);
@@ -567,6 +569,8 @@ static inline int usb_gadget_frame_number(struct usb_gadget *gadget)
{ return 0; }
static inline int usb_gadget_wakeup(struct usb_gadget *gadget)
{ return 0; }
static int usb_gadget_func_wakeup(struct usb_gadget *gadget, int interface_id)
{ return 0; }
static inline int usb_gadget_set_selfpowered(struct usb_gadget *gadget)
{ return 0; }
static inline int usb_gadget_clear_selfpowered(struct usb_gadget *gadget)
@@ -805,6 +809,24 @@ int usb_otg_descriptor_init(struct usb_gadget *gadget,
		struct usb_descriptor_header *otg_desc);
/*-------------------------------------------------------------------------*/

/**
 * usb_func_ep_queue - queues (submits) an I/O request to a function endpoint.
 * This function is similar to the usb_ep_queue function, but in addition it
 * also checks whether the function is in Super Speed USB Function Suspend
 * state, and if so a Function Wake notification is sent to the host
 * (USB 3.0 spec, section 9.2.5.2).
 * @func: the function which issues the USB I/O request.
 * @ep:the endpoint associated with the request
 * @req:the request being submitted
 * @gfp_flags: GFP_* flags to use in case the lower level driver couldn't
 *	pre-allocate all necessary memory with the request.
 *
 */
int usb_func_ep_queue(struct usb_function *func, struct usb_ep *ep,
				struct usb_request *req, gfp_t gfp_flags);

/*-------------------------------------------------------------------------*/

/* utility to simplify map/unmap of usb_requests to/from DMA */

#ifdef	CONFIG_HAS_DMA