| // SPDX-License-Identifier: GPL-2.0-or-later |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| #include <linux/configfs.h> |
| #include <scsi/scsi.h> |
| #include <scsi/scsi_tcq.h> |
| #include <scsi/scsi_host.h> |
| #include <scsi/scsi_device.h> |
| #include <scsi/scsi_cmnd.h> |
| |
| #include <target/target_core_base.h> |
| #include <target/target_core_fabric.h> |
| |
| #include "tcm_remote.h" |
| |
| static inline struct tcm_remote_tpg *remote_tpg(struct se_portal_group *se_tpg) |
| { |
| return container_of(se_tpg, struct tcm_remote_tpg, remote_se_tpg); |
| } |
| |
| static char *tcm_remote_get_endpoint_wwn(struct se_portal_group *se_tpg) |
| { |
| /* |
| * Return the passed NAA identifier for the Target Port |
| */ |
| return &remote_tpg(se_tpg)->remote_hba->remote_wwn_address[0]; |
| } |
| |
| static u16 tcm_remote_get_tag(struct se_portal_group *se_tpg) |
| { |
| /* |
| * This Tag is used when forming SCSI Name identifier in EVPD=1 0x83 |
| * to represent the SCSI Target Port. |
| */ |
| return remote_tpg(se_tpg)->remote_tpgt; |
| } |
| |
| static int tcm_remote_dummy_cmd_fn(struct se_cmd *se_cmd) |
| { |
| return 0; |
| } |
| |
| static void tcm_remote_dummy_cmd_void_fn(struct se_cmd *se_cmd) |
| { |
| |
| } |
| |
| static char *tcm_remote_dump_proto_id(struct tcm_remote_hba *remote_hba) |
| { |
| switch (remote_hba->remote_proto_id) { |
| case SCSI_PROTOCOL_SAS: |
| return "SAS"; |
| case SCSI_PROTOCOL_SRP: |
| return "SRP"; |
| case SCSI_PROTOCOL_FCP: |
| return "FCP"; |
| case SCSI_PROTOCOL_ISCSI: |
| return "iSCSI"; |
| default: |
| break; |
| } |
| |
| return "Unknown"; |
| } |
| |
| static int tcm_remote_port_link( |
| struct se_portal_group *se_tpg, |
| struct se_lun *lun) |
| { |
| pr_debug("TCM_Remote_ConfigFS: Port Link LUN %lld Successful\n", |
| lun->unpacked_lun); |
| return 0; |
| } |
| |
| static void tcm_remote_port_unlink( |
| struct se_portal_group *se_tpg, |
| struct se_lun *lun) |
| { |
| pr_debug("TCM_Remote_ConfigFS: Port Unlink LUN %lld Successful\n", |
| lun->unpacked_lun); |
| } |
| |
| static struct se_portal_group *tcm_remote_make_tpg( |
| struct se_wwn *wwn, |
| const char *name) |
| { |
| struct tcm_remote_hba *remote_hba = container_of(wwn, |
| struct tcm_remote_hba, remote_hba_wwn); |
| struct tcm_remote_tpg *remote_tpg; |
| unsigned long tpgt; |
| int ret; |
| |
| if (strstr(name, "tpgt_") != name) { |
| pr_err("Unable to locate \"tpgt_#\" directory group\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| if (kstrtoul(name + 5, 10, &tpgt)) |
| return ERR_PTR(-EINVAL); |
| |
| if (tpgt >= TL_TPGS_PER_HBA) { |
| pr_err("Passed tpgt: %lu exceeds TL_TPGS_PER_HBA: %u\n", |
| tpgt, TL_TPGS_PER_HBA); |
| return ERR_PTR(-EINVAL); |
| } |
| remote_tpg = &remote_hba->remote_hba_tpgs[tpgt]; |
| remote_tpg->remote_hba = remote_hba; |
| remote_tpg->remote_tpgt = tpgt; |
| /* |
| * Register the remote_tpg as a emulated TCM Target Endpoint |
| */ |
| ret = core_tpg_register(wwn, &remote_tpg->remote_se_tpg, |
| remote_hba->remote_proto_id); |
| if (ret < 0) |
| return ERR_PTR(-ENOMEM); |
| |
| pr_debug("TCM_Remote_ConfigFS: Allocated Emulated %s Target Port %s,t,0x%04lx\n", |
| tcm_remote_dump_proto_id(remote_hba), |
| config_item_name(&wwn->wwn_group.cg_item), tpgt); |
| return &remote_tpg->remote_se_tpg; |
| } |
| |
| static void tcm_remote_drop_tpg(struct se_portal_group *se_tpg) |
| { |
| struct se_wwn *wwn = se_tpg->se_tpg_wwn; |
| struct tcm_remote_tpg *remote_tpg = container_of(se_tpg, |
| struct tcm_remote_tpg, remote_se_tpg); |
| struct tcm_remote_hba *remote_hba; |
| unsigned short tpgt; |
| |
| remote_hba = remote_tpg->remote_hba; |
| tpgt = remote_tpg->remote_tpgt; |
| |
| /* |
| * Deregister the remote_tpg as a emulated TCM Target Endpoint |
| */ |
| core_tpg_deregister(se_tpg); |
| |
| remote_tpg->remote_hba = NULL; |
| remote_tpg->remote_tpgt = 0; |
| |
| pr_debug("TCM_Remote_ConfigFS: Deallocated Emulated %s Target Port %s,t,0x%04x\n", |
| tcm_remote_dump_proto_id(remote_hba), |
| config_item_name(&wwn->wwn_group.cg_item), tpgt); |
| } |
| |
| static struct se_wwn *tcm_remote_make_wwn( |
| struct target_fabric_configfs *tf, |
| struct config_group *group, |
| const char *name) |
| { |
| struct tcm_remote_hba *remote_hba; |
| char *ptr; |
| int ret, off = 0; |
| |
| remote_hba = kzalloc(sizeof(*remote_hba), GFP_KERNEL); |
| if (!remote_hba) |
| return ERR_PTR(-ENOMEM); |
| |
| /* |
| * Determine the emulated Protocol Identifier and Target Port Name |
| * based on the incoming configfs directory name. |
| */ |
| ptr = strstr(name, "naa."); |
| if (ptr) { |
| remote_hba->remote_proto_id = SCSI_PROTOCOL_SAS; |
| goto check_len; |
| } |
| ptr = strstr(name, "fc."); |
| if (ptr) { |
| remote_hba->remote_proto_id = SCSI_PROTOCOL_FCP; |
| off = 3; /* Skip over "fc." */ |
| goto check_len; |
| } |
| ptr = strstr(name, "0x"); |
| if (ptr) { |
| remote_hba->remote_proto_id = SCSI_PROTOCOL_SRP; |
| off = 2; /* Skip over "0x" */ |
| goto check_len; |
| } |
| ptr = strstr(name, "iqn."); |
| if (!ptr) { |
| pr_err("Unable to locate prefix for emulated Target Port: %s\n", |
| name); |
| ret = -EINVAL; |
| goto out; |
| } |
| remote_hba->remote_proto_id = SCSI_PROTOCOL_ISCSI; |
| |
| check_len: |
| if (strlen(name) >= TL_WWN_ADDR_LEN) { |
| pr_err("Emulated NAA %s Address: %s, exceeds max: %d\n", |
| name, tcm_remote_dump_proto_id(remote_hba), TL_WWN_ADDR_LEN); |
| ret = -EINVAL; |
| goto out; |
| } |
| snprintf(&remote_hba->remote_wwn_address[0], TL_WWN_ADDR_LEN, "%s", &name[off]); |
| |
| pr_debug("TCM_Remote_ConfigFS: Allocated emulated Target %s Address: %s\n", |
| tcm_remote_dump_proto_id(remote_hba), name); |
| return &remote_hba->remote_hba_wwn; |
| out: |
| kfree(remote_hba); |
| return ERR_PTR(ret); |
| } |
| |
| static void tcm_remote_drop_wwn(struct se_wwn *wwn) |
| { |
| struct tcm_remote_hba *remote_hba = container_of(wwn, |
| struct tcm_remote_hba, remote_hba_wwn); |
| |
| pr_debug("TCM_Remote_ConfigFS: Deallocating emulated Target %s Address: %s\n", |
| tcm_remote_dump_proto_id(remote_hba), |
| remote_hba->remote_wwn_address); |
| kfree(remote_hba); |
| } |
| |
| static ssize_t tcm_remote_wwn_version_show(struct config_item *item, char *page) |
| { |
| return sprintf(page, "TCM Remote Fabric module %s\n", TCM_REMOTE_VERSION); |
| } |
| |
| CONFIGFS_ATTR_RO(tcm_remote_wwn_, version); |
| |
| static struct configfs_attribute *tcm_remote_wwn_attrs[] = { |
| &tcm_remote_wwn_attr_version, |
| NULL, |
| }; |
| |
| static const struct target_core_fabric_ops remote_ops = { |
| .module = THIS_MODULE, |
| .fabric_name = "remote", |
| .tpg_get_wwn = tcm_remote_get_endpoint_wwn, |
| .tpg_get_tag = tcm_remote_get_tag, |
| .check_stop_free = tcm_remote_dummy_cmd_fn, |
| .release_cmd = tcm_remote_dummy_cmd_void_fn, |
| .write_pending = tcm_remote_dummy_cmd_fn, |
| .queue_data_in = tcm_remote_dummy_cmd_fn, |
| .queue_status = tcm_remote_dummy_cmd_fn, |
| .queue_tm_rsp = tcm_remote_dummy_cmd_void_fn, |
| .aborted_task = tcm_remote_dummy_cmd_void_fn, |
| .fabric_make_wwn = tcm_remote_make_wwn, |
| .fabric_drop_wwn = tcm_remote_drop_wwn, |
| .fabric_make_tpg = tcm_remote_make_tpg, |
| .fabric_drop_tpg = tcm_remote_drop_tpg, |
| .fabric_post_link = tcm_remote_port_link, |
| .fabric_pre_unlink = tcm_remote_port_unlink, |
| .tfc_wwn_attrs = tcm_remote_wwn_attrs, |
| }; |
| |
| static int __init tcm_remote_fabric_init(void) |
| { |
| return target_register_template(&remote_ops); |
| } |
| |
| static void __exit tcm_remote_fabric_exit(void) |
| { |
| target_unregister_template(&remote_ops); |
| } |
| |
| MODULE_DESCRIPTION("TCM virtual remote target"); |
| MODULE_AUTHOR("Dmitry Bogdanov <d.bogdanov@yadro.com>"); |
| MODULE_LICENSE("GPL"); |
| module_init(tcm_remote_fabric_init); |
| module_exit(tcm_remote_fabric_exit); |