[PATCHv5 3/3] FunctionFS: enable multiple functions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



FunctionFS: enable multiple functions

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
Acked-by: Michal Nazarewicz <mina86@xxxxxxxxxx>
---
 Documentation/usb/functionfs.txt |   67 +++++++++++++
 drivers/usb/gadget/f_fs.c        |   38 ++++++--
 drivers/usb/gadget/g_ffs.c       |  200 +++++++++++++++++++++++++++++++++-----
 include/linux/usb/functionfs.h   |    4 +-
 4 files changed, 277 insertions(+), 32 deletions(-)
 create mode 100644 Documentation/usb/functionfs.txt

diff --git a/Documentation/usb/functionfs.txt b/Documentation/usb/functionfs.txt
new file mode 100644
index 0000000..eaaaea0
--- /dev/null
+++ b/Documentation/usb/functionfs.txt
@@ -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.
+
diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c
index a39b19e..f0e18cb 100644
--- a/drivers/usb/gadget/f_fs.c
+++ b/drivers/usb/gadget/f_fs.c
@@ -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;
@@ -1171,20 +1183,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
@@ -1193,8 +1214,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 = {
@@ -1262,6 +1285,7 @@ static void ffs_data_put(struct ffs_data *ffs)
 		       spin_is_locked(&ffs->ev.waitq.lock) ||
 		       waitqueue_active(&ffs->ev.waitq) ||
 		       waitqueue_active(&ffs->ep0req_completion.wait));
+		kfree(ffs->dev_name);
 		kfree(ffs);
 	}
 }
diff --git a/drivers/usb/gadget/g_ffs.c b/drivers/usb/gadget/g_ffs.c
index 735c087..d5f47b1 100644
--- a/drivers/usb/gadget/g_ffs.c
+++ b/drivers/usb/gadget/g_ffs.c
@@ -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 struct gfs_ffs_obj *gfs_find_dev(const char *dev_name)
+{
+	int i;
+
+	ENTER();
+
+	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 (WARN_ON(test_and_set_bit(0, &gfs_registered)))
-		return -EBUSY;
+	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
diff --git a/include/linux/usb/functionfs.h b/include/linux/usb/functionfs.h
index 7587ef9..a843d08 100644
--- a/include/linux/usb/functionfs.h
+++ b/include/linux/usb/functionfs.h
@@ -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
-- 
1.7.0.4

--
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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux