FunctionFS: enable multiple functions
Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Acked-by: Michal Nazarewicz <mina86@mina86.com> Cc: Felipe Balbi <balbi@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Родитель
304f0b2453
Коммит
581791f5c7
|
@ -0,0 +1,67 @@
|
|||
*How FunctionFS works*
|
||||
|
||||
From kernel point of view it is just a composite function with some
|
||||
unique behaviour. It may be added to an USB configuration only after
|
||||
the user space driver has registered by writing descriptors and
|
||||
strings (the user space program has to provide the same information
|
||||
that kernel level composite functions provide when they are added to
|
||||
the configuration).
|
||||
|
||||
This in particular means that the composite initialisation functions
|
||||
may not be in init section (ie. may not use the __init tag).
|
||||
|
||||
From user space point of view it is a file system which when
|
||||
mounted provides an "ep0" file. User space driver need to
|
||||
write descriptors and strings to that file. It does not need
|
||||
to worry about endpoints, interfaces or strings numbers but
|
||||
simply provide descriptors such as if the function was the
|
||||
only one (endpoints and strings numbers starting from one and
|
||||
interface numbers starting from zero). The FunctionFS changes
|
||||
them as needed also handling situation when numbers differ in
|
||||
different configurations.
|
||||
|
||||
When descriptors and strings are written "ep#" files appear
|
||||
(one for each declared endpoint) which handle communication on
|
||||
a single endpoint. Again, FunctionFS takes care of the real
|
||||
numbers and changing of the configuration (which means that
|
||||
"ep1" file may be really mapped to (say) endpoint 3 (and when
|
||||
configuration changes to (say) endpoint 2)). "ep0" is used
|
||||
for receiving events and handling setup requests.
|
||||
|
||||
When all files are closed the function disables itself.
|
||||
|
||||
What I also want to mention is that the FunctionFS is designed in such
|
||||
a way that it is possible to mount it several times so in the end
|
||||
a gadget could use several FunctionFS functions. The idea is that
|
||||
each FunctionFS instance is identified by the device name used
|
||||
when mounting.
|
||||
|
||||
One can imagine a gadget that has an Ethernet, MTP and HID interfaces
|
||||
where the last two are implemented via FunctionFS. On user space
|
||||
level it would look like this:
|
||||
|
||||
$ insmod g_ffs.ko idVendor=<ID> iSerialNumber=<string> functions=mtp,hid
|
||||
$ mkdir /dev/ffs-mtp && mount -t functionfs mtp /dev/ffs-mtp
|
||||
$ ( cd /dev/ffs-mtp && mtp-daemon ) &
|
||||
$ mkdir /dev/ffs-hid && mount -t functionfs hid /dev/ffs-hid
|
||||
$ ( cd /dev/ffs-hid && hid-daemon ) &
|
||||
|
||||
On kernel level the gadget checks ffs_data->dev_name to identify
|
||||
whether it's FunctionFS designed for MTP ("mtp") or HID ("hid").
|
||||
|
||||
If no "functions" module parameters is supplied, the driver accepts
|
||||
just one function with any name.
|
||||
|
||||
When "functions" module parameter is supplied, only functions
|
||||
with listed names are accepted. In particular, if the "functions"
|
||||
parameter's value is just a one-element list, then the behaviour
|
||||
is similar to when there is no "functions" at all; however,
|
||||
only a function with the specified name is accepted.
|
||||
|
||||
The gadget is registered only after all the declared function
|
||||
filesystems have been mounted and USB descriptors of all functions
|
||||
have been written to their ep0's.
|
||||
|
||||
Conversely, the gadget is unregistered after the first USB function
|
||||
closes its endpoints.
|
||||
|
|
@ -1031,6 +1031,12 @@ struct ffs_sb_fill_data {
|
|||
struct ffs_file_perms perms;
|
||||
umode_t root_mode;
|
||||
const char *dev_name;
|
||||
union {
|
||||
/* set by ffs_fs_mount(), read by ffs_sb_fill() */
|
||||
void *private_data;
|
||||
/* set by ffs_sb_fill(), read by ffs_fs_mount */
|
||||
struct ffs_data *ffs_data;
|
||||
};
|
||||
};
|
||||
|
||||
static int ffs_sb_fill(struct super_block *sb, void *_data, int silent)
|
||||
|
@ -1047,8 +1053,14 @@ static int ffs_sb_fill(struct super_block *sb, void *_data, int silent)
|
|||
goto Enomem;
|
||||
|
||||
ffs->sb = sb;
|
||||
ffs->dev_name = data->dev_name;
|
||||
ffs->dev_name = kstrdup(data->dev_name, GFP_KERNEL);
|
||||
if (unlikely(!ffs->dev_name))
|
||||
goto Enomem;
|
||||
ffs->file_perms = data->perms;
|
||||
ffs->private_data = data->private_data;
|
||||
|
||||
/* used by the caller of this function */
|
||||
data->ffs_data = ffs;
|
||||
|
||||
sb->s_fs_info = ffs;
|
||||
sb->s_blocksize = PAGE_CACHE_SIZE;
|
||||
|
@ -1167,20 +1179,29 @@ ffs_fs_mount(struct file_system_type *t, int flags,
|
|||
},
|
||||
.root_mode = S_IFDIR | 0500,
|
||||
};
|
||||
struct dentry *rv;
|
||||
int ret;
|
||||
void *ffs_dev;
|
||||
|
||||
ENTER();
|
||||
|
||||
ret = functionfs_check_dev_callback(dev_name);
|
||||
if (unlikely(ret < 0))
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ret = ffs_fs_parse_opts(&data, opts);
|
||||
if (unlikely(ret < 0))
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ffs_dev = functionfs_acquire_dev_callback(dev_name);
|
||||
if (IS_ERR(ffs_dev))
|
||||
return ffs_dev;
|
||||
|
||||
data.dev_name = dev_name;
|
||||
return mount_single(t, flags, &data, ffs_sb_fill);
|
||||
data.private_data = ffs_dev;
|
||||
rv = mount_nodev(t, flags, &data, ffs_sb_fill);
|
||||
|
||||
/* data.ffs_data is set by ffs_sb_fill */
|
||||
if (IS_ERR(rv))
|
||||
functionfs_release_dev_callback(data.ffs_data);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1189,8 +1210,10 @@ ffs_fs_kill_sb(struct super_block *sb)
|
|||
ENTER();
|
||||
|
||||
kill_litter_super(sb);
|
||||
if (sb->s_fs_info)
|
||||
if (sb->s_fs_info) {
|
||||
functionfs_release_dev_callback(sb->s_fs_info);
|
||||
ffs_data_put(sb->s_fs_info);
|
||||
}
|
||||
}
|
||||
|
||||
static struct file_system_type ffs_fs_type = {
|
||||
|
@ -1256,6 +1279,7 @@ static void ffs_data_put(struct ffs_data *ffs)
|
|||
ffs_data_clear(ffs);
|
||||
BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
|
||||
waitqueue_active(&ffs->ep0req_completion.wait));
|
||||
kfree(ffs->dev_name);
|
||||
kfree(ffs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,15 @@ MODULE_LICENSE("GPL");
|
|||
#define GFS_VENDOR_ID 0x1d6b /* Linux Foundation */
|
||||
#define GFS_PRODUCT_ID 0x0105 /* FunctionFS Gadget */
|
||||
|
||||
#define GFS_MAX_DEVS 10
|
||||
|
||||
struct gfs_ffs_obj {
|
||||
const char *name;
|
||||
bool mounted;
|
||||
bool desc_ready;
|
||||
struct ffs_data *ffs_data;
|
||||
};
|
||||
|
||||
static struct usb_device_descriptor gfs_dev_desc = {
|
||||
.bLength = sizeof gfs_dev_desc,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
|
@ -78,12 +87,17 @@ static struct usb_device_descriptor gfs_dev_desc = {
|
|||
.idProduct = cpu_to_le16(GFS_PRODUCT_ID),
|
||||
};
|
||||
|
||||
static char *func_names[GFS_MAX_DEVS];
|
||||
static unsigned int func_num;
|
||||
|
||||
module_param_named(bDeviceClass, gfs_dev_desc.bDeviceClass, byte, 0644);
|
||||
MODULE_PARM_DESC(bDeviceClass, "USB Device class");
|
||||
module_param_named(bDeviceSubClass, gfs_dev_desc.bDeviceSubClass, byte, 0644);
|
||||
MODULE_PARM_DESC(bDeviceSubClass, "USB Device subclass");
|
||||
module_param_named(bDeviceProtocol, gfs_dev_desc.bDeviceProtocol, byte, 0644);
|
||||
MODULE_PARM_DESC(bDeviceProtocol, "USB Device protocol");
|
||||
module_param_array_named(functions, func_names, charp, &func_num, 0);
|
||||
MODULE_PARM_DESC(functions, "USB Functions list");
|
||||
|
||||
static const struct usb_descriptor_header *gfs_otg_desc[] = {
|
||||
(const struct usb_descriptor_header *)
|
||||
|
@ -158,13 +172,34 @@ static struct usb_composite_driver gfs_driver = {
|
|||
.iProduct = DRIVER_DESC,
|
||||
};
|
||||
|
||||
static struct ffs_data *gfs_ffs_data;
|
||||
static unsigned long gfs_registered;
|
||||
static DEFINE_MUTEX(gfs_lock);
|
||||
static unsigned int missing_funcs;
|
||||
static bool gfs_ether_setup;
|
||||
static bool gfs_registered;
|
||||
static bool gfs_single_func;
|
||||
static struct gfs_ffs_obj *ffs_tab;
|
||||
|
||||
static int __init gfs_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
ENTER();
|
||||
|
||||
if (!func_num) {
|
||||
gfs_single_func = true;
|
||||
func_num = 1;
|
||||
}
|
||||
|
||||
ffs_tab = kcalloc(func_num, sizeof *ffs_tab, GFP_KERNEL);
|
||||
if (!ffs_tab)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!gfs_single_func)
|
||||
for (i = 0; i < func_num; i++)
|
||||
ffs_tab[i].name = func_names[i];
|
||||
|
||||
missing_funcs = func_num;
|
||||
|
||||
return functionfs_init();
|
||||
}
|
||||
module_init(gfs_init);
|
||||
|
@ -172,63 +207,165 @@ module_init(gfs_init);
|
|||
static void __exit gfs_exit(void)
|
||||
{
|
||||
ENTER();
|
||||
mutex_lock(&gfs_lock);
|
||||
|
||||
if (test_and_clear_bit(0, &gfs_registered))
|
||||
if (gfs_registered)
|
||||
usb_composite_unregister(&gfs_driver);
|
||||
gfs_registered = false;
|
||||
|
||||
functionfs_cleanup();
|
||||
|
||||
mutex_unlock(&gfs_lock);
|
||||
kfree(ffs_tab);
|
||||
}
|
||||
module_exit(gfs_exit);
|
||||
|
||||
static int functionfs_ready_callback(struct ffs_data *ffs)
|
||||
static struct gfs_ffs_obj *gfs_find_dev(const char *dev_name)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
ENTER();
|
||||
|
||||
if (WARN_ON(test_and_set_bit(0, &gfs_registered)))
|
||||
return -EBUSY;
|
||||
if (gfs_single_func)
|
||||
return &ffs_tab[0];
|
||||
|
||||
for (i = 0; i < func_num; i++)
|
||||
if (strcmp(ffs_tab[i].name, dev_name) == 0)
|
||||
return &ffs_tab[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int functionfs_ready_callback(struct ffs_data *ffs)
|
||||
{
|
||||
struct gfs_ffs_obj *ffs_obj;
|
||||
int ret;
|
||||
|
||||
ENTER();
|
||||
mutex_lock(&gfs_lock);
|
||||
|
||||
ffs_obj = ffs->private_data;
|
||||
if (!ffs_obj) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (WARN_ON(ffs_obj->desc_ready)) {
|
||||
ret = -EBUSY;
|
||||
goto done;
|
||||
}
|
||||
ffs_obj->desc_ready = true;
|
||||
ffs_obj->ffs_data = ffs;
|
||||
|
||||
if (--missing_funcs) {
|
||||
ret = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (gfs_registered) {
|
||||
ret = -EBUSY;
|
||||
goto done;
|
||||
}
|
||||
gfs_registered = true;
|
||||
|
||||
gfs_ffs_data = ffs;
|
||||
ret = usb_composite_probe(&gfs_driver, gfs_bind);
|
||||
if (unlikely(ret < 0))
|
||||
clear_bit(0, &gfs_registered);
|
||||
gfs_registered = false;
|
||||
|
||||
done:
|
||||
mutex_unlock(&gfs_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void functionfs_closed_callback(struct ffs_data *ffs)
|
||||
{
|
||||
struct gfs_ffs_obj *ffs_obj;
|
||||
|
||||
ENTER();
|
||||
mutex_lock(&gfs_lock);
|
||||
|
||||
if (test_and_clear_bit(0, &gfs_registered))
|
||||
ffs_obj = ffs->private_data;
|
||||
if (!ffs_obj)
|
||||
goto done;
|
||||
|
||||
ffs_obj->desc_ready = false;
|
||||
missing_funcs++;
|
||||
|
||||
if (gfs_registered)
|
||||
usb_composite_unregister(&gfs_driver);
|
||||
gfs_registered = false;
|
||||
|
||||
done:
|
||||
mutex_unlock(&gfs_lock);
|
||||
}
|
||||
|
||||
static int functionfs_check_dev_callback(const char *dev_name)
|
||||
static void *functionfs_acquire_dev_callback(const char *dev_name)
|
||||
{
|
||||
return 0;
|
||||
struct gfs_ffs_obj *ffs_dev;
|
||||
|
||||
ENTER();
|
||||
mutex_lock(&gfs_lock);
|
||||
|
||||
ffs_dev = gfs_find_dev(dev_name);
|
||||
if (!ffs_dev) {
|
||||
ffs_dev = ERR_PTR(-ENODEV);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ffs_dev->mounted) {
|
||||
ffs_dev = ERR_PTR(-EBUSY);
|
||||
goto done;
|
||||
}
|
||||
ffs_dev->mounted = true;
|
||||
|
||||
done:
|
||||
mutex_unlock(&gfs_lock);
|
||||
return ffs_dev;
|
||||
}
|
||||
|
||||
static void functionfs_release_dev_callback(struct ffs_data *ffs_data)
|
||||
{
|
||||
struct gfs_ffs_obj *ffs_dev;
|
||||
|
||||
ENTER();
|
||||
mutex_lock(&gfs_lock);
|
||||
|
||||
ffs_dev = ffs_data->private_data;
|
||||
if (ffs_dev)
|
||||
ffs_dev->mounted = false;
|
||||
|
||||
mutex_unlock(&gfs_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* It is assumed that gfs_bind is called from a context where gfs_lock is held
|
||||
*/
|
||||
static int gfs_bind(struct usb_composite_dev *cdev)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
ENTER();
|
||||
|
||||
if (WARN_ON(!gfs_ffs_data))
|
||||
if (missing_funcs)
|
||||
return -ENODEV;
|
||||
|
||||
ret = gether_setup(cdev->gadget, gfs_hostaddr);
|
||||
if (unlikely(ret < 0))
|
||||
goto error_quick;
|
||||
gfs_ether_setup = true;
|
||||
|
||||
ret = usb_string_ids_tab(cdev, gfs_strings);
|
||||
if (unlikely(ret < 0))
|
||||
goto error;
|
||||
|
||||
ret = functionfs_bind(gfs_ffs_data, cdev);
|
||||
if (unlikely(ret < 0))
|
||||
goto error;
|
||||
for (i = func_num; --i; ) {
|
||||
ret = functionfs_bind(ffs_tab[i].ffs_data, cdev);
|
||||
if (unlikely(ret < 0)) {
|
||||
while (++i < func_num)
|
||||
functionfs_unbind(ffs_tab[i].ffs_data);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(gfs_configurations); ++i) {
|
||||
struct gfs_configuration *c = gfs_configurations + i;
|
||||
|
@ -246,16 +383,22 @@ static int gfs_bind(struct usb_composite_dev *cdev)
|
|||
return 0;
|
||||
|
||||
error_unbind:
|
||||
functionfs_unbind(gfs_ffs_data);
|
||||
for (i = 0; i < func_num; i++)
|
||||
functionfs_unbind(ffs_tab[i].ffs_data);
|
||||
error:
|
||||
gether_cleanup();
|
||||
error_quick:
|
||||
gfs_ffs_data = NULL;
|
||||
gfs_ether_setup = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is assumed that gfs_unbind is called from a context where gfs_lock is held
|
||||
*/
|
||||
static int gfs_unbind(struct usb_composite_dev *cdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
ENTER();
|
||||
|
||||
/*
|
||||
|
@ -266,22 +409,29 @@ static int gfs_unbind(struct usb_composite_dev *cdev)
|
|||
* from composite on orror recovery, but what you're gonna
|
||||
* do...?
|
||||
*/
|
||||
if (gfs_ffs_data) {
|
||||
if (gfs_ether_setup)
|
||||
gether_cleanup();
|
||||
functionfs_unbind(gfs_ffs_data);
|
||||
gfs_ffs_data = NULL;
|
||||
}
|
||||
gfs_ether_setup = false;
|
||||
|
||||
for (i = func_num; --i; )
|
||||
if (ffs_tab[i].ffs_data)
|
||||
functionfs_unbind(ffs_tab[i].ffs_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is assumed that gfs_do_config is called from a context where
|
||||
* gfs_lock is held
|
||||
*/
|
||||
static int gfs_do_config(struct usb_configuration *c)
|
||||
{
|
||||
struct gfs_configuration *gc =
|
||||
container_of(c, struct gfs_configuration, c);
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(!gfs_ffs_data))
|
||||
if (missing_funcs)
|
||||
return -ENODEV;
|
||||
|
||||
if (gadget_is_otg(c->cdev->gadget)) {
|
||||
|
@ -295,9 +445,11 @@ static int gfs_do_config(struct usb_configuration *c)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = functionfs_bind_config(c->cdev, c, gfs_ffs_data);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
for (i = 0; i < func_num; i++) {
|
||||
ret = functionfs_bind_config(c->cdev, c, ffs_tab[i].ffs_data);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* After previous do_configs there may be some invalid
|
||||
|
|
|
@ -190,8 +190,10 @@ static int functionfs_ready_callback(struct ffs_data *ffs)
|
|||
__attribute__((warn_unused_result, nonnull));
|
||||
static void functionfs_closed_callback(struct ffs_data *ffs)
|
||||
__attribute__((nonnull));
|
||||
static int functionfs_check_dev_callback(const char *dev_name)
|
||||
static void *functionfs_acquire_dev_callback(const char *dev_name)
|
||||
__attribute__((warn_unused_result, nonnull));
|
||||
static void functionfs_release_dev_callback(struct ffs_data *ffs_data)
|
||||
__attribute__((nonnull));
|
||||
|
||||
|
||||
#endif
|
||||
|
|
Загрузка…
Ссылка в новой задаче