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

Commit 44583bda authored by Danny Segal's avatar Danny Segal
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.

Change-Id: I51713eac557eabc7b465d161377c09d4b6afa152
Signed-off-by: default avatarDanny Segal <dsegal@codeaurora.org>
parent e1ce01c5
Loading
Loading
Loading
Loading
+80 −2
Original line number Diff line number Diff line
@@ -338,6 +338,75 @@ int usb_interface_id(struct usb_configuration *config,
}
EXPORT_SYMBOL_GPL(usb_interface_id);

/**
 * usb_get_func_interface_id() - Find the interface ID of a function
 * @function: the function for which want to find the interface ID
 * Context: single threaded
 *
 * Returns the interface ID of the function or -ENODEV if this function
 * is not part of this configuration
 */
int usb_get_func_interface_id(struct usb_function *func)
{
	int id;
	struct usb_configuration *config;

	if (!func)
		return -EINVAL;

	config = func->config;

	for (id = 0; id < MAX_CONFIG_INTERFACES; id++) {
		if (config->interface[id] == func)
			return id;
	}
	return -ENODEV;
}

int usb_func_wakeup(struct usb_function *func)
{
	int ret;
	unsigned interface_id;
	struct usb_gadget *gadget;

	pr_debug("%s function wakeup\n",
		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",
			gadget->speed,
			func->func_wakeup_allowed);

		return -ENOTSUPP;
	}

	ret = usb_get_func_interface_id(func);
	if (ret < 0) {
		ERROR(func->config->cdev,
			"Function %s - Unknown interface id. Canceling USB request. ret=%d",
			func->name ? func->name : "", ret);
		return ret;
	}

	interface_id = ret;
	ret = usb_gadget_func_wakeup(gadget, interface_id);
	if (ret) {
		ERROR(func->config->cdev,
			"Failed to wake function %s from suspend state. interface id: %d, ret=%d. Canceling USB request.",
			func->name ? func->name : "",
			interface_id, ret);
		return ret;
	}

	return 0;
}

static u8 encode_bMaxPower(enum usb_device_speed speed,
		struct usb_configuration *c)
{
@@ -595,6 +664,10 @@ 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;

		bitmap_zero(f->endpoints, 32);
	}
	cdev->config = NULL;
@@ -1442,8 +1515,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",
+31 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@

#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/composite.h>

/**
 * struct usb_udc - describes one usb device controller
@@ -437,6 +438,36 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
}
EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver);

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 || !ep || !req) {
		pr_err("Invalid argument. func=%p, ep=%p, req=%p\n",
			func, ep, req);
		return -EINVAL;
	}

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

	gadget = func->config->cdev->gadget;
	if ((gadget->speed == USB_SPEED_SUPER) && func->func_is_suspended) {
		ret = usb_func_wakeup(func);
		if (ret) {
			pr_err("Failed to send function wake up notification. func name:%s, ep:%u\n",
				func->name ? func->name : "", ep->address);
			return ret;
		}
	}

	ret = usb_ep_queue(ep, req, gfp_flags);
	return ret;
}


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

static ssize_t usb_udc_srp_store(struct device *dev,
+10 −1
Original line number Diff line number Diff line
@@ -94,7 +94,11 @@ struct usb_configuration;
 * @get_status: Returns function status as a reply to
 *	GetStatus() request when the recepient 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).
 *
 * 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
@@ -158,6 +162,8 @@ 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;
	/* private: */
	/* internals */
	struct list_head		list;
@@ -171,6 +177,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);
+39 −0
Original line number Diff line number Diff line
@@ -471,6 +471,7 @@ struct usb_gadget_driver;
struct usb_gadget_ops {
	int	(*get_frame)(struct usb_gadget *);
	int	(*wakeup)(struct usb_gadget *);
	int	(*func_wakeup)(struct usb_gadget *, 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);
@@ -645,6 +646,26 @@ static inline int usb_gadget_wakeup(struct usb_gadget *gadget)
	return gadget->ops->wakeup(gadget);
}

/**
 * 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.
 */
static inline 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);
}

/**
 * usb_gadget_set_selfpowered - sets the device selfpowered feature.
 * @gadget:the device being declared as self-powered
@@ -987,6 +1008,24 @@ void usb_free_all_descriptors(struct usb_function *f);

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

/**
 * 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 */

extern int usb_gadget_map_request(struct usb_gadget *gadget,