diff options
Diffstat (limited to 'drivers/usb/gadget/composite.c')
| -rw-r--r-- | drivers/usb/gadget/composite.c | 343 |
1 files changed, 278 insertions, 65 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index a5ebfa9a2f82..442d44278f33 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -23,6 +23,23 @@ #include <asm/unaligned.h> #include "u_os_desc.h" +#define SSUSB_GADGET_VBUS_DRAW 900 /* in mA */ +#define SSUSB_GADGET_VBUS_DRAW_UNITS 8 +#define HSUSB_GADGET_VBUS_DRAW_UNITS 2 + +/* + * Based on enumerated USB speed, draw power with set_config and resume + * HSUSB: 500mA, SSUSB: 900mA + */ +#define USB_VBUS_DRAW(speed)\ + (speed == USB_SPEED_SUPER ?\ + SSUSB_GADGET_VBUS_DRAW : CONFIG_USB_GADGET_VBUS_DRAW) + +/* disable LPM by default */ +static bool disable_l1_for_hs = true; +module_param(disable_l1_for_hs, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(disable_l1_for_hs, + "Disable support for L1 LPM for HS devices"); /** * struct usb_os_string - represents OS String to be reported by a gadget @@ -205,7 +222,7 @@ int usb_add_function(struct usb_configuration *config, { int value = -EINVAL; - DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n", + DBG(config->cdev, "adding '%s'/%pK to config '%s'/%pK\n", function->name, function, config->label, config); @@ -213,6 +230,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) { @@ -245,7 +263,7 @@ int usb_add_function(struct usb_configuration *config, done: if (value) - DBG(config->cdev, "adding '%s'/%p --> %d\n", + DBG(config->cdev, "adding '%s'/%pK --> %d\n", function->name, function, value); return value; } @@ -361,6 +379,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; } @@ -368,22 +388,110 @@ 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_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 || !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 && !func->func_wakeup_allowed) { + ret = -ENOTSUPP; + goto done; + } + + ret = usb_ep_queue(ep, req, gfp_flags); +done: + return ret; +} + static u8 encode_bMaxPower(enum usb_device_speed speed, struct usb_configuration *c) { - unsigned val; + unsigned val = CONFIG_USB_GADGET_VBUS_DRAW; - if (c->MaxPower) - val = c->MaxPower; - else - val = CONFIG_USB_GADGET_VBUS_DRAW; - if (!val) - return 0; switch (speed) { case USB_SPEED_SUPER: - return DIV_ROUND_UP(val, 8); + /* with super-speed report 900mA */ + val = SSUSB_GADGET_VBUS_DRAW; + return (u8)(val / SSUSB_GADGET_VBUS_DRAW_UNITS); default: - return DIV_ROUND_UP(val, 2); + return DIV_ROUND_UP(val, HSUSB_GADGET_VBUS_DRAW_UNITS); } } @@ -564,7 +672,8 @@ static int bos_desc(struct usb_composite_dev *cdev) /* * A SuperSpeed device shall include the USB2.0 extension descriptor - * and shall support LPM when operating in USB2.0 HS mode. + * and shall support LPM when operating in USB2.0 HS mode, as well as + * a HS device when operating in USB2.1 HS mode. */ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); bos->bNumDeviceCaps++; @@ -574,33 +683,37 @@ static int bos_desc(struct usb_composite_dev *cdev) usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT | USB_BESL_SUPPORT); - /* - * The Superspeed USB Capability descriptor shall be implemented by all - * SuperSpeed devices. - */ - ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); - ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; - ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; - ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; - ss_cap->bmAttributes = 0; /* LTM is not supported yet */ - ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | - USB_FULL_SPEED_OPERATION | - USB_HIGH_SPEED_OPERATION | - USB_5GBPS_OPERATION); - ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; - - /* Get Controller configuration */ - if (cdev->gadget->ops->get_config_params) - cdev->gadget->ops->get_config_params(&dcd_config_params); - else { - dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT; - dcd_config_params.bU2DevExitLat = - cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + if (gadget_is_superspeed(cdev->gadget)) { + /* + * The Superspeed USB Capability descriptor shall be + * implemented by all SuperSpeed devices. + */ + ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* LTM is not supported yet */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | + USB_FULL_SPEED_OPERATION | + USB_HIGH_SPEED_OPERATION | + USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) + cdev->gadget->ops->get_config_params + (&dcd_config_params); + else { + dcd_config_params.bU1devExitLat = + USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; } - ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; - ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; return le16_to_cpu(bos->wTotalLength); } @@ -634,6 +747,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; @@ -646,9 +764,18 @@ static int set_config(struct usb_composite_dev *cdev, struct usb_gadget *gadget = cdev->gadget; struct usb_configuration *c = NULL; int result = -EINVAL; - unsigned power = gadget_is_otg(gadget) ? 8 : 100; int tmp; + /* + * ignore 2nd time SET_CONFIGURATION + * only for same config value twice. + */ + if (cdev->config && (cdev->config->bConfigurationValue == number)) { + DBG(cdev, "already in the same config with value %d\n", + number); + return 0; + } + if (number) { list_for_each_entry(c, &cdev->configs, list) { if (c->bConfigurationValue == number) { @@ -680,6 +807,8 @@ static int set_config(struct usb_composite_dev *cdev, usb_gadget_set_state(gadget, USB_STATE_CONFIGURED); cdev->config = c; + c->num_ineps_used = 0; + c->num_outeps_used = 0; /* Initialize all interfaces by setting them to altsetting zero. */ for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) { @@ -697,6 +826,12 @@ static int set_config(struct usb_composite_dev *cdev, */ switch (gadget->speed) { case USB_SPEED_SUPER: + if (!f->ss_descriptors) { + pr_err("%s(): No SS desc for function:%s\n", + __func__, f->name); + usb_gadget_set_state(gadget, USB_STATE_ADDRESS); + return -EINVAL; + } descriptors = f->ss_descriptors; break; case USB_SPEED_HIGH: @@ -717,11 +852,15 @@ static int set_config(struct usb_composite_dev *cdev, addr = ((ep->bEndpointAddress & 0x80) >> 3) | (ep->bEndpointAddress & 0x0f); set_bit(addr, f->endpoints); + if (usb_endpoint_dir_in(ep)) + c->num_ineps_used++; + else + c->num_outeps_used++; } result = f->set_alt(f, tmp, 0); if (result < 0) { - DBG(cdev, "interface %d (%s/%p) alt 0 --> %d\n", + DBG(cdev, "interface %d (%s/%pK) alt 0 --> %d\n", tmp, f->name, f, result); reset_config(cdev); @@ -738,10 +877,8 @@ static int set_config(struct usb_composite_dev *cdev, } } - /* when we return, be sure our power usage is valid */ - power = c->MaxPower ? c->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW; done: - usb_gadget_vbus_draw(gadget, power); + usb_gadget_vbus_draw(gadget, USB_VBUS_DRAW(gadget->speed)); if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; return result; @@ -796,7 +933,7 @@ int usb_add_config(struct usb_composite_dev *cdev, if (!bind) goto done; - DBG(cdev, "adding config #%u '%s'/%p\n", + DBG(cdev, "adding config #%u '%s'/%pK\n", config->bConfigurationValue, config->label, config); @@ -813,7 +950,7 @@ int usb_add_config(struct usb_composite_dev *cdev, struct usb_function, list); list_del(&f->list); if (f->unbind) { - DBG(cdev, "unbind function '%s'/%p\n", + DBG(cdev, "unbind function '%s'/%pK\n", f->name, f); f->unbind(config, f); /* may free memory for "f" */ @@ -824,7 +961,7 @@ int usb_add_config(struct usb_composite_dev *cdev, } else { unsigned i; - DBG(cdev, "cfg %d/%p speeds:%s%s%s\n", + DBG(cdev, "cfg %d/%pK speeds:%s%s%s\n", config->bConfigurationValue, config, config->superspeed ? " super" : "", config->highspeed ? " high" : "", @@ -839,7 +976,7 @@ int usb_add_config(struct usb_composite_dev *cdev, if (!f) continue; - DBG(cdev, " interface %d = %s/%p\n", + DBG(cdev, " interface %d = %s/%pK\n", i, f->name, f); } } @@ -865,14 +1002,14 @@ static void remove_config(struct usb_composite_dev *cdev, struct usb_function, list); list_del(&f->list); if (f->unbind) { - DBG(cdev, "unbind function '%s'/%p\n", f->name, f); + DBG(cdev, "unbind function '%s'/%pK\n", f->name, f); f->unbind(config, f); /* may free memory for "f" */ } } list_del(&config->list); if (config->unbind) { - DBG(cdev, "unbind config '%s'/%p\n", config->label, config); + DBG(cdev, "unbind config '%s'/%pK\n", config->label, config); config->unbind(config); /* may free memory for "c" */ } @@ -1280,7 +1417,7 @@ static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req) else if (cdev->os_desc_req == req) cdev->os_desc_pending = false; else - WARN(1, "unknown request %p\n", req); + WARN(1, "unknown request %pK\n", req); } static int composite_ep0_queue(struct usb_composite_dev *cdev, @@ -1295,7 +1432,7 @@ static int composite_ep0_queue(struct usb_composite_dev *cdev, else if (cdev->os_desc_req == req) cdev->os_desc_pending = true; else - WARN(1, "unknown request %p\n", req); + WARN(1, "unknown request %pK\n", req); } return ret; @@ -1500,17 +1637,24 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) case USB_DT_DEVICE: cdev->desc.bNumConfigurations = count_configs(cdev, USB_DT_DEVICE); + if (cdev->desc.bNumConfigurations == 0) { + pr_err("%s:config is not active. send stall\n", + __func__); + break; + } + cdev->desc.bMaxPacketSize0 = cdev->gadget->ep0->maxpacket; + cdev->desc.bcdUSB = cpu_to_le16(0x0200); if (gadget_is_superspeed(gadget)) { if (gadget->speed >= USB_SPEED_SUPER) { - cdev->desc.bcdUSB = cpu_to_le16(0x0300); + cdev->desc.bcdUSB = cpu_to_le16(0x0310); cdev->desc.bMaxPacketSize0 = 9; - } else { + } else if (!disable_l1_for_hs) { cdev->desc.bcdUSB = cpu_to_le16(0x0210); + DBG(cdev, + "Config HS device with LPM(L1)\n"); } - } else { - cdev->desc.bcdUSB = cpu_to_le16(0x0200); } value = min(w_length, (u16) sizeof cdev->desc); @@ -1520,7 +1664,9 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) if (!gadget_is_dualspeed(gadget) || gadget->speed >= USB_SPEED_SUPER) break; + spin_lock(&cdev->lock); device_qual(cdev); + spin_unlock(&cdev->lock); value = min_t(int, w_length, sizeof(struct usb_qualifier_descriptor)); break; @@ -1530,18 +1676,24 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) break; /* FALLTHROUGH */ case USB_DT_CONFIG: + spin_lock(&cdev->lock); value = config_desc(cdev, w_value); + spin_unlock(&cdev->lock); if (value >= 0) value = min(w_length, (u16) value); break; case USB_DT_STRING: + spin_lock(&cdev->lock); value = get_string(cdev, req->buf, w_index, w_value & 0xff); + spin_unlock(&cdev->lock); if (value >= 0) value = min(w_length, (u16) value); break; case USB_DT_BOS: - if (gadget_is_superspeed(gadget)) { + if ((gadget_is_superspeed(gadget) && + (gadget->speed >= USB_SPEED_SUPER)) + || !disable_l1_for_hs) { value = bos_desc(cdev); value = min(w_length, (u16) value); } @@ -1687,8 +1839,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", @@ -1775,6 +1932,16 @@ unknown: } break; } + + if (value < 0) { + DBG(cdev, "%s: unhandled os desc request\n", + __func__); + DBG(cdev, "req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + return value; + } + req->length = value; req->context = cdev; req->zero = value < w_length; @@ -1782,7 +1949,9 @@ unknown: if (value < 0) { DBG(cdev, "ep_queue --> %d\n", value); req->status = 0; - composite_setup_complete(gadget->ep0, req); + if (value != -ESHUTDOWN) + composite_setup_complete(gadget->ep0, + req); } return value; } @@ -1815,6 +1984,8 @@ unknown: break; case USB_RECIP_ENDPOINT: + if (!cdev->config) + break; endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f); list_for_each_entry(f, &cdev->config->functions, list) { if (test_bit(endp, f->endpoints)) @@ -1848,6 +2019,14 @@ try_fun_setup: if (f->setup) value = f->setup(f, ctrl); } + if (value == USB_GADGET_DELAYED_STATUS) { + DBG(cdev, + "%s: interface %d (%s) requested delayed status\n", + __func__, intf, f->name); + cdev->delayed_status++; + DBG(cdev, "delayed_status count %d\n", + cdev->delayed_status); + } goto done; } @@ -1861,7 +2040,8 @@ try_fun_setup: if (value < 0) { DBG(cdev, "ep_queue --> %d\n", value); req->status = 0; - composite_setup_complete(gadget->ep0, req); + if (value != -ESHUTDOWN) + composite_setup_complete(gadget->ep0, req); } } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) { WARN(cdev, @@ -1893,6 +2073,10 @@ void composite_disconnect(struct usb_gadget *gadget) reset_config(cdev); if (cdev->driver->disconnect) cdev->driver->disconnect(cdev); + if (cdev->delayed_status != 0) { + INFO(cdev, "delayed status mismatch..resetting\n"); + cdev->delayed_status = 0; + } spin_unlock_irqrestore(&cdev->lock, flags); } @@ -2062,14 +2246,18 @@ void composite_dev_cleanup(struct usb_composite_dev *cdev) usb_ep_dequeue(cdev->gadget->ep0, cdev->os_desc_req); kfree(cdev->os_desc_req->buf); + cdev->os_desc_req->buf = NULL; usb_ep_free_request(cdev->gadget->ep0, cdev->os_desc_req); + cdev->os_desc_req = NULL; } if (cdev->req) { if (cdev->setup_pending) usb_ep_dequeue(cdev->gadget->ep0, cdev->req); kfree(cdev->req->buf); + cdev->req->buf = NULL; usb_ep_free_request(cdev->gadget->ep0, cdev->req); + cdev->req = NULL; } cdev->next_string_id = 0; device_remove_file(&cdev->gadget->dev, &dev_attr_suspended); @@ -2130,11 +2318,13 @@ void composite_suspend(struct usb_gadget *gadget) { struct usb_composite_dev *cdev = get_gadget_data(gadget); struct usb_function *f; + unsigned long flags; /* REVISIT: should we have config level * suspend/resume callbacks? */ DBG(cdev, "suspend\n"); + spin_lock_irqsave(&cdev->lock, flags); if (cdev->config) { list_for_each_entry(f, &cdev->config->functions, list) { if (f->suspend) @@ -2145,6 +2335,7 @@ void composite_suspend(struct usb_gadget *gadget) cdev->driver->suspend(cdev); cdev->suspended = 1; + spin_unlock_irqrestore(&cdev->lock, flags); usb_gadget_vbus_draw(gadget, 2); } @@ -2153,7 +2344,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? @@ -2161,18 +2353,33 @@ 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); } - maxpower = cdev->config->MaxPower; - - usb_gadget_vbus_draw(gadget, maxpower ? - maxpower : CONFIG_USB_GADGET_VBUS_DRAW); + usb_gadget_vbus_draw(gadget, USB_VBUS_DRAW(gadget->speed)); } + spin_unlock_irqrestore(&cdev->lock, flags); cdev->suspended = 0; } @@ -2264,7 +2471,13 @@ void usb_composite_setup_continue(struct usb_composite_dev *cdev) spin_lock_irqsave(&cdev->lock, flags); if (cdev->delayed_status == 0) { + if (!cdev->config) { + spin_unlock_irqrestore(&cdev->lock, flags); + return; + } + spin_unlock_irqrestore(&cdev->lock, flags); WARN(cdev, "%s: Unexpected call\n", __func__); + return; } else if (--cdev->delayed_status == 0) { DBG(cdev, "%s: Completing delayed status\n", __func__); |
