summaryrefslogtreecommitdiffstats
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.c316
1 files changed, 315 insertions, 1 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index fab9064..f801519 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"
@@ -422,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;
@@ -440,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:
@@ -634,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. */
@@ -960,6 +993,19 @@ 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;
@@ -1206,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
@@ -1415,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,
@@ -1638,6 +1919,29 @@ fail:
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;
@@ -1646,6 +1950,10 @@ void composite_dev_cleanup(struct usb_composite_dev *cdev)
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);
@@ -1683,6 +1991,12 @@ 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? */
OpenPOWER on IntegriCloud