As of now it supports HS only (SS is not yet supported). Only commands are passed (read/write requests are handled but TASK MANAGEMENT functions and others from this category are not). Temporary, it requires the transfer direction to be hardcoded. I need to figure out how to bypass informations to the gadget framework. Until then, enjoy the_only_tpg_I_currently_have. Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx> --- drivers/target/Kconfig | 1 + drivers/target/Makefile | 1 + drivers/target/uasp/Kconfig | 6 + drivers/target/uasp/Makefile | 5 + drivers/target/uasp/base.h | 39 ++ drivers/target/uasp/configfs.c | 518 +++++++++++++++++++++++++ drivers/target/uasp/configfs.h | 7 + drivers/target/uasp/fabric.c | 407 ++++++++++++++++++++ drivers/target/uasp/fabric.h | 39 ++ drivers/target/uasp/gadget.c | 770 ++++++++++++++++++++++++++++++++++++++ drivers/target/uasp/gadget.h | 81 ++++ drivers/target/uasp/gadget_ops.h | 13 + drivers/usb/gadget/Kconfig | 3 + 13 files changed, 1890 insertions(+), 0 deletions(-) create mode 100644 drivers/target/uasp/Kconfig create mode 100644 drivers/target/uasp/Makefile create mode 100644 drivers/target/uasp/base.h create mode 100644 drivers/target/uasp/configfs.c create mode 100644 drivers/target/uasp/configfs.h create mode 100644 drivers/target/uasp/fabric.c create mode 100644 drivers/target/uasp/fabric.h create mode 100644 drivers/target/uasp/gadget.c create mode 100644 drivers/target/uasp/gadget.h create mode 100644 drivers/target/uasp/gadget_ops.h diff --git a/drivers/target/Kconfig b/drivers/target/Kconfig index 7a7e442..d1ebc58 100644 --- a/drivers/target/Kconfig +++ b/drivers/target/Kconfig @@ -43,3 +43,4 @@ source "drivers/target/tcm_qla2xxx/Kconfig" source "drivers/target/tcm_vhost/Kconfig" endif +source "drivers/target/uasp/Kconfig" diff --git a/drivers/target/Makefile b/drivers/target/Makefile index 8ecfaae..5e1cd20 100644 --- a/drivers/target/Makefile +++ b/drivers/target/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_TCM_FC) += tcm_fc/ obj-$(CONFIG_ISCSI_TARGET) += iscsi/ obj-$(CONFIG_TCM_QLA2XXX) += tcm_qla2xxx/ obj-$(CONFIG_TCM_VHOST) += tcm_vhost/ +obj-$(CONFIG_TARGET_UASP) += uasp/ diff --git a/drivers/target/uasp/Kconfig b/drivers/target/uasp/Kconfig new file mode 100644 index 0000000..0d48a58 --- /dev/null +++ b/drivers/target/uasp/Kconfig @@ -0,0 +1,6 @@ +config TARGET_UASP + tristate "UASP fabric module" + depends on TARGET_CORE && CONFIGFS_FS + depends on USB_GADGET + ---help--- + Say Y here to enable the UASP fabric module diff --git a/drivers/target/uasp/Makefile b/drivers/target/uasp/Makefile new file mode 100644 index 0000000..e7237b9 --- /dev/null +++ b/drivers/target/uasp/Makefile @@ -0,0 +1,5 @@ +CFLAGS_gadget.o := -I$(srctree)/drivers/usb/gadget +tcm_uasp-objs := fabric.o \ + gadget.o \ + configfs.o +obj-$(CONFIG_TARGET_UASP) += tcm_uasp.o diff --git a/drivers/target/uasp/base.h b/drivers/target/uasp/base.h new file mode 100644 index 0000000..cfea496d --- /dev/null +++ b/drivers/target/uasp/base.h @@ -0,0 +1,39 @@ +#define UASP_VERSION "v0.1" +#define UASP_NAMELEN 32 + +struct uasp_nacl { + /* Binary World Wide unique Port Name for SAS Initiator port */ + u64 iport_wwpn; + /* ASCII formatted WWPN for Sas Initiator port */ + char iport_name[UASP_NAMELEN]; + /* Returned by uasp_make_nodeacl() */ + struct se_node_acl se_node_acl; +}; + +struct tcm_uasp_nexus { + struct se_session *tvn_se_sess; +}; + +struct uasp_tpg { + struct mutex tpg_mutex; + /* SAS port target portal group tag for TCM */ + u16 tport_tpgt; + /* Pointer back to uasp_tport */ + struct uasp_tport *tport; + /* Returned by uasp_make_tpg() */ + struct se_portal_group se_tpg; + u32 gadget_connect; + struct tcm_uasp_nexus *tpg_nexus; + atomic_t tpg_port_count; +}; + +struct uasp_tport { + /* SCSI protocol the tport is providing */ + u8 tport_proto_id; + /* Binary World Wide unique Port Name for SAS Target port */ + u64 tport_wwpn; + /* ASCII formatted WWPN for SAS Target port */ + char tport_name[UASP_NAMELEN]; + /* Returned by uasp_make_tport() */ + struct se_wwn tport_wwn; +}; diff --git a/drivers/target/uasp/configfs.c b/drivers/target/uasp/configfs.c new file mode 100644 index 0000000..01160ba --- /dev/null +++ b/drivers/target/uasp/configfs.c @@ -0,0 +1,518 @@ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <generated/utsrelease.h> +#include <linux/utsname.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <asm/unaligned.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> + +#include "base.h" +#include "fabric.h" +#include "gadget_ops.h" + +/* Local pointer to allocated TCM configfs fabric module */ +struct target_fabric_configfs *uasp_fabric_configfs; + +static const char *uasp_check_wwn(const char *name) +{ + const char *n; + unsigned int len; + + n = strstr(name, "naa."); + if (!n) + return NULL; + n += 4; + len = strlen(n); + if (len == 0 || len > UASP_NAMELEN - 1) + return NULL; + return n; +} + +static struct se_node_acl *uasp_make_nodeacl( + struct se_portal_group *se_tpg, + struct config_group *group, + const char *name) +{ + struct se_node_acl *se_nacl, *se_nacl_new; + struct uasp_nacl *nacl; + u64 wwpn = 0; + u32 nexus_depth; + const char *wnn_name; + + wnn_name = uasp_check_wwn(name); + if (!wnn_name) + return ERR_PTR(-EINVAL); + se_nacl_new = uasp_alloc_fabric_acl(se_tpg); + if (!(se_nacl_new)) + return ERR_PTR(-ENOMEM); +//#warning FIXME: Hardcoded nexus depth in uasp_make_nodeacl() + nexus_depth = 1; + /* + * se_nacl_new may be released by core_tpg_add_initiator_node_acl() + * when converting a NodeACL from demo mode -> explict + */ + se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new, + name, nexus_depth); + if (IS_ERR(se_nacl)) { + uasp_release_fabric_acl(se_tpg, se_nacl_new); + return se_nacl; + } + /* + * Locate our struct uasp_nacl and set the FC Nport WWPN + */ + nacl = container_of(se_nacl, struct uasp_nacl, se_node_acl); + nacl->iport_wwpn = wwpn; + snprintf(nacl->iport_name, sizeof(nacl->iport_name), "%s", name); + return se_nacl; +} + +static void uasp_drop_nodeacl(struct se_node_acl *se_acl) +{ + struct uasp_nacl *nacl = container_of(se_acl, + struct uasp_nacl, se_node_acl); + core_tpg_del_initiator_node_acl(se_acl->se_tpg, se_acl, 1); + kfree(nacl); +} + +struct uasp_tpg *the_only_tpg_I_currently_have; + +static struct se_portal_group *uasp_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct uasp_tport *tport = container_of(wwn, struct uasp_tport, + tport_wwn); + struct uasp_tpg *tpg; + unsigned long tpgt; + int ret; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX) + return ERR_PTR(-EINVAL); + + tpg = kzalloc(sizeof(struct uasp_tpg), GFP_KERNEL); + if (!tpg) { + printk(KERN_ERR "Unable to allocate struct uasp_tpg"); + return ERR_PTR(-ENOMEM); + } + mutex_init(&tpg->tpg_mutex); + atomic_set(&tpg->tpg_port_count, 0); + tpg->tport = tport; + tpg->tport_tpgt = tpgt; + + ret = core_tpg_register(&uasp_fabric_configfs->tf_ops, wwn, + &tpg->se_tpg, tpg, + TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) { + kfree(tpg); + return NULL; + } + if (the_only_tpg_I_currently_have) + BUG(); + the_only_tpg_I_currently_have = tpg; + return &tpg->se_tpg; +} + +static void uasp_drop_tpg(struct se_portal_group *se_tpg) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + + core_tpg_deregister(se_tpg); + kfree(tpg); + BUG_ON(!the_only_tpg_I_currently_have); + the_only_tpg_I_currently_have = NULL; +} + +static struct se_wwn *uasp_make_tport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct uasp_tport *tport; + const char *wnn_name; + u64 wwpn = 0; + + wnn_name = uasp_check_wwn(name); + if (!wnn_name) + return ERR_PTR(-EINVAL); + + tport = kzalloc(sizeof(struct uasp_tport), GFP_KERNEL); + if (!(tport)) { + printk(KERN_ERR "Unable to allocate struct uasp_tport"); + return ERR_PTR(-ENOMEM); + } + tport->tport_wwpn = wwpn; + snprintf(tport->tport_name, sizeof(tport->tport_name), wnn_name); + return &tport->tport_wwn; +} + +static void uasp_drop_tport(struct se_wwn *wwn) +{ + struct uasp_tport *tport = container_of(wwn, + struct uasp_tport, tport_wwn); + kfree(tport); +} + +static ssize_t uasp_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, "UASP fabric module %s on %s/%s" + "on "UTS_RELEASE"\n", UASP_VERSION, utsname()->sysname, + utsname()->machine); +} + +TF_WWN_ATTR_RO(uasp, version); + +static struct configfs_attribute *uasp_wwn_attrs[] = { + &uasp_wwn_version.attr, + NULL, +}; + +static ssize_t tcm_uasp_tpg_show_gadget_connect( + struct se_portal_group *se_tpg, + char *page) +{ + struct uasp_tpg *tpg = container_of(se_tpg, struct uasp_tpg, se_tpg); + + return snprintf(page, PAGE_SIZE, "%u\n", tpg->gadget_connect); +} + +static ssize_t tcm_uasp_tpg_store_gadget_connect( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct uasp_tpg *tpg = container_of(se_tpg, struct uasp_tpg, se_tpg); + unsigned int op; + ssize_t ret; + + ret = kstrtoul(page, 0, &op); + if (ret < 0) + return -EINVAL; + if (op > 1) + return -EINVAL; + + if (op && tpg->gadget_connect) + goto out; + if (!op && !tpg->gadget_connect) + goto out; + + if (op) { + ret = gadget_attach(); + if (ret) + goto out; + ret = count; + } else { + gadget_detach(); + } + tpg->gadget_connect = op; +out: + return ret; +} + +TF_TPG_BASE_ATTR(tcm_uasp, gadget_connect, S_IRUGO | S_IWUSR); + +static ssize_t tcm_uasp_tpg_show_nexus( + struct se_portal_group *se_tpg, + char *page) +{ + struct uasp_tpg *tpg = container_of(se_tpg, struct uasp_tpg, se_tpg); + struct tcm_uasp_nexus *tv_nexus; + ssize_t ret; + + mutex_lock(&tpg->tpg_mutex); + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + ret = -ENODEV; + goto out; + } + ret = snprintf(page, PAGE_SIZE, "%s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); +out: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int tcm_uasp_make_nexus(struct uasp_tpg *tpg, char *name) +{ + struct se_portal_group *se_tpg; + struct tcm_uasp_nexus *tv_nexus; + int ret; + + mutex_lock(&tpg->tpg_mutex); + if (tpg->tpg_nexus) { + ret = -EEXIST; + pr_debug("tpg->tpg_nexus already exists\n"); + goto err_unlock; + } + se_tpg = &tpg->se_tpg; + + ret = -ENOMEM; + tv_nexus = kzalloc(sizeof(*tv_nexus), GFP_KERNEL); + if (!tv_nexus) { + pr_err("Unable to allocate struct tcm_vhost_nexus\n"); + goto err_unlock; + } + tv_nexus->tvn_se_sess = transport_init_session(); + if (IS_ERR(tv_nexus->tvn_se_sess)) + goto err_free; + + /* + * Since we are running in 'demo mode' this call with generate a + * struct se_node_acl for the tcm_vhost struct se_portal_group with + * the SCSI Initiator port name of the passed configfs group 'name'. + */ + tv_nexus->tvn_se_sess->se_node_acl = core_tpg_check_initiator_node_acl( + se_tpg, name); + if (!tv_nexus->tvn_se_sess->se_node_acl) { + pr_debug("core_tpg_check_initiator_node_acl() failed" + " for %s\n", name); + goto err_session; + } + /* + * Now register the TCM vHost virtual I_T Nexus as active with the + * call to __transport_register_session() + */ + __transport_register_session(se_tpg, tv_nexus->tvn_se_sess->se_node_acl, + tv_nexus->tvn_se_sess, tv_nexus); + tpg->tpg_nexus = tv_nexus; + mutex_unlock(&tpg->tpg_mutex); + return 0; + +err_session: + transport_free_session(tv_nexus->tvn_se_sess); +err_free: + kfree(tv_nexus); +err_unlock: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int tcm_uasp_drop_nexus(struct uasp_tpg *tpg) +{ + struct se_session *se_sess; + struct tcm_uasp_nexus *tv_nexus; + int ret = -ENODEV; + + mutex_lock(&tpg->tpg_mutex); + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) + goto out; + + se_sess = tv_nexus->tvn_se_sess; + if (!se_sess) + goto out; + + if (atomic_read(&tpg->tpg_port_count)) { + ret = -EPERM; + pr_err("Unable to remove Host I_T Nexus with" + " active TPG port count: %d\n", + atomic_read(&tpg->tpg_port_count)); + goto out; + } + + pr_debug("Removing I_T Nexus to Initiator Port: %s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); + /* + * Release the SCSI I_T Nexus to the emulated vHost Target Port + */ + transport_deregister_session(tv_nexus->tvn_se_sess); + tpg->tpg_nexus = NULL; + + kfree(tv_nexus); +out: + mutex_unlock(&tpg->tpg_mutex); + return 0; +} + +static ssize_t tcm_uasp_tpg_store_nexus( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct uasp_tpg *tpg = container_of(se_tpg, struct uasp_tpg, se_tpg); + unsigned char i_port[UASP_NAMELEN], *ptr; + int ret; + + if (!strncmp(page, "NULL", 4)) { + ret = tcm_uasp_drop_nexus(tpg); + return (!ret) ? count : ret; + } + if (strlen(page) > UASP_NAMELEN) { + pr_err("Emulated NAA Sas Address: %s, exceeds" + " max: %d\n", page, UASP_NAMELEN); + return -EINVAL; + } + snprintf(i_port, UASP_NAMELEN, "%s", page); + + ptr = strstr(i_port, "naa."); + if (!ptr) { + pr_err("Missing 'naa.' prefix\n"); + return -EINVAL; + } + + if (i_port[strlen(i_port) - 1] == '\n') + i_port[strlen(i_port) - 1] = '\0'; + + ret = tcm_uasp_make_nexus(tpg, &i_port[4]); + if (ret < 0) + return ret; + return count; +} + +TF_TPG_BASE_ATTR(tcm_uasp, nexus, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *uasp_base_attrs[] = { + &tcm_uasp_tpg_gadget_connect.attr, + &tcm_uasp_tpg_nexus.attr, + NULL, +}; + +static int uasp_port_link(struct se_portal_group *se_tpg, struct se_lun *lun) +{ + struct uasp_tpg *tpg = container_of(se_tpg, struct uasp_tpg, se_tpg); + + atomic_inc(&tpg->tpg_port_count); + smp_mb__after_atomic_inc(); + return 0; +} + +static void uasp_port_unlink(struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct uasp_tpg *tpg = container_of(se_tpg, struct uasp_tpg, se_tpg); + + atomic_dec(&tpg->tpg_port_count); + smp_mb__after_atomic_dec(); +} + +int uasp_check_stop_free(struct se_cmd *se_cmd); + +static struct target_core_fabric_ops uasp_ops = { + .get_fabric_name = uasp_get_fabric_name, + .get_fabric_proto_ident = uasp_get_fabric_proto_ident, + .tpg_get_wwn = uasp_get_fabric_wwn, + .tpg_get_tag = uasp_get_tag, + .tpg_get_default_depth = uasp_get_default_depth, + .tpg_get_pr_transport_id = uasp_get_pr_transport_id, + .tpg_get_pr_transport_id_len = uasp_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = uasp_parse_pr_out_transport_id, + .tpg_check_demo_mode = uasp_check_true, + .tpg_check_demo_mode_cache = uasp_check_false, + .tpg_check_demo_mode_write_protect = uasp_check_false, + .tpg_check_prod_mode_write_protect = uasp_check_false, + .tpg_alloc_fabric_acl = uasp_alloc_fabric_acl, + .tpg_release_fabric_acl = uasp_release_fabric_acl, + .tpg_get_inst_index = uasp_tpg_get_inst_index, + .cmd_queue_supported = uasp_cmd_queue_supported, + .new_cmd_map = uasp_new_cmd, + .release_cmd = uasp_release_cmd, + .shutdown_session = uasp_shutdown_session, + .close_session = uasp_close_session, + .stop_session = uasp_stop_session, + .fall_back_to_erl0 = uasp_reset_nexus, + .sess_logged_in = uasp_sess_logged_in, + .sess_get_index = uasp_sess_get_index, + .sess_get_initiator_sid = NULL, + .write_pending = uasp_write_pending, + .write_pending_status = uasp_write_pending_status, + .set_default_node_attributes = uasp_set_default_node_attrs, + .get_task_tag = uasp_get_task_tag, + .get_cmd_state = uasp_get_cmd_state, + .queue_data_in = uasp_queue_data_in, + .queue_status = uasp_queue_status, + .queue_tm_rsp = uasp_queue_tm_rsp, + .get_fabric_sense_len = uasp_get_fabric_sense_len, + .set_fabric_sense_len = uasp_set_fabric_sense_len, + .is_state_remove = uasp_is_state_remove, + .check_stop_free = uasp_check_stop_free, + + /* + * Setup function pointers for generic logic in target_core_fabric_configfs.c + */ + .fabric_make_wwn = uasp_make_tport, + .fabric_drop_wwn = uasp_drop_tport, + .fabric_make_tpg = uasp_make_tpg, + .fabric_drop_tpg = uasp_drop_tpg, + .fabric_post_link = uasp_port_link, + .fabric_pre_unlink = uasp_port_unlink, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = uasp_make_nodeacl, + .fabric_drop_nodeacl = uasp_drop_nodeacl, +}; + +int uasp_register_configfs(void) +{ + struct target_fabric_configfs *fabric; + int ret; + + printk(KERN_INFO "UASP fabric module %s on %s/%s" + " on " UTS_RELEASE "\n", UASP_VERSION, utsname()->sysname, + utsname()->machine); + /* + * Register the top level struct config_item_type with TCM core + */ + fabric = target_fabric_configfs_init(THIS_MODULE, "uasp"); + if (IS_ERR(fabric)) { + printk(KERN_ERR "target_fabric_configfs_init() failed\n"); + return PTR_ERR(fabric); + } + /* + * Setup fabric->tf_ops from our local uasp_ops + */ + fabric->tf_ops = uasp_ops; + /* + * Setup default attribute lists for various fabric->tf_cit_tmpl + */ + TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = uasp_wwn_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = uasp_base_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; + /* + * Register the fabric for use within TCM + */ + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + printk(KERN_ERR "target_fabric_configfs_register() failed" + " for UASP\n"); + return ret; + } + /* + * Setup our local pointer to *fabric + */ + uasp_fabric_configfs = fabric; + printk(KERN_INFO "UASP[0] - Set fabric -> uasp_fabric_configfs\n"); + return 0; +}; + +void uasp_deregister_configfs(void) +{ + if (!(uasp_fabric_configfs)) + return; + + target_fabric_configfs_deregister(uasp_fabric_configfs); + uasp_fabric_configfs = NULL; + printk(KERN_INFO "UASP[0] - Cleared uasp_fabric_configfs\n"); +}; diff --git a/drivers/target/uasp/configfs.h b/drivers/target/uasp/configfs.h new file mode 100644 index 0000000..646c096 --- /dev/null +++ b/drivers/target/uasp/configfs.h @@ -0,0 +1,7 @@ +#ifndef __UASP_CONFIGFS_H_ +#define __UASP_CONFIGFS_H_ + +int uasp_register_configfs(void); +void uasp_deregister_configfs(void); + +#endif diff --git a/drivers/target/uasp/fabric.c b/drivers/target/uasp/fabric.c new file mode 100644 index 0000000..7f16709 --- /dev/null +++ b/drivers/target/uasp/fabric.c @@ -0,0 +1,407 @@ +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <asm/unaligned.h> +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/libfc.h> +#include <scsi/scsi_tcq.h> + +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/target_core_configfs.h> + +#include "base.h" +#include "fabric.h" +#include "gadget_ops.h" +#include "gadget.h" + +#define UAS_SIMPLE_TAG 0 +#define UAS_HEAD_TAG 1 +#define UAS_ORDERED_TAG 2 +#define UAS_ACA 4 + +extern struct uasp_tpg *the_only_tpg_I_currently_have; + +void uasp_submit_command(struct f_uas *fu, void *cmdbuf, unsigned int len) +{ + struct command_iu *cmd_iu = cmdbuf; + struct uasp_cmd *cmd; + struct uasp_tpg *tpg; + struct se_session *se_sess; + struct se_cmd *se_cmd; + struct tcm_uasp_nexus *tv_nexus; + u32 cmd_len; + u32 prio; + u32 unpacked_lun; + int ret; + + if (cmd_iu->iu_id != IU_ID_COMMAND) { + pr_err("Unsupported type %d\n", cmd_iu->iu_id); + return; + } + + cmd = kzalloc(sizeof *cmd, GFP_ATOMIC); + if (!cmd) + return; + + cmd->fu = fu; + + /* XXX until I figure out why I can't free in on complete */ + kref_init(&cmd->ref); + kref_get(&cmd->ref); + + tpg = the_only_tpg_I_currently_have; + + cmd_len = cmd_iu->len + 16; + if (cmd_len > UASP_MAX_CMD) { + pr_err("%s() CDB too long\n", __func__); + goto err; + } + + memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); + + cmd->tag = le16_to_cpup(&cmd_iu->tag); + se_cmd = &cmd->tvc_se_cmd; + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + goto err; + } + + se_sess = tv_nexus->tvn_se_sess; + + /* XXX HS */ + if (0) { + switch (cmd_iu->prio_attr) { + case UAS_HEAD_TAG: + prio = MSG_HEAD_TAG; + break; + case UAS_ORDERED_TAG: + prio = MSG_ORDERED_TAG; + break; + + case UAS_ACA: + prio = MSG_ACA_TAG; + break; + + default: + pr_debug_once("Unsupported prio_attr: %02x.\n", + cmd_iu->prio_attr); + case UAS_SIMPLE_TAG: + prio = MSG_SIMPLE_TAG; + break; + } + } else + prio = MSG_SIMPLE_TAG; + + cmd->data_dir = cmd_iu->rsvd5; + se_cmd->unknown_data_length = 1; + transport_init_se_cmd(se_cmd, tpg->se_tpg.se_tpg_tfo, se_sess, + 0, cmd->data_dir, + prio, cmd->sense_iu.sense); + + unpacked_lun = scsilun_to_int(&cmd_iu->lun); + ret = transport_lookup_cmd_lun(se_cmd, unpacked_lun); + if (ret) { + pr_err("ERR: %s(%d)\n", __func__, __LINE__); + goto err; + } + + ret = transport_generic_handle_cdb_map(&cmd->tvc_se_cmd); + if (ret) { + pr_err("ERR: %s(%d)\n", __func__, __LINE__); + goto err; + } + return; +err: + kfree(cmd); +} + +int uasp_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +int uasp_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +char *uasp_get_fabric_name(void) +{ + return "uasp"; +} + +u8 uasp_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + struct uasp_tport *tport = tpg->tport; + u8 proto_id; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + proto_id = sas_get_fabric_proto_ident(se_tpg); + break; + } + + return proto_id; +} + +char *uasp_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + struct uasp_tport *tport = tpg->tport; + + return &tport->tport_name[0]; +} + +u16 uasp_get_tag(struct se_portal_group *se_tpg) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + return tpg->tport_tpgt; +} + +u32 uasp_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +u32 uasp_get_pr_transport_id( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code, + unsigned char *buf) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + struct uasp_tport *tport = tpg->tport; + int ret = 0; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + ret = sas_get_pr_transport_id(se_tpg, se_nacl, pr_reg, + format_code, buf); + break; + } + + return ret; +} + +u32 uasp_get_pr_transport_id_len( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + struct uasp_tport *tport = tpg->tport; + int ret = 0; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + ret = sas_get_pr_transport_id_len(se_tpg, se_nacl, pr_reg, + format_code); + break; + } + + return ret; +} + +char *uasp_parse_pr_out_transport_id( + struct se_portal_group *se_tpg, + const char *buf, + u32 *out_tid_len, + char **port_nexus_ptr) +{ + struct uasp_tpg *tpg = container_of(se_tpg, + struct uasp_tpg, se_tpg); + struct uasp_tport *tport = tpg->tport; + char *tid = NULL; + + switch (tport->tport_proto_id) { + case SCSI_PROTOCOL_SAS: + default: + tid = sas_parse_pr_out_transport_id(se_tpg, buf, out_tid_len, + port_nexus_ptr); + } + + return tid; +} + +struct se_node_acl *uasp_alloc_fabric_acl(struct se_portal_group *se_tpg) +{ + struct uasp_nacl *nacl; + + nacl = kzalloc(sizeof(struct uasp_nacl), GFP_KERNEL); + if (!nacl) { + printk(KERN_ERR "Unable to alocate struct uasp_nacl\n"); + return NULL; + } + + return &nacl->se_node_acl; +} + +void uasp_release_fabric_acl( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl) +{ + struct uasp_nacl *nacl = container_of(se_nacl, + struct uasp_nacl, se_node_acl); + kfree(nacl); +} + +u32 uasp_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + return 1; +} + +int uasp_cmd_queue_supported(struct se_cmd *se_cmd) +{ + return 0; +} + +int uasp_new_cmd(struct se_cmd *se_cmd) +{ + struct uasp_cmd *cmd = container_of(se_cmd, struct uasp_cmd, + tvc_se_cmd); + int ret; + + ret = transport_generic_allocate_tasks(se_cmd, cmd->cmd_buf); + if (ret) + return ret; + + return transport_generic_map_mem_to_cmd(se_cmd, NULL, 0, 0, 0); +} + +void uasp_cmd_release(struct kref *ref) +{ + struct uasp_cmd *cmd = container_of(ref, struct uasp_cmd, + ref); + + kfree(cmd->data_buf); + transport_generic_free_cmd(&cmd->tvc_se_cmd, 0); +} + +void uasp_release_cmd(struct se_cmd *se_cmd) +{ + struct uasp_cmd *cmd = container_of(se_cmd, struct uasp_cmd, + tvc_se_cmd); + kfree(cmd); + return; +} + +int uasp_shutdown_session(struct se_session *se_sess) +{ + return 0; +} + +void uasp_close_session(struct se_session *se_sess) +{ + return; +} + +void uasp_stop_session(struct se_session *se_sess, int sess_sleep, + int conn_sleep) +{ + return; +} + +void uasp_reset_nexus(struct se_session *se_sess) +{ + return; +} + +int uasp_sess_logged_in(struct se_session *se_sess) +{ + return 0; +} + +u32 uasp_sess_get_index(struct se_session *se_sess) +{ + return 0; +} + +int uasp_write_pending(struct se_cmd *se_cmd) +{ + guas_send_write_request(se_cmd); + return 0; +} + +int uasp_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +void uasp_set_default_node_attrs(struct se_node_acl *nacl) +{ + return; +} + +u32 uasp_get_task_tag(struct se_cmd *se_cmd) +{ + return 0; +} + +int uasp_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +#include <target/target_core_backend.h> + +int uasp_queue_data_in(struct se_cmd *se_cmd) +{ + guas_send_read_response(se_cmd); + return 0; +} + +int uasp_queue_status(struct se_cmd *se_cmd) +{ + guas_send_status_response(se_cmd); + return 0; +} + +int uasp_queue_tm_rsp(struct se_cmd *se_cmd) +{ + return 0; +} + +u16 uasp_set_fabric_sense_len(struct se_cmd *se_cmd, u32 sense_length) +{ + return 0; +} + +u16 uasp_get_fabric_sense_len(void) +{ + return 0; +} + +int uasp_is_state_remove(struct se_cmd *se_cmd) +{ + return 0; +} + +int uasp_check_stop_free(struct se_cmd *se_cmd) +{ + struct uasp_cmd *cmd = container_of(se_cmd, struct uasp_cmd, + tvc_se_cmd); + + kref_put(&cmd->ref, uasp_cmd_release); + return 1; +} diff --git a/drivers/target/uasp/fabric.h b/drivers/target/uasp/fabric.h new file mode 100644 index 0000000..5cb982e --- /dev/null +++ b/drivers/target/uasp/fabric.h @@ -0,0 +1,39 @@ +int uasp_check_true(struct se_portal_group *); +int uasp_check_false(struct se_portal_group *); +char *uasp_get_fabric_name(void); +u8 uasp_get_fabric_proto_ident(struct se_portal_group *); +char *uasp_get_fabric_wwn(struct se_portal_group *); +u16 uasp_get_tag(struct se_portal_group *); +u32 uasp_get_default_depth(struct se_portal_group *); +u32 uasp_get_pr_transport_id(struct se_portal_group *, + struct se_node_acl *, struct t10_pr_registration *, + int *, unsigned char *); +u32 uasp_get_pr_transport_id_len(struct se_portal_group *, + struct se_node_acl *, struct t10_pr_registration *, + int *); +char *uasp_parse_pr_out_transport_id(struct se_portal_group *, + const char *, u32 *, char **); +struct se_node_acl *uasp_alloc_fabric_acl(struct se_portal_group *); +void uasp_release_fabric_acl(struct se_portal_group *, + struct se_node_acl *); +u32 uasp_tpg_get_inst_index(struct se_portal_group *); +int uasp_new_cmd(struct se_cmd *se_cmd); +int uasp_cmd_queue_supported(struct se_cmd *se_cmd); +void uasp_release_cmd(struct se_cmd *); +int uasp_shutdown_session(struct se_session *); +void uasp_close_session(struct se_session *); +void uasp_stop_session(struct se_session *, int, int); +void uasp_reset_nexus(struct se_session *); +int uasp_sess_logged_in(struct se_session *); +u32 uasp_sess_get_index(struct se_session *); +int uasp_write_pending(struct se_cmd *); +int uasp_write_pending_status(struct se_cmd *); +void uasp_set_default_node_attrs(struct se_node_acl *); +u32 uasp_get_task_tag(struct se_cmd *); +int uasp_get_cmd_state(struct se_cmd *); +int uasp_queue_data_in(struct se_cmd *); +int uasp_queue_status(struct se_cmd *); +int uasp_queue_tm_rsp(struct se_cmd *); +u16 uasp_set_fabric_sense_len(struct se_cmd *, u32); +u16 uasp_get_fabric_sense_len(void); +int uasp_is_state_remove(struct se_cmd *); diff --git a/drivers/target/uasp/gadget.c b/drivers/target/uasp/gadget.c new file mode 100644 index 0000000..5b7ccaf --- /dev/null +++ b/drivers/target/uasp/gadget.c @@ -0,0 +1,770 @@ + +#include <linux/kernel.h> +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> +#include <linux/usb/storage.h> + +#include <target/target_core_base.h> + +#include "gadget.h" +#include "gadget_ops.h" +#include "configfs.h" + +#include "usbstring.c" +#include "epautoconf.c" +#include "config.c" +#include "composite.c" + +#define UAS_G_STR_MANUFACTOR 1 +#define UAS_G_STR_PRODUCT 2 +#define UAS_G_STR_SERIAL 3 +#define UAS_G_STR_CONFIG 4 +#define UAS_G_STR_INTERFACE 5 + +#define UASP_SS_EP_COMP_NUM_STREAMS 0 + +struct f_uas { + struct usb_function function; + u16 iface; + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_status; + struct usb_ep *ep_cmd; + + struct usb_request *req_in; + struct usb_request *req_out; + struct usb_request *req_status; + struct usb_request *req_cmd; + + void *cmd_buff; + u32 flags; +#define UASP_ACTIVE (1 << 0) +#define UASP_SS_MODE (1 << 1) +}; + +static struct usb_interface_descriptor uasp_intf_desc = { + .bLength = sizeof(uasp_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 4, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, + .bInterfaceProtocol = USB_PR_UAS, + .iInterface = UAS_G_STR_INTERFACE, +}; + +static struct usb_endpoint_descriptor uasp_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_pipe_usage_descriptor uasp_bi_pipe_desc = { + .bLength = sizeof(uasp_bi_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = PIPE_ID_DATA_IN, +}; + +static struct usb_endpoint_descriptor uasp_ss_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { + .bLength = sizeof(uasp_bi_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, /* + * Doesn't support burst. Maybe update later? + * Should it be HW dependent? + */ + .bmAttributes = UASP_SS_EP_COMP_NUM_STREAMS, + .wBytesPerInterval = 0, +}; + +static struct usb_endpoint_descriptor uasp_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_pipe_usage_descriptor uasp_bo_pipe_desc = { + .bLength = sizeof(uasp_bo_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = PIPE_ID_DATA_OUT, + .Reserved = 0, +}; + +static struct usb_endpoint_descriptor uasp_ss_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(0x400), +}; + +static struct usb_ss_ep_comp_descriptor uasp_bulk_out_ep_comp_desc = { + .bLength = sizeof(uasp_bulk_out_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bmAttributes = UASP_SS_EP_COMP_NUM_STREAMS, +}; + +static struct usb_endpoint_descriptor uasp_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_pipe_usage_descriptor uasp_status_pipe_desc = { + .bLength = sizeof(uasp_status_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = PIPE_ID_STS, +}; + +static struct usb_endpoint_descriptor uasp_ss_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_status_in_ep_comp_desc = { + .bLength = sizeof(uasp_status_in_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bmAttributes = UASP_SS_EP_COMP_NUM_STREAMS, +}; + +static struct usb_endpoint_descriptor uasp_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_pipe_usage_descriptor uasp_cmd_pipe_desc = { + .bLength = sizeof(uasp_cmd_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = PIPE_ID_CMD, +}; + +static struct usb_endpoint_descriptor uasp_ss_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_cmd_comp_desc = { + .bLength = sizeof(uasp_cmd_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *uasp_hs_function_desc[] = { + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_status_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +static struct usb_descriptor_header *uasp_ss_function_desc[] = { + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_ss_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_bo_desc, + (struct usb_descriptor_header *) &uasp_bulk_out_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_status_desc, + (struct usb_descriptor_header *) &uasp_status_in_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_comp_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +#define UAS_VENDOR_ID 0x0525 /* NetChip */ +#define UAS_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + +static struct usb_device_descriptor uas_device_desc = { + .bLength = sizeof(uas_device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = cpu_to_le16(UAS_VENDOR_ID), + .idProduct = cpu_to_le16(UAS_PRODUCT_ID), + .iManufacturer = UAS_G_STR_MANUFACTOR, + .iProduct = UAS_G_STR_PRODUCT, + .iSerialNumber = UAS_G_STR_SERIAL, + + .bNumConfigurations = 1, +}; + +static struct usb_string uas_us_strings[] = { + { UAS_G_STR_MANUFACTOR, "UAS Manufactor"}, + { UAS_G_STR_PRODUCT, "UAS Product"}, + { UAS_G_STR_SERIAL, "UAS Serial"}, + { UAS_G_STR_CONFIG, "UAS Config"}, + { UAS_G_STR_INTERFACE, "UAS Interface"}, + { }, +}; + +static struct usb_gadget_strings uas_stringtab = { + .language = 0x0409, + .strings = uas_us_strings, +}; + +static struct usb_gadget_strings *uas_strings[] = { + &uas_stringtab, + NULL, +}; + +static int guas_unbind(struct usb_composite_dev *cdev) +{ + return 0; +} + +static struct usb_configuration uasp_config_driver = { + .label = "Linux UASP Storage", + .bConfigurationValue = 1, + .iConfiguration = UAS_G_STR_CONFIG, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +static struct f_uas *to_f_uas(struct usb_function *f) +{ + return container_of(f, struct f_uas, function); +} + +static void give_back_ep(struct usb_ep **pep) +{ + struct usb_ep *ep = *pep; + if (!ep) + return; + ep->driver_data = NULL; +} + +static int uasp_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_gadget *gadget = c->cdev->gadget; + struct usb_ep *ep; + int iface; + + iface = usb_interface_id(c, f); + if (iface < 0) + return iface; + + uasp_intf_desc.bInterfaceNumber = iface; + fu->iface = iface; + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc, + &uasp_bi_ep_comp_desc); + if (!ep) + goto ep_fail; + + ep->driver_data = fu; + fu->ep_in = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc, + &uasp_bulk_out_ep_comp_desc); + if (!ep) + goto ep_fail; + ep->driver_data = fu; + fu->ep_out = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc, + &uasp_status_in_ep_comp_desc); + if (!ep) + goto ep_fail; + ep->driver_data = fu; + fu->ep_status = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc, + &uasp_cmd_comp_desc); + if (!ep) + goto ep_fail; + ep->driver_data = fu; + fu->ep_cmd = ep; + + /* Assume endpoint addresses are the same for both speeds */ + uasp_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; + uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_status_desc.bEndpointAddress = + uasp_ss_status_desc.bEndpointAddress; + uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + f->ss_descriptors = uasp_ss_function_desc; + return 0; +ep_fail: + pr_err("Can't claim all required eps\n"); + + give_back_ep(&fu->ep_in); + give_back_ep(&fu->ep_out); + give_back_ep(&fu->ep_status); + give_back_ep(&fu->ep_cmd); + return -ENOTSUPP; +} + +static void uasp_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + kfree(fu); +} + +static void uasp_cleanup_old_alt(struct f_uas *fu) +{ + if (!(fu->flags & UASP_ACTIVE)) + return; + fu->flags = 0; + + usb_ep_disable(fu->ep_in); + usb_ep_disable(fu->ep_out); + usb_ep_disable(fu->ep_status); + usb_ep_disable(fu->ep_cmd); + + usb_ep_free_request(fu->ep_in, fu->req_in); + usb_ep_free_request(fu->ep_out, fu->req_out); + usb_ep_free_request(fu->ep_status, fu->req_status); + usb_ep_free_request(fu->ep_cmd, fu->req_cmd); + + fu->req_in = NULL; + fu->req_out = NULL; + fu->req_status = NULL; + fu->req_cmd = NULL; + + kfree(fu->cmd_buff); + fu->cmd_buff = NULL; +} + +struct delayed_queue_item { + struct usb_ep *ep; + struct usb_request *req; + struct list_head lh; +}; + +void uasp_cmd_release(struct kref *ref); + +static void guas_cleanup_cmd(struct uasp_cmd *cmd) +{ + kref_put(&cmd->ref, uasp_cmd_release); +} + +static void guas_status_data_cmpl(struct usb_ep *ep, struct usb_request *req); +static int guas_prepare_r_request(struct uasp_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->tvc_se_cmd; + + if (1) { + /* XXX no SG support */ + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + sg_copy_to_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + + fu->req_in->buf = cmd->data_buf; + } else { + /* XXX setup sgl */ + } + + fu->req_in->complete = guas_status_data_cmpl; + fu->req_in->length = se_cmd->data_length; + fu->req_in->context = cmd; + + cmd->state = UASP_SEND_STATUS; + return 0; +} + +static void guas_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct uasp_cmd *cmd = req->context; + struct se_cmd *se_cmd = &cmd->tvc_se_cmd; + struct f_uas *fu = cmd->fu; + + if (req->status < 0) { + pr_err("%s() state %d transfer failed\n", __func__, cmd->state); + goto cleanup; + } + + sg_copy_from_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + + complete(&cmd->write_complete); + return; + + /* Sync with next command */ +cleanup: + guas_cleanup_cmd(cmd); + usb_ep_queue(fu->ep_cmd, fu->req_cmd, GFP_ATOMIC); +} + +static int guas_prepare_w_request(struct uasp_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->tvc_se_cmd; + + if (1) { + /* XXX no SG support */ + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + fu->req_out->buf = cmd->data_buf; + } else { + /* XXX setup sgl */ + } + + fu->req_out->complete = guas_data_write_cmpl; + fu->req_out->length = se_cmd->data_length; + fu->req_out->context = cmd; + return 0; +} + +static void guas_prepare_status(struct uasp_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->tvc_se_cmd; + struct sense_iu *iu = &cmd->sense_iu; + + cmd->state = UASP_QUEUE_COMMAND; + iu->iu_id = IU_ID_STATUS; + /* we recycle tag from last transfer */ + + /* iu->status_qual = cpu_to_be16(STATUS QUALIFIER SAM-4. Where are you?); */ + iu->len = cpu_to_be16(se_cmd->scsi_sense_length); + iu->status = se_cmd->scsi_status; + fu->req_status->length = se_cmd->scsi_sense_length + 16; + fu->req_status->buf = iu; +} + +static void guas_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct uasp_cmd *cmd = req->context; + struct f_uas *fu = cmd->fu; + int ret; + + if (req->status < 0) { + pr_err("%s() state %d transfer failed\n", __func__, cmd->state); + goto cleanup; + } + + switch (cmd->state) { + case UASP_SEND_DATA: + ret = guas_prepare_r_request(cmd); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_in, fu->req_in, GFP_ATOMIC); + break; + + case UASP_RECEIVE_DATA: + ret = guas_prepare_w_request(cmd); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, fu->req_out, GFP_ATOMIC); + break; + + case UASP_SEND_STATUS: + guas_prepare_status(cmd); + ret = usb_ep_queue(fu->ep_status, fu->req_status, GFP_ATOMIC); + break; + + case UASP_QUEUE_COMMAND: + guas_cleanup_cmd(cmd); + usb_ep_queue(fu->ep_cmd, fu->req_cmd, GFP_ATOMIC); + break; + + default: + BUG(); + }; + return; + + /* Sync with next command */ +cleanup: + pr_err("%s() cleanup\n", __func__); + guas_cleanup_cmd(cmd); + usb_ep_queue(fu->ep_cmd, fu->req_cmd, GFP_ATOMIC); +} + +void guas_send_status_response(struct se_cmd *se_cmd) +{ + struct uasp_cmd *cmd = container_of(se_cmd, struct uasp_cmd, + tvc_se_cmd); + struct f_uas *fu = cmd->fu; + int ret; + + fu->req_status->complete = guas_status_data_cmpl; + fu->req_status->context = cmd; + cmd->fu = fu; + guas_prepare_status(cmd); + ret = usb_ep_queue(fu->ep_status, fu->req_status, GFP_ATOMIC); +} + +void guas_send_read_response(struct se_cmd *se_cmd) +{ + struct uasp_cmd *cmd = container_of(se_cmd, struct uasp_cmd, + tvc_se_cmd); + struct f_uas *fu = cmd->fu; + struct sense_iu *iu = &cmd->sense_iu; + + fu->req_status->context = cmd; + cmd->fu = fu; + + iu->tag = cpu_to_be16(cmd->tag); + /* On HS to send a READ request while on SS we can send the data */ + if (1) { + int ret; + + /* HS */ + iu->iu_id = IU_ID_READ_READY; + fu->req_status->complete = guas_status_data_cmpl; + + cmd->state = UASP_SEND_DATA; + fu->req_status->buf = iu; + fu->req_status->length = sizeof(struct read_write_iu); + + ret = usb_ep_queue(fu->ep_status, fu->req_status, GFP_ATOMIC); + } else { + /* SS XXX */ + } +} + +void guas_send_write_request(struct se_cmd *se_cmd) +{ + struct uasp_cmd *cmd = container_of(se_cmd, struct uasp_cmd, + tvc_se_cmd); + struct f_uas *fu = cmd->fu; + struct sense_iu *iu = &cmd->sense_iu; + + init_completion(&cmd->write_complete); + fu->req_status->context = cmd; + cmd->fu = fu; + + iu->tag = cpu_to_be16(cmd->tag); + /* On HS to send a WRITE request while on SS we can read the data */ + + if (1) { + int ret; + + iu->iu_id = IU_ID_WRITE_READY; + fu->req_status->complete = guas_status_data_cmpl; + + cmd->state = UASP_RECEIVE_DATA; + fu->req_status->buf = iu; + fu->req_status->length = sizeof(struct read_write_iu); + + ret = usb_ep_queue(fu->ep_status, fu->req_status, GFP_ATOMIC); + } else { + /* SS XXX */ + } + + wait_for_completion(&cmd->write_complete); + transport_generic_process_write(se_cmd); +} + +static void guas_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uas *fu = req->context; + + if (req->status < 0) { + pr_err("%s() bad status\n", __func__); + return; + } + + uasp_submit_command(fu, req->buf, req->actual); +} + +static int uasp_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_gadget *gadget = f->config->cdev->gadget; + int ret; + + uasp_cleanup_old_alt(fu); + + if (gadget->speed == USB_SPEED_SUPER) + fu->flags |= UASP_SS_MODE; + + config_ep_by_speed(gadget, f, fu->ep_in); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed(gadget, f, fu->ep_out); + ret = usb_ep_enable(fu->ep_out); + if (ret) + goto err_b_out; + + config_ep_by_speed(gadget, f, fu->ep_cmd); + ret = usb_ep_enable(fu->ep_cmd); + if (ret) + goto err_cmd; + config_ep_by_speed(gadget, f, fu->ep_status); + ret = usb_ep_enable(fu->ep_status); + if (ret) + goto err_status; + + ret = -ENOMEM; + fu->req_in = usb_ep_alloc_request(fu->ep_in, GFP_ATOMIC); + if (!fu->req_in) + goto err_req_bi; + + fu->req_out = usb_ep_alloc_request(fu->ep_out, GFP_ATOMIC); + if (!fu->req_out) + goto err_req_bo; + + fu->req_status = usb_ep_alloc_request(fu->ep_status, GFP_ATOMIC); + if (!fu->req_status) + goto err_req_status; + + fu->req_cmd = usb_ep_alloc_request(fu->ep_cmd, GFP_ATOMIC); + if (!fu->req_cmd) + goto err_req_cmd; + + fu->cmd_buff = kmalloc(fu->ep_cmd->maxpacket, GFP_ATOMIC); + if (!fu->cmd_buff) + goto err_cmd_buf; + + fu->req_cmd->complete = guas_cmd_complete; + fu->req_cmd->buf = fu->cmd_buff; + fu->req_cmd->length = fu->ep_cmd->maxpacket; + fu->req_cmd->context = fu; + + /* + * XXX On HS one should be enough but on SS we need to setup one req + * for each stream.... + */ + ret = usb_ep_queue(fu->ep_cmd, fu->req_cmd, GFP_ATOMIC); + if (ret) + goto err_enqueue; + + fu->flags |= UASP_ACTIVE; + return 0; + +err_enqueue: + kfree(fu->cmd_buff); + fu->cmd_buff = NULL; +err_cmd_buf: + usb_ep_free_request(fu->ep_cmd, fu->req_cmd); + fu->req_cmd = NULL; +err_req_cmd: + usb_ep_free_request(fu->ep_status, fu->req_status); + fu->req_status = NULL; +err_req_status: + usb_ep_free_request(fu->ep_out, fu->req_out); + fu->req_out = NULL; +err_req_bo: + usb_ep_free_request(fu->ep_in, fu->req_in); + fu->req_in = NULL; +err_req_bi: + usb_ep_disable(fu->ep_status); +err_status: + usb_ep_disable(fu->ep_cmd); +err_cmd: + usb_ep_disable(fu->ep_out); +err_b_out: + usb_ep_disable(fu->ep_in); +err_b_in: + fu->flags = 0; + return ret; +} + +static void uasp_disable(struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + uasp_cleanup_old_alt(fu); +} + +static int uas_cfg_bind(struct usb_configuration *c) +{ + struct f_uas *fu; + int ret; + + fu = kzalloc(sizeof(*fu), GFP_KERNEL); + if (!fu) + return -ENOMEM; + fu->function.name = "UASP Function"; + fu->function.descriptors = uasp_hs_function_desc; + fu->function.hs_descriptors = uasp_hs_function_desc; + fu->function.bind = uasp_bind; + fu->function.unbind = uasp_unbind; + fu->function.set_alt = uasp_set_alt; + fu->function.disable = uasp_disable; + + ret = usb_add_function(c, &fu->function); + if (ret) + goto err; + return 0; +err: + kfree(fu); + return ret; +} + +static int guas_bind(struct usb_composite_dev *cdev) +{ + int ret; + + ret = usb_add_config(cdev, &uasp_config_driver, + uas_cfg_bind); + return 0; +} + +static struct usb_composite_driver uas_driver = { + .name = "g_uas", + .dev = &uas_device_desc, + .strings = uas_strings, + .max_speed = USB_SPEED_SUPER, + .unbind = guas_unbind, +}; + +int gadget_attach(void) +{ + return usb_composite_probe(&uas_driver, guas_bind); +} + +void gadget_detach(void) +{ + usb_composite_unregister(&uas_driver); +} + +static int __init guas_init(void) +{ + int ret; + + ret = uasp_register_configfs(); + return ret; +} +module_init(guas_init); + +static void __exit guas_exit(void) +{ + uasp_deregister_configfs(); +} +module_exit(guas_exit); + +MODULE_AUTHOR("Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("UAS faabric"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/target/uasp/gadget.h b/drivers/target/uasp/gadget.h new file mode 100644 index 0000000..cb5965e --- /dev/null +++ b/drivers/target/uasp/gadget.h @@ -0,0 +1,81 @@ +#ifndef __GTARGET_UAS_H__ +#define __GTARGET_UAS_H__ + +#include <scsi/scsi.h> +#include <target/target_core_fabric.h> + +#define IU_ID_COMMAND 0x01 +#define IU_ID_STATUS 0x03 +#define IU_ID_RESPONSE 0x04 +#define IU_ID_TASK_MGMT 0x05 +#define IU_ID_READ_READY 0x06 +#define IU_ID_WRITE_READY 0x07 + +struct usb_pipe_usage_descriptor { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bPipeID; + /* Pipe ID defenitions: Table 9 from UAS spec*/ +#define PIPE_ID_CMD 0x01 /* Command pipe */ +#define PIPE_ID_STS 0x02 /* Status pipe */ +#define PIPE_ID_DATA_IN 0x03 /* Data-in piep */ +#define PIPE_ID_DATA_OUT 0x04 /* Data-out pipe */ + __u8 Reserved; +} __attribute__((__packed__)); + +struct command_iu { + __u8 iu_id; + __u8 rsvd1; + __be16 tag; + __u8 prio_attr; + __u8 rsvd5; + __u8 len; + __u8 rsvd7; + struct scsi_lun lun; + __u8 cdb[16]; +} __packed; + +struct sense_iu { + __u8 iu_id; + __u8 rsvd1; + __be16 tag; + __be16 status_qual; + __u8 status; + __u8 rsvd7[7]; + __be16 len; + __u8 sense[SCSI_SENSE_BUFFERSIZE]; +} __packed; + +struct read_write_iu { + __u8 iu_id; + __u8 rsvd1; + __be16 tag; +} __packed; + +enum uasp_state { + UASP_SEND_DATA, + UASP_RECEIVE_DATA, + UASP_SEND_STATUS, + UASP_QUEUE_COMMAND, +}; + +#include <linux/kref.h> + +#define UASP_MAX_CMD 64 +struct uasp_cmd { + u8 cmd_buf[UASP_MAX_CMD]; + u16 tag; + u16 data_dir; + u32 cmd_len; + atomic_t users; + struct se_cmd tvc_se_cmd; + struct sense_iu sense_iu; + void *data_buf; + enum uasp_state state; + struct f_uas *fu; + struct completion write_complete; + struct kref ref; +}; + +#endif diff --git a/drivers/target/uasp/gadget_ops.h b/drivers/target/uasp/gadget_ops.h new file mode 100644 index 0000000..a839004 --- /dev/null +++ b/drivers/target/uasp/gadget_ops.h @@ -0,0 +1,13 @@ +#ifndef __GADGET_OPS_H__ +#define __GADGET_OPS_H__ + +struct f_uas; + +int gadget_attach(void); +void gadget_detach(void); +void uasp_submit_command(struct f_uas *fu, void *cmdbuf, unsigned int len); +void guas_send_read_response(struct se_cmd *se_cmd); +void guas_send_status_response(struct se_cmd *se_cmd); +void guas_send_write_request(struct se_cmd *se_cmd); + +#endif diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 23a4473..99beba9 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -802,6 +802,9 @@ config USB_MASS_STORAGE Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_mass_storage". +comment "You need to go to the target framework for UASP support" + depends on TARGET_UASP=n + config USB_G_SERIAL tristate "Serial Gadget (with CDC ACM and CDC OBEX support)" help -- 1.7.7.3 -- 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