Add support for OS descriptors. The new format of descriptors is used, because the "flags" field is required for extensions. os_count gives the number of OSDesc[] elements. The format of descriptors is given in include/uapi/linux/usb/functionfs.h. For extended properties descriptor the usb_ext_prop_desc structure covers only a part of a descriptor, because the wPropertyNameLength is unknown up front. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> --- Rebased onto Felipe's testing/next with gadget directory cleanup patches applied: http://www.spinics.net/lists/linux-usb/msg109928.html This is meant for 3.17. @Michal: I kindly ask for your review. Your comments will be very valuable. drivers/usb/gadget/function/f_fs.c | 345 +++++++++++++++++++++++++++++++++++- drivers/usb/gadget/function/u_fs.h | 7 + include/uapi/linux/usb/functionfs.h | 87 ++++++++- 3 files changed, 432 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 88d6fa2..55c0db7 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -34,6 +34,7 @@ #include "u_fs.h" #include "u_f.h" +#include "u_os_desc.h" #include "configfs.h" #define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ @@ -1644,11 +1645,18 @@ enum ffs_entity_type { FFS_DESCRIPTOR, FFS_INTERFACE, FFS_STRING, FFS_ENDPOINT }; +enum ffs_os_desc_type { + FFS_OS_DESC, FFS_OS_DESC_EXT_COMPAT, FFS_OS_DESC_EXT_PROP +}; + typedef int (*ffs_entity_callback)(enum ffs_entity_type entity, u8 *valuep, struct usb_descriptor_header *desc, void *priv); +typedef int (*ffs_os_desc_callback)(enum ffs_os_desc_type entity, + u8 *valuep, void *data, void *priv); + static int __must_check ffs_do_desc(char *data, unsigned len, ffs_entity_callback entity, void *priv) { @@ -1855,11 +1863,190 @@ static int __ffs_data_do_entity(enum ffs_entity_type type, return 0; } +/* + * Process all extended compatibility/extended property descriptors + * of a feature descriptor + */ +static int __must_check ffs_do_os_desc(char *data, unsigned len, u8 *valuep, + u16 feature_count, + ffs_os_desc_callback entity, void *priv, + struct usb_os_desc_header *desc) +{ + int ret; + enum ffs_os_desc_type *type = (enum ffs_os_desc_type *)valuep; + const unsigned _len = len; + + ENTER(); + + /* loop over all ext compat/ext prop descriptors */ + while (feature_count--) { + ret = entity(*type, (u8 *)desc, (void *)data, priv); + if (unlikely(ret < 0)) { + pr_debug("bad OS descriptor, type: %d\n", *valuep); + return ret; + } + data += ret; + len -= ret; + } + return _len - len; +} + +/* Process a number of complete Feature Descriptors (Ext Compat or Ext Prop) */ +static int __must_check ffs_do_os_descs(unsigned count, + char *data, unsigned len, + ffs_os_desc_callback entity, void *priv) +{ + const unsigned _len = len; + unsigned long num = 0; + + ENTER(); + + for (;;) { + int ret; + enum ffs_os_desc_type type; + u16 feature_count; + struct usb_os_desc_header *desc = (void *)data; + + if (num == count) + return _len - len; + + /* Record "descriptor" entity */ + /* + * Process dwLength, bcdVersion, wIndex, + * get b/wCount. + * Move the data pointer to the beginning of + * extended compatibilities proper or + * extended properties proper portions of the + * data + */ + ret = entity(FFS_OS_DESC, (u8 *)&type, desc, priv); + if (unlikely(ret < 0)) { + pr_debug("entity OS_DESCRIPTOR(%02lx); ret = %d\n", + num, ret); + return ret; + } + if (type == FFS_OS_DESC_EXT_COMPAT) { + struct usb_ext_compat_desc_header *h = + (struct usb_ext_compat_desc_header *)data; + feature_count = h->bCount; + } else if (type == FFS_OS_DESC_EXT_PROP) { + struct usb_ext_prop_desc_header *h = (void *)data; + + feature_count = le16_to_cpu(h->wCount); + } else { + return -EINVAL; + } + len -= (ret + 2); + data += (ret + 2); + + /* + * Process all function/property descriptors + * of this Feature Descriptor + */ + ret = ffs_do_os_desc(data, len, (u8 *)&type, feature_count, + entity, priv, desc); + if (unlikely(ret < 0)) { + pr_debug("%s returns %d\n", __func__, ret); + return ret; + } + + len -= ret; + data += ret; + ++num; + } +} + +/** + * Validate contents of the buffer from userspace related to OS descriptors. + */ +static int __ffs_data_do_os_desc(enum ffs_os_desc_type type, + u8 *valuep, void *data, void *priv) +{ + struct ffs_data *ffs = priv; + __u8 length; + + ENTER(); + + switch (type) { + case FFS_OS_DESC: { + struct usb_os_desc_header *desc = data; + enum ffs_os_desc_type *next_type = + (enum ffs_os_desc_type *)valuep; + __u16 bcd_version = le16_to_cpu(desc->bcdVersion); + __u16 w_index = le16_to_cpu(desc->wIndex); + + if (bcd_version != 0x1) { + pr_vdebug("unsupported os descriptors version: %d", + bcd_version); + return -EINVAL; + } + if (w_index != 0x4 && w_index != 0x5) { + pr_vdebug("unsupported os descriptor type: %d", + w_index); + return -EINVAL; + } + switch (w_index) { + case 0x4: + *next_type = FFS_OS_DESC_EXT_COMPAT; + break; + case 0x5: + *next_type = FFS_OS_DESC_EXT_PROP; + break; + } + length = sizeof(*desc); + } + break; + case FFS_OS_DESC_EXT_COMPAT: { + struct usb_ext_compat_desc *d = data; + + if (d->bFirstInterfaceNumber >= ffs->interfaces_count) + return -EINVAL; + + length = sizeof(struct usb_ext_compat_desc); + } + break; + case FFS_OS_DESC_EXT_PROP: { + struct usb_os_desc_header *desc = + (struct usb_os_desc_header *)valuep; + struct usb_ext_prop_desc *d = data; + __le32 type, pdl; + __le16 pnl; + + if (desc->interface >= ffs->interfaces_count) + return -EINVAL; + length = le32_to_cpu(d->dwSize); + type = le32_to_cpu(d->dwPropertyDataType); + if (type < USB_EXT_PROP_UNICODE || + type > USB_EXT_PROP_UNICODE_MULTI) { + pr_vdebug("unsupported os descriptor property type: %d", + type); + return -EINVAL; + } + pnl = le16_to_cpu(d->wPropertyNameLength); + pdl = le32_to_cpu(*(u32 *)(data + 10 + pnl)); + if (length != 14 + pnl + pdl) { +#define FMT "invalid os descriptor length: %d pnl:%d pdl:%d (descriptor %d)\n" + pr_vdebug(FMT, length, pnl, pdl, type); +#undef FMT + return -EINVAL; + } + ++ffs->ms_os_descs_ext_prop_count; + ffs->ms_os_descs_ext_prop_name_len += (pnl << 1); + ffs->ms_os_descs_ext_prop_data_len += pdl; + } + break; + default: + pr_vdebug("unknown descriptor: %d\n", type); + return -EINVAL; + } + return length; +} + static int __ffs_data_got_descs(struct ffs_data *ffs, char *const _data, size_t len) { char *data = _data, *raw_descs; - unsigned counts[3], flags; + unsigned os_descs_count = 0, counts[3], flags; int ret = -EINVAL, i; ENTER(); @@ -1877,7 +2064,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, flags = get_unaligned_le32(data + 8); if (flags & ~(FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC | - FUNCTIONFS_HAS_SS_DESC)) { + FUNCTIONFS_HAS_SS_DESC | + FUNCTIONFS_HAS_MS_OS_DESC)) { ret = -ENOSYS; goto error; } @@ -1900,6 +2088,11 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, len -= 4; } } + if (flags & (1 << i)) { + os_descs_count = get_unaligned_le32(data); + data += 4; + len -= 4; + }; /* Read descriptors */ raw_descs = data; @@ -1913,6 +2106,14 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, data += ret; len -= ret; } + if (os_descs_count) { + ret = ffs_do_os_descs(os_descs_count, data, len, + __ffs_data_do_os_desc, ffs); + if (ret < 0) + goto error; + data += ret; + len -= ret; + } if (raw_descs == data || len) { ret = -EINVAL; @@ -1925,6 +2126,7 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, ffs->fs_descs_count = counts[0]; ffs->hs_descs_count = counts[1]; ffs->ss_descs_count = counts[2]; + ffs->ms_os_descs_count = os_descs_count; return 0; @@ -2091,6 +2293,7 @@ static void __ffs_event_add(struct ffs_data *ffs, rem_type2 = FUNCTIONFS_SUSPEND; /* FALL THROUGH */ case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_SETUP: rem_type1 = type; /* Discard all similar events */ @@ -2266,6 +2469,88 @@ static int __ffs_func_bind_do_nums(enum ffs_entity_type type, u8 *valuep, return 0; } +static int __ffs_func_bind_do_os_desc(enum ffs_os_desc_type type, + u8 *valuep, void *data, void *priv) +{ + struct ffs_function *func = priv; + __u8 length = 0; + + switch (type) { + case FFS_OS_DESC: { + struct usb_os_desc_header *desc = data; + enum ffs_os_desc_type *next_type = + (enum ffs_os_desc_type *)valuep; + __u16 w_index = le16_to_cpu(desc->wIndex); + + switch (w_index) { + case 0x4: + *next_type = FFS_OS_DESC_EXT_COMPAT; + break; + case 0x5: + *next_type = FFS_OS_DESC_EXT_PROP; + break; + } + length = sizeof(*desc); + } + break; + case FFS_OS_DESC_EXT_COMPAT: { + struct usb_ext_compat_desc *desc = data; + struct usb_os_desc_table *t; + + t = &func->function.os_desc_table[desc->bFirstInterfaceNumber]; + t->if_id = func->interfaces_nums[desc->bFirstInterfaceNumber]; + memcpy(t->os_desc->ext_compat_id, (void *)desc + 2, 16); + length = sizeof(*desc); + } + break; + case FFS_OS_DESC_EXT_PROP: { + struct usb_os_desc_header *h = + (struct usb_os_desc_header *)valuep; + struct usb_ext_prop_desc *desc = data; + struct usb_os_desc_table *t; + struct usb_os_desc_ext_prop *ext_prop; + char *ext_prop_name; + char *ext_prop_data; + + t = &func->function.os_desc_table[h->interface]; + t->if_id = func->interfaces_nums[h->interface]; + + ext_prop = func->ffs->ms_os_descs_ext_prop_avail; + func->ffs->ms_os_descs_ext_prop_avail += sizeof(*ext_prop); + + ext_prop->type = le32_to_cpu(desc->dwPropertyDataType); + ext_prop->name_len = le16_to_cpu(desc->wPropertyNameLength); + ext_prop->data_len = + le32_to_cpu(*(u32 *)(data + 10 + ext_prop->name_len)); + length = ext_prop->name_len + ext_prop->data_len + 14; + + ext_prop_name = func->ffs->ms_os_descs_ext_prop_name_avail; + func->ffs->ms_os_descs_ext_prop_name_avail += + ext_prop->name_len; + + ext_prop_data = func->ffs->ms_os_descs_ext_prop_data_avail; + func->ffs->ms_os_descs_ext_prop_data_avail += + ext_prop->data_len; + memcpy(ext_prop_data, data + 14 + ext_prop->name_len, + ext_prop->data_len); + ext_prop->data_len <<= 1; + ext_prop->data = ext_prop_data; + + memcpy(ext_prop_name, data + 10, ext_prop->name_len); + ext_prop->name_len <<= 1; + ext_prop->name = ext_prop_name; + + t->os_desc->ext_prop_len += + (ext_prop->name_len + ext_prop->data_len + 14); + ++t->os_desc->ext_prop_count; + list_add_tail(&ext_prop->entry, &t->os_desc->ext_prop); + } + break; + } + + return length; +} + static inline struct f_fs_opts *ffs_do_functionfs_bind(struct usb_function *f, struct usb_configuration *c) { @@ -2327,7 +2612,7 @@ static int _ffs_func_bind(struct usb_configuration *c, const int super = gadget_is_superspeed(func->gadget) && func->ffs->ss_descs_count; - int fs_len, hs_len, ret; + int fs_len, hs_len, ss_len, ret, i; /* Make it a single chunk, less management later on */ vla_group(d); @@ -2339,6 +2624,18 @@ static int _ffs_func_bind(struct usb_configuration *c, vla_item_with_sz(d, struct usb_descriptor_header *, ss_descs, super ? ffs->ss_descs_count + 1 : 0); vla_item_with_sz(d, short, inums, ffs->interfaces_count); + vla_item_with_sz(d, struct usb_os_desc_table, os_desc_table, + c->cdev->use_os_string ? ffs->interfaces_count : 0); + vla_item_with_sz(d, char[16], ext_compat, + c->cdev->use_os_string ? ffs->interfaces_count : 0); + vla_item_with_sz(d, struct usb_os_desc, os_desc, + c->cdev->use_os_string ? ffs->interfaces_count : 0); + vla_item_with_sz(d, struct usb_os_desc_ext_prop, ext_prop, + ffs->ms_os_descs_ext_prop_count); + vla_item_with_sz(d, char, ext_prop_name, + ffs->ms_os_descs_ext_prop_name_len); + vla_item_with_sz(d, char, ext_prop_data, + ffs->ms_os_descs_ext_prop_data_len); vla_item_with_sz(d, char, raw_descs, ffs->raw_descs_length); char *vlabuf; @@ -2353,8 +2650,20 @@ static int _ffs_func_bind(struct usb_configuration *c, if (unlikely(!vlabuf)) return -ENOMEM; + ffs->ms_os_descs_ext_prop_avail = vla_ptr(vlabuf, d, ext_prop); + ffs->ms_os_descs_ext_prop_name_avail = + vla_ptr(vlabuf, d, ext_prop_name); + ffs->ms_os_descs_ext_prop_data_avail = + vla_ptr(vlabuf, d, ext_prop_data); + /* Zero */ memset(vla_ptr(vlabuf, d, eps), 0, d_eps__sz); + memset(vla_ptr(vlabuf, d, os_desc_table), 0, d_os_desc_table__sz); + memset(vla_ptr(vlabuf, d, ext_compat), 0, d_ext_compat__sz); + memset(vla_ptr(vlabuf, d, os_desc), 0, d_os_desc__sz); + memset(vla_ptr(vlabuf, d, ext_prop), 0, d_ext_prop__sz); + memset(vla_ptr(vlabuf, d, ext_prop_name), 0, d_ext_prop_name__sz); + memset(vla_ptr(vlabuf, d, ext_prop_data), 0, d_ext_prop_data__sz); /* Copy descriptors */ memcpy(vla_ptr(vlabuf, d, raw_descs), ffs->raw_descs, ffs->raw_descs_length); @@ -2408,12 +2717,16 @@ static int _ffs_func_bind(struct usb_configuration *c, if (likely(super)) { func->function.ss_descriptors = vla_ptr(vlabuf, d, ss_descs); - ret = ffs_do_descs(ffs->ss_descs_count, + ss_len = ffs_do_descs(ffs->ss_descs_count, vla_ptr(vlabuf, d, raw_descs) + fs_len + hs_len, d_raw_descs__sz - fs_len - hs_len, __ffs_func_bind_do_descs, func); - if (unlikely(ret < 0)) + if (unlikely(ss_len < 0)) { + ret = ss_len; goto error; + } + } else { + ss_len = 0; } /* @@ -2429,6 +2742,28 @@ static int _ffs_func_bind(struct usb_configuration *c, if (unlikely(ret < 0)) goto error; + func->function.os_desc_table = vla_ptr(vlabuf, d, os_desc_table); + if (c->cdev->use_os_string) + for (i = 0; i < ffs->interfaces_count; ++i) { + struct usb_os_desc *desc; + + desc = func->function.os_desc_table[i].os_desc = + vla_ptr(vlabuf, d, os_desc) + + i * sizeof(struct usb_os_desc); + desc->ext_compat_id = + vla_ptr(vlabuf, d, ext_compat) + i * 16; + INIT_LIST_HEAD(&desc->ext_prop); + } + ret = ffs_do_os_descs(ffs->ms_os_descs_count, + vla_ptr(vlabuf, d, raw_descs) + + fs_len + hs_len + ss_len, + d_raw_descs__sz - fs_len - hs_len - ss_len, + __ffs_func_bind_do_os_desc, func); + func->function.os_desc_n = + c->cdev->use_os_string ? ffs->interfaces_count : 0; + if (unlikely(ret < 0)) + goto error; + /* And we're done */ ffs_event_add(ffs, FUNCTIONFS_BIND); return 0; diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index bf0ba37..63d6e71 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -216,6 +216,13 @@ struct ffs_data { unsigned fs_descs_count; unsigned hs_descs_count; unsigned ss_descs_count; + unsigned ms_os_descs_count; + unsigned ms_os_descs_ext_prop_count; + unsigned ms_os_descs_ext_prop_name_len; + unsigned ms_os_descs_ext_prop_data_len; + void *ms_os_descs_ext_prop_avail; + void *ms_os_descs_ext_prop_name_avail; + void *ms_os_descs_ext_prop_data_avail; unsigned short strings_count; unsigned short interfaces_count; diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 2a4b4a7..4ec3798 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -18,10 +18,9 @@ enum functionfs_flags { FUNCTIONFS_HAS_FS_DESC = 1, FUNCTIONFS_HAS_HS_DESC = 2, FUNCTIONFS_HAS_SS_DESC = 4, + FUNCTIONFS_HAS_MS_OS_DESC = 8, }; -#ifndef __KERNEL__ - /* Descriptor of an non-audio endpoint */ struct usb_endpoint_descriptor_no_audio { __u8 bLength; @@ -33,6 +32,42 @@ struct usb_endpoint_descriptor_no_audio { __u8 bInterval; } __attribute__((packed)); +/* MS OS Descriptor header */ +struct usb_os_desc_header { + __u8 interface; + __u32 dwLength; + __u16 bcdVersion; + __u16 wIndex; +} __attribute__((packed)); + +/* MS OS Extended Compatibility Descriptor header */ +struct usb_ext_compat_desc_header { + struct usb_os_desc_header header; + __u8 bCount; + __u8 Reserved; +} __attribute__((packed)); + +struct usb_ext_compat_desc { + __u8 bFirstInterfaceNumber; + __u8 Reserved1; + __u8 CompatibleID[8]; + __u8 SubCompatibleID[8]; + __u8 Reserved2[6]; +}; + +/* MS OS Extended Properties Descriptor header */ +struct usb_ext_prop_desc_header { + struct usb_os_desc_header header; + __u16 wCount; +} __attribute__((packed)); + +struct usb_ext_prop_desc { + __u32 dwSize; + __u32 dwPropertyDataType; + __u16 wPropertyNameLength; +} __attribute__((packed)); + +#ifndef __KERNEL__ /* * Descriptors format: @@ -45,9 +80,11 @@ struct usb_endpoint_descriptor_no_audio { * | | fs_count | LE32 | number of full-speed descriptors | * | | hs_count | LE32 | number of high-speed descriptors | * | | ss_count | LE32 | number of super-speed descriptors | + * | | os_count | LE32 | number of MS OS descriptors | * | | fs_descrs | Descriptor[] | list of full-speed descriptors | * | | hs_descrs | Descriptor[] | list of high-speed descriptors | * | | ss_descrs | Descriptor[] | list of super-speed descriptors | + * | | os_descrs | OSDesc[] | list of MS OS descriptors | * * Depending on which flags are set, various fields may be missing in the * structure. Any flags that are not recognised cause the whole block to be @@ -74,6 +111,52 @@ struct usb_endpoint_descriptor_no_audio { * | 0 | bLength | U8 | length of the descriptor | * | 1 | bDescriptorType | U8 | descriptor type | * | 2 | payload | | descriptor's payload | + * + * OSDesc[] is an array of valid MS OS Feature Descriptors which have one of + * the following formats: + * + * | off | name | type | description | + * |-----+-----------------+------+--------------------------| + * | 0 | inteface | U8 | related interface number | + * | 1 | dwLength | U32 | length of the descriptor | + * | 5 | bcdVersion | U16 | currently supported: 1 | + * | 7 | wIndex | U16 | currently supported: 4 | + * | 9 | bCount | U8 | number of ext. compat. | + * | 10 | Reserved | U8 | 0 | + * | 11 | ExtCompat[] | | list of ext. compat. d. | + * + * | off | name | type | description | + * |-----+-----------------+------+--------------------------| + * | 0 | inteface | U8 | related interface number | + * | 1 | dwLength | U32 | length of the descriptor | + * | 5 | bcdVersion | U16 | currently supported: 1 | + * | 7 | wIndex | U16 | currently supported: 5 | + * | 9 | wCount | U16 | number of ext. compat. | + * | 11 | ExtProp[] | | list of ext. prop. d. | + * + * ExtCompat[] is an array of valid Extended Compatiblity descriptors + * which have the following format: + * + * | off | name | type | description | + * |-----+-----------------------+------+-------------------------------------| + * | 0 | bFirstInterfaceNumber | U8 | index of the interface or of the 1st| + * | | | | interface in an IAD group | + * | 1 | Reserved | U8 | 0 | + * | 2 | CompatibleID | U8[8]| compatible ID string | + * | 10 | SubCompatibleID | U8[8]| subcompatible ID string | + * | 18 | Reserved | U8[6]| 0 | + * + * ExtProp[] is an array of valid Extended Properties descriptors + * which have the following format: + * + * | off | name | type | description | + * |-----+-----------------------+------+-------------------------------------| + * | 0 | dwSize | U32 | length of the descriptor | + * | 4 | dwPropertyDataType | U32 | 1..7 | + * | 8 | wPropertyNameLength | U16 | bPropertyName length (NL) | + * | 10 | bPropertyName |U8[NL]| name of this property | + * |10+NL| dwPropertyDataLength | U32 | bPropertyData length (DL) | + * |14+NL| bProperty |U8[DL]| payload of this property | */ struct usb_functionfs_strings_head { -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html