[PATCH 2] introduce-mass-storage-arbitration.patch

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

 



From: Andy Green <andy.green@xxxxxxxx>

Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
Cc: Linux USB list <linux-usb@xxxxxxxxxxxxxxx>

This patch adds support for arbitrating backing storage access between
the gadget mass storage driver and userspace.

The functionality it provides allows userspace to keep a shared partition
mounted all the time except when the partition is being accessed by a host.
When the host "ejects" the logical storage device, userspace is informed and
able to automatically mount it again.  The user can replug the storage to
repeat the arbitration and make it visible on the host again.  So
availability of the filesystem being shared is maximized for the device
automatically.

When the host starts trying to access the backing storage, the patch issues
a netlink packet of the kernel uevent variety many systems already watch for
battery power_supply management.  The userspace netlink handler code should
respond by unmounting the shared storage if possible, and writing to the
/sys path provided in DEVPATH in the netlink message to indicate that the
arbitration succeeded or failed.  This is the same kind of handshake seen
in the kernel firmware API.

Additionally, when the host performs an "eject", the patch sees it and
notifies userspace again by netlink that it's safe to remount the shared
partition.

The new arbitration code is disabled by default.  The module parameter
g_file_storage.arbitration must be set to 1 to enable the new behaviour.

The code is tested on Linux, PC and Mac hosts.


It was a bit of a struggle to get the uevent message sent.  If there's a
better way than replacing the kset_uevent_ops for the device it'd be
interesting to know.

Thanks to Oliver Neukum for spotting a completion race in a previous version.

Signed-off-by: Andy Green <andy@xxxxxxxxxxx>
---

 drivers/usb/gadget/file_storage.c |  228 ++++++++++++++++++++++++++++++++++++-
 1 files changed, 219 insertions(+), 9 deletions(-)

diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c
index 020d42e..5658d1a 100644
--- a/drivers/usb/gadget/file_storage.c
+++ b/drivers/usb/gadget/file_storage.c
@@ -247,6 +247,7 @@
 #include <linux/string.h>
 #include <linux/freezer.h>
 #include <linux/utsname.h>
+#include <linux/kobject.h>
 
 #include <asm/unaligned.h>
 
@@ -355,6 +356,7 @@ static struct {
 	int		removable;
 	int		can_stall;
 	int		cdrom;
+	int		arbitration;
 
 	char		*transport_parm;
 	char		*protocol_parm;
@@ -378,6 +380,7 @@ static struct {
 	.product		= DRIVER_PRODUCT_ID,
 	.release		= 0xffff,	// Use controller chip type
 	.buflen			= 16384,
+	.arbitration		= 0,
 	};
 
 
@@ -400,6 +403,9 @@ MODULE_PARM_DESC(stall, "false to prevent bulk stalls");
 module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO);
 MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk");
 
+module_param_named(arbitration, mod_data.arbitration, int, S_IRUGO);
+MODULE_PARM_DESC(arbitration, "Set to 1 to stall initial access of backing "
+			      "file until userspace netlink OKs it down sysfs");
 
 /* In the non-TEST version, only the module parameters listed above
  * are available. */
@@ -573,6 +579,12 @@ struct lun {
 	loff_t		file_length;
 	loff_t		num_sectors;
 
+	int		lun_index;
+	struct completion arbitration_completion;
+
+	unsigned int	arbitration_result :1;
+	unsigned int	arbitration_done : 1;
+	unsigned int 	ejected : 1;
 	unsigned int	ro : 1;
 	unsigned int	prevent_medium_removal : 1;
 	unsigned int	registered : 1;
@@ -742,7 +754,9 @@ static struct fsg_dev			*the_fsg;
 static struct usb_gadget_driver		fsg_driver;
 
 static void	close_backing_file(struct lun *curlun);
+static int open_backing_file(struct lun *curlun, const char *filename);
 
+static struct mutex arbitration_wait_mutex;
 
 /*-------------------------------------------------------------------------*/
 
@@ -1548,6 +1562,70 @@ static int sleep_thread(struct fsg_dev *fsg)
 	return rc;
 }
 
+/*
+ * All read or write access to the backing file calls this first.
+ *
+ * If arbitration is enbaled, then we ping the userland arbitrator
+ * by Netlink, and when he has umounted or done what he needs to
+ * do he pongs us back down the sysfs path given in netlink env and
+ * we allow the accesses to occur for the remainder of the session.
+ *
+ * We timeout waiting after 5s (the read or write are disallowed)
+ * and there's a mutex to synchronize module removal with any wait here.
+ */
+
+int confirm_backing_arbitration(struct lun *curlun)
+{
+	int ret;
+	char *envp[2];
+	char buf[256];
+
+	if (!mod_data.arbitration)
+		return 1;
+
+	if (curlun->arbitration_done)
+		goto bail;
+
+	/*
+	 * invite netlink handler in userspace to unmount the backing file
+	 * and inform us that we can start using it
+	 */
+
+	mutex_lock(&arbitration_wait_mutex);
+
+	INIT_COMPLETION(curlun->arbitration_completion);
+
+	sprintf(buf, "REQUEST_ARBITRATION=%s",
+					      mod_data.file[curlun->lun_index]);
+	envp[0] = buf;
+	envp[1] = NULL;
+	kobject_uevent_env(&curlun->dev.kobj, KOBJ_CHANGE, envp);
+	sysfs_notify(&curlun->dev.kobj, NULL, "arbitration");
+
+	ret = wait_for_completion_interruptible_timeout(
+				       &curlun->arbitration_completion, HZ * 5);
+
+	mutex_unlock(&arbitration_wait_mutex);
+
+	if (!curlun->arbitration_done) {
+		dev_err(&curlun->dev, "arbitration failed with timeout\n");
+		return -ETIME;
+	}
+
+	/*
+	 * it's helpful for debugging at least to confirm we accepted it
+	 */
+
+	sprintf(buf, "ARBITRATION_OK=%s", mod_data.file[curlun->lun_index]);
+	envp[0] = buf;
+	envp[1] = NULL;
+	kobject_uevent_env(&curlun->dev.kobj, KOBJ_CHANGE, envp);
+	sysfs_notify(&curlun->dev.kobj, NULL, "arbitration");
+
+bail:
+	return curlun->arbitration_result;
+}
+
 
 /*-------------------------------------------------------------------------*/
 
@@ -1563,6 +1641,9 @@ static int do_read(struct fsg_dev *fsg)
 	unsigned int		partial_page;
 	ssize_t			nread;
 
+	if (confirm_backing_arbitration(curlun) != 1)
+		return -EBUSY;
+
 	/* Get the starting Logical Block Address and check that it's
 	 * not too big */
 	if (fsg->cmnd[0] == SC_READ_6)
@@ -1690,6 +1771,9 @@ static int do_write(struct fsg_dev *fsg)
 	ssize_t			nwritten;
 	int			rc;
 
+	if (confirm_backing_arbitration(curlun) != 1)
+		return -EBUSY;
+
 	if (curlun->ro) {
 		curlun->sense_data = SS_WRITE_PROTECTED;
 		return -EINVAL;
@@ -2279,6 +2363,20 @@ static int do_start_stop(struct fsg_dev *fsg)
 	loej = fsg->cmnd[4] & 0x02;
 	start = fsg->cmnd[4] & 0x01;
 
+	/* have we been ejected? */
+
+	if (loej && !start) {
+
+		/* Eject current medium */
+
+		curlun->ejected = 1;
+
+		if (backing_file_is_open(curlun)) {
+			close_backing_file(curlun);
+			curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
+		}
+	}
+
 #ifdef CONFIG_USB_FILE_STORAGE_TEST
 	if ((fsg->cmnd[1] & ~0x01) != 0 ||		// Mask away Immed
 			(fsg->cmnd[4] & ~0x03) != 0) {	// Mask LoEj, Start
@@ -3510,6 +3608,18 @@ static void handle_exception(struct fsg_dev *fsg)
 	case FSG_STATE_DISCONNECT:
 		fsync_all(fsg);
 		do_set_config(fsg, 0);		// Unconfigured state
+
+		dev_err(&fsg->gadget->dev, "clearing down lun arb state\n");
+
+		for (i = 0; i < fsg->nluns; ++i) {
+			fsg->luns[i].ejected = 0;
+			fsg->luns[i].arbitration_done = 0;
+
+			/* reopen file in case of prior eject */
+			if (!fsg->luns[i].filp)
+				open_backing_file(
+					       &fsg->luns[i], mod_data.file[i]);
+		}
 		break;
 
 	case FSG_STATE_EXIT:
@@ -3609,6 +3719,7 @@ static int open_backing_file(struct lun *curlun, const char *filename)
 	loff_t				size;
 	loff_t				num_sectors;
 	loff_t				min_sectors;
+	char *envp[3];
 
 	/* R/W if we can, R/O if we must */
 	ro = curlun->ro;
@@ -3678,6 +3789,16 @@ static int open_backing_file(struct lun *curlun, const char *filename)
 	LDBG(curlun, "open backing file: %s\n", filename);
 	rc = 0;
 
+	if (mod_data.arbitration) {
+
+		/* notify that we are online */
+
+		envp[0] = "EJECTED=0";
+		envp[1] = NULL;
+		kobject_uevent_env(&curlun->dev.kobj, KOBJ_CHANGE, envp);
+		sysfs_notify(&curlun->dev.kobj, NULL, "removed");
+	}
+
 out:
 	filp_close(filp, current->files);
 	return rc;
@@ -3686,15 +3807,37 @@ out:
 
 static void close_backing_file(struct lun *curlun)
 {
-	if (curlun->filp) {
-		LDBG(curlun, "close backing file\n");
-		fput(curlun->filp);
-		curlun->filp = NULL;
+	char *envp[2];
+
+	if (!curlun->filp)
+		return;
+
+	LDBG(curlun, "close backing file\n");
+	fput(curlun->filp);
+	curlun->filp = NULL;
+	curlun->arbitration_done = 0;
+
+	if (mod_data.arbitration) {
+
+		/* notify that we went offline */
+
+		envp[0] = "EJECTED=1";
+		envp[1] = NULL;
+		kobject_uevent_env(&curlun->dev.kobj, KOBJ_CHANGE, envp);
+		sysfs_notify(&curlun->dev.kobj, NULL, "removed");
 	}
 }
 
+static ssize_t show_ejected(struct device *dev, struct device_attribute *attr,
+								      char *buf)
+{
+	struct lun	*curlun = dev_to_lun(dev);
 
-static ssize_t show_ro(struct device *dev, struct device_attribute *attr, char *buf)
+	return sprintf(buf, "%d\n", curlun->ejected);
+}
+
+static ssize_t show_ro(struct device *dev, struct device_attribute *attr,
+								      char *buf)
 {
 	struct lun	*curlun = dev_to_lun(dev);
 
@@ -3789,10 +3932,28 @@ static ssize_t store_file(struct device *dev, struct device_attribute *attr,
 }
 
 
+static ssize_t set_arbitration(struct device *dev,
+		   struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct lun *curlun = dev_to_lun(dev);
+
+	if (!curlun->arbitration_done) {
+		curlun->arbitration_result = (buf[0] == '1');
+		curlun->arbitration_done = 1;
+		dev_err(dev, "arbitration result seen %d\n",
+						    curlun->arbitration_result);
+		complete(&curlun->arbitration_completion);
+	}
+
+	return count;
+}
+
+
 /* The write permissions and store_xxx pointers are set in fsg_bind() */
 static DEVICE_ATTR(ro, 0444, show_ro, NULL);
 static DEVICE_ATTR(file, 0444, show_file, NULL);
-
+static DEVICE_ATTR(ejected, 0444, show_ejected, NULL);
+static DEVICE_ATTR(arbitration, 0200, NULL, set_arbitration);
 
 /*-------------------------------------------------------------------------*/
 
@@ -3811,6 +3972,31 @@ static void lun_release(struct device *dev)
 	kref_put(&fsg->ref, fsg_release);
 }
 
+static struct kset_uevent_ops unfiltered_device_uevent_ops;
+static struct kset_uevent_ops *original_device_uevent_ops;
+
+void remove_device_uevent_filter(struct device *dev)
+{
+	if (!unfiltered_device_uevent_ops.uevent) {
+
+		memcpy(&unfiltered_device_uevent_ops,
+		    dev->kobj.kset->uevent_ops, sizeof(struct kset_uevent_ops));
+		unfiltered_device_uevent_ops.filter = NULL;
+		/* force uevent to use our kobject name as subsystem name */
+		unfiltered_device_uevent_ops.name = NULL;
+
+		original_device_uevent_ops = dev->kobj.kset->uevent_ops;
+	}
+
+	dev->kobj.kset->uevent_ops = &unfiltered_device_uevent_ops;
+}
+
+void set_original_uevent_ops(struct device *dev)
+{
+	dev->kobj.kset->uevent_ops = original_device_uevent_ops;
+}
+
+
 static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget)
 {
 	struct fsg_dev		*fsg = get_gadget_data(gadget);
@@ -3818,6 +4004,10 @@ static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget)
 	struct lun		*curlun;
 	struct usb_request	*req = fsg->ep0req;
 
+	/* module might get removed while we wait for arbitration */
+
+	mutex_lock(&arbitration_wait_mutex);
+
 	DBG(fsg, "unbind\n");
 	clear_bit(REGISTERED, &fsg->atomic_bitflags);
 
@@ -3825,8 +4015,12 @@ static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget)
 	for (i = 0; i < fsg->nluns; ++i) {
 		curlun = &fsg->luns[i];
 		if (curlun->registered) {
+			/* remove the uevent changes */
+			set_original_uevent_ops(&curlun->dev);
 			device_remove_file(&curlun->dev, &dev_attr_ro);
 			device_remove_file(&curlun->dev, &dev_attr_file);
+			device_remove_file(&curlun->dev, &dev_attr_ejected);
+			device_remove_file(&curlun->dev, &dev_attr_arbitration);
 			close_backing_file(curlun);
 			device_unregister(&curlun->dev);
 			curlun->registered = 0;
@@ -3842,6 +4036,8 @@ static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget)
 		complete(&fsg->thread_notifier);
 	}
 
+	mutex_unlock(&arbitration_wait_mutex);
+
 	/* Free the data buffers */
 	for (i = 0; i < NUM_BUFFERS; ++i)
 		kfree(fsg->buffhds[i].buf);
@@ -3944,7 +4140,6 @@ static int __init check_parameters(struct fsg_dev *fsg)
 	return 0;
 }
 
-
 static int __init fsg_bind(struct usb_gadget *gadget)
 {
 	struct fsg_dev		*fsg = the_fsg;
@@ -3960,6 +4155,8 @@ static int __init fsg_bind(struct usb_gadget *gadget)
 	fsg->ep0 = gadget->ep0;
 	fsg->ep0->driver_data = fsg;
 
+	mutex_init(&arbitration_wait_mutex);
+
 	if ((rc = check_parameters(fsg)) != 0)
 		goto out;
 
@@ -3993,12 +4190,15 @@ static int __init fsg_bind(struct usb_gadget *gadget)
 
 	for (i = 0; i < fsg->nluns; ++i) {
 		curlun = &fsg->luns[i];
+		curlun->lun_index = i;
 		curlun->ro = mod_data.ro[i];
 		if (mod_data.cdrom)
 			curlun->ro = 1;
 		curlun->dev.release = lun_release;
 		curlun->dev.parent = &gadget->dev;
 		curlun->dev.driver = &fsg_driver.driver;
+		curlun->arbitration_done = 0;
+
 		dev_set_drvdata(&curlun->dev, fsg);
 		dev_set_name(&curlun->dev,"%s-lun%d",
 			     dev_name(&gadget->dev), i);
@@ -4008,13 +4208,23 @@ static int __init fsg_bind(struct usb_gadget *gadget)
 			goto out;
 		}
 		if ((rc = device_create_file(&curlun->dev,
-					&dev_attr_ro)) != 0 ||
+					&dev_attr_ro)) ||
+				(rc = device_create_file(&curlun->dev,
+					&dev_attr_file)) ||
+				(rc = device_create_file(&curlun->dev,
+					&dev_attr_ejected)) ||
 				(rc = device_create_file(&curlun->dev,
-					&dev_attr_file)) != 0) {
+					&dev_attr_arbitration))) {
 			device_unregister(&curlun->dev);
 			goto out;
 		}
 		curlun->registered = 1;
+
+		/* enable us to send uevents on netlink */
+		remove_device_uevent_filter(&curlun->dev);
+		curlun->dev.kobj.uevent_suppress = 0;
+		init_completion(&curlun->arbitration_completion);
+
 		kref_get(&fsg->ref);
 
 		if (mod_data.file[i] && *mod_data.file[i]) {

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