usb: gadget: OS Feature Descriptors support
authorAndrzej Pietrasiewicz <andrzej.p@samsung.com>
Thu, 8 May 2014 12:06:23 +0000 (14:06 +0200)
committerFelipe Balbi <balbi@ti.com>
Wed, 14 May 2014 14:38:16 +0000 (09:38 -0500)
There is a custom (non-USB IF) extension to the USB standard:

http://msdn.microsoft.com/library/windows/hardware/gg463182

They grant permission to use the specification - there is
"Microsoft OS Descriptor Specification License Agreement"
under the link mentioned above, and its Section 2 "Grant
of License", letter (b) reads:

"Patent license. Microsoft hereby grants to You a nonexclusive,
royalty-free, nontransferable, worldwide license under Microsoft’s
patents embodied solely within the Specification and that are owned
or licensable by Microsoft to make, use, import, offer to sell,
sell and distribute directly or indirectly to Your Licensees Your
Implementation. You may sublicense this patent license to Your
Licensees under the same terms and conditions."

The said extension is maintained by Microsoft for Microsoft.

Yet it is fairly common for various devices to use it, and a
popular proprietary operating system expects devices to provide
"OS descriptors", so Linux-based USB gadgets whishing to be able
to talk to a variety of operating systems should be able to provide
the "OS descriptors".

This patch adds optional support for gadgets whishing to expose
the so called "OS Feature Descriptors", that is "Extended Compatibility ID"
and "Extended Properties".

Hosts which do request "OS descriptors" from gadgets do so during
the enumeration phase and before the configuration is set with
SET_CONFIGURATION. What is more, those hosts never ask for configurations
at indices other than 0. Therefore, gadgets whishing to provide
"OS descriptors" must designate one configuration to be used with
this kind of hosts - this is what os_desc_config is added for in
struct usb_composite_dev. There is an additional advantage to it:
if a gadget provides "OS descriptors" and designates one configuration
to be used with such non-USB-compliant hosts it can invoke
"usb_add_config" in any order because the designated configuration
will be reported to be at index 0 anyway.

This patch also adds handling vendor-specific requests addressed
at device or interface and related to handling "OS descriptors".

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
Acked-by: Michal Nazarewicz <mina86@mina86.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/gadget/composite.c
drivers/usb/gadget/u_os_desc.h [new file with mode: 0644]
include/linux/usb/composite.h

index 2f87b1697bf53f790656123c7bec4f2d8c83e0c8..042c66b71df8a2c831370667e819585a04774621 100644 (file)
@@ -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? */
diff --git a/drivers/usb/gadget/u_os_desc.h b/drivers/usb/gadget/u_os_desc.h
new file mode 100644 (file)
index 0000000..ea5cf8c
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * u_os_desc.h
+ *
+ * Utility definitions for "OS Descriptors" support
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __U_OS_DESC_H__
+#define __U_OS_DESC_H__
+
+#include <asm/unaligned.h>
+#include <linux/nls.h>
+
+#define USB_EXT_PROP_DW_SIZE                   0
+#define USB_EXT_PROP_DW_PROPERTY_DATA_TYPE     4
+#define USB_EXT_PROP_W_PROPERTY_NAME_LENGTH    8
+#define USB_EXT_PROP_B_PROPERTY_NAME           10
+#define USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH   10
+#define USB_EXT_PROP_B_PROPERTY_DATA           14
+
+#define USB_EXT_PROP_RESERVED                  0
+#define USB_EXT_PROP_UNICODE                   1
+#define USB_EXT_PROP_UNICODE_ENV               2
+#define USB_EXT_PROP_BINARY                    3
+#define USB_EXT_PROP_LE32                      4
+#define USB_EXT_PROP_BE32                      5
+#define USB_EXT_PROP_UNICODE_LINK              6
+#define USB_EXT_PROP_UNICODE_MULTI             7
+
+static inline void usb_ext_prop_put_size(u8 *buf, int dw_size)
+{
+       put_unaligned_le32(dw_size, &buf[USB_EXT_PROP_DW_SIZE]);
+}
+
+static inline void usb_ext_prop_put_type(u8 *buf, int type)
+{
+       put_unaligned_le32(type, &buf[USB_EXT_PROP_DW_PROPERTY_DATA_TYPE]);
+}
+
+static inline int usb_ext_prop_put_name(u8 *buf, const char *name, int pnl)
+{
+       int result;
+
+       put_unaligned_le16(pnl, &buf[USB_EXT_PROP_W_PROPERTY_NAME_LENGTH]);
+       result = utf8s_to_utf16s(name, strlen(name), UTF16_LITTLE_ENDIAN,
+               (wchar_t *) &buf[USB_EXT_PROP_B_PROPERTY_NAME], pnl - 2);
+       if (result < 0)
+               return result;
+
+       put_unaligned_le16(0, &buf[USB_EXT_PROP_B_PROPERTY_NAME + pnl]);
+
+       return pnl;
+}
+
+static inline void usb_ext_prop_put_binary(u8 *buf, int pnl, const u8 *data,
+                                          int data_len)
+{
+       put_unaligned_le32(data_len,
+                          &buf[USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH + pnl]);
+       memcpy(&buf[USB_EXT_PROP_B_PROPERTY_DATA + pnl], data, data_len);
+}
+
+static inline int usb_ext_prop_put_unicode(u8 *buf, int pnl, const char *string,
+                                          int data_len)
+{
+       int result;
+       put_unaligned_le32(data_len,
+                       &buf[USB_EXT_PROP_DW_PROPERTY_DATA_LENGTH + pnl]);
+
+       result = utf8s_to_utf16s(string, data_len >> 1, UTF16_LITTLE_ENDIAN,
+                       (wchar_t *) &buf[USB_EXT_PROP_B_PROPERTY_DATA + pnl],
+                       data_len - 2);
+       if (result < 0)
+               return result;
+
+       put_unaligned_le16(0,
+                       &buf[USB_EXT_PROP_B_PROPERTY_DATA + pnl + data_len]);
+
+       return data_len;
+}
+
+#endif /* __U_OS_DESC_H__ */
index 7d29ee9363e8cb43e760a5dedc60490fc1c9ab83..549f5382b01ae49472c97c8fc1e5a5957a8fec84 100644 (file)
 #define USB_MS_TO_HS_INTERVAL(x)       (ilog2((x * 1000 / 125)) + 1)
 struct usb_configuration;
 
+/**
+ * struct usb_os_desc_ext_prop - describes one "Extended Property"
+ * @entry: used to keep a list of extended properties
+ * @type: Extended Property type
+ * @name_len: Extended Property unicode name length, including terminating '\0'
+ * @name: Extended Property name
+ * @data_len: Length of Extended Property blob (for unicode store double len)
+ * @data: Extended Property blob
+ */
+struct usb_os_desc_ext_prop {
+       struct list_head        entry;
+       u8                      type;
+       int                     name_len;
+       char                    *name;
+       int                     data_len;
+       char                    *data;
+};
+
+/**
+ * struct usb_os_desc - describes OS descriptors associated with one interface
+ * @ext_compat_id: 16 bytes of "Compatible ID" and "Subcompatible ID"
+ * @ext_prop: Extended Properties list
+ * @ext_prop_len: Total length of Extended Properties blobs
+ * @ext_prop_count: Number of Extended Properties
+ */
+struct usb_os_desc {
+       char                    *ext_compat_id;
+       struct list_head        ext_prop;
+       int                     ext_prop_len;
+       int                     ext_prop_count;
+};
+
+/**
+ * struct usb_os_desc_table - describes OS descriptors associated with one
+ * interface of a usb_function
+ * @if_id: Interface id
+ * @os_desc: "Extended Compatibility ID" and "Extended Properties" of the
+ *     interface
+ *
+ * Each interface can have at most one "Extended Compatibility ID" and a
+ * number of "Extended Properties".
+ */
+struct usb_os_desc_table {
+       int                     if_id;
+       struct usb_os_desc      *os_desc;
+};
+
 /**
  * struct usb_function - describes one function of a configuration
  * @name: For diagnostics, identifies the function.
@@ -73,6 +120,10 @@ struct usb_configuration;
  *     be available at super speed.
  * @config: assigned when @usb_add_function() is called; this is the
  *     configuration with which this function is associated.
+ * @os_desc_table: Table of (interface id, os descriptors) pairs. The function
+ *     can expose more than one interface. If an interface is a member of
+ *     an IAD, only the first interface of IAD has its entry in the table.
+ * @os_desc_n: Number of entries in os_desc_table
  * @bind: Before the gadget can register, all of its functions bind() to the
  *     available resources including string and interface identifiers used
  *     in interface or class descriptors; endpoints; I/O buffers; and so on.
@@ -129,6 +180,9 @@ struct usb_function {
 
        struct usb_configuration        *config;
 
+       struct usb_os_desc_table        *os_desc_table;
+       unsigned                        os_desc_n;
+
        /* REVISIT:  bind() functions can be marked __init, which
         * makes trouble for section mismatch analysis.  See if
         * we can't restructure things to avoid mismatching.
@@ -342,10 +396,12 @@ static inline struct usb_composite_driver *to_cdriver(
  * struct usb_composite_device - represents one composite usb gadget
  * @gadget: read-only, abstracts the gadget's usb peripheral controller
  * @req: used for control responses; buffer is pre-allocated
+ * @os_desc_req: used for OS descriptors responses; buffer is pre-allocated
  * @config: the currently active configuration
  * @qw_sign: qwSignature part of the OS string
  * @b_vendor_code: bMS_VendorCode part of the OS string
  * @use_os_string: false by default, interested gadgets set it
+ * @os_desc_config: the configuration to be used with OS descriptors
  *
  * One of these devices is allocated and initialized before the
  * associated device driver's bind() is called.
@@ -375,12 +431,14 @@ static inline struct usb_composite_driver *to_cdriver(
 struct usb_composite_dev {
        struct usb_gadget               *gadget;
        struct usb_request              *req;
+       struct usb_request              *os_desc_req;
 
        struct usb_configuration        *config;
 
        /* OS String is a custom (yet popular) extension to the USB standard. */
        u8                              qw_sign[OS_STRING_QW_SIGN_LEN];
        u8                              b_vendor_code;
+       struct usb_configuration        *os_desc_config;
        unsigned int                    use_os_string:1;
 
        /* private: */