diff options
Diffstat (limited to 'drivers/usb/gadget/composite.c')
| -rw-r--r-- | drivers/usb/gadget/composite.c | 812 |
1 files changed, 585 insertions, 227 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index dd74c99d6ce1..5b3866909b75 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * composite.c - infrastructure for Composite USB Gadgets * * Copyright (C) 2006-2008 David Brownell - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ /* #define VERBOSE_DEBUG */ @@ -17,10 +13,13 @@ #include <linux/module.h> #include <linux/device.h> #include <linux/utsname.h> +#include <linux/bitfield.h> +#include <linux/uuid.h> #include <linux/usb/composite.h> #include <linux/usb/otg.h> -#include <asm/unaligned.h> +#include <linux/usb/webusb.h> +#include <linux/unaligned.h> #include "u_os_desc.h" @@ -76,17 +75,17 @@ function_descriptors(struct usb_function *f, descriptors = f->ssp_descriptors; if (descriptors) break; - /* FALLTHROUGH */ + fallthrough; case USB_SPEED_SUPER: descriptors = f->ss_descriptors; if (descriptors) break; - /* FALLTHROUGH */ + fallthrough; case USB_SPEED_HIGH: descriptors = f->hs_descriptors; if (descriptors) break; - /* FALLTHROUGH */ + fallthrough; default: descriptors = f->fs_descriptors; } @@ -100,40 +99,43 @@ function_descriptors(struct usb_function *f, } /** - * next_ep_desc() - advance to the next EP descriptor + * next_desc() - advance to the next desc_type descriptor * @t: currect pointer within descriptor array + * @desc_type: descriptor type * - * Return: next EP descriptor or NULL + * Return: next desc_type descriptor or NULL * - * Iterate over @t until either EP descriptor found or + * Iterate over @t until either desc_type descriptor found or * NULL (that indicates end of list) encountered */ static struct usb_descriptor_header** -next_ep_desc(struct usb_descriptor_header **t) +next_desc(struct usb_descriptor_header **t, u8 desc_type) { for (; *t; t++) { - if ((*t)->bDescriptorType == USB_DT_ENDPOINT) + if ((*t)->bDescriptorType == desc_type) return t; } return NULL; } /* - * for_each_ep_desc()- iterate over endpoint descriptors in the - * descriptors list - * @start: pointer within descriptor array. - * @ep_desc: endpoint descriptor to use as the loop cursor + * for_each_desc() - iterate over desc_type descriptors in the + * descriptors list + * @start: pointer within descriptor array. + * @iter_desc: desc_type descriptor to use as the loop cursor + * @desc_type: wanted descriptr type */ -#define for_each_ep_desc(start, ep_desc) \ - for (ep_desc = next_ep_desc(start); \ - ep_desc; ep_desc = next_ep_desc(ep_desc+1)) +#define for_each_desc(start, iter_desc, desc_type) \ + for (iter_desc = next_desc(start, desc_type); \ + iter_desc; iter_desc = next_desc(iter_desc + 1, desc_type)) /** - * config_ep_by_speed() - configures the given endpoint + * config_ep_by_speed_and_alt() - configures the given endpoint * according to gadget speed. * @g: pointer to the gadget * @f: usb function * @_ep: the endpoint to configure + * @alt: alternate setting number * * Return: error code, 0 on success * @@ -146,18 +148,21 @@ next_ep_desc(struct usb_descriptor_header **t) * Note: the supplied function should hold all the descriptors * for supported speeds */ -int config_ep_by_speed(struct usb_gadget *g, - struct usb_function *f, - struct usb_ep *_ep) +int config_ep_by_speed_and_alt(struct usb_gadget *g, + struct usb_function *f, + struct usb_ep *_ep, + u8 alt) { - struct usb_composite_dev *cdev = get_gadget_data(g); struct usb_endpoint_descriptor *chosen_desc = NULL; + struct usb_interface_descriptor *int_desc = NULL; struct usb_descriptor_header **speed_desc = NULL; struct usb_ss_ep_comp_descriptor *comp_desc = NULL; int want_comp_desc = 0; struct usb_descriptor_header **d_spd; /* cursor for speed desc */ + struct usb_composite_dev *cdev; + bool incomplete_desc = false; if (!g || !f || !_ep) return -EIO; @@ -165,30 +170,52 @@ int config_ep_by_speed(struct usb_gadget *g, /* select desired speed */ switch (g->speed) { case USB_SPEED_SUPER_PLUS: - if (gadget_is_superspeed_plus(g)) { + if (f->ssp_descriptors) { speed_desc = f->ssp_descriptors; want_comp_desc = 1; break; } - /* else: Fall trough */ + incomplete_desc = true; + fallthrough; case USB_SPEED_SUPER: - if (gadget_is_superspeed(g)) { + if (f->ss_descriptors) { speed_desc = f->ss_descriptors; want_comp_desc = 1; break; } - /* else: Fall trough */ + incomplete_desc = true; + fallthrough; case USB_SPEED_HIGH: - if (gadget_is_dualspeed(g)) { + if (f->hs_descriptors) { speed_desc = f->hs_descriptors; break; } - /* else: fall through */ + incomplete_desc = true; + fallthrough; default: speed_desc = f->fs_descriptors; } + + cdev = get_gadget_data(g); + if (incomplete_desc) + WARNING(cdev, + "%s doesn't hold the descriptors for current speed\n", + f->name); + + /* find correct alternate setting descriptor */ + for_each_desc(speed_desc, d_spd, USB_DT_INTERFACE) { + int_desc = (struct usb_interface_descriptor *)*d_spd; + + if (int_desc->bAlternateSetting == alt) { + speed_desc = d_spd; + goto intf_found; + } + } + return -EIO; + +intf_found: /* find descriptors */ - for_each_ep_desc(speed_desc, d_spd) { + for_each_desc(speed_desc, d_spd, USB_DT_ENDPOINT) { chosen_desc = (struct usb_endpoint_descriptor *)*d_spd; if (chosen_desc->bEndpointAddress == _ep->address) goto ep_found; @@ -224,6 +251,7 @@ ep_found: case USB_ENDPOINT_XFER_ISOC: /* mult: bits 1:0 of bmAttributes */ _ep->mult = (comp_desc->bmAttributes & 0x3) + 1; + fallthrough; case USB_ENDPOINT_XFER_BULK: case USB_ENDPOINT_XFER_INT: _ep->maxburst = comp_desc->bMaxBurst + 1; @@ -237,6 +265,32 @@ ep_found: } return 0; } +EXPORT_SYMBOL_GPL(config_ep_by_speed_and_alt); + +/** + * config_ep_by_speed() - configures the given endpoint + * according to gadget speed. + * @g: pointer to the gadget + * @f: usb function + * @_ep: the endpoint to configure + * + * Return: error code, 0 on success + * + * This function chooses the right descriptors for a given + * endpoint according to gadget speed and saves it in the + * endpoint desc field. If the endpoint already has a descriptor + * assigned to it - overwrites it with currently corresponding + * descriptor. The endpoint maxpacket field is updated according + * to the chosen descriptor. + * Note: the supplied function should hold all the descriptors + * for supported speeds + */ +int config_ep_by_speed(struct usb_gadget *g, + struct usb_function *f, + struct usb_ep *_ep) +{ + return config_ep_by_speed_and_alt(g, f, _ep, 0); +} EXPORT_SYMBOL_GPL(config_ep_by_speed); /** @@ -348,8 +402,11 @@ int usb_function_deactivate(struct usb_function *function) spin_lock_irqsave(&cdev->lock, flags); - if (cdev->deactivations == 0) + if (cdev->deactivations == 0) { + spin_unlock_irqrestore(&cdev->lock, flags); status = usb_gadget_deactivate(cdev->gadget); + spin_lock_irqsave(&cdev->lock, flags); + } if (status == 0) cdev->deactivations++; @@ -380,8 +437,11 @@ int usb_function_activate(struct usb_function *function) status = -EINVAL; else { cdev->deactivations--; - if (cdev->deactivations == 0) + if (cdev->deactivations == 0) { + spin_unlock_irqrestore(&cdev->lock, flags); status = usb_gadget_activate(cdev->gadget); + spin_lock_irqsave(&cdev->lock, flags); + } } spin_unlock_irqrestore(&cdev->lock, flags); @@ -426,22 +486,77 @@ int usb_interface_id(struct usb_configuration *config, } EXPORT_SYMBOL_GPL(usb_interface_id); +/** + * usb_func_wakeup - sends function wake notification to the host. + * @func: function that sends the remote wakeup notification. + * + * Applicable to devices operating at enhanced superspeed when usb + * functions are put in function suspend state and armed for function + * remote wakeup. On completion, function wake notification is sent. If + * the device is in low power state it tries to bring the device to active + * state before sending the wake notification. Since it is a synchronous + * call, caller must take care of not calling it in interrupt context. + * For devices operating at lower speeds returns negative errno. + * + * Returns zero on success, else negative errno. + */ +int usb_func_wakeup(struct usb_function *func) +{ + struct usb_gadget *gadget = func->config->cdev->gadget; + int id; + + if (!gadget->ops->func_wakeup) + return -EOPNOTSUPP; + + if (!func->func_wakeup_armed) { + ERROR(func->config->cdev, "not armed for func remote wakeup\n"); + return -EINVAL; + } + + for (id = 0; id < MAX_CONFIG_INTERFACES; id++) + if (func->config->interface[id] == func) + break; + + if (id == MAX_CONFIG_INTERFACES) { + ERROR(func->config->cdev, "Invalid function\n"); + return -EINVAL; + } + + return gadget->ops->func_wakeup(gadget, id); +} +EXPORT_SYMBOL_GPL(usb_func_wakeup); + static u8 encode_bMaxPower(enum usb_device_speed speed, struct usb_configuration *c) { unsigned val; - if (c->MaxPower) + if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) 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); - default: - return DIV_ROUND_UP(val, 2); + if (speed < USB_SPEED_SUPER) + return min(val, 500U) / 2; + else + /* + * USB 3.x supports up to 900mA, but since 900 isn't divisible + * by 8 the integral division will effectively cap to 896mA. + */ + return min(val, 900U) / 8; +} + +void check_remote_wakeup_config(struct usb_gadget *g, + struct usb_configuration *c) +{ + if (USB_CONFIG_ATT_WAKEUP & c->bmAttributes) { + /* Reset the rw bit if gadget is not capable of it */ + if (!g->wakeup_capable && g->ops->set_remote_wakeup) { + WARN(c->cdev, "Clearing wakeup bit for config c.%d\n", + c->bConfigurationValue); + c->bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; + } } } @@ -612,6 +727,7 @@ static int bos_desc(struct usb_composite_dev *cdev) struct usb_ext_cap_descriptor *usb_ext; struct usb_dcd_config_params dcd_config_params; struct usb_bos_descriptor *bos = cdev->req->buf; + unsigned int besl = 0; bos->bLength = USB_DT_BOS_SIZE; bos->bDescriptorType = USB_DT_BOS; @@ -619,17 +735,43 @@ static int bos_desc(struct usb_composite_dev *cdev) bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE); bos->bNumDeviceCaps = 0; + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) { + cdev->gadget->ops->get_config_params(cdev->gadget, + &dcd_config_params); + } else { + dcd_config_params.besl_baseline = + USB_DEFAULT_BESL_UNSPECIFIED; + dcd_config_params.besl_deep = + USB_DEFAULT_BESL_UNSPECIFIED; + dcd_config_params.bU1devExitLat = + USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + + if (dcd_config_params.besl_baseline != USB_DEFAULT_BESL_UNSPECIFIED) + besl = USB_BESL_BASELINE_VALID | + USB_SET_BESL_BASELINE(dcd_config_params.besl_baseline); + + if (dcd_config_params.besl_deep != USB_DEFAULT_BESL_UNSPECIFIED) + besl |= USB_BESL_DEEP_VALID | + USB_SET_BESL_DEEP(dcd_config_params.besl_deep); + /* * A SuperSpeed device shall include the USB2.0 extension descriptor * and shall support LPM when operating in USB2.0 HS mode. */ - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE); - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT | USB_BESL_SUPPORT); + if (cdev->gadget->lpm_capable) { + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE); + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT | + USB_BESL_SUPPORT | besl); + } /* * The Superspeed USB Capability descriptor shall be implemented by all @@ -650,17 +792,6 @@ static int bos_desc(struct usb_composite_dev *cdev) 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; } @@ -668,47 +799,108 @@ static int bos_desc(struct usb_composite_dev *cdev) /* The SuperSpeedPlus USB Device Capability descriptor */ if (gadget_is_superspeed_plus(cdev->gadget)) { struct usb_ssp_cap_descriptor *ssp_cap; + u8 ssac = 1; + u8 ssic; + int i; - ssp_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; + if (cdev->gadget->max_ssp_rate == USB_SSP_GEN_2x2) + ssac = 3; /* - * Report typical values. + * Paired RX and TX sublink speed attributes share + * the same SSID. */ + ssic = (ssac + 1) / 2 - 1; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SSP_CAP_SIZE(1)); - ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(1); + ssp_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SSP_CAP_SIZE(ssac)); + ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac); ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE; ssp_cap->bReserved = 0; ssp_cap->wReserved = 0; - /* SSAC = 1 (2 attributes) */ - ssp_cap->bmAttributes = cpu_to_le32(1); + ssp_cap->bmAttributes = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic)); - /* Min RX/TX Lane Count = 1 */ ssp_cap->wFunctionalitySupport = - cpu_to_le16((1 << 8) | (1 << 12)); + cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID, 0) | + FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) | + FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1)); /* - * bmSublinkSpeedAttr[0]: - * ST = Symmetric, RX - * LSE = 3 (Gbps) - * LP = 1 (SuperSpeedPlus) - * LSM = 10 (10 Gbps) - */ - ssp_cap->bmSublinkSpeedAttr[0] = - cpu_to_le32((3 << 4) | (1 << 14) | (0xa << 16)); - /* - * bmSublinkSpeedAttr[1] = - * ST = Symmetric, TX - * LSE = 3 (Gbps) - * LP = 1 (SuperSpeedPlus) - * LSM = 10 (10 Gbps) + * Use 1 SSID if the gadget supports up to gen2x1 or not + * specified: + * - SSID 0 for symmetric RX/TX sublink speed of 10 Gbps. + * + * Use 1 SSID if the gadget supports up to gen1x2: + * - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. + * + * Use 2 SSIDs if the gadget supports up to gen2x2: + * - SSID 0 for symmetric RX/TX sublink speed of 5 Gbps. + * - SSID 1 for symmetric RX/TX sublink speed of 10 Gbps. */ - ssp_cap->bmSublinkSpeedAttr[1] = - cpu_to_le32((3 << 4) | (1 << 14) | - (0xa << 16) | (1 << 7)); + for (i = 0; i < ssac + 1; i++) { + u8 ssid; + u8 mantissa; + u8 type; + + ssid = i >> 1; + + if (cdev->gadget->max_ssp_rate == USB_SSP_GEN_2x1 || + cdev->gadget->max_ssp_rate == USB_SSP_GEN_UNKNOWN) + mantissa = 10; + else + mantissa = 5 << ssid; + + if (i % 2) + type = USB_SSP_SUBLINK_SPEED_ST_SYM_TX; + else + type = USB_SSP_SUBLINK_SPEED_ST_SYM_RX; + + ssp_cap->bmSublinkSpeedAttr[i] = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, + USB_SSP_SUBLINK_SPEED_LSE_GBPS) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, type) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, + USB_SSP_SUBLINK_SPEED_LP_SSP) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, mantissa)); + } + } + + /* The WebUSB Platform Capability descriptor */ + if (cdev->use_webusb) { + struct usb_plat_dev_cap_descriptor *webusb_cap; + struct usb_webusb_cap_data *webusb_cap_data; + guid_t webusb_uuid = WEBUSB_UUID; + + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + webusb_cap_data = (struct usb_webusb_cap_data *) webusb_cap->CapabilityData; + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, + USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE)); + + webusb_cap->bLength = USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE); + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE; + webusb_cap->bReserved = 0; + export_guid(webusb_cap->UUID, &webusb_uuid); + + if (cdev->bcd_webusb_version != 0) + webusb_cap_data->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version); + else + webusb_cap_data->bcdVersion = WEBUSB_VERSION_1_00; + + webusb_cap_data->bVendorCode = cdev->b_webusb_vendor_code; + + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0) + webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_PRESENT; + else + webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_NOT_PRESENT; } return le16_to_cpu(bos->wTotalLength); @@ -743,6 +935,9 @@ static void reset_config(struct usb_composite_dev *cdev) if (f->disable) f->disable(f); + /* Section 9.1.1.6, disable remote wakeup when device is reset */ + f->func_wakeup_armed = false; + bitmap_zero(f->endpoints, 32); } cdev->config = NULL; @@ -753,24 +948,25 @@ static int set_config(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl, unsigned number) { struct usb_gadget *gadget = cdev->gadget; - struct usb_configuration *c = NULL; + struct usb_configuration *c = NULL, *iter; int result = -EINVAL; unsigned power = gadget_is_otg(gadget) ? 8 : 100; int tmp; if (number) { - list_for_each_entry(c, &cdev->configs, list) { - if (c->bConfigurationValue == number) { - /* - * We disable the FDs of the previous - * configuration only if the new configuration - * is a valid one - */ - if (cdev->config) - reset_config(cdev); - result = 0; - break; - } + list_for_each_entry(iter, &cdev->configs, list) { + if (iter->bConfigurationValue != number) + continue; + /* + * We disable the FDs of the previous + * configuration only if the new configuration + * is a valid one + */ + if (cdev->config) + reset_config(cdev); + c = iter; + result = 0; + break; } if (result < 0) goto done; @@ -780,9 +976,9 @@ static int set_config(struct usb_composite_dev *cdev, result = 0; } - INFO(cdev, "%s config #%d: %s\n", - usb_speed_string(gadget->speed), - number, c ? c->label : "unconfigured"); + DBG(cdev, "%s config #%d: %s\n", + usb_speed_string(gadget->speed), + number, c ? c->label : "unconfigured"); if (!c) goto done; @@ -815,7 +1011,7 @@ static int set_config(struct usb_composite_dev *cdev, ep = (struct usb_endpoint_descriptor *)*descriptors; addr = ((ep->bEndpointAddress & 0x80) >> 3) - | (ep->bEndpointAddress & 0x0f); + | usb_endpoint_num(ep); set_bit(addr, f->endpoints); } @@ -839,8 +1035,27 @@ 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; + if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) + power = c->MaxPower; + else + power = CONFIG_USB_GADGET_VBUS_DRAW; + + if (gadget->speed < USB_SPEED_SUPER) + power = min(power, 500U); + else + power = min(power, 900U); + + if (USB_CONFIG_ATT_WAKEUP & c->bmAttributes) + usb_gadget_set_remote_wakeup(gadget, 1); + else + usb_gadget_set_remote_wakeup(gadget, 0); done: + if (power > USB_SELF_POWER_VBUS_MAX_DRAW || + (c && !(c->bmAttributes & USB_CONFIG_ATT_SELFPOWER))) + usb_gadget_clear_selfpowered(gadget); + else + usb_gadget_set_selfpowered(gadget); + usb_gadget_vbus_draw(gadget, power); if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; @@ -905,6 +1120,10 @@ int usb_add_config(struct usb_composite_dev *cdev, goto done; status = bind(config); + + if (status == 0) + status = usb_gadget_check_config(cdev->gadget); + if (status < 0) { while (!list_empty(&config->functions)) { struct usb_function *f; @@ -975,30 +1194,6 @@ static void remove_config(struct usb_composite_dev *cdev, } } -/** - * usb_remove_config() - remove a configuration from a device. - * @cdev: wraps the USB gadget - * @config: the configuration - * - * Drivers must call usb_gadget_disconnect before calling this function - * to disconnect the device from the host and make sure the host will not - * try to enumerate the device while we are changing the config list. - */ -void usb_remove_config(struct usb_composite_dev *cdev, - struct usb_configuration *config) -{ - unsigned long flags; - - spin_lock_irqsave(&cdev->lock, flags); - - if (cdev->config == config) - reset_config(cdev); - - spin_unlock_irqrestore(&cdev->lock, flags); - - remove_config(cdev, config); -} - /*-------------------------------------------------------------------------*/ /* We support strings in multiple languages ... string descriptor zero @@ -1016,7 +1211,7 @@ static void collect_langs(struct usb_gadget_strings **sp, __le16 *buf) while (*sp) { s = *sp; language = cpu_to_le16(s->language); - for (tmp = buf; *tmp && tmp < &buf[126]; tmp++) { + for (tmp = buf; *tmp && tmp < &buf[USB_MAX_STRING_LEN]; tmp++) { if (*tmp == language) goto repeat; } @@ -1091,7 +1286,7 @@ static int get_string(struct usb_composite_dev *cdev, collect_langs(sp, s->wData); } - for (len = 0; len <= 126 && s->wData[len]; len++) + for (len = 0; len <= USB_MAX_STRING_LEN && s->wData[len]; len++) continue; if (!len) return -EINVAL; @@ -1176,7 +1371,7 @@ int usb_string_id(struct usb_composite_dev *cdev) EXPORT_SYMBOL_GPL(usb_string_id); /** - * usb_string_ids() - allocate unused string IDs in batch + * usb_string_ids_tab() - allocate unused string IDs in batch * @cdev: the device whose string descriptor IDs are being allocated * @str: an array of usb_string objects to assign numbers to * Context: single threaded during gadget setup @@ -1422,11 +1617,12 @@ static int count_ext_compat(struct usb_configuration *c) return res; } -static void fill_ext_compat(struct usb_configuration *c, u8 *buf) +static int fill_ext_compat(struct usb_configuration *c, u8 *buf) { int i, count; count = 16; + buf += 16; for (i = 0; i < c->next_interface_id; ++i) { struct usb_function *f; int j; @@ -1449,10 +1645,12 @@ static void fill_ext_compat(struct usb_configuration *c, u8 *buf) buf += 23; } count += 24; - if (count >= 4096) - return; + if (count + 24 >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; } } + + return count; } static int count_ext_prop(struct usb_configuration *c, int interface) @@ -1497,25 +1695,21 @@ static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) struct usb_os_desc *d; struct usb_os_desc_ext_prop *ext_prop; int j, count, n, ret; - u8 *start = buf; f = c->interface[interface]; + count = 10; /* header length */ + buf += 10; for (j = 0; j < f->os_desc_n; ++j) { if (interface != f->os_desc_table[j].if_id) continue; d = f->os_desc_table[j].os_desc; if (d) list_for_each_entry(ext_prop, &d->ext_prop, entry) { - /* 4kB minus header length */ - n = buf - start; - if (n >= 4086) - return 0; - - count = ext_prop->data_len + + n = ext_prop->data_len + ext_prop->name_len + 14; - if (count > 4086 - n) - return -EINVAL; - usb_ext_prop_put_size(buf, count); + if (count + n >= USB_COMP_EP0_OS_DESC_BUFSIZ) + return count; + usb_ext_prop_put_size(buf, n); usb_ext_prop_put_type(buf, ext_prop->type); ret = usb_ext_prop_put_name(buf, ext_prop->name, ext_prop->name_len); @@ -1541,11 +1735,12 @@ static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf) default: return -EINVAL; } - buf += count; + buf += n; + count += n; } } - return 0; + return count; } /* @@ -1567,8 +1762,21 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) u16 w_value = le16_to_cpu(ctrl->wValue); u16 w_length = le16_to_cpu(ctrl->wLength); struct usb_function *f = NULL; + struct usb_function *iter; u8 endp; + if (w_length > USB_COMP_EP0_BUFSIZ) { + if (ctrl->bRequestType & USB_DIR_IN) { + /* Cast away the const, we are going to overwrite on purpose. */ + __le16 *temp = (__le16 *)&ctrl->wLength; + + *temp = cpu_to_le16(USB_COMP_EP0_BUFSIZ); + w_length = USB_COMP_EP0_BUFSIZ; + } else { + goto done; + } + } + /* partial re-init of the response message; the function or the * gadget might need to intercept e.g. a control-OUT completion * when we delegate to it. @@ -1601,19 +1809,19 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) cdev->gadget->ep0->maxpacket; if (gadget_is_superspeed(gadget)) { if (gadget->speed >= USB_SPEED_SUPER) { - cdev->desc.bcdUSB = cpu_to_le16(0x0310); + cdev->desc.bcdUSB = cpu_to_le16(0x0320); cdev->desc.bMaxPacketSize0 = 9; } else { cdev->desc.bcdUSB = cpu_to_le16(0x0210); } } else { - if (gadget->lpm_capable) + if (gadget->lpm_capable || cdev->use_webusb) cdev->desc.bcdUSB = cpu_to_le16(0x0201); else cdev->desc.bcdUSB = cpu_to_le16(0x0200); } - value = min(w_length, (u16) sizeof cdev->desc); + value = min_t(u16, w_length, sizeof(cdev->desc)); memcpy(req->buf, &cdev->desc, value); break; case USB_DT_DEVICE_QUALIFIER: @@ -1628,23 +1836,23 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) if (!gadget_is_dualspeed(gadget) || gadget->speed >= USB_SPEED_SUPER) break; - /* FALLTHROUGH */ + fallthrough; case USB_DT_CONFIG: value = config_desc(cdev, w_value); if (value >= 0) - value = min(w_length, (u16) value); + value = min_t(u16, w_length, value); break; case USB_DT_STRING: value = get_string(cdev, req->buf, w_index, w_value & 0xff); if (value >= 0) - value = min(w_length, (u16) value); + value = min_t(u16, w_length, value); break; case USB_DT_BOS: if (gadget_is_superspeed(gadget) || - gadget->lpm_capable) { + gadget->lpm_capable || cdev->use_webusb) { value = bos_desc(cdev); - value = min(w_length, (u16) value); + value = min_t(u16, w_length, value); } break; case USB_DT_OTG: @@ -1699,7 +1907,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) *(u8 *)req->buf = cdev->config->bConfigurationValue; else *(u8 *)req->buf = 0; - value = min(w_length, (u16) 1); + value = min_t(u16, w_length, 1); break; /* function drivers must handle get/set altsetting */ @@ -1719,6 +1927,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) */ if (w_value && !f->get_alt) break; + + spin_lock(&cdev->lock); value = f->set_alt(f, w_index, w_value); if (value == USB_GADGET_DELAYED_STATUS) { DBG(cdev, @@ -1728,6 +1938,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) DBG(cdev, "delayed_status count %d\n", cdev->delayed_status); } + spin_unlock(&cdev->lock); break; case USB_REQ_GET_INTERFACE: if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) @@ -1742,7 +1953,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) if (value < 0) break; *((u8 *)req->buf) = value; - value = min(w_length, (u16) 1); + value = min_t(u16, w_length, 1); break; case USB_REQ_GET_STATUS: if (gadget_is_otg(gadget) && gadget->hnp_polling_support && @@ -1773,9 +1984,18 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) f = cdev->config->interface[intf]; if (!f) break; - status = f->get_status ? f->get_status(f) : 0; - if (status < 0) - break; + + if (f->get_status) { + status = f->get_status(f); + + if (status < 0) + break; + + /* if D5 is not set, then device is not wakeup capable */ + if (!(f->config->bmAttributes & USB_CONFIG_ATT_WAKEUP)) + status &= ~(USB_INTRF_STAT_FUNC_RW_CAP | USB_INTRF_STAT_FUNC_RW); + } + put_unaligned_le16(status & 0x0000ffff, req->buf); break; /* @@ -1797,8 +2017,44 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) if (!f) break; value = 0; - if (f->func_suspend) + if (f->func_suspend) { value = f->func_suspend(f, w_index >> 8); + /* SetFeature(FUNCTION_SUSPEND) */ + } else if (ctrl->bRequest == USB_REQ_SET_FEATURE) { + if (!(f->config->bmAttributes & + USB_CONFIG_ATT_WAKEUP) && + (w_index & USB_INTRF_FUNC_SUSPEND_RW)) + break; + + f->func_wakeup_armed = !!(w_index & + USB_INTRF_FUNC_SUSPEND_RW); + + if (w_index & USB_INTRF_FUNC_SUSPEND_LP) { + if (f->suspend && !f->func_suspended) { + f->suspend(f); + f->func_suspended = true; + } + /* + * Handle cases where host sends function resume + * through SetFeature(FUNCTION_SUSPEND) but low power + * bit reset + */ + } else { + if (f->resume && f->func_suspended) { + f->resume(f); + f->func_suspended = false; + } + } + /* ClearFeature(FUNCTION_SUSPEND) */ + } else if (ctrl->bRequest == USB_REQ_CLEAR_FEATURE) { + f->func_wakeup_armed = false; + + if (f->resume && f->func_suspended) { + f->resume(f); + f->func_suspended = false; + } + } + if (value < 0) { ERROR(cdev, "func_suspend() returned error %d\n", @@ -1816,7 +2072,6 @@ unknown: if (cdev->use_os_string && cdev->os_desc_config && (ctrl->bRequestType & USB_TYPE_VENDOR) && ctrl->bRequest == cdev->b_vendor_code) { - struct usb_request *req; struct usb_configuration *os_desc_cfg; u8 *buf; int interface; @@ -1827,79 +2082,109 @@ unknown: req->complete = composite_setup_complete; buf = req->buf; os_desc_cfg = cdev->os_desc_config; + w_length = min_t(u16, w_length, USB_COMP_EP0_OS_DESC_BUFSIZ); memset(buf, 0, w_length); buf[5] = 0x01; switch (ctrl->bRequestType & USB_RECIP_MASK) { + /* + * The Microsoft CompatID OS Descriptor Spec(w_index = 0x4) and + * Extended Prop OS Desc Spec(w_index = 0x5) state that the + * HighByte of wValue is the InterfaceNumber and the LowByte is + * the PageNumber. This high/low byte ordering is incorrectly + * documented in the Spec. USB analyzer output on the below + * request packets show the high/low byte inverted i.e LowByte + * is the InterfaceNumber and the HighByte is the PageNumber. + * Since we dont support >64KB CompatID/ExtendedProp descriptors, + * PageNumber is set to 0. Hence verify that the HighByte is 0 + * for below two cases. + */ case USB_RECIP_DEVICE: if (w_index != 0x4 || (w_value >> 8)) break; buf[6] = w_index; - if (w_length == 0x10) { - /* Number of ext compat interfaces */ - count = count_ext_compat(os_desc_cfg); - buf[8] = count; - count *= 24; /* 24 B/ext compat desc */ - count += 16; /* header */ - put_unaligned_le32(count, buf); - value = w_length; - } else { - /* "extended compatibility ID"s */ - count = count_ext_compat(os_desc_cfg); - buf[8] = count; - count *= 24; /* 24 B/ext compat desc */ - count += 16; /* header */ - put_unaligned_le32(count, buf); - buf += 16; - fill_ext_compat(os_desc_cfg, buf); - value = w_length; + /* Number of ext compat interfaces */ + count = count_ext_compat(os_desc_cfg); + buf[8] = count; + count *= 24; /* 24 B/ext compat desc */ + count += 16; /* header */ + put_unaligned_le32(count, buf); + value = w_length; + if (w_length > 0x10) { + value = fill_ext_compat(os_desc_cfg, buf); + value = min_t(u16, w_length, value); } break; case USB_RECIP_INTERFACE: if (w_index != 0x5 || (w_value >> 8)) break; interface = w_value & 0xFF; + if (interface >= MAX_CONFIG_INTERFACES || + !os_desc_cfg->interface[interface]) + break; buf[6] = w_index; - if (w_length == 0x0A) { - count = count_ext_prop(os_desc_cfg, - interface); - put_unaligned_le16(count, buf + 8); - count = len_ext_prop(os_desc_cfg, - interface); - put_unaligned_le32(count, buf); - - value = w_length; - } else { - count = count_ext_prop(os_desc_cfg, - interface); - put_unaligned_le16(count, buf + 8); - count = len_ext_prop(os_desc_cfg, - interface); - put_unaligned_le32(count, buf); - buf += 10; + count = count_ext_prop(os_desc_cfg, + interface); + put_unaligned_le16(count, buf + 8); + count = len_ext_prop(os_desc_cfg, + interface); + put_unaligned_le32(count, buf); + value = w_length; + if (w_length > 0x0A) { value = fill_ext_prop(os_desc_cfg, interface, buf); - if (value < 0) - return value; - - value = w_length; + if (value >= 0) + value = min_t(u16, w_length, value); } break; } - if (value >= 0) { - req->length = value; - req->context = cdev; - req->zero = value < w_length; - value = composite_ep0_queue(cdev, req, - GFP_ATOMIC); - if (value < 0) { - DBG(cdev, "ep_queue --> %d\n", value); - req->status = 0; - composite_setup_complete(gadget->ep0, - req); - } + goto check_value; + } + + /* + * WebUSB URL descriptor handling, following: + * https://wicg.github.io/webusb/#device-requests + */ + if (cdev->use_webusb && + ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) && + w_index == WEBUSB_GET_URL && + w_value == WEBUSB_LANDING_PAGE_PRESENT && + ctrl->bRequest == cdev->b_webusb_vendor_code) { + unsigned int landing_page_length; + unsigned int landing_page_offset; + struct webusb_url_descriptor *url_descriptor = + (struct webusb_url_descriptor *)cdev->req->buf; + + url_descriptor->bDescriptorType = WEBUSB_URL_DESCRIPTOR_TYPE; + + if (strncasecmp(cdev->landing_page, "https://", 8) == 0) { + landing_page_offset = 8; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTPS; + } else if (strncasecmp(cdev->landing_page, "http://", 7) == 0) { + landing_page_offset = 7; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTP; + } else { + landing_page_offset = 0; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_NONE; } - return value; + + landing_page_length = strnlen(cdev->landing_page, + sizeof(url_descriptor->URL) + - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset); + + if (w_length < WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_length) + landing_page_length = w_length + - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset; + + memcpy(url_descriptor->URL, + cdev->landing_page + landing_page_offset, + landing_page_length - landing_page_offset); + url_descriptor->bLength = landing_page_length + - landing_page_offset + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH; + + value = url_descriptor->bLength; + + goto check_value; } VDBG(cdev, @@ -1937,12 +2222,12 @@ unknown: 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)) + list_for_each_entry(iter, &cdev->config->functions, list) { + if (test_bit(endp, iter->endpoints)) { + f = iter; break; + } } - if (&f->list == &cdev->config->functions) - f = NULL; break; } try_fun_setup: @@ -1973,6 +2258,7 @@ try_fun_setup: goto done; } +check_value: /* respond with data transfer before status phase? */ if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) { req->length = value; @@ -1995,7 +2281,7 @@ done: return value; } -void composite_disconnect(struct usb_gadget *gadget) +static void __composite_disconnect(struct usb_gadget *gadget) { struct usb_composite_dev *cdev = get_gadget_data(gadget); unsigned long flags; @@ -2004,6 +2290,7 @@ void composite_disconnect(struct usb_gadget *gadget) * disconnect callbacks? */ spin_lock_irqsave(&cdev->lock, flags); + cdev->suspended = 0; if (cdev->config) reset_config(cdev); if (cdev->driver->disconnect) @@ -2011,6 +2298,23 @@ void composite_disconnect(struct usb_gadget *gadget) spin_unlock_irqrestore(&cdev->lock, flags); } +void composite_disconnect(struct usb_gadget *gadget) +{ + usb_gadget_vbus_draw(gadget, 0); + __composite_disconnect(gadget); +} + +void composite_reset(struct usb_gadget *gadget) +{ + /* + * Section 1.4.13 Standard Downstream Port of the USB battery charging + * specification v1.2 states that a device connected on a SDP shall only + * draw at max 100mA while in a connected, but unconfigured state. + */ + usb_gadget_vbus_draw(gadget, 100); + __composite_disconnect(gadget); +} + /*-------------------------------------------------------------------------*/ static ssize_t suspended_show(struct device *dev, struct device_attribute *attr, @@ -2026,6 +2330,8 @@ static DEVICE_ATTR_RO(suspended); static void __composite_unbind(struct usb_gadget *gadget, bool unbind_driver) { struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_gadget_strings *gstr = cdev->driver->strings[0]; + struct usb_string *dev_str = gstr->strings; /* composite_disconnect() must already have been called * by the underlying peripheral controller driver! @@ -2045,6 +2351,9 @@ static void __composite_unbind(struct usb_gadget *gadget, bool unbind_driver) composite_dev_cleanup(cdev); + if (dev_str[USB_GADGET_MANUFACTURER_IDX].s == cdev->def_manufacturer) + dev_str[USB_GADGET_MANUFACTURER_IDX].s = ""; + kfree(cdev->def_manufacturer); kfree(cdev); set_gadget_data(gadget, NULL); @@ -2104,7 +2413,7 @@ int composite_dev_prepare(struct usb_composite_driver *composite, if (!cdev->req) return -ENOMEM; - cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); + cdev->req->buf = kzalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); if (!cdev->req->buf) goto fail; @@ -2151,11 +2460,16 @@ int composite_os_desc_req_prepare(struct usb_composite_dev *cdev, goto end; } - /* OS feature descriptor length <= 4kB */ - cdev->os_desc_req->buf = kmalloc(4096, GFP_KERNEL); + cdev->os_desc_req->buf = kmalloc(USB_COMP_EP0_OS_DESC_BUFSIZ, + GFP_KERNEL); if (!cdev->os_desc_req->buf) { ret = -ENOMEM; usb_ep_free_request(ep0, cdev->os_desc_req); + /* + * Set os_desc_req to NULL so that composite_dev_cleanup() + * will not try to free it again. + */ + cdev->os_desc_req = NULL; goto end; } cdev->os_desc_req->context = cdev; @@ -2167,6 +2481,7 @@ end: void composite_dev_cleanup(struct usb_composite_dev *cdev) { struct usb_gadget_string_container *uc, *tmp; + struct usb_ep *ep, *tmp_ep; list_for_each_entry_safe(uc, tmp, &cdev->gstrings, list) { list_del(&uc->list); @@ -2177,17 +2492,36 @@ 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); + + /* + * Some UDC backends have a dynamic EP allocation scheme. + * + * In that case, the dispose() callback is used to notify the + * backend that the EPs are no longer in use. + * + * Note: The UDC backend can remove the EP from the ep_list as + * a result, so we need to use the _safe list iterator. + */ + list_for_each_entry_safe(ep, tmp_ep, + &cdev->gadget->ep_list, ep_list) { + if (ep->ops->dispose) + ep->ops->dispose(ep); + } } static int composite_bind(struct usb_gadget *gadget, @@ -2261,6 +2595,10 @@ void composite_suspend(struct usb_gadget *gadget) cdev->suspended = 1; + if (cdev->config && + cdev->config->bmAttributes & USB_CONFIG_ATT_SELFPOWER) + usb_gadget_set_selfpowered(gadget); + usb_gadget_vbus_draw(gadget, 2); } @@ -2268,7 +2606,7 @@ void composite_resume(struct usb_gadget *gadget) { struct usb_composite_dev *cdev = get_gadget_data(gadget); struct usb_function *f; - u16 maxpower; + unsigned maxpower; /* REVISIT: should we have config level * suspend/resume callbacks? @@ -2278,14 +2616,33 @@ void composite_resume(struct usb_gadget *gadget) cdev->driver->resume(cdev); if (cdev->config) { list_for_each_entry(f, &cdev->config->functions, list) { - if (f->resume) + /* + * Check for func_suspended flag to see if the function is + * in USB3 FUNCTION_SUSPEND state. In this case resume is + * done via FUNCTION_SUSPEND feature selector. + */ + if (f->resume && !f->func_suspended) f->resume(f); } - maxpower = cdev->config->MaxPower; + maxpower = cdev->config->MaxPower ? + cdev->config->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW; + if (gadget->speed < USB_SPEED_SUPER) + maxpower = min(maxpower, 500U); + else + maxpower = min(maxpower, 900U); + + if (maxpower > USB_SELF_POWER_VBUS_MAX_DRAW || + !(cdev->config->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) + usb_gadget_clear_selfpowered(gadget); + else + usb_gadget_set_selfpowered(gadget); - usb_gadget_vbus_draw(gadget, maxpower ? - maxpower : CONFIG_USB_GADGET_VBUS_DRAW); + usb_gadget_vbus_draw(gadget, maxpower); + } else { + maxpower = CONFIG_USB_GADGET_VBUS_DRAW; + maxpower = min(maxpower, 100U); + usb_gadget_vbus_draw(gadget, maxpower); } cdev->suspended = 0; @@ -2298,7 +2655,7 @@ static const struct usb_gadget_driver composite_driver_template = { .unbind = composite_unbind, .setup = composite_setup, - .reset = composite_disconnect, + .reset = composite_reset, .disconnect = composite_disconnect, .suspend = composite_suspend, @@ -2342,7 +2699,7 @@ int usb_composite_probe(struct usb_composite_driver *driver) gadget_driver->driver.name = driver->name; gadget_driver->max_speed = driver->max_speed; - return usb_gadget_probe_driver(gadget_driver); + return usb_gadget_register_driver(gadget_driver); } EXPORT_SYMBOL_GPL(usb_composite_probe); @@ -2440,5 +2797,6 @@ void usb_composite_overwrite_options(struct usb_composite_dev *cdev, } EXPORT_SYMBOL_GPL(usb_composite_overwrite_options); +MODULE_DESCRIPTION("infrastructure for Composite USB Gadgets"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Brownell"); |
