summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/gadget/composite.c121
-rw-r--r--include/linux/usb/composite.h11
-rw-r--r--include/linux/usb/gadget.h39
3 files changed, 168 insertions, 3 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 96e088d8ee49..7735bc9ed892 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -363,6 +363,114 @@ 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\n",
+ 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\n",
+ func->name ? func->name : "", ret);
+ return ret;
+ }
+
+ interface_id = ret;
+ ret = usb_gadget_func_wakeup(gadget, interface_id);
+ if (ret) {
+ if (ret == -EAGAIN) {
+ DBG(func->config->cdev,
+ "Function wakeup for %s could not be complete. Retry is needed.\n",
+ func->name ? func->name : "");
+ } else {
+ ERROR(func->config->cdev,
+ "Failed to wake function %s from suspend state. interface id: %d, ret=%d. Canceling USB request.\n",
+ func->name ? func->name : "",
+ interface_id, ret);
+ }
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(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 || !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) {
+ if (ret != -EAGAIN)
+ 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;
+}
+EXPORT_SYMBOL_GPL(usb_func_ep_queue);
+
static u8 encode_bMaxPower(enum usb_device_speed speed,
struct usb_configuration *c)
{
@@ -629,6 +737,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;
@@ -1678,8 +1790,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",
diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
index 15d7c311e86e..bbb5c4c619ed 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -154,7 +154,11 @@ 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).
*
* 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
@@ -223,6 +227,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;
@@ -238,6 +244,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);
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h
index 3d583a10b926..0303e5843501 100644
--- a/include/linux/usb/gadget.h
+++ b/include/linux/usb/gadget.h
@@ -547,6 +547,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 *, 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);
@@ -774,6 +775,26 @@ static inline int usb_gadget_wakeup(struct usb_gadget *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
*
@@ -1196,6 +1217,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 */
extern int usb_gadget_map_request(struct usb_gadget *gadget,