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