Commit da424314 authored by Andrzej Pietrasiewicz's avatar Andrzej Pietrasiewicz Committed by Felipe Balbi

usb: gadget: configfs: OS Extended Compatibility descriptors support

Add handling of OS Extended Compatibility descriptors from configfs interface.
Hosts which expect the "OS Descriptors" ask only for configurations @ index 0,
but linux-based USB devices can provide more than one configuration.
This patch adds marking one of gadget's configurations the configuration
to be reported at index 0, regardless of the actual sequence of usb_add_config
invocations used for adding the configurations. The configuration is selected
by creating a symbolic link pointing to it from the "os_desc" directory
located at the top of a gadget's directory hierarchy.

One kind of "OS Descriptors" are "Extended Compatibility Descriptors",
which need to be specified per interface. This patch adds interface.<n>
directory in function's configfs directory to represent each interface
defined by the function. Each interface's directory contains two attributes:
"compatible_id" and "sub_compatible_id", which represent 8-byte
strings to be reported to the host as the "Compatible ID" and "Sub Compatible
ID".
Signed-off-by: default avatarAndrzej Pietrasiewicz <andrzej.p@samsung.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 87213d38
...@@ -62,6 +62,19 @@ KernelVersion: 3.11 ...@@ -62,6 +62,19 @@ KernelVersion: 3.11
Description: Description:
This group contains functions available to this USB gadget. This group contains functions available to this USB gadget.
What: /config/usb-gadget/gadget/functions/<func>.<inst>/interface.<n>
Date: May 2014
KernelVersion: 3.16
Description:
This group contains "Feature Descriptors" specific for one
gadget's USB interface or one interface group described
by an IAD.
The attributes:
compatible_id - 8-byte string for "Compatible ID"
sub_compatible_id - 8-byte string for "Sub Compatible ID"
What: /config/usb-gadget/gadget/strings What: /config/usb-gadget/gadget/strings
Date: Jun 2013 Date: Jun 2013
KernelVersion: 3.11 KernelVersion: 3.11
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <linux/usb/composite.h> #include <linux/usb/composite.h>
#include <linux/usb/gadget_configfs.h> #include <linux/usb/gadget_configfs.h>
#include "configfs.h" #include "configfs.h"
#include "u_f.h"
int check_user_usb_string(const char *name, int check_user_usb_string(const char *name,
struct usb_gadget_strings *stringtab_dev) struct usb_gadget_strings *stringtab_dev)
...@@ -872,10 +873,63 @@ static void os_desc_attr_release(struct config_item *item) ...@@ -872,10 +873,63 @@ static void os_desc_attr_release(struct config_item *item)
kfree(os_desc); kfree(os_desc);
} }
static int os_desc_link(struct config_item *os_desc_ci,
struct config_item *usb_cfg_ci)
{
struct gadget_info *gi = container_of(to_config_group(os_desc_ci),
struct gadget_info, os_desc_group);
struct usb_composite_dev *cdev = &gi->cdev;
struct config_usb_cfg *c_target =
container_of(to_config_group(usb_cfg_ci),
struct config_usb_cfg, group);
struct usb_configuration *c;
int ret;
mutex_lock(&gi->lock);
list_for_each_entry(c, &cdev->configs, list) {
if (c == &c_target->c)
break;
}
if (c != &c_target->c) {
ret = -EINVAL;
goto out;
}
if (cdev->os_desc_config) {
ret = -EBUSY;
goto out;
}
cdev->os_desc_config = &c_target->c;
ret = 0;
out:
mutex_unlock(&gi->lock);
return ret;
}
static int os_desc_unlink(struct config_item *os_desc_ci,
struct config_item *usb_cfg_ci)
{
struct gadget_info *gi = container_of(to_config_group(os_desc_ci),
struct gadget_info, os_desc_group);
struct usb_composite_dev *cdev = &gi->cdev;
mutex_lock(&gi->lock);
if (gi->udc_name)
unregister_gadget(gi);
cdev->os_desc_config = NULL;
WARN_ON(gi->udc_name);
mutex_unlock(&gi->lock);
return 0;
}
static struct configfs_item_operations os_desc_ops = { static struct configfs_item_operations os_desc_ops = {
.release = os_desc_attr_release, .release = os_desc_attr_release,
.show_attribute = os_desc_attr_show, .show_attribute = os_desc_attr_show,
.store_attribute = os_desc_attr_store, .store_attribute = os_desc_attr_store,
.allow_link = os_desc_link,
.drop_link = os_desc_unlink,
}; };
static struct config_item_type os_desc_type = { static struct config_item_type os_desc_type = {
...@@ -884,6 +938,133 @@ static struct config_item_type os_desc_type = { ...@@ -884,6 +938,133 @@ static struct config_item_type os_desc_type = {
.ct_owner = THIS_MODULE, .ct_owner = THIS_MODULE,
}; };
CONFIGFS_ATTR_STRUCT(usb_os_desc);
CONFIGFS_ATTR_OPS(usb_os_desc);
static struct configfs_item_operations interf_item_ops = {
.show_attribute = usb_os_desc_attr_show,
.store_attribute = usb_os_desc_attr_store,
};
static ssize_t rndis_grp_compatible_id_show(struct usb_os_desc *desc,
char *page)
{
memcpy(page, desc->ext_compat_id, 8);
return 8;
}
static ssize_t rndis_grp_compatible_id_store(struct usb_os_desc *desc,
const char *page, size_t len)
{
int l;
l = min_t(int, 8, len);
if (page[l - 1] == '\n')
--l;
if (desc->opts_mutex)
mutex_lock(desc->opts_mutex);
memcpy(desc->ext_compat_id, page, l);
desc->ext_compat_id[l] = '\0';
if (desc->opts_mutex)
mutex_unlock(desc->opts_mutex);
return len;
}
static struct usb_os_desc_attribute rndis_grp_attr_compatible_id =
__CONFIGFS_ATTR(compatible_id, S_IRUGO | S_IWUSR,
rndis_grp_compatible_id_show,
rndis_grp_compatible_id_store);
static ssize_t rndis_grp_sub_compatible_id_show(struct usb_os_desc *desc,
char *page)
{
memcpy(page, desc->ext_compat_id + 8, 8);
return 8;
}
static ssize_t rndis_grp_sub_compatible_id_store(struct usb_os_desc *desc,
const char *page, size_t len)
{
int l;
l = min_t(int, 8, len);
if (page[l - 1] == '\n')
--l;
if (desc->opts_mutex)
mutex_lock(desc->opts_mutex);
memcpy(desc->ext_compat_id + 8, page, l);
desc->ext_compat_id[l + 8] = '\0';
if (desc->opts_mutex)
mutex_unlock(desc->opts_mutex);
return len;
}
static struct usb_os_desc_attribute rndis_grp_attr_sub_compatible_id =
__CONFIGFS_ATTR(sub_compatible_id, S_IRUGO | S_IWUSR,
rndis_grp_sub_compatible_id_show,
rndis_grp_sub_compatible_id_store);
static struct configfs_attribute *interf_grp_attrs[] = {
&rndis_grp_attr_compatible_id.attr,
&rndis_grp_attr_sub_compatible_id.attr,
NULL
};
int usb_os_desc_prepare_interf_dir(struct config_group *parent,
int n_interf,
struct usb_os_desc **desc,
struct module *owner)
{
struct config_group **f_default_groups, *os_desc_group,
**interface_groups;
struct config_item_type *os_desc_type, *interface_type;
vla_group(data_chunk);
vla_item(data_chunk, struct config_group *, f_default_groups, 2);
vla_item(data_chunk, struct config_group, os_desc_group, 1);
vla_item(data_chunk, struct config_group *, interface_groups,
n_interf + 1);
vla_item(data_chunk, struct config_item_type, os_desc_type, 1);
vla_item(data_chunk, struct config_item_type, interface_type, 1);
char *vlabuf = kzalloc(vla_group_size(data_chunk), GFP_KERNEL);
if (!vlabuf)
return -ENOMEM;
f_default_groups = vla_ptr(vlabuf, data_chunk, f_default_groups);
os_desc_group = vla_ptr(vlabuf, data_chunk, os_desc_group);
os_desc_type = vla_ptr(vlabuf, data_chunk, os_desc_type);
interface_groups = vla_ptr(vlabuf, data_chunk, interface_groups);
interface_type = vla_ptr(vlabuf, data_chunk, interface_type);
parent->default_groups = f_default_groups;
os_desc_type->ct_owner = owner;
config_group_init_type_name(os_desc_group, "os_desc", os_desc_type);
f_default_groups[0] = os_desc_group;
os_desc_group->default_groups = interface_groups;
interface_type->ct_item_ops = &interf_item_ops;
interface_type->ct_attrs = interf_grp_attrs;
interface_type->ct_owner = owner;
while (n_interf--) {
struct usb_os_desc *d;
d = desc[n_interf];
config_group_init_type_name(&d->group, "", interface_type);
config_item_set_name(&d->group.cg_item, "interface.%d",
n_interf);
interface_groups[n_interf] = &d->group;
}
return 0;
}
EXPORT_SYMBOL(usb_os_desc_prepare_interf_dir);
static int configfs_do_nothing(struct usb_composite_dev *cdev) static int configfs_do_nothing(struct usb_composite_dev *cdev)
{ {
WARN_ON(1); WARN_ON(1);
...@@ -893,6 +1074,9 @@ static int configfs_do_nothing(struct usb_composite_dev *cdev) ...@@ -893,6 +1074,9 @@ static int configfs_do_nothing(struct usb_composite_dev *cdev)
int composite_dev_prepare(struct usb_composite_driver *composite, int composite_dev_prepare(struct usb_composite_driver *composite,
struct usb_composite_dev *dev); struct usb_composite_dev *dev);
int composite_os_desc_req_prepare(struct usb_composite_dev *cdev,
struct usb_ep *ep0);
static void purge_configs_funcs(struct gadget_info *gi) static void purge_configs_funcs(struct gadget_info *gi)
{ {
struct usb_configuration *c; struct usb_configuration *c;
...@@ -1028,6 +1212,12 @@ static int configfs_composite_bind(struct usb_gadget *gadget, ...@@ -1028,6 +1212,12 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
} }
usb_ep_autoconfig_reset(cdev->gadget); usb_ep_autoconfig_reset(cdev->gadget);
} }
if (cdev->use_os_string) {
ret = composite_os_desc_req_prepare(cdev, gadget->ep0);
if (ret)
goto err_purge_funcs;
}
usb_ep_autoconfig_reset(cdev->gadget); usb_ep_autoconfig_reset(cdev->gadget);
return 0; return 0;
......
#ifndef USB__GADGET__CONFIGFS__H #ifndef USB__GADGET__CONFIGFS__H
#define USB__GADGET__CONFIGFS__H #define USB__GADGET__CONFIGFS__H
#include <linux/configfs.h>
void unregister_gadget_item(struct config_item *item); void unregister_gadget_item(struct config_item *item);
int usb_os_desc_prepare_interf_dir(struct config_group *parent,
int n_interf,
struct usb_os_desc **desc,
struct module *owner);
static inline struct usb_os_desc *to_usb_os_desc(struct config_item *item)
{
return container_of(to_config_group(item), struct usb_os_desc, group);
}
#endif /* USB__GADGET__CONFIGFS__H */ #endif /* USB__GADGET__CONFIGFS__H */
...@@ -80,12 +80,16 @@ struct usb_os_desc_ext_prop { ...@@ -80,12 +80,16 @@ struct usb_os_desc_ext_prop {
* @ext_prop: Extended Properties list * @ext_prop: Extended Properties list
* @ext_prop_len: Total length of Extended Properties blobs * @ext_prop_len: Total length of Extended Properties blobs
* @ext_prop_count: Number of Extended Properties * @ext_prop_count: Number of Extended Properties
* @opts_mutex: Optional mutex protecting config data of a usb_function_instance
* @group: Represents OS descriptors associated with an interface in configfs
*/ */
struct usb_os_desc { struct usb_os_desc {
char *ext_compat_id; char *ext_compat_id;
struct list_head ext_prop; struct list_head ext_prop;
int ext_prop_len; int ext_prop_len;
int ext_prop_count; int ext_prop_count;
struct mutex *opts_mutex;
struct config_group group;
}; };
/** /**
...@@ -381,6 +385,8 @@ extern void usb_composite_unregister(struct usb_composite_driver *driver); ...@@ -381,6 +385,8 @@ extern void usb_composite_unregister(struct usb_composite_driver *driver);
extern void usb_composite_setup_continue(struct usb_composite_dev *cdev); extern void usb_composite_setup_continue(struct usb_composite_dev *cdev);
extern int composite_dev_prepare(struct usb_composite_driver *composite, extern int composite_dev_prepare(struct usb_composite_driver *composite,
struct usb_composite_dev *cdev); struct usb_composite_dev *cdev);
extern int composite_os_desc_req_prepare(struct usb_composite_dev *cdev,
struct usb_ep *ep0);
void composite_dev_cleanup(struct usb_composite_dev *cdev); void composite_dev_cleanup(struct usb_composite_dev *cdev);
static inline struct usb_composite_driver *to_cdriver( static inline struct usb_composite_driver *to_cdriver(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment