|
|
@@ -21,6 +21,8 @@
|
|
|
#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
|
|
|
@@ -438,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;
|
|
|
|
|
|
@@ -456,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:
|
|
|
@@ -1236,6 +1252,158 @@ 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, res;
|
|
|
+
|
|
|
+ res = 0;
|
|
|
+
|
|
|
+ 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 res;
|
|
|
+}
|
|
|
+
|
|
|
+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
|
|
|
@@ -1445,6 +1613,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,
|
|
|
@@ -1668,6 +1921,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;
|
|
|
@@ -1676,6 +1952,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);
|
|
|
@@ -1713,6 +1993,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? */
|