This is an example of how to create an adapter module for an old-style gadget. It provides the legacy interface while internally operates on configfs. Usage is like g_mass_storage.ko, but instead the user needs to do e.g.: insmod libcomposite.ko insmod f_mass_storage.ko (not required if request_module works) insmod g_usb_functions.ko (not required if request_module works) insmod g_mass_storage_adapter.ko removable=1 Then the user can do something on the lines of: echo <some file>.img > /sys/devices/platform/s3c-hsotg/gadget/lun0/file and they can enjoy the mass storage gadget functionality. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- drivers/usb/gadget/Kconfig | 9 + drivers/usb/gadget/Makefile | 4 + drivers/usb/gadget/ufg_mass_storage.c | 608 +++++++++++++++++++++++++++++++++ 3 files changed, 621 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/ufg_mass_storage.c diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 61fa2a7..6f6aebd 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -537,6 +537,15 @@ config USB_FG a USB configuration is composed of and enabling/disabling the gadget. +config USB_MASS_STORAGE_TO_UFG + tristate "Mass Storage Gadget to USB Functions Gadget adapter" + depends on USB_FG + help + The Mass Storage Gadget used to act as a USB Mass Storage disk drive. + Its implementation is now considered obsolete. In order to maintain + compatibility an adapter module is used to provide the old interface + while using the new gadget infrastructure. + config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" select USB_LIBCOMPOSITE diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index db8281a..6be8d8e 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -56,6 +56,7 @@ g_webcam-y := webcam.o g_ncm-y := ncm.o g_acm_ms-y := acm_ms.o g_tcm_usb_gadget-y := tcm_usb_gadget.o +g_mass_storage_adapter-y := ufg_mass_storage.o obj-$(CONFIG_USB_FG) += g_usb_functions.o obj-$(CONFIG_USB_ZERO) += g_zero.o @@ -79,3 +80,6 @@ obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o # USB Functions obj-$(CONFIG_USB_F_MASS_STORAGE) += f_mass_storage.o + +# Gadget adapters +obj-$(CONFIG_USB_MASS_STORAGE_TO_UFG) += g_mass_storage_adapter.o diff --git a/drivers/usb/gadget/ufg_mass_storage.c b/drivers/usb/gadget/ufg_mass_storage.c new file mode 100644 index 0000000..3a66b8a --- /dev/null +++ b/drivers/usb/gadget/ufg_mass_storage.c @@ -0,0 +1,608 @@ +/* + * ufg_mass_storage.c -- Mass Storage USB Gadget to UFG adapter + * + * Copyright (C) 2012 Samsung Electronics + * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/fs.h> + +#include "usb_functions.h" +#include "storage_common.h" + +#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") + +#define UFG_MODULE (UFG_SUBSYSTEM->subsys.su_group.cg_item.ci_type->ct_owner) +#define LUN(i) config->luns[i] + +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 */ +}; + +struct fsg_config { + unsigned nluns; + unsigned configfs_nluns; + struct fsg_lun_config { + const char *filename; + char ro; + char removable; + char cdrom; + char nofua; + struct device dev; + struct dentry *dentry; + } luns[FSG_MAX_LUNS]; + + char can_stall; + + struct dentry *root; + struct dentry *gadget; + struct dentry *conf; + struct dentry *func; + struct dentry *f; +}; + +/* + * ATTENTION: + * + * struct configfs_dirent is "borrowed" from fs/configfs/configfs_internal.h. + * + * The adapters by default will be phased out, so this _should_ be acceptable. + */ +struct configfs_dirent { + atomic_t s_count; + int s_dependent_count; + struct list_head s_sibling; + struct list_head s_children; + struct list_head s_links; + void * s_element; + int s_type; + umode_t s_mode; + struct dentry * s_dentry; + struct iattr * s_iattr; +#ifdef CONFIG_LOCKDEP + int s_depth; +#endif +}; + +static struct fsg_module_parameters mod_data = { + .stall = 1 +}; + +FSG_MODULE_PARAMETERS(/* no prefix */, mod_data); + +static struct fsg_config config_struct; + +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; + } + + /* Finalise */ + cfg->can_stall = params->stall; +} + +static inline void init_name(struct qstr *n, const char *s) +{ + n->name = s; + n->len = strlen(n->name); + n->hash = full_name_hash(n->name, n->len); +} + +/* + * ATTENTION: + * + * to_item is "borrowed" from fs/configfs/configfs_internal.h. + * + * The adapters by default will be phased out, so this _should_ be acceptable. + */ +static inline struct config_item *to_item(struct dentry * dentry) +{ + struct configfs_dirent *sd = dentry->d_fsdata; + return ((struct config_item *) sd->s_element); +} + +static inline struct config_item *mkdir(struct dentry *parent, const char *s, + struct dentry **new) +{ + struct qstr name; + struct config_item *ci; + int rc; + + init_name(&name, s); + *new = d_alloc(parent, &name); + if (IS_ERR_OR_NULL(*new)) + return ERR_PTR(-ENOMEM); + + d_add(*new, NULL); + rc = ufg_mkdir(parent, *new); + if (rc) { + d_drop(*new); + dput(*new); + + return ERR_PTR(rc); + } + dput(*new); + + ci = to_item(*new); + if (!ci) { + ufg_rmdir(parent, *new); + + return ERR_PTR(-ENODEV); + } + return ci; +} + +static int do_store(struct config_item *ci, const char *a, const char *s, int n) +{ + struct configfs_attribute *attr; + int i; + bool found = false; + + if (!ci) + return -ENODEV; + + for (i = 0; (attr = ci->ci_type->ct_attrs[i]) != NULL; i++) + if (!strcmp(attr->ca_name, a)) { + found = true; + break; + } + if (!found) + return -ENODEV; + + return ci->ci_type->ct_item_ops->store_attribute(ci, attr, s, n); +} + +static inline int str_store(struct config_item *ci, const char *a, const char *s) +{ + return do_store(ci, a, s, strlen(s)); +} + +static inline int formatted_store(struct config_item *ci, const char *a, int i, const char *f) +{ + char buf[UFG_STR_LEN]; + + snprintf(buf, UFG_STR_LEN, f, i); + return str_store(ci, a, buf); +} + +static inline int hex_store(struct config_item *ci, const char *a, int i) +{ + return formatted_store(ci, a, i, "%x"); +} + +static inline int int_store(struct config_item *ci, const char *a, int i) +{ + return formatted_store(ci, a, i, "%d"); +} + +static int do_show(struct config_item *ci, const char *a, char *s) +{ + struct configfs_attribute *attr; + int i; + bool found = false; + + if (!ci) + return -ENODEV; + + for (i = 0; (attr = ci->ci_type->ct_attrs[i]) != NULL; i++) + if (!strcmp(attr->ca_name, a)) { + found = true; + break; + } + if (!found) + return -ENODEV; + + return ci->ci_type->ct_item_ops->show_attribute(ci, attr, s); +} + +static struct device *configfs_prepare(struct fsg_config *config) +{ + struct config_item *gadget_ci, *conf_ci, *func_ci, *f_ci; + struct ufg_gadget_grp *gadget_grp; + struct ufg_dev *ufg_dev; + int rc, i; + + if (!UFG_SUBSYSTEM) + return ERR_PTR(-ENODEV); + + config->root = UFG_SUBSYSTEM->subsys.su_group.cg_item.ci_dentry; + + /* mkdir the gadget and fill its attributes */ + gadget_ci = mkdir(config->root, "msg", &config->gadget); + if (IS_ERR_OR_NULL(gadget_ci)) + return ERR_PTR(PTR_ERR(gadget_ci)); + rc = hex_store(gadget_ci, "idVendor", 0x0525); + if (rc < 0) + goto remove_gadget; + rc = hex_store(gadget_ci, "idProduct", 0xa4a5); + if (rc < 0) + goto remove_gadget; + rc = hex_store(gadget_ci, "bcdDevice", 0xff); + if (rc < 0) + goto remove_gadget; + rc = str_store(gadget_ci, "iManufacturer", "Linux"); + if (rc < 0) + goto remove_gadget; + rc = str_store(gadget_ci, "iProduct", + LUN(0).cdrom ? "File-Stor Gadget" : "File-CD Gadget"); + if (rc < 0) + goto remove_gadget; + + /* mkdir the config and fill its attributes */ + conf_ci = mkdir(config->gadget, "conf", &config->conf); + if (IS_ERR_OR_NULL(conf_ci)) { + rc = PTR_ERR(conf_ci); + goto remove_gadget; + } + + /* mkdir the function and fill its attributes */ + func_ci = mkdir(config->conf, "func", &config->func); + if (IS_ERR_OR_NULL(func_ci)) { + rc = PTR_ERR(func_ci); + goto remove_conf; + } + rc = str_store(func_ci, "name", "MassStorage"); + if (rc < 0) + goto remove_func; + /* mkdir the mass storage function and fill its attributes */ + f_ci = config_group_find_item(to_config_group(to_item(config->func)), "MassStorage"); + if (IS_ERR_OR_NULL(f_ci)) { + rc = PTR_ERR(f_ci); + goto remove_func; + } + config->f = f_ci->ci_dentry; + rc = int_store(f_ci, "luns", config->nluns); + if (rc < 0) + goto remove_f; + + for (i = 0; i < config->nluns; i++) { + struct config_item *lun_ci; + char buf[UFG_STR_LEN]; + + /* mkdir the lun and fill its attributes */ + snprintf(buf, UFG_STR_LEN, "lun%d", i); + lun_ci = config_group_find_item(to_config_group(to_item(config->f)), buf); + if (IS_ERR_OR_NULL(lun_ci)) { + rc = PTR_ERR(lun_ci); + if (i--) + goto remove_luns; + else + goto remove_f; + } + LUN(i).dentry = lun_ci->ci_dentry; + if (LUN(i).filename) { + rc = str_store(lun_ci, "file", LUN(i).filename); + if (rc < 0) + goto remove_luns; + } + if (LUN(i).ro) { + rc = int_store(lun_ci, "ro", 1); + if (rc < 0) + goto remove_luns; + } + if (LUN(i).nofua) { + rc = int_store(lun_ci, "nofua", 1); + if (rc < 0) + goto remove_luns; + } + if (LUN(i).removable) { + rc = int_store(lun_ci, "removable", 1); + if (rc < 0) + goto remove_luns; + } + } + i--; + + rc = -ENODEV; + gadget_grp = to_ufg_gadget_grp(gadget_ci); + if (!gadget_grp) + goto remove_luns; + + rc = int_store(gadget_ci, "ready", 1); + if (rc < 0) + goto remove_luns; + ufg_dev = gadget_grp->gadget_grp_data; + if (!ufg_dev) + goto remove_luns; + + return &ufg_dev->cdev->gadget->dev; + +remove_luns: + while (i >= 0) + ufg_rmdir(config->f, LUN(i--).dentry); + +remove_f: + ufg_rmdir(config->func, config->f); + +remove_func: + ufg_rmdir(config->conf, config->func); + +remove_conf: + ufg_rmdir(config->gadget, config->conf); + +remove_gadget: + ufg_rmdir(config->root, config->gadget); + return ERR_PTR(rc); +} + +static void lun_release(struct device *dev) +{ +} + +static inline struct fsg_lun_config *dev_to_fsg_lun_config(struct device *dev) +{ + return container_of(dev, struct fsg_lun_config, dev); +} + +static ssize_t fsg_show(struct device *dev, struct device_attribute *attr, + const char *configfs_name, char *buf) +{ + struct fsg_lun_config *lun = dev_to_fsg_lun_config(dev); + struct config_item *ci; + + if (!lun) + return -ENODEV; + + ci = to_item(lun->dentry); + if (!ci) + return -ENODEV; + + return do_show(ci, configfs_name, buf); +} + +static inline ssize_t fsg_show_ro(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return fsg_show(dev, attr, "ro", buf); +} + +static inline ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return fsg_show(dev, attr, "nofua", buf); +} + +static inline ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return fsg_show(dev, attr, "file", buf); +} + +static ssize_t fsg_store(struct device *dev, struct device_attribute *attr, + const char *configfs_name, const char *buf, size_t count) +{ + struct fsg_lun_config *lun = dev_to_fsg_lun_config(dev); + struct config_item *ci; + + if (!lun) + return -ENODEV; + + ci = to_item(lun->dentry); + if (!ci) + return -ENODEV; + + return do_store(ci, configfs_name, buf, count); +} + +static inline ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return fsg_store(dev, attr, "ro", buf, count); +} + +static inline ssize_t fsg_store_nofua(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return fsg_store(dev, attr, "nofua", buf, count); +} + +static inline ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return fsg_store(dev, attr, "file", buf, count); +} + +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); + +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); + +static void devices_teardown(struct fsg_config *config) +{ + int i = config->nluns; + + while (i--) { + device_remove_file(&LUN(i).dev, &dev_attr_nofua); + device_remove_file(&LUN(i).dev, + LUN(i).cdrom + ? &dev_attr_ro_cdrom + : &dev_attr_ro); + device_remove_file(&LUN(i).dev, + LUN(i).removable + ? &dev_attr_file + : &dev_attr_file_nonremovable); + device_unregister(&LUN(i).dev); + } +} + +static void configfs_teardown(struct fsg_config *config) +{ + struct config_item *ci; + int i = config->configfs_nluns; + + ci = to_item(config->gadget); + if (ci) + int_store(ci, "ready", 0); + while (i--) + ufg_rmdir(config->f, LUN(i).dentry); + + ufg_rmdir(config->func, config->f); + ufg_rmdir(config->conf, config->func); + ufg_rmdir(config->gadget, config->conf); + ufg_rmdir(config->root, config->gadget); +} + +static int __init msg_init(void) +{ + struct device *gadget_dev; + struct fsg_config *config = &config_struct; + int i = 0, rc; + + rc = request_module("usb_functions"); + if (rc < 0) + return rc; + rc = try_module_get(UFG_MODULE); + if (rc < 0) + return rc; + + fsg_config_from_params(config, &mod_data); + gadget_dev = configfs_prepare(config); + if (IS_ERR_OR_NULL(gadget_dev)) { + rc = PTR_ERR(gadget_dev); + goto error_configfs; + } + config->configfs_nluns = config->nluns; + for (i = 0; i < config->nluns; ++i) { + dev_set_name(&LUN(i).dev, "lun%d", i); + LUN(i).dev.release = lun_release; + LUN(i).dev.parent = gadget_dev; + + rc = device_register(&LUN(i).dev); + if (rc) { + pr_info("failed to register LUN%d: %d\n", i, rc); + put_device(&LUN(i).dev); + goto error_release; + } + + rc = device_create_file(&LUN(i).dev, + LUN(i).cdrom + ? &dev_attr_ro_cdrom + : &dev_attr_ro); + if (rc) + goto error_ro; + rc = device_create_file(&LUN(i).dev, + LUN(i).removable + ? &dev_attr_file + : &dev_attr_file_nonremovable); + if (rc) + goto error_file; + rc = device_create_file(&LUN(i).dev, &dev_attr_nofua); + if (rc) + goto error_nofua; + + if (!LUN(i).filename && !LUN(i).removable) { + pr_err("no file given for LUN%d\n", i); + rc = -EINVAL; + goto error_filename; + } + } + + return 0; + +error_filename: + device_remove_file(&LUN(i).dev, &dev_attr_nofua); + +error_nofua: + device_remove_file(&LUN(i).dev, + LUN(i).removable + ? &dev_attr_file + : &dev_attr_file_nonremovable); + +error_file: + device_remove_file(&LUN(i).dev, + LUN(i).cdrom + ? &dev_attr_ro_cdrom + : &dev_attr_ro); + +error_ro: + device_unregister(&LUN(i).dev); + +error_release: + config->nluns = i; + devices_teardown(config); + configfs_teardown(config); + +error_configfs: + module_put(UFG_MODULE); + return rc; +} +module_init(msg_init); + +static void msg_cleanup(void) +{ + struct fsg_config *config = &config_struct; + + devices_teardown(config); + configfs_teardown(config); + module_put(UFG_MODULE); +} +module_exit(msg_cleanup); + +MODULE_DESCRIPTION("Mass Storage USB Gadget to UFG adapter"); +MODULE_AUTHOR("Andrzej Pietrasiewicz"); +MODULE_LICENSE("GPL"); + -- 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