Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- drivers/usb/gadget/Kconfig | 4 + drivers/usb/gadget/Makefile | 3 + drivers/usb/gadget/f_mass_storage.c | 825 +++++++++++++++++------------------ drivers/usb/gadget/f_mass_storage.h | 99 +++++ drivers/usb/gadget/storage_common.c | 175 +++++--- drivers/usb/gadget/storage_common.h | 43 ++ 6 files changed, 648 insertions(+), 501 deletions(-) create mode 100644 drivers/usb/gadget/f_mass_storage.h create mode 100644 drivers/usb/gadget/storage_common.h diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 80ab9a5..f142db3 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -501,6 +501,9 @@ config USB_LIBCOMPOSITE tristate depends on USB_GADGET +config USB_F_MASS_STORAGE + tristate + choice tristate "USB Gadget Drivers" default USB_ETH @@ -524,6 +527,7 @@ choice config USB_FG tristate "USB Functions Gadget" + select USB_F_MASS_STORAGE depends on CONFIGFS_FS help USB Functions Gadget is a device which aggregates a number of diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 378296b..536f4d6 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -75,3 +75,6 @@ obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o + +# USB Functions +obj-$(CONFIG_USB_F_MASS_STORAGE) += f_mass_storage.o \ No newline at end of file diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 5d027b3..a558f32 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -213,12 +213,14 @@ #include <linux/spinlock.h> #include <linux/string.h> #include <linux/freezer.h> +#include <linux/module.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/usb/composite.h> #include "gadget_chips.h" +#include "usb_functions.h" /*------------------------------------------------------------------------*/ @@ -229,124 +231,11 @@ static const char fsg_string_interface[] = "Mass Storage"; #include "storage_common.c" - +#include "f_mass_storage.h" /*-------------------------------------------------------------------------*/ -struct fsg_dev; -struct fsg_common; - -/* FSF callback functions */ -struct fsg_operations { - /* - * Callback function to call when thread exits. If no - * callback is set or it returns value lower then zero MSF - * will force eject all LUNs it operates on (including those - * marked as non-removable or with prevent_medium_removal flag - * set). - */ - int (*thread_exits)(struct fsg_common *common); - - /* - * Called prior to ejection. Negative return means error, - * zero means to continue with ejection, positive means not to - * eject. - */ - int (*pre_eject)(struct fsg_common *common, - struct fsg_lun *lun, int num); - /* - * Called after ejection. Negative return means error, zero - * or positive is just a success. - */ - int (*post_eject)(struct fsg_common *common, - struct fsg_lun *lun, int num); -}; - -/* Data shared by all the FSG instances. */ -struct fsg_common { - struct usb_gadget *gadget; - struct usb_composite_dev *cdev; - struct fsg_dev *fsg, *new_fsg; - wait_queue_head_t fsg_wait; - - /* filesem protects: backing files in use */ - struct rw_semaphore filesem; - - /* lock protects: state, all the req_busy's */ - spinlock_t lock; - - struct usb_ep *ep0; /* Copy of gadget->ep0 */ - struct usb_request *ep0req; /* Copy of cdev->req */ - unsigned int ep0_req_tag; - - struct fsg_buffhd *next_buffhd_to_fill; - struct fsg_buffhd *next_buffhd_to_drain; - struct fsg_buffhd *buffhds; - - int cmnd_size; - u8 cmnd[MAX_COMMAND_SIZE]; - - unsigned int nluns; - unsigned int lun; - struct fsg_lun *luns; - struct fsg_lun *curlun; - - unsigned int bulk_out_maxpacket; - enum fsg_state state; /* For exception handling */ - unsigned int exception_req_tag; - - enum data_direction data_dir; - u32 data_size; - u32 data_size_from_cmnd; - u32 tag; - u32 residue; - u32 usb_amount_left; - - unsigned int can_stall:1; - unsigned int free_storage_on_release:1; - unsigned int phase_error:1; - unsigned int short_packet_received:1; - unsigned int bad_lun_okay:1; - unsigned int running:1; - - int thread_wakeup_needed; - struct completion thread_notifier; - struct task_struct *thread_task; - - /* Callback functions. */ - const struct fsg_operations *ops; - /* Gadget's private data. */ - void *private_data; - - /* - * Vendor (8 chars), product (16 chars), release (4 - * hexadecimal digits) and NUL byte - */ - char inquiry_string[8 + 16 + 4 + 1]; - - struct kref ref; -}; - -struct fsg_config { - unsigned nluns; - struct fsg_lun_config { - const char *filename; - char ro; - char removable; - char cdrom; - char nofua; - } luns[FSG_MAX_LUNS]; - - /* Callback functions. */ - const struct fsg_operations *ops; - /* Gadget's private data. */ - void *private_data; - - const char *vendor_name; /* 8 characters or less */ - const char *product_name; /* 16 characters or less */ - - char can_stall; -}; +static unsigned long msg_registered; struct fsg_dev { struct usb_function function; @@ -1374,26 +1263,13 @@ static int do_start_stop(struct fsg_common *common) if (!loej) return 0; - /* Simulate an unload/eject */ - if (common->ops && common->ops->pre_eject) { - int r = common->ops->pre_eject(common, curlun, - curlun - common->luns); - if (unlikely(r < 0)) - return r; - else if (r) - return 0; - } - up_read(&common->filesem); down_write(&common->filesem); fsg_lun_close(curlun); up_write(&common->filesem); down_read(&common->filesem); - return common->ops && common->ops->post_eject - ? min(0, common->ops->post_eject(common, curlun, - curlun - common->luns)) - : 0; + return 0; } static int do_prevent_allow(struct fsg_common *common) @@ -2196,9 +2072,23 @@ static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) if (common->data_size == 0) common->data_dir = DATA_DIR_NONE; common->lun = cbw->Lun; - if (common->lun >= 0 && common->lun < common->nluns) - common->curlun = &common->luns[common->lun]; - else + if (common->lun >= 0 && common->lun < common->nluns) { + struct config_item *it; + + mutex_lock(&common->group.group.cg_subsys->su_mutex); + list_for_each_entry(it, &common->group.group.cg_children, + ci_entry) { + struct fsg_lun *lun; + + lun = to_fsg_lun(it); + if (lun->n_lun == common->lun) { + common->curlun = lun; + + break; + } + } + mutex_unlock(&common->group.group.cg_subsys->su_mutex); + } else common->curlun = NULL; common->tag = cbw->Tag; return 0; @@ -2258,6 +2148,7 @@ static int alloc_request(struct fsg_common *common, struct usb_ep *ep, /* Reset interface setting and re-init endpoint state (toggle etc). */ static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) { + struct config_item *item; struct fsg_dev *fsg; int i, rc = 0; @@ -2342,8 +2233,14 @@ reset: } common->running = 1; - for (i = 0; i < common->nluns; ++i) - common->luns[i].unit_attention_data = SS_RESET_OCCURRED; + mutex_lock(&common->group.group.cg_subsys->su_mutex); + list_for_each_entry(item, &common->group.group.cg_children, ci_entry) { + struct fsg_lun *lun; + + lun = to_fsg_lun(item); + lun->unit_attention_data = SS_RESET_OCCURRED; + } + mutex_unlock(&common->group.group.cg_subsys->su_mutex); return rc; } @@ -2374,7 +2271,6 @@ static void handle_exception(struct fsg_common *common) int i; struct fsg_buffhd *bh; enum fsg_state old_state; - struct fsg_lun *curlun; unsigned int exception_req_tag; /* @@ -2442,14 +2338,21 @@ static void handle_exception(struct fsg_common *common) if (old_state == FSG_STATE_ABORT_BULK_OUT) common->state = FSG_STATE_STATUS_PHASE; else { - for (i = 0; i < common->nluns; ++i) { - curlun = &common->luns[i]; + struct config_item *it; + + mutex_lock(&common->group.group.cg_subsys->su_mutex); + list_for_each_entry(it, &common->group.group.cg_children, + ci_entry) { + struct fsg_lun *curlun; + + curlun = to_fsg_lun(it); curlun->prevent_medium_removal = 0; curlun->sense_data = SS_NO_SENSE; curlun->unit_attention_data = SS_NO_SENSE; curlun->sense_data_info = 0; curlun->info_valid = 0; } + mutex_unlock(&common->group.group.cg_subsys->su_mutex); common->state = FSG_STATE_IDLE; } spin_unlock_irq(&common->lock); @@ -2582,17 +2485,25 @@ static int fsg_main_thread(void *common_) if (!common->ops || !common->ops->thread_exits || common->ops->thread_exits(common) < 0) { - struct fsg_lun *curlun = common->luns; - unsigned i = common->nluns; + struct list_head *cursor; down_write(&common->filesem); - for (; i--; ++curlun) { + + mutex_lock(&common->group.group.cg_subsys->su_mutex); + list_for_each_prev(cursor, &common->group.group.cg_children) { + struct config_item *item; + struct fsg_lun *curlun; + + item = list_entry(cursor, struct config_item, ci_entry); + + curlun = to_fsg_lun(item); if (!fsg_lun_is_open(curlun)) continue; fsg_lun_close(curlun); curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; } + mutex_unlock(&common->group.group.cg_subsys->su_mutex); up_write(&common->filesem); } @@ -2600,156 +2511,241 @@ static int fsg_main_thread(void *common_) complete_and_exit(&common->thread_notifier, 0); } +/****************************** FSG COMMON ******************************/ -/*************************** DEVICE ATTRIBUTES ***************************/ +static void fsg_common_release(struct kref *ref); -static DEVICE_ATTR(ro, 0644, fsg_show_ro, fsg_store_ro); -static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, fsg_store_nofua); -static DEVICE_ATTR(file, 0644, fsg_show_file, fsg_store_file); +void fsg_common_get(struct fsg_common *common) +{ + kref_get(&common->ref); +} +EXPORT_SYMBOL(fsg_common_get); -static struct device_attribute dev_attr_ro_cdrom = - __ATTR(ro, 0444, fsg_show_ro, NULL); -static struct device_attribute dev_attr_file_nonremovable = - __ATTR(file, 0444, fsg_show_file, NULL); +void fsg_common_put(struct fsg_common *common) +{ + kref_put(&common->ref, fsg_common_release); +} +EXPORT_SYMBOL(fsg_common_put); +static struct fsg_lun *alloc_fsg_lun(struct config_group *group, + const char *name) +{ + struct fsg_common *common; + struct ufg_function_grp *ufg_function_grp; + struct fsg_lun *lun; + struct config_item *item; + unsigned int tmp; + unsigned int leading_zeros; + int len; + const char *p; + + ufg_function_grp = group ? + container_of(group, struct ufg_function_grp, group) : NULL; + if (!ufg_function_grp) + return ERR_PTR(-ENOMEM); + common = ufg_function_grp ? + container_of(ufg_function_grp, struct fsg_common, group) : NULL; -/****************************** FSG COMMON ******************************/ + if (strncmp(name, "lun", 3)) + return ERR_PTR(-EINVAL); + p = name + 3; + if (sscanf(p, "%d%n", &tmp, &len) < 1) + return ERR_PTR(-EINVAL); + leading_zeros = 0; + while (*p++ == '0') + leading_zeros++; + if (!tmp) + leading_zeros--; + if (leading_zeros > 0) + return ERR_PTR(-EINVAL); + if (strlen(name) != len + 3) + return ERR_PTR(-EINVAL); -static void fsg_common_release(struct kref *ref); + list_for_each_entry(item, &common->group.group.cg_children, ci_entry) { + lun = to_fsg_lun(item); + if (tmp == lun->n_lun) + return ERR_PTR(-EBUSY); + } -static void fsg_lun_release(struct device *dev) + lun = kzalloc(sizeof(*lun), GFP_KERNEL); + if (!lun) + return ERR_PTR(-ENOMEM); + lun->filesem = &common->filesem; + lun->n_lun = tmp; + + config_item_init_type_name(&lun->item.cg_item, name, + &fsg_lun_item_type); + + LINFO(lun, "LUN: %s%s%sfile: %s\n", + lun->removable ? "removable " : "", + lun->ro ? "read only " : "", + lun->cdrom ? "CD-ROM " : "", + "(no medium)"); + + return lun; +} + +static ssize_t fsg_common_show_luns(struct fsg_common *common, char *buf) { - /* Nothing needs to be done */ + return sprintf(buf, "%d\n", common->nluns); } -static inline void fsg_common_get(struct fsg_common *common) +static ssize_t fsg_common_store_luns(struct fsg_common *common, const char *buf, + size_t count) { - kref_get(&common->ref); + struct config_item *function, *config, *gadget; + struct fsg_lun *new; + char n[LUN_NAME_MAX]; + u16 tmp; + char *p = (char *)buf; + int rc; + + function = common->group.group.cg_item.ci_parent; + if (!function) + return -EBUSY; + + config = function->ci_parent; + if (!config) + return -EBUSY; + + gadget = config->ci_parent; + if (!gadget) + return -EBUSY; + + rc = kstrtou16(p, 10, &tmp); + if (rc < 0) + return rc; + if (tmp > FSG_MAX_LUNS) + return -ERANGE; + + common->nluns = tmp; + for (tmp = 0; tmp < common->nluns; tmp++) { + + sprintf(n, "lun%d", tmp); + new = alloc_fsg_lun(&common->group.group, n); + if (IS_ERR(new)) + goto rollback; + + rc = configfs_create_group(&common->group.group, &new->item); + if (rc) { + kfree(new); + + goto rollback; + } + + } + + return count; + +rollback: + while (tmp--) { + struct config_item *child; + + sprintf(n, "lun%d", tmp); + child = config_group_find_item(&common->group.group, n); + if (child) + configfs_remove_group(to_config_group(child)); + } + return rc; } -static inline void fsg_common_put(struct fsg_common *common) +static ssize_t fsg_common_show_stall(struct fsg_common *common, char *buf) { - kref_put(&common->ref, fsg_common_release); + return sprintf(buf, "%d\n", common->can_stall); } -static struct fsg_common *fsg_common_init(struct fsg_common *common, - struct usb_composite_dev *cdev, - struct fsg_config *cfg) +static ssize_t fsg_common_store_stall(struct fsg_common *common, + const char *buf, size_t count) +{ + if (count > 2) + return -EINVAL; + + if (buf[0] != '0' && buf[0] != '1') + return -EINVAL; + + if (count > 1 && buf[1] != '\0') + return -EINVAL; + + common->can_stall = buf[0] == '1'; + + return count; +} + +CONFIGFS_ATTR_STRUCT(fsg_common); + +#define FSG_CONFIG_ATTR_RW(_name) \ +static struct fsg_common_attribute fsg_common_##_name = \ + __CONFIGFS_ATTR(_name, S_IRUGO | S_IWUSR, fsg_common_show_##_name,\ + fsg_common_store_##_name) + +#define FSG_CONFIG_ATTR_RO(_name) \ +static struct fsg_common_attribute fsg_common_##_name = \ + __CONFIGFS_ATTR(_name, S_IRUGO , fsg_common_show_##_name, NULL) + +FSG_CONFIG_ATTR_RW(luns); +FSG_CONFIG_ATTR_RW(stall); + +static struct configfs_attribute *fsg_common_attrs[] = { + &fsg_common_luns.attr, + &fsg_common_stall.attr, + NULL, +}; + +static struct fsg_common *to_fsg_common(struct config_item *item) +{ + struct ufg_function_grp *ufg_function_grp; + + ufg_function_grp = + item ? container_of(to_config_group(item), + struct ufg_function_grp, group) : NULL; + return ufg_function_grp ? container_of(ufg_function_grp, + struct fsg_common, group) : NULL; +} + +CONFIGFS_ATTR_OPS(fsg_common); + +static void fsg_common_release_item(struct config_item *item) +{ + kfree(to_fsg_common(item)); +} + +static struct configfs_item_operations fsg_common_item_ops = { + .show_attribute = fsg_common_attr_show, + .store_attribute = fsg_common_attr_store, + .release = fsg_common_release_item, +}; + +static struct config_item_type fsg_common_item_type = { + .ct_attrs = fsg_common_attrs, + .ct_item_ops = &fsg_common_item_ops, + .ct_owner = THIS_MODULE, +}; + +struct fsg_common *fsg_common_init(struct fsg_common *common) { - struct usb_gadget *gadget = cdev->gadget; struct fsg_buffhd *bh; - struct fsg_lun *curlun; - struct fsg_lun_config *lcfg; - int nluns, i, rc; - char *pathbuf; + int i, rc; rc = fsg_num_buffers_validate(); if (rc != 0) return ERR_PTR(rc); - /* Find out how many LUNs there should be */ - nluns = cfg->nluns; - if (nluns < 1 || nluns > FSG_MAX_LUNS) { - dev_err(&gadget->dev, "invalid number of LUNs: %u\n", nluns); + /* TODO: move it somewhere else */ + /*if (common->nluns < 1 || common->nluns > FSG_MAX_LUNS) { + printk("invalid number of LUNs: %u\n", nluns); return ERR_PTR(-EINVAL); - } - - /* Allocate? */ - if (!common) { - common = kzalloc(sizeof *common, GFP_KERNEL); - if (!common) - return ERR_PTR(-ENOMEM); - common->free_storage_on_release = 1; - } else { - memset(common, 0, sizeof *common); - common->free_storage_on_release = 0; - } + }*/ common->buffhds = kcalloc(fsg_num_buffers, sizeof *(common->buffhds), GFP_KERNEL); - if (!common->buffhds) { - if (common->free_storage_on_release) - kfree(common); + if (!common->buffhds) return ERR_PTR(-ENOMEM); - } - - common->ops = cfg->ops; - common->private_data = cfg->private_data; - common->gadget = gadget; - common->ep0 = gadget->ep0; - common->ep0req = cdev->req; - common->cdev = cdev; - - /* Maybe allocate device-global string IDs, and patch descriptors */ - if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { - rc = usb_string_id(cdev); - if (unlikely(rc < 0)) - goto error_release; - fsg_strings[FSG_STRING_INTERFACE].id = rc; - fsg_intf_desc.iInterface = rc; - } - - /* - * Create the LUNs, open their backing files, and register the - * LUN devices in sysfs. - */ - curlun = kcalloc(nluns, sizeof(*curlun), GFP_KERNEL); - if (unlikely(!curlun)) { - rc = -ENOMEM; - goto error_release; - } - common->luns = curlun; + common->ops = NULL; + common->private_data = NULL; init_rwsem(&common->filesem); - for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun, ++lcfg) { - curlun->cdrom = !!lcfg->cdrom; - curlun->ro = lcfg->cdrom || lcfg->ro; - curlun->initially_ro = curlun->ro; - curlun->removable = lcfg->removable; - curlun->dev.release = fsg_lun_release; - curlun->dev.parent = &gadget->dev; - /* curlun->dev.driver = &fsg_driver.driver; XXX */ - dev_set_drvdata(&curlun->dev, &common->filesem); - dev_set_name(&curlun->dev, "lun%d", i); - - rc = device_register(&curlun->dev); - if (rc) { - INFO(common, "failed to register LUN%d: %d\n", i, rc); - common->nluns = i; - put_device(&curlun->dev); - goto error_release; - } - - rc = device_create_file(&curlun->dev, - curlun->cdrom - ? &dev_attr_ro_cdrom - : &dev_attr_ro); - if (rc) - goto error_luns; - rc = device_create_file(&curlun->dev, - curlun->removable - ? &dev_attr_file - : &dev_attr_file_nonremovable); - if (rc) - goto error_luns; - rc = device_create_file(&curlun->dev, &dev_attr_nofua); - if (rc) - goto error_luns; - - if (lcfg->filename) { - rc = fsg_lun_open(curlun, lcfg->filename); - if (rc) - goto error_luns; - } else if (!curlun->removable) { - ERROR(common, "no file given for LUN%d\n", i); - rc = -EINVAL; - goto error_luns; - } - } - common->nluns = nluns; - /* Data buffers cyclic list */ bh = common->buffhds; i = fsg_num_buffers; @@ -2766,24 +2762,6 @@ buffhds_first_it: } while (--i); bh->next = common->buffhds; - /* Prepare inquiryString */ - i = get_default_bcdDevice(); - snprintf(common->inquiry_string, sizeof common->inquiry_string, - "%-8s%-16s%04x", cfg->vendor_name ?: "Linux", - /* Assume product name dependent on the first LUN */ - cfg->product_name ?: (common->luns->cdrom - ? "File-Stor Gadget" - : "File-CD Gadget"), - i); - - /* - * Some peripheral controllers are known not to be able to - * halt bulk endpoints correctly. If one of them is present, - * disable stalls. - */ - common->can_stall = cfg->can_stall && - !(gadget_is_at91(common->gadget)); - spin_lock_init(&common->lock); kref_init(&common->ref); @@ -2798,49 +2776,100 @@ buffhds_first_it: init_waitqueue_head(&common->fsg_wait); /* Information */ - INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); - INFO(common, "Number of LUNs=%d\n", common->nluns); - - pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); - for (i = 0, nluns = common->nluns, curlun = common->luns; - i < nluns; - ++curlun, ++i) { - char *p = "(no medium)"; - if (fsg_lun_is_open(curlun)) { - p = "(error)"; - if (pathbuf) { - p = d_path(&curlun->filp->f_path, - pathbuf, PATH_MAX); - if (IS_ERR(p)) - p = "(error)"; - } - } - LINFO(curlun, "LUN: %s%s%sfile: %s\n", - curlun->removable ? "removable " : "", - curlun->ro ? "read only " : "", - curlun->cdrom ? "CD-ROM " : "", - p); - } - kfree(pathbuf); + pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); + pr_info("Number of LUNs=%d\n", common->nluns); - DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task)); + pr_info("I/O thread pid: %d\n", task_pid_nr(common->thread_task)); wake_up_process(common->thread_task); return common; -error_luns: - common->nluns = i + 1; error_release: common->state = FSG_STATE_TERMINATED; /* The thread is dead */ /* Call fsg_common_release() directly, ref might be not initialised. */ fsg_common_release(&common->ref); return ERR_PTR(rc); } +EXPORT_SYMBOL(fsg_common_init); + +static struct fsg_common *fsg_common_init_cdev(struct fsg_common *common, + struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int rc, i; + + common->gadget = gadget; + common->ep0 = gadget->ep0; + common->ep0req = cdev->req; + common->cdev = cdev; + + /* Maybe allocate device-global string IDs, and patch descriptors */ + if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { + rc = usb_string_id(cdev); + if (unlikely(rc < 0)) + goto error_release; + fsg_strings[FSG_STRING_INTERFACE].id = rc; + fsg_intf_desc.iInterface = rc; + } + + /* Prepare inquiryString */ + i = get_default_bcdDevice(); + snprintf(common->inquiry_string, sizeof(common->inquiry_string), + "%-8s%-16s%04x", "Linux", + /* Assume product name dependent on the first LUN */ + /* TODO: actually check first child's "cdrom" flag */ + "USB mass storage", i); + + /* + * Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + common->can_stall = common->can_stall && + !(gadget_is_at91(common->gadget)); + + return common; + +error_release: + common->state = FSG_STATE_TERMINATED; /* The thread is dead */ + /* Call fsg_common_release() directly, ref might be not initialised. */ + fsg_common_release(&common->ref); + return ERR_PTR(rc); +} + +struct config_group *alloc_fsg_common(struct config_group *group, + const char *n) +{ + struct config_item *item; + struct fsg_common *common, *ret; + + list_for_each_entry(item, &group->cg_children, ci_entry) + if (!strcmp(n, item->ci_name)) + return ERR_PTR(-EBUSY); + + common = kzalloc(sizeof(*common), GFP_KERNEL); + if (!common) + return ERR_PTR(-ENOMEM); + + ret = fsg_common_init(common); + if (IS_ERR(ret)) { + kfree(common); + return (struct config_group *)ret; + } + common->group.type = UFG_FUNCTION; + + config_group_init_type_name(&common->group.group, n, + &fsg_common_item_type); + + return &common->group.group; +} +EXPORT_SYMBOL(alloc_fsg_common); static void fsg_common_release(struct kref *ref) { struct fsg_common *common = container_of(ref, struct fsg_common, ref); + struct config_item *item; /* If the thread isn't already dead, tell it to exit now */ if (common->state != FSG_STATE_TERMINATED) { @@ -2848,28 +2877,10 @@ static void fsg_common_release(struct kref *ref) wait_for_completion(&common->thread_notifier); } - if (likely(common->luns)) { - struct fsg_lun *lun = common->luns; - unsigned i = common->nluns; - - /* In error recovery common->nluns may be zero. */ - for (; i; --i, ++lun) { - device_remove_file(&lun->dev, &dev_attr_nofua); - device_remove_file(&lun->dev, - lun->cdrom - ? &dev_attr_ro_cdrom - : &dev_attr_ro); - device_remove_file(&lun->dev, - lun->removable - ? &dev_attr_file - : &dev_attr_file_nonremovable); - fsg_lun_close(lun); - device_unregister(&lun->dev); - } - - kfree(common->luns); + list_for_each_entry(item, &common->group.group.cg_children, ci_entry) { + struct fsg_lun *lun = to_fsg_lun(item); + fsg_lun_close(lun); } - { struct fsg_buffhd *bh = common->buffhds; unsigned i = fsg_num_buffers; @@ -2879,11 +2890,8 @@ static void fsg_common_release(struct kref *ref) } kfree(common->buffhds); - if (common->free_storage_on_release) - kfree(common); } - /*-------------------------------------------------------------------------*/ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) @@ -2899,9 +2907,14 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) wait_event(common->fsg_wait, common->fsg != fsg); } + usb_free_all_descriptors(f); fsg_common_put(common); - usb_free_all_descriptors(&fsg->function); - kfree(fsg); + usb_put_function(f); +} + +static void fsg_free(struct usb_function *f) +{ + kfree(fsg_from_func(f)); } static int fsg_bind(struct usb_configuration *c, struct usb_function *f) @@ -2965,22 +2978,61 @@ autoconf_fail: } /****************************** ADD FUNCTION ******************************/ +static void msg_cleanup(void) +{ + clear_bit(0, &msg_registered); +} + +static int msg_thread_exits(struct fsg_common *common) +{ + msg_cleanup(); + return 0; +} + +static int fsg_add_function(struct usb_configuration *c, struct usb_function *f, + struct config_item *item, void *data) +{ + static const struct fsg_operations ops = { + .thread_exits = msg_thread_exits, + }; + + struct fsg_common *common = to_fsg_common(item); + struct fsg_dev *fsg; + struct usb_composite_dev *cdev = data; + int status; + + common->ops = &ops; + fsg_common_init_cdev(common, cdev); + + fsg = container_of(f, struct fsg_dev, function); + fsg->common = common; + + status = usb_add_function(c, f); + if (status) { + usb_put_function(f); + fsg_common_put(common); + return status; + } + fsg_common_get(common); + set_bit(0, &msg_registered); + + return status; +} + +/****************************** ALLOCATE FUNCTION *************************/ static struct usb_gadget_strings *fsg_strings_array[] = { &fsg_stringtab, NULL, }; -static int fsg_bind_config(struct usb_composite_dev *cdev, - struct usb_configuration *c, - struct fsg_common *common) +static struct usb_function *fsg_alloc(void) { struct fsg_dev *fsg; - int rc; - fsg = kzalloc(sizeof *fsg, GFP_KERNEL); + fsg = kzalloc(sizeof(*fsg), GFP_KERNEL); if (unlikely(!fsg)) - return -ENOMEM; + return NULL; fsg->function.name = FSG_DRIVER_DESC; fsg->function.strings = fsg_strings_array; @@ -2989,8 +3041,10 @@ static int fsg_bind_config(struct usb_composite_dev *cdev, fsg->function.setup = fsg_setup; fsg->function.set_alt = fsg_set_alt; fsg->function.disable = fsg_disable; + fsg->function.free_func = fsg_free; + fsg->function.make_group = alloc_fsg_common; + fsg->function.add_function = fsg_add_function; - fsg->common = common; /* * Our caller holds a reference to common structure so we * don't have to be worry about it being freed until we return @@ -2999,100 +3053,9 @@ static int fsg_bind_config(struct usb_composite_dev *cdev, * call to usb_add_function() was successful. */ - rc = usb_add_function(c, &fsg->function); - if (unlikely(rc)) - kfree(fsg); - else - fsg_common_get(fsg->common); - return rc; + return &fsg->function; } - -/************************* Module parameters *************************/ - -struct fsg_module_parameters { - char *file[FSG_MAX_LUNS]; - bool ro[FSG_MAX_LUNS]; - bool removable[FSG_MAX_LUNS]; - bool cdrom[FSG_MAX_LUNS]; - bool nofua[FSG_MAX_LUNS]; - - unsigned int file_count, ro_count, removable_count, cdrom_count; - unsigned int nofua_count; - unsigned int luns; /* nluns */ - bool stall; /* can_stall */ -}; - -#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc) \ - module_param_array_named(prefix ## name, params.name, type, \ - &prefix ## params.name ## _count, \ - S_IRUGO); \ - MODULE_PARM_DESC(prefix ## name, desc) - -#define _FSG_MODULE_PARAM(prefix, params, name, type, desc) \ - module_param_named(prefix ## name, params.name, type, \ - S_IRUGO); \ - MODULE_PARM_DESC(prefix ## name, desc) - -#define FSG_MODULE_PARAMETERS(prefix, params) \ - _FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp, \ - "names of backing files or devices"); \ - _FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool, \ - "true to force read-only"); \ - _FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool, \ - "true to simulate removable media"); \ - _FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool, \ - "true to simulate CD-ROM instead of disk"); \ - _FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool, \ - "true to ignore SCSI WRITE(10,12) FUA bit"); \ - _FSG_MODULE_PARAM(prefix, params, luns, uint, \ - "number of LUNs"); \ - _FSG_MODULE_PARAM(prefix, params, stall, bool, \ - "false to prevent bulk stalls") - -static void -fsg_config_from_params(struct fsg_config *cfg, - const struct fsg_module_parameters *params) -{ - struct fsg_lun_config *lun; - unsigned i; - - /* Configure LUNs */ - cfg->nluns = - min(params->luns ?: (params->file_count ?: 1u), - (unsigned)FSG_MAX_LUNS); - for (i = 0, lun = cfg->luns; i < cfg->nluns; ++i, ++lun) { - lun->ro = !!params->ro[i]; - lun->cdrom = !!params->cdrom[i]; - lun->removable = !!params->removable[i]; - lun->filename = - params->file_count > i && params->file[i][0] - ? params->file[i] - : 0; - } - - /* Let MSF use defaults */ - cfg->vendor_name = 0; - cfg->product_name = 0; - - cfg->ops = NULL; - cfg->private_data = NULL; - - /* Finalise */ - cfg->can_stall = params->stall; -} - -static inline struct fsg_common * -fsg_common_from_params(struct fsg_common *common, - struct usb_composite_dev *cdev, - const struct fsg_module_parameters *params) - __attribute__((unused)); -static inline struct fsg_common * -fsg_common_from_params(struct fsg_common *common, - struct usb_composite_dev *cdev, - const struct fsg_module_parameters *params) -{ - struct fsg_config cfg; - fsg_config_from_params(&cfg, params); - return fsg_common_init(common, cdev, &cfg); -} +DECLARE_USB_FUNCTION(MassStorage, fsg_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michal Nazarewicz"); diff --git a/drivers/usb/gadget/f_mass_storage.h b/drivers/usb/gadget/f_mass_storage.h new file mode 100644 index 0000000..fa583db --- /dev/null +++ b/drivers/usb/gadget/f_mass_storage.h @@ -0,0 +1,99 @@ +#ifndef F_MASS_STORAGE_H +#define F_MASS_STORAGE_H + +#include "storage_common.h" +#include "usb_functions.h" + +#define LUN_NAME_MAX 16 + +struct fsg_common; +struct fsg_operations; +struct usb_composite_dev; +struct fsg_dev; +struct fsg_lun; + +/* FSF callback functions */ +struct fsg_operations { + /* + * Callback function to call when thread exits. If no + * callback is set or it returns value lower then zero MSF + * will force eject all LUNs it operates on (including those + * marked as non-removable or with prevent_medium_removal flag + * set). + */ + int (*thread_exits)(struct fsg_common *common); +}; + +/* Data shared by all the FSG instances. */ +struct fsg_common { + struct ufg_function_grp group; + enum ufg_hdr_type type; + + struct usb_gadget *gadget; + struct usb_composite_dev *cdev; + struct fsg_dev *fsg, *new_fsg; + wait_queue_head_t fsg_wait; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* lock protects: state, all the req_busy's */ + spinlock_t lock; + + struct usb_ep *ep0; /* Copy of gadget->ep0 */ + struct usb_request *ep0req; /* Copy of cdev->req */ + unsigned int ep0_req_tag; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd *buffhds; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + unsigned int lun; + struct fsg_lun *curlun; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + unsigned int exception_req_tag; + + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + u32 residue; + u32 usb_amount_left; + + unsigned int can_stall:1; + unsigned int free_storage_on_release:1; + unsigned int phase_error:1; + unsigned int short_packet_received:1; + unsigned int bad_lun_okay:1; + unsigned int running:1; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + /* + * Vendor (8 chars), product (16 chars), release (4 + * hexadecimal digits) and NUL byte + */ + char inquiry_string[8 + 16 + 4 + 1]; + + struct kref ref; +}; + +struct fsg_common *fsg_common_init(struct fsg_common *common); + +void fsg_common_get(struct fsg_common *common); +void fsg_common_put(struct fsg_common *common); + +#endif diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 0e3ae43..9e6136c 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -25,19 +25,12 @@ #include <linux/usb/storage.h> +#include <linux/configfs.h> #include <scsi/scsi.h> #include <asm/unaligned.h> +#include "storage_common.h" -/* - * Thanks to NetChip Technologies for donating this product ID. - * - * DO NOT REUSE THESE IDs with any other driver!! Ever!! - * Instead: allocate your own, using normal USB-IF procedures. - */ -#define FSG_VENDOR_ID 0x0525 /* NetChip */ -#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ - /*-------------------------------------------------------------------------*/ @@ -53,10 +46,9 @@ #define VLDBG(lun, fmt, args...) do { } while (0) #endif /* VERBOSE_DEBUG */ -#define LDBG(lun, fmt, args...) dev_dbg (&(lun)->dev, fmt, ## args) -#define LERROR(lun, fmt, args...) dev_err (&(lun)->dev, fmt, ## args) -#define LWARN(lun, fmt, args...) dev_warn(&(lun)->dev, fmt, ## args) -#define LINFO(lun, fmt, args...) dev_info(&(lun)->dev, fmt, ## args) +#define LERROR(lun, fmt, args...) pr_err(fmt, ## args) +#define LDBG(lun, fmt, args...) pr_debug(fmt, ## args) +#define LINFO(lun, fmt, args...) pr_info(fmt, ## args) #ifdef DUMP_MSGS @@ -105,9 +97,6 @@ struct interrupt_data { #define USB_CBI_ADSC_REQUEST 0x00 -/* Length of a SCSI Command Data Block */ -#define MAX_COMMAND_SIZE 16 - /* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ #define SS_NO_SENSE 0 #define SS_COMMUNICATION_FAILURE 0x040800 @@ -152,7 +141,11 @@ struct fsg_lun { unsigned int blkbits; /* Bits of logical block size of bound block device */ unsigned int blksize; /* logical block size of bound block device */ - struct device dev; + + /* configfs-related section */ + struct config_group item; + struct rw_semaphore *filesem; + unsigned int n_lun; }; static inline bool fsg_lun_is_open(struct fsg_lun *curlun) @@ -160,11 +153,63 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun) return curlun->filp != NULL; } -static inline struct fsg_lun *fsg_lun_from_dev(struct device *dev) +CONFIGFS_ATTR_STRUCT(fsg_lun); + +#define FSG_LUN_ATTR_RW(_name) \ +static struct fsg_lun_attribute fsg_lun_##_name = \ + __CONFIGFS_ATTR(_name, S_IRUGO | S_IWUSR, fsg_lun_show_##_name, \ + fsg_lun_store_##_name) + +static ssize_t fsg_lun_show_ro(struct fsg_lun *curlun, char *buf); +static ssize_t fsg_lun_show_nofua(struct fsg_lun *curlun, char *buf); +static ssize_t fsg_lun_show_file(struct fsg_lun *curlun, char *buf); +static ssize_t fsg_lun_store_ro(struct fsg_lun *curlun, const char *buf, + size_t count); +static ssize_t fsg_lun_store_nofua(struct fsg_lun *curlun, const char *buf, + size_t count); +static ssize_t fsg_lun_store_file(struct fsg_lun *curlun, const char *buf, + size_t count); +static ssize_t fsg_lun_show_removable(struct fsg_lun *curlun, char *buf); +static ssize_t fsg_lun_store_removable(struct fsg_lun *curlun, const char *buf, + size_t count); + +FSG_LUN_ATTR_RW(ro); +FSG_LUN_ATTR_RW(nofua); +FSG_LUN_ATTR_RW(file); +FSG_LUN_ATTR_RW(removable); + +static struct configfs_attribute *fsg_lun_attrs[] = { + &fsg_lun_ro.attr, + &fsg_lun_nofua.attr, + &fsg_lun_file.attr, + &fsg_lun_removable.attr, + NULL, +}; + +static struct fsg_lun *to_fsg_lun(struct config_item *item) +{ + return item ? container_of(to_config_group(item), struct fsg_lun, item) + : NULL; +} + +CONFIGFS_ATTR_OPS(fsg_lun); + +static void fsg_lun_item_release(struct config_item *item) { - return container_of(dev, struct fsg_lun, dev); + kfree(to_fsg_lun(item)); } +static struct configfs_item_operations fsg_lun_ops = { + .show_attribute = fsg_lun_attr_show, + .store_attribute = fsg_lun_attr_store, + .release = fsg_lun_item_release, +}; + +static struct config_item_type fsg_lun_item_type = { + .ct_attrs = fsg_lun_attrs, + .ct_item_ops = &fsg_lun_ops, + .ct_owner = THIS_MODULE, +}; /* Big enough to hold our biggest descriptor */ #define EP0_BUFSIZE 256 @@ -199,9 +244,6 @@ static inline int fsg_num_buffers_validate(void) /* Default size of buffer length. */ #define FSG_BUFLEN ((u32)16384) -/* Maximal number of LUNs supported in mass storage function */ -#define FSG_MAX_LUNS 8 - enum fsg_buffer_state { BUF_STATE_EMPTY = 0, BUF_STATE_FULL, @@ -226,29 +268,6 @@ struct fsg_buffhd { int outreq_busy; }; -enum fsg_state { - /* This one isn't used anywhere */ - FSG_STATE_COMMAND_PHASE = -10, - FSG_STATE_DATA_PHASE, - FSG_STATE_STATUS_PHASE, - - FSG_STATE_IDLE = 0, - FSG_STATE_ABORT_BULK_OUT, - FSG_STATE_RESET, - FSG_STATE_INTERFACE_CHANGE, - FSG_STATE_CONFIG_CHANGE, - FSG_STATE_DISCONNECT, - FSG_STATE_EXIT, - FSG_STATE_TERMINATED -}; - -enum data_direction { - DATA_DIR_UNKNOWN = 0, - DATA_DIR_FROM_HOST, - DATA_DIR_TO_HOST, - DATA_DIR_NONE -}; - /*-------------------------------------------------------------------------*/ @@ -468,6 +487,8 @@ static void fsg_lun_close(struct fsg_lun *curlun) LDBG(curlun, "close backing file\n"); fput(curlun->filp); curlun->filp = NULL; + configfs_undepend_item(curlun->item.cg_subsys, + &curlun->item.cg_item); } } @@ -484,6 +505,8 @@ static int fsg_lun_open(struct fsg_lun *curlun, const char *filename) unsigned int blkbits; unsigned int blksize; + configfs_depend_item(curlun->item.cg_subsys, &curlun->item.cg_item); + /* R/W if we can, R/O if we must */ ro = curlun->initially_ro; if (!ro) { @@ -567,6 +590,9 @@ static int fsg_lun_open(struct fsg_lun *curlun, const char *filename) out: fput(filp); + if (rc) + configfs_undepend_item(curlun->item.cg_subsys, + &curlun->item.cg_item); return rc; } @@ -608,29 +634,21 @@ static void store_cdrom_address(u8 *dest, int msf, u32 addr) /*-------------------------------------------------------------------------*/ -static ssize_t fsg_show_ro(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t fsg_lun_show_ro(struct fsg_lun *curlun, char *buf) { - struct fsg_lun *curlun = fsg_lun_from_dev(dev); - return sprintf(buf, "%d\n", fsg_lun_is_open(curlun) ? curlun->ro : curlun->initially_ro); } -static ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t fsg_lun_show_nofua(struct fsg_lun *curlun, char *buf) { - struct fsg_lun *curlun = fsg_lun_from_dev(dev); - return sprintf(buf, "%u\n", curlun->nofua); } -static ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t fsg_lun_show_file(struct fsg_lun *curlun, char *buf) { - struct fsg_lun *curlun = fsg_lun_from_dev(dev); - struct rw_semaphore *filesem = dev_get_drvdata(dev); + struct rw_semaphore *filesem = curlun->filesem; char *p; ssize_t rc; @@ -653,13 +671,11 @@ static ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, return rc; } - -static ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t fsg_lun_store_ro(struct fsg_lun *curlun, const char *buf, + size_t count) { ssize_t rc; - struct fsg_lun *curlun = fsg_lun_from_dev(dev); - struct rw_semaphore *filesem = dev_get_drvdata(dev); + struct rw_semaphore *filesem = curlun->filesem; unsigned ro; rc = kstrtouint(buf, 2, &ro); @@ -684,11 +700,9 @@ static ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr, return rc; } -static ssize_t fsg_store_nofua(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t fsg_lun_store_nofua(struct fsg_lun *curlun, const char *buf, + size_t count) { - struct fsg_lun *curlun = fsg_lun_from_dev(dev); unsigned nofua; int ret; @@ -705,11 +719,10 @@ static ssize_t fsg_store_nofua(struct device *dev, return count; } -static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t fsg_lun_store_file(struct fsg_lun *curlun, const char *buf, + size_t count) { - struct fsg_lun *curlun = fsg_lun_from_dev(dev); - struct rw_semaphore *filesem = dev_get_drvdata(dev); + struct rw_semaphore *filesem = curlun->filesem; int rc = 0; if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) { @@ -736,3 +749,25 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, up_write(filesem); return (rc < 0 ? rc : count); } + +static ssize_t fsg_lun_show_removable(struct fsg_lun *curlun, char *buf) +{ + return sprintf(buf, "%d\n", curlun->removable); +} + +static ssize_t fsg_lun_store_removable(struct fsg_lun *curlun, const char *buf, + size_t count) +{ + if (fsg_lun_is_open(curlun)) { + LDBG(curlun, "media type change prevented\n"); + return -EBUSY; + } + + if (buf[0] != '0' && buf[0] != '1') + return -EINVAL; + + curlun->removable = buf[0] == '1'; + + return count; +} + diff --git a/drivers/usb/gadget/storage_common.h b/drivers/usb/gadget/storage_common.h new file mode 100644 index 0000000..52528f2 --- /dev/null +++ b/drivers/usb/gadget/storage_common.h @@ -0,0 +1,43 @@ +#ifndef STORAGE_COMMON_H +#define STORAGE_COMMON_H + +/* + * Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with any other driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#define FSG_VENDOR_ID 0x0525 /* NetChip */ +#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + +/* Maximal number of LUNs supported in mass storage function */ +#define FSG_MAX_LUNS 8 + +/* Length of a SCSI Command Data Block */ +#define MAX_COMMAND_SIZE 16 + +enum fsg_state { + /* This one isn't used anywhere */ + FSG_STATE_COMMAND_PHASE = -10, + FSG_STATE_DATA_PHASE, + FSG_STATE_STATUS_PHASE, + + FSG_STATE_IDLE = 0, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_RESET, + FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_CONFIG_CHANGE, + FSG_STATE_DISCONNECT, + FSG_STATE_EXIT, + FSG_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + + +#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