diff options
| -rw-r--r-- | drivers/usb/gadget/composite.c | 121 | ||||
| -rw-r--r-- | include/linux/usb/composite.h | 11 | ||||
| -rw-r--r-- | include/linux/usb/gadget.h | 39 |
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, |
