diff options
-rw-r--r-- | drivers/usb/gadget/composite.c | 288 | ||||
-rw-r--r-- | drivers/usb/gadget/u_os_desc.h | 90 | ||||
-rw-r--r-- | include/linux/usb/composite.h | 58 |
3 files changed, 435 insertions, 1 deletions
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 2f87b1697bf5..042c66b71df8 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -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 index 000000000000..ea5cf8c2da28 --- /dev/null +++ b/drivers/usb/gadget/u_os_desc.h @@ -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__ */ diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index 7d29ee9363e8..549f5382b01a 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -57,6 +57,53 @@ 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. * @strings: tables of strings, keyed by identifiers assigned during bind() @@ -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: */ |