aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/composite.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/composite.c')
-rw-r--r--drivers/usb/gadget/composite.c675
1 files changed, 605 insertions, 70 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 2a6bfe759c2..f8015193205 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -21,6 +21,24 @@
#include <linux/usb/composite.h>
#include <asm/unaligned.h>
+#include "u_os_desc.h"
+
+/**
+ * struct usb_os_string - represents OS String to be reported by a gadget
+ * @bLength: total length of the entire descritor, always 0x12
+ * @bDescriptorType: USB_DT_STRING
+ * @qwSignature: the OS String proper
+ * @bMS_VendorCode: code used by the host for subsequent requests
+ * @bPad: not used, must be zero
+ */
+struct usb_os_string {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 qwSignature[OS_STRING_QW_SIGN_LEN];
+ __u8 bMS_VendorCode;
+ __u8 bPad;
+} __packed;
+
/*
* The code in this file is utility code, used to build a gadget driver
* from one or more "function" drivers, one or more "configuration"
@@ -28,6 +46,12 @@
* with the relevant device-wide data.
*/
+static struct usb_gadget_strings **get_containers_gs(
+ struct usb_gadget_string_container *uc)
+{
+ return (struct usb_gadget_strings **)uc->stash;
+}
+
/**
* next_ep_desc() - advance to the next EP descriptor
* @t: currect pointer within descriptor array
@@ -215,6 +239,18 @@ done:
}
EXPORT_SYMBOL_GPL(usb_add_function);
+void usb_remove_function(struct usb_configuration *c, struct usb_function *f)
+{
+ if (f->disable)
+ f->disable(f);
+
+ bitmap_zero(f->endpoints, 32);
+ list_del(&f->list);
+ if (f->unbind)
+ f->unbind(c, f);
+}
+EXPORT_SYMBOL_GPL(usb_remove_function);
+
/**
* usb_function_deactivate - prevent function and gadget enumeration
* @function: the function that isn't yet ready to respond
@@ -320,6 +356,25 @@ int usb_interface_id(struct usb_configuration *config,
}
EXPORT_SYMBOL_GPL(usb_interface_id);
+static u8 encode_bMaxPower(enum usb_device_speed speed,
+ struct usb_configuration *c)
+{
+ unsigned val;
+
+ 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);
+ default:
+ return DIV_ROUND_UP(val, 2);
+ }
+}
+
static int config_buf(struct usb_configuration *config,
enum usb_device_speed speed, void *buf, u8 type)
{
@@ -339,7 +394,7 @@ static int config_buf(struct usb_configuration *config,
c->bConfigurationValue = config->bConfigurationValue;
c->iConfiguration = config->iConfiguration;
c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes;
- c->bMaxPower = config->bMaxPower ? : (CONFIG_USB_GADGET_VBUS_DRAW / 2);
+ c->bMaxPower = encode_bMaxPower(speed, config);
/* There may be e.g. OTG descriptors */
if (config->descriptors) {
@@ -385,6 +440,7 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
{
struct usb_gadget *gadget = cdev->gadget;
struct usb_configuration *c;
+ struct list_head *pos;
u8 type = w_value >> 8;
enum usb_device_speed speed = USB_SPEED_UNKNOWN;
@@ -403,7 +459,20 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
/* This is a lookup by config *INDEX* */
w_value &= 0xff;
- list_for_each_entry(c, &cdev->configs, list) {
+
+ pos = &cdev->configs;
+ c = cdev->os_desc_config;
+ if (c)
+ goto check_config;
+
+ while ((pos = pos->next) != &cdev->configs) {
+ c = list_entry(pos, typeof(*c), list);
+
+ /* skip OS Descriptors config which is handled separately */
+ if (c == cdev->os_desc_config)
+ continue;
+
+check_config:
/* ignore configs that won't work at this speed */
switch (speed) {
case USB_SPEED_SUPER:
@@ -556,6 +625,7 @@ static void reset_config(struct usb_composite_dev *cdev)
bitmap_zero(f->endpoints, 32);
}
cdev->config = NULL;
+ cdev->delayed_status = 0;
}
static int set_config(struct usb_composite_dev *cdev,
@@ -596,6 +666,7 @@ static int set_config(struct usb_composite_dev *cdev,
if (!c)
goto done;
+ usb_gadget_set_state(gadget, USB_STATE_CONFIGURED);
cdev->config = c;
/* Initialize all interfaces by setting them to altsetting zero. */
@@ -656,7 +727,7 @@ static int set_config(struct usb_composite_dev *cdev,
}
/* when we return, be sure our power usage is valid */
- power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW;
+ power = c->MaxPower ? c->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW;
done:
usb_gadget_vbus_draw(gadget, power);
if (result >= 0 && cdev->delayed_status)
@@ -664,6 +735,31 @@ done:
return result;
}
+int usb_add_config_only(struct usb_composite_dev *cdev,
+ struct usb_configuration *config)
+{
+ struct usb_configuration *c;
+
+ if (!config->bConfigurationValue)
+ return -EINVAL;
+
+ /* Prevent duplicate configuration identifiers */
+ list_for_each_entry(c, &cdev->configs, list) {
+ if (c->bConfigurationValue == config->bConfigurationValue)
+ return -EBUSY;
+ }
+
+ config->cdev = cdev;
+ list_add_tail(&config->list, &cdev->configs);
+
+ INIT_LIST_HEAD(&config->functions);
+ config->next_interface_id = 0;
+ memset(config->interface, 0, sizeof(config->interface));
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_add_config_only);
+
/**
* usb_add_config() - add a configuration to a device.
* @cdev: wraps the USB gadget
@@ -684,30 +780,18 @@ int usb_add_config(struct usb_composite_dev *cdev,
int (*bind)(struct usb_configuration *))
{
int status = -EINVAL;
- struct usb_configuration *c;
+
+ if (!bind)
+ goto done;
DBG(cdev, "adding config #%u '%s'/%p\n",
config->bConfigurationValue,
config->label, config);
- if (!config->bConfigurationValue || !bind)
+ status = usb_add_config_only(cdev, config);
+ if (status)
goto done;
- /* Prevent duplicate configuration identifiers */
- list_for_each_entry(c, &cdev->configs, list) {
- if (c->bConfigurationValue == config->bConfigurationValue) {
- status = -EBUSY;
- goto done;
- }
- }
-
- config->cdev = cdev;
- list_add_tail(&config->list, &cdev->configs);
-
- INIT_LIST_HEAD(&config->functions);
- config->next_interface_id = 0;
- memset(config->interface, 0, sizeof(config->interface));
-
status = bind(config);
if (status < 0) {
while (!list_empty(&config->functions)) {
@@ -860,6 +944,7 @@ static int get_string(struct usb_composite_dev *cdev,
void *buf, u16 language, int id)
{
struct usb_composite_driver *composite = cdev->driver;
+ struct usb_gadget_string_container *uc;
struct usb_configuration *c;
struct usb_function *f;
int len;
@@ -892,6 +977,12 @@ static int get_string(struct usb_composite_dev *cdev,
collect_langs(sp, s->wData);
}
}
+ list_for_each_entry(uc, &cdev->gstrings, list) {
+ struct usb_gadget_strings **sp;
+
+ sp = get_containers_gs(uc);
+ collect_langs(sp, s->wData);
+ }
for (len = 0; len <= 126 && s->wData[len]; len++)
continue;
@@ -902,6 +993,28 @@ static int get_string(struct usb_composite_dev *cdev,
return s->bLength;
}
+ if (cdev->use_os_string && language == 0 && id == OS_STRING_IDX) {
+ struct usb_os_string *b = buf;
+ b->bLength = sizeof(*b);
+ b->bDescriptorType = USB_DT_STRING;
+ compiletime_assert(
+ sizeof(b->qwSignature) == sizeof(cdev->qw_sign),
+ "qwSignature size must be equal to qw_sign");
+ memcpy(&b->qwSignature, cdev->qw_sign, sizeof(b->qwSignature));
+ b->bMS_VendorCode = cdev->b_vendor_code;
+ b->bPad = 0;
+ return sizeof(*b);
+ }
+
+ list_for_each_entry(uc, &cdev->gstrings, list) {
+ struct usb_gadget_strings **sp;
+
+ sp = get_containers_gs(uc);
+ len = lookup_string(sp, buf, language, id);
+ if (len > 0)
+ return len;
+ }
+
/* String IDs are device-scoped, so we look up each string
* table we're told about. These lookups are infrequent;
* simpler-is-better here.
@@ -987,6 +1100,119 @@ int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str)
}
EXPORT_SYMBOL_GPL(usb_string_ids_tab);
+static struct usb_gadget_string_container *copy_gadget_strings(
+ struct usb_gadget_strings **sp, unsigned n_gstrings,
+ unsigned n_strings)
+{
+ struct usb_gadget_string_container *uc;
+ struct usb_gadget_strings **gs_array;
+ struct usb_gadget_strings *gs;
+ struct usb_string *s;
+ unsigned mem;
+ unsigned n_gs;
+ unsigned n_s;
+ void *stash;
+
+ mem = sizeof(*uc);
+ mem += sizeof(void *) * (n_gstrings + 1);
+ mem += sizeof(struct usb_gadget_strings) * n_gstrings;
+ mem += sizeof(struct usb_string) * (n_strings + 1) * (n_gstrings);
+ uc = kmalloc(mem, GFP_KERNEL);
+ if (!uc)
+ return ERR_PTR(-ENOMEM);
+ gs_array = get_containers_gs(uc);
+ stash = uc->stash;
+ stash += sizeof(void *) * (n_gstrings + 1);
+ for (n_gs = 0; n_gs < n_gstrings; n_gs++) {
+ struct usb_string *org_s;
+
+ gs_array[n_gs] = stash;
+ gs = gs_array[n_gs];
+ stash += sizeof(struct usb_gadget_strings);
+ gs->language = sp[n_gs]->language;
+ gs->strings = stash;
+ org_s = sp[n_gs]->strings;
+
+ for (n_s = 0; n_s < n_strings; n_s++) {
+ s = stash;
+ stash += sizeof(struct usb_string);
+ if (org_s->s)
+ s->s = org_s->s;
+ else
+ s->s = "";
+ org_s++;
+ }
+ s = stash;
+ s->s = NULL;
+ stash += sizeof(struct usb_string);
+
+ }
+ gs_array[n_gs] = NULL;
+ return uc;
+}
+
+/**
+ * usb_gstrings_attach() - attach gadget strings to a cdev and assign ids
+ * @cdev: the device whose string descriptor IDs are being allocated
+ * and attached.
+ * @sp: an array of usb_gadget_strings to attach.
+ * @n_strings: number of entries in each usb_strings array (sp[]->strings)
+ *
+ * This function will create a deep copy of usb_gadget_strings and usb_string
+ * and attach it to the cdev. The actual string (usb_string.s) will not be
+ * copied but only a referenced will be made. The struct usb_gadget_strings
+ * array may contain multiple languges and should be NULL terminated.
+ * The ->language pointer of each struct usb_gadget_strings has to contain the
+ * same amount of entries.
+ * For instance: sp[0] is en-US, sp[1] is es-ES. It is expected that the first
+ * usb_string entry of es-ES containts the translation of the first usb_string
+ * entry of en-US. Therefore both entries become the same id assign.
+ */
+struct usb_string *usb_gstrings_attach(struct usb_composite_dev *cdev,
+ struct usb_gadget_strings **sp, unsigned n_strings)
+{
+ struct usb_gadget_string_container *uc;
+ struct usb_gadget_strings **n_gs;
+ unsigned n_gstrings = 0;
+ unsigned i;
+ int ret;
+
+ for (i = 0; sp[i]; i++)
+ n_gstrings++;
+
+ if (!n_gstrings)
+ return ERR_PTR(-EINVAL);
+
+ uc = copy_gadget_strings(sp, n_gstrings, n_strings);
+ if (IS_ERR(uc))
+ return ERR_CAST(uc);
+
+ n_gs = get_containers_gs(uc);
+ ret = usb_string_ids_tab(cdev, n_gs[0]->strings);
+ if (ret)
+ goto err;
+
+ for (i = 1; i < n_gstrings; i++) {
+ struct usb_string *m_s;
+ struct usb_string *s;
+ unsigned n;
+
+ m_s = n_gs[0]->strings;
+ s = n_gs[i]->strings;
+ for (n = 0; n < n_strings; n++) {
+ s->id = m_s->id;
+ s++;
+ m_s++;
+ }
+ }
+ list_add_tail(&uc->list, &cdev->gstrings);
+ return n_gs[0]->strings;
+err:
+ kfree(uc);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(usb_gstrings_attach);
+
/**
* usb_string_ids_n() - allocate unused string IDs in batch
* @c: the device whose string descriptor IDs are being allocated
@@ -1026,6 +1252,156 @@ static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req)
req->status, req->actual, req->length);
}
+static int count_ext_compat(struct usb_configuration *c)
+{
+ int i, res;
+
+ res = 0;
+ for (i = 0; i < c->next_interface_id; ++i) {
+ struct usb_function *f;
+ int j;
+
+ f = c->interface[i];
+ for (j = 0; j < f->os_desc_n; ++j) {
+ struct usb_os_desc *d;
+
+ if (i != f->os_desc_table[j].if_id)
+ continue;
+ d = f->os_desc_table[j].os_desc;
+ if (d && d->ext_compat_id)
+ ++res;
+ }
+ }
+ BUG_ON(res > 255);
+ return res;
+}
+
+static void fill_ext_compat(struct usb_configuration *c, u8 *buf)
+{
+ int i, count;
+
+ count = 16;
+ for (i = 0; i < c->next_interface_id; ++i) {
+ struct usb_function *f;
+ int j;
+
+ f = c->interface[i];
+ for (j = 0; j < f->os_desc_n; ++j) {
+ struct usb_os_desc *d;
+
+ if (i != f->os_desc_table[j].if_id)
+ continue;
+ d = f->os_desc_table[j].os_desc;
+ if (d && d->ext_compat_id) {
+ *buf++ = i;
+ *buf++ = 0x01;
+ memcpy(buf, d->ext_compat_id, 16);
+ buf += 22;
+ } else {
+ ++buf;
+ *buf = 0x01;
+ buf += 23;
+ }
+ count += 24;
+ if (count >= 4096)
+ return;
+ }
+ }
+}
+
+static int count_ext_prop(struct usb_configuration *c, int interface)
+{
+ struct usb_function *f;
+ int j;
+
+ f = c->interface[interface];
+ for (j = 0; j < f->os_desc_n; ++j) {
+ struct usb_os_desc *d;
+
+ if (interface != f->os_desc_table[j].if_id)
+ continue;
+ d = f->os_desc_table[j].os_desc;
+ if (d && d->ext_compat_id)
+ return d->ext_prop_count;
+ }
+ return 0;
+}
+
+static int len_ext_prop(struct usb_configuration *c, int interface)
+{
+ struct usb_function *f;
+ struct usb_os_desc *d;
+ int j, res;
+
+ res = 10; /* header length */
+ f = c->interface[interface];
+ 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)
+ return min(res + d->ext_prop_len, 4096);
+ }
+ return res;
+}
+
+static int fill_ext_prop(struct usb_configuration *c, int interface, u8 *buf)
+{
+ struct usb_function *f;
+ 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];
+ 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 +
+ ext_prop->name_len + 14;
+ if (count > 4086 - n)
+ return -EINVAL;
+ usb_ext_prop_put_size(buf, count);
+ usb_ext_prop_put_type(buf, ext_prop->type);
+ ret = usb_ext_prop_put_name(buf, ext_prop->name,
+ ext_prop->name_len);
+ if (ret < 0)
+ return ret;
+ switch (ext_prop->type) {
+ case USB_EXT_PROP_UNICODE:
+ case USB_EXT_PROP_UNICODE_ENV:
+ case USB_EXT_PROP_UNICODE_LINK:
+ usb_ext_prop_put_unicode(buf, ret,
+ ext_prop->data,
+ ext_prop->data_len);
+ break;
+ case USB_EXT_PROP_BINARY:
+ usb_ext_prop_put_binary(buf, ret,
+ ext_prop->data,
+ ext_prop->data_len);
+ break;
+ case USB_EXT_PROP_LE32:
+ /* not implemented */
+ case USB_EXT_PROP_BE32:
+ /* not implemented */
+ default:
+ return -EINVAL;
+ }
+ buf += count;
+ }
+ }
+
+ return 0;
+}
+
/*
* The setup() callback implements all the ep0 functionality that's
* not handled lower down, in hardware or the hardware driver(like
@@ -1033,7 +1409,7 @@ static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req)
* housekeeping for the gadget function we're implementing. Most of
* the work is in config and function specific setup.
*/
-static int
+int
composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = get_gadget_data(gadget);
@@ -1235,6 +1611,91 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
break;
default:
unknown:
+ /*
+ * OS descriptors handling
+ */
+ if (cdev->use_os_string && cdev->os_desc_config &&
+ (ctrl->bRequest & USB_TYPE_VENDOR) &&
+ ctrl->bRequest == cdev->b_vendor_code) {
+ struct usb_request *req;
+ struct usb_configuration *os_desc_cfg;
+ u8 *buf;
+ int interface;
+ int count = 0;
+
+ req = cdev->os_desc_req;
+ req->complete = composite_setup_complete;
+ buf = req->buf;
+ os_desc_cfg = cdev->os_desc_config;
+ memset(buf, 0, w_length);
+ buf[5] = 0x01;
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ 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;
+ }
+ break;
+ case USB_RECIP_INTERFACE:
+ if (w_index != 0x5 || (w_value >> 8))
+ break;
+ interface = w_value & 0xFF;
+ 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;
+ value = fill_ext_prop(os_desc_cfg,
+ interface, buf);
+ if (value < 0)
+ return value;
+
+ value = w_length;
+ }
+ break;
+ }
+ req->length = value;
+ req->zero = value < w_length;
+ value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0) {
+ DBG(cdev, "ep_queue --> %d\n", value);
+ req->status = 0;
+ composite_setup_complete(gadget->ep0, req);
+ }
+ return value;
+ }
+
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
@@ -1272,8 +1733,22 @@ unknown:
struct usb_configuration *c;
c = cdev->config;
- if (c && c->setup)
+ if (!c)
+ goto done;
+
+ /* try current config's setup */
+ if (c->setup) {
value = c->setup(c, ctrl);
+ goto done;
+ }
+
+ /* try the only function in the current config */
+ if (!list_is_singular(&c->functions))
+ goto done;
+ f = list_first_entry(&c->functions, struct usb_function,
+ list);
+ if (f->setup)
+ value = f->setup(f, ctrl);
}
goto done;
@@ -1300,7 +1775,7 @@ done:
return value;
}
-static void composite_disconnect(struct usb_gadget *gadget)
+void composite_disconnect(struct usb_gadget *gadget)
{
struct usb_composite_dev *cdev = get_gadget_data(gadget);
unsigned long flags;
@@ -1318,20 +1793,17 @@ static void composite_disconnect(struct usb_gadget *gadget)
/*-------------------------------------------------------------------------*/
-static ssize_t composite_show_suspended(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t suspended_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct usb_gadget *gadget = dev_to_usb_gadget(dev);
struct usb_composite_dev *cdev = get_gadget_data(gadget);
return sprintf(buf, "%d\n", cdev->suspended);
}
+static DEVICE_ATTR_RO(suspended);
-static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL);
-
-static void
-composite_unbind(struct usb_gadget *gadget)
+static void __composite_unbind(struct usb_gadget *gadget, bool unbind_driver)
{
struct usb_composite_dev *cdev = get_gadget_data(gadget);
@@ -1348,19 +1820,21 @@ composite_unbind(struct usb_gadget *gadget)
struct usb_configuration, list);
remove_config(cdev, c);
}
- if (cdev->driver->unbind)
+ if (cdev->driver->unbind && unbind_driver)
cdev->driver->unbind(cdev);
- if (cdev->req) {
- kfree(cdev->req->buf);
- usb_ep_free_request(gadget->ep0, cdev->req);
- }
- device_remove_file(&gadget->dev, &dev_attr_suspended);
+ composite_dev_cleanup(cdev);
+
kfree(cdev->def_manufacturer);
kfree(cdev);
set_gadget_data(gadget, NULL);
}
+static void composite_unbind(struct usb_gadget *gadget)
+{
+ __composite_unbind(gadget, true);
+}
+
static void update_unchanged_dev_desc(struct usb_device_descriptor *new,
const struct usb_device_descriptor *old)
{
@@ -1399,34 +1873,25 @@ static void update_unchanged_dev_desc(struct usb_device_descriptor *new,
new->iProduct = iProduct;
}
-static struct usb_composite_driver *to_cdriver(struct usb_gadget_driver *gdrv)
-{
- return container_of(gdrv, struct usb_composite_driver, gadget_driver);
-}
-
-static int composite_bind(struct usb_gadget *gadget,
- struct usb_gadget_driver *gdriver)
+int composite_dev_prepare(struct usb_composite_driver *composite,
+ struct usb_composite_dev *cdev)
{
- struct usb_composite_dev *cdev;
- struct usb_composite_driver *composite = to_cdriver(gdriver);
- int status = -ENOMEM;
-
- cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
- if (!cdev)
- return status;
-
- spin_lock_init(&cdev->lock);
- cdev->gadget = gadget;
- set_gadget_data(gadget, cdev);
- INIT_LIST_HEAD(&cdev->configs);
+ struct usb_gadget *gadget = cdev->gadget;
+ int ret = -ENOMEM;
/* preallocate control response and buffer */
cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
if (!cdev->req)
- goto fail;
+ return -ENOMEM;
+
cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL);
if (!cdev->req->buf)
goto fail;
+
+ ret = device_create_file(&gadget->dev, &dev_attr_suspended);
+ if (ret)
+ goto fail_dev;
+
cdev->req->complete = composite_setup_complete;
gadget->ep0->driver_data = cdev;
@@ -1444,7 +1909,79 @@ static int composite_bind(struct usb_gadget *gadget,
* we force endpoints to start unassigned; few controller
* drivers will zero ep->driver_data.
*/
- usb_ep_autoconfig_reset(cdev->gadget);
+ usb_ep_autoconfig_reset(gadget);
+ return 0;
+fail_dev:
+ kfree(cdev->req->buf);
+fail:
+ usb_ep_free_request(gadget->ep0, cdev->req);
+ cdev->req = NULL;
+ return ret;
+}
+
+int composite_os_desc_req_prepare(struct usb_composite_dev *cdev,
+ struct usb_ep *ep0)
+{
+ int ret = 0;
+
+ cdev->os_desc_req = usb_ep_alloc_request(ep0, GFP_KERNEL);
+ if (!cdev->os_desc_req) {
+ ret = PTR_ERR(cdev->os_desc_req);
+ goto end;
+ }
+
+ /* OS feature descriptor length <= 4kB */
+ cdev->os_desc_req->buf = kmalloc(4096, GFP_KERNEL);
+ if (!cdev->os_desc_req->buf) {
+ ret = PTR_ERR(cdev->os_desc_req->buf);
+ kfree(cdev->os_desc_req);
+ goto end;
+ }
+ cdev->os_desc_req->complete = composite_setup_complete;
+end:
+ return ret;
+}
+
+void composite_dev_cleanup(struct usb_composite_dev *cdev)
+{
+ struct usb_gadget_string_container *uc, *tmp;
+
+ list_for_each_entry_safe(uc, tmp, &cdev->gstrings, list) {
+ list_del(&uc->list);
+ kfree(uc);
+ }
+ if (cdev->os_desc_req) {
+ kfree(cdev->os_desc_req->buf);
+ usb_ep_free_request(cdev->gadget->ep0, cdev->os_desc_req);
+ }
+ if (cdev->req) {
+ kfree(cdev->req->buf);
+ usb_ep_free_request(cdev->gadget->ep0, cdev->req);
+ }
+ cdev->next_string_id = 0;
+ device_remove_file(&cdev->gadget->dev, &dev_attr_suspended);
+}
+
+static int composite_bind(struct usb_gadget *gadget,
+ struct usb_gadget_driver *gdriver)
+{
+ struct usb_composite_dev *cdev;
+ struct usb_composite_driver *composite = to_cdriver(gdriver);
+ int status = -ENOMEM;
+
+ cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
+ if (!cdev)
+ return status;
+
+ spin_lock_init(&cdev->lock);
+ cdev->gadget = gadget;
+ set_gadget_data(gadget, cdev);
+ INIT_LIST_HEAD(&cdev->configs);
+ INIT_LIST_HEAD(&cdev->gstrings);
+
+ status = composite_dev_prepare(composite, cdev);
+ if (status)
+ goto fail;
/* composite gadget needs to assign strings for whole device (like
* serial number), register function drivers, potentially update
@@ -1454,22 +1991,23 @@ static int composite_bind(struct usb_gadget *gadget,
if (status < 0)
goto fail;
+ if (cdev->use_os_string) {
+ status = composite_os_desc_req_prepare(cdev, gadget->ep0);
+ if (status)
+ goto fail;
+ }
+
update_unchanged_dev_desc(&cdev->desc, composite->dev);
/* has userspace failed to provide a serial number? */
if (composite->needs_serial && !cdev->desc.iSerialNumber)
WARNING(cdev, "userspace failed to provide iSerialNumber\n");
- /* finish up */
- status = device_create_file(&gadget->dev, &dev_attr_suspended);
- if (status)
- goto fail;
-
INFO(cdev, "%s ready\n", composite->name);
return 0;
fail:
- composite_unbind(gadget);
+ __composite_unbind(gadget, false);
return status;
}
@@ -1504,7 +2042,7 @@ composite_resume(struct usb_gadget *gadget)
{
struct usb_composite_dev *cdev = get_gadget_data(gadget);
struct usb_function *f;
- u8 maxpower;
+ u16 maxpower;
/* REVISIT: should we have config level
* suspend/resume callbacks?
@@ -1518,10 +2056,10 @@ composite_resume(struct usb_gadget *gadget)
f->resume(f);
}
- maxpower = cdev->config->bMaxPower;
+ maxpower = cdev->config->MaxPower;
usb_gadget_vbus_draw(gadget, maxpower ?
- (2 * maxpower) : CONFIG_USB_GADGET_VBUS_DRAW);
+ maxpower : CONFIG_USB_GADGET_VBUS_DRAW);
}
cdev->suspended = 0;
@@ -1547,10 +2085,7 @@ static const struct usb_gadget_driver composite_driver_template = {
/**
* usb_composite_probe() - register a composite driver
* @driver: the driver to register
- * @bind: the callback used to allocate resources that are shared across the
- * whole device, such as string IDs, and add its configurations using
- * @usb_add_config(). This may fail by returning a negative errno
- * value; it should return zero on successful initialization.
+ *
* Context: single threaded during gadget setup
*
* This function is used to register drivers using the composite driver