269 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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);
 |