[PATCH RESEND] FunctionFS: enable multiple functions

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

 



usb: gadget: FunctionFS: enable multiple USB function instances

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx>
Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>

---

RESEND: This is the same patch but with a corrected title.

Dear All,

This patch enables using multiple instances of USB functions in FunctionFS.

Example usage (*):

On the gadget device:

$ insmod g_ffs.ko idVendor=<ID> iSerialNumber=<string> functions=adb,ptp
mount -t  functionfs adb /dev/usbgadget/adb -o uid=2000,gid=2000
mount -t  functionfs ptp /dev/usbgadget/ptp -o uid=2000,gid=2000

./adbd &
./ptp `pwd`/images 2>ptp.log

If no "functions" module parameters is supplied, the driver defaults to old
behaviour, i.e. it accepts just one function with any name.

When "functions" module parameter is supplied, only functions with listed names
are accepted.

The gadget is registered only after all the function filesystems have been
mounted and USB descriptors of all functions have been written to their ep0's.

Conversly, the gadget is unregistered after the first USB function closes
its endpoints.

(*) I used a modified adbd daemon which uses FunctionFS as a USB access method.


 drivers/usb/gadget/f_fs.c      |   26 ++++-
 drivers/usb/gadget/g_ffs.c     |  208 +++++++++++++++++++++++++++++++++++----
 include/linux/usb/functionfs.h |    4 +-
 3 files changed, 209 insertions(+), 29 deletions(-)

diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c
index acb3800..2d23aa2 100644
--- a/drivers/usb/gadget/f_fs.c
+++ b/drivers/usb/gadget/f_fs.c
@@ -183,7 +183,7 @@ struct ffs_data {
 	 * Device name, write once when file system is mounted.
 	 * Intended for user to read if she wants.
 	 */
-	const char			*dev_name;
+	char				*dev_name;
 	/* Private data for our user (ie. gadget).  Managed by user. */
 	void				*private_data;
 
@@ -1048,7 +1048,9 @@ static int ffs_sb_fill(struct super_block *sb, void *_data, int silent)
 		goto enomem0;
 
 	ffs->sb              = sb;
-	ffs->dev_name        = data->dev_name;
+	ffs->dev_name        = kstrdup(data->dev_name, GFP_KERNEL);
+	if (unlikely(!ffs->dev_name))
+		goto enomem1;
 	ffs->file_perms      = data->perms;
 
 	sb->s_fs_info        = ffs;
@@ -1177,20 +1179,28 @@ ffs_fs_mount(struct file_system_type *t, int flags,
 		},
 		.root_mode = S_IFDIR | 0500,
 	};
+	struct dentry *rv;
 	int ret;
 
 	ENTER();
 
-	ret = functionfs_check_dev_callback(dev_name);
+	ret = functionfs_acquire_dev_callback(dev_name);
 	if (unlikely(ret < 0))
 		return ERR_PTR(ret);
 
 	ret = ffs_fs_parse_opts(&data, opts);
-	if (unlikely(ret < 0))
+	if (unlikely(ret < 0)) {
+		functionfs_release_dev_callback(dev_name);
 		return ERR_PTR(ret);
+	}
 
 	data.dev_name = dev_name;
-	return mount_single(t, flags, &data, ffs_sb_fill);
+	rv = mount_nodev(t, flags, &data, ffs_sb_fill);
+
+	if (IS_ERR(rv))
+		functionfs_release_dev_callback(dev_name);
+
+	return rv;
 }
 
 static void
@@ -1202,8 +1212,11 @@ ffs_fs_kill_sb(struct super_block *sb)
 
 	kill_litter_super(sb);
 	ptr = xchg(&sb->s_fs_info, NULL);
-	if (ptr)
+	if (ptr) {
+		functionfs_release_dev_callback(
+			((struct ffs_data *)ptr)->dev_name);
 		ffs_data_put(ptr);
+	}
 }
 
 static struct file_system_type ffs_fs_type = {
@@ -1271,6 +1284,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 0519d77..3a81430 100644
--- a/drivers/usb/gadget/g_ffs.c
+++ b/drivers/usb/gadget/g_ffs.c
@@ -14,6 +14,7 @@
 
 #include <linux/module.h>
 #include <linux/utsname.h>
+#include <linux/list.h>
 
 /*
  * kbuild is not very cooperative with respect to linking separately
@@ -67,6 +68,17 @@ 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 {
+	struct list_head item;
+
+	const char *name;
+	bool mounted;
+	bool desc_rdy;
+	struct ffs_data *ffs_data;
+};
+
 static struct usb_device_descriptor gfs_dev_desc = {
 	.bLength		= sizeof gfs_dev_desc,
 	.bDescriptorType	= USB_DT_DEVICE,
@@ -78,12 +90,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 +175,36 @@ 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 LIST_HEAD(ffs_list);
+static unsigned int missing_funcs;
+static bool gfs_ether_setup;
+static bool gfs_registered;
+static struct gfs_ffs_obj gfs_default_func;
+static struct gfs_ffs_obj *ffs_tab;
 
 static int  gfs_init(void)
 {
+	int i;
+
 	ENTER();
 
+	if (func_num > 0) {
+		i = 0;
+		ffs_tab = kzalloc(func_num * sizeof *ffs_tab, GFP_KERNEL);
+		if (!ffs_tab)
+			return -ENOMEM;
+
+		for (i = 0; i < func_num; i++) {
+			ffs_tab[i].name = func_names[i];
+			list_add_tail(&ffs_tab[i].item, &ffs_list);
+		}
+		missing_funcs = func_num;
+	} else {
+		list_add_tail(&gfs_default_func.item, &ffs_list);
+		missing_funcs++;
+	}
+
 	return functionfs_init();
 }
 module_init(gfs_init);
@@ -172,63 +212,170 @@ module_init(gfs_init);
 static void  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();
+
+	kfree(ffs_tab);
+	mutex_unlock(&gfs_lock);
 }
 module_exit(gfs_exit);
 
+static struct gfs_ffs_obj *gfs_find_dev(const char *dev_name)
+{
+	struct gfs_ffs_obj *i;
+
+	ENTER();
+
+	if (!func_num)
+		return &gfs_default_func;
+
+	list_for_each_entry(i, &ffs_list, item)
+		if (strcmp(i->name, dev_name) == 0)
+				return i;
+
+	return NULL;
+}
+
 static int functionfs_ready_callback(struct ffs_data *ffs)
 {
+	struct gfs_ffs_obj *i;
 	int ret;
 
 	ENTER();
 
-	if (WARN_ON(test_and_set_bit(0, &gfs_registered)))
+	mutex_lock(&gfs_lock);
+	i = gfs_find_dev(ffs->dev_name);
+	if (!i) {
+		mutex_unlock(&gfs_lock);
+		return -EINVAL;
+	}
+
+	if (WARN_ON(i->desc_rdy)) {
+		mutex_unlock(&gfs_lock);
 		return -EBUSY;
+	}
+	i->desc_rdy = true;
+	i->ffs_data = ffs;
+
+	if (--missing_funcs) {
+		mutex_unlock(&gfs_lock);
+		return 0;
+	}
+
+	if (gfs_registered) {
+		mutex_unlock(&gfs_lock);
+		return -EBUSY;
+	}
+	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;
+	mutex_unlock(&gfs_lock);
+
 	return ret;
 }
 
 static void functionfs_closed_callback(struct ffs_data *ffs)
 {
+	struct gfs_ffs_obj *i;
+
 	ENTER();
 
-	if (test_and_clear_bit(0, &gfs_registered))
+	mutex_lock(&gfs_lock);
+	i = gfs_find_dev(ffs->dev_name);
+	if (!i) {
+		mutex_unlock(&gfs_lock);
+		return;
+	}
+	i->desc_rdy = false;
+	missing_funcs++;
+
+	if (gfs_registered)
 		usb_composite_unregister(&gfs_driver);
+	gfs_registered = false;
+	mutex_unlock(&gfs_lock);
 }
 
-static int functionfs_check_dev_callback(const char *dev_name)
+static int functionfs_acquire_dev_callback(const char *dev_name)
 {
+	struct gfs_ffs_obj *i;
+
+	ENTER();
+
+	mutex_lock(&gfs_lock);
+	i = gfs_find_dev(dev_name);
+	if (!i) {
+		mutex_unlock(&gfs_lock);
+		return -EINVAL;
+	}
+
+	if (i->mounted) {
+		mutex_unlock(&gfs_lock);
+		return -EBUSY;
+	}
+	i->mounted = true;
+	mutex_unlock(&gfs_lock);
+
 	return 0;
 }
 
+static void functionfs_release_dev_callback(const char *dev_name)
+{
+	struct gfs_ffs_obj *i;
+
+	ENTER();
+
+	mutex_lock(&gfs_lock);
+	i = gfs_find_dev(dev_name);
+	if (!i) {
+		mutex_unlock(&gfs_lock);
+		return;
+	}
+
+	i->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)
 {
+	struct gfs_ffs_obj *fun;
 	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;
+	list_for_each_entry(fun, &ffs_list, item) {
+		ret = functionfs_bind(fun->ffs_data, cdev);
+		if (unlikely(ret < 0)) {
+			struct gfs_ffs_obj *f;
+			list_for_each_entry(f, &ffs_list, item) {
+				if (f == fun)
+					break;
+				functionfs_unbind(f->ffs_data);
+			}
+			goto error;
+		}
+	}
 
 	for (i = 0; i < ARRAY_SIZE(gfs_configurations); ++i) {
 		struct gfs_configuration *c = gfs_configurations + i;
@@ -246,16 +393,22 @@ static int gfs_bind(struct usb_composite_dev *cdev)
 	return 0;
 
 error_unbind:
-	functionfs_unbind(gfs_ffs_data);
+	list_for_each_entry(fun, &ffs_list, item)
+		functionfs_unbind(fun->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)
 {
+	struct gfs_ffs_obj *i;
+
 	ENTER();
 
 	/*
@@ -266,22 +419,31 @@ 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;
+
+	list_for_each_entry(i, &ffs_list, item)
+		if (i->ffs_data) {
+			functionfs_unbind(i->ffs_data);
+			i->ffs_data = NULL;
+		}
 
 	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);
+	struct gfs_ffs_obj *i;
 	int ret;
 
-	if (WARN_ON(!gfs_ffs_data))
+	if (missing_funcs)
 		return -ENODEV;
 
 	if (gadget_is_otg(c->cdev->gadget)) {
@@ -295,9 +457,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;
+	list_for_each_entry(i, &ffs_list, item) {
+		ret = functionfs_bind_config(c->cdev, c, 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..c1353ae 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 int functionfs_acquire_dev_callback(const char *dev_name)
 	__attribute__((warn_unused_result, nonnull));
+static void functionfs_release_dev_callback(const char *dev_name)
+	__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