Merge branch 'sched-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[firefly-linux-kernel-4.4.55.git] / drivers / usb / gadget / composite.c
index fab906429b80533fc18a965efa5b9eb504f46ea7..f80151932053d1f0216e9f21156c1a38e85a34bd 100644 (file)
 #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? */