|  | /* | 
|  | * Copyright (c) 2016 Avago Technologies.  All rights reserved. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of version 2 of the GNU General Public License as | 
|  | * published by the Free Software Foundation. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful. | 
|  | * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, | 
|  | * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A | 
|  | * PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED, EXCEPT TO | 
|  | * THE EXTENT THAT SUCH DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. | 
|  | * See the GNU General Public License for more details, a copy of which | 
|  | * can be found in the file COPYING included with this package | 
|  | */ | 
|  | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
|  | #include <linux/module.h> | 
|  | #include <linux/parser.h> | 
|  | #include <uapi/scsi/fc/fc_fs.h> | 
|  |  | 
|  | #include "../host/nvme.h" | 
|  | #include "../target/nvmet.h" | 
|  | #include <linux/nvme-fc-driver.h> | 
|  | #include <linux/nvme-fc.h> | 
|  |  | 
|  |  | 
|  | enum { | 
|  | NVMF_OPT_ERR		= 0, | 
|  | NVMF_OPT_WWNN		= 1 << 0, | 
|  | NVMF_OPT_WWPN		= 1 << 1, | 
|  | NVMF_OPT_ROLES		= 1 << 2, | 
|  | NVMF_OPT_FCADDR		= 1 << 3, | 
|  | NVMF_OPT_LPWWNN		= 1 << 4, | 
|  | NVMF_OPT_LPWWPN		= 1 << 5, | 
|  | }; | 
|  |  | 
|  | struct fcloop_ctrl_options { | 
|  | int			mask; | 
|  | u64			wwnn; | 
|  | u64			wwpn; | 
|  | u32			roles; | 
|  | u32			fcaddr; | 
|  | u64			lpwwnn; | 
|  | u64			lpwwpn; | 
|  | }; | 
|  |  | 
|  | static const match_table_t opt_tokens = { | 
|  | { NVMF_OPT_WWNN,	"wwnn=%s"	}, | 
|  | { NVMF_OPT_WWPN,	"wwpn=%s"	}, | 
|  | { NVMF_OPT_ROLES,	"roles=%d"	}, | 
|  | { NVMF_OPT_FCADDR,	"fcaddr=%x"	}, | 
|  | { NVMF_OPT_LPWWNN,	"lpwwnn=%s"	}, | 
|  | { NVMF_OPT_LPWWPN,	"lpwwpn=%s"	}, | 
|  | { NVMF_OPT_ERR,		NULL		} | 
|  | }; | 
|  |  | 
|  | static int | 
|  | fcloop_parse_options(struct fcloop_ctrl_options *opts, | 
|  | const char *buf) | 
|  | { | 
|  | substring_t args[MAX_OPT_ARGS]; | 
|  | char *options, *o, *p; | 
|  | int token, ret = 0; | 
|  | u64 token64; | 
|  |  | 
|  | options = o = kstrdup(buf, GFP_KERNEL); | 
|  | if (!options) | 
|  | return -ENOMEM; | 
|  |  | 
|  | while ((p = strsep(&o, ",\n")) != NULL) { | 
|  | if (!*p) | 
|  | continue; | 
|  |  | 
|  | token = match_token(p, opt_tokens, args); | 
|  | opts->mask |= token; | 
|  | switch (token) { | 
|  | case NVMF_OPT_WWNN: | 
|  | if (match_u64(args, &token64)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | opts->wwnn = token64; | 
|  | break; | 
|  | case NVMF_OPT_WWPN: | 
|  | if (match_u64(args, &token64)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | opts->wwpn = token64; | 
|  | break; | 
|  | case NVMF_OPT_ROLES: | 
|  | if (match_int(args, &token)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | opts->roles = token; | 
|  | break; | 
|  | case NVMF_OPT_FCADDR: | 
|  | if (match_hex(args, &token)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | opts->fcaddr = token; | 
|  | break; | 
|  | case NVMF_OPT_LPWWNN: | 
|  | if (match_u64(args, &token64)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | opts->lpwwnn = token64; | 
|  | break; | 
|  | case NVMF_OPT_LPWWPN: | 
|  | if (match_u64(args, &token64)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | opts->lpwwpn = token64; | 
|  | break; | 
|  | default: | 
|  | pr_warn("unknown parameter or missing value '%s'\n", p); | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | } | 
|  |  | 
|  | out_free_options: | 
|  | kfree(options); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | fcloop_parse_nm_options(struct device *dev, u64 *nname, u64 *pname, | 
|  | const char *buf) | 
|  | { | 
|  | substring_t args[MAX_OPT_ARGS]; | 
|  | char *options, *o, *p; | 
|  | int token, ret = 0; | 
|  | u64 token64; | 
|  |  | 
|  | *nname = -1; | 
|  | *pname = -1; | 
|  |  | 
|  | options = o = kstrdup(buf, GFP_KERNEL); | 
|  | if (!options) | 
|  | return -ENOMEM; | 
|  |  | 
|  | while ((p = strsep(&o, ",\n")) != NULL) { | 
|  | if (!*p) | 
|  | continue; | 
|  |  | 
|  | token = match_token(p, opt_tokens, args); | 
|  | switch (token) { | 
|  | case NVMF_OPT_WWNN: | 
|  | if (match_u64(args, &token64)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | *nname = token64; | 
|  | break; | 
|  | case NVMF_OPT_WWPN: | 
|  | if (match_u64(args, &token64)) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | *pname = token64; | 
|  | break; | 
|  | default: | 
|  | pr_warn("unknown parameter or missing value '%s'\n", p); | 
|  | ret = -EINVAL; | 
|  | goto out_free_options; | 
|  | } | 
|  | } | 
|  |  | 
|  | out_free_options: | 
|  | kfree(options); | 
|  |  | 
|  | if (!ret) { | 
|  | if (*nname == -1) | 
|  | return -EINVAL; | 
|  | if (*pname == -1) | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | #define LPORT_OPTS	(NVMF_OPT_WWNN | NVMF_OPT_WWPN) | 
|  |  | 
|  | #define RPORT_OPTS	(NVMF_OPT_WWNN | NVMF_OPT_WWPN |  \ | 
|  | NVMF_OPT_LPWWNN | NVMF_OPT_LPWWPN) | 
|  |  | 
|  | #define TGTPORT_OPTS	(NVMF_OPT_WWNN | NVMF_OPT_WWPN) | 
|  |  | 
|  |  | 
|  | static DEFINE_SPINLOCK(fcloop_lock); | 
|  | static LIST_HEAD(fcloop_lports); | 
|  | static LIST_HEAD(fcloop_nports); | 
|  |  | 
|  | struct fcloop_lport { | 
|  | struct nvme_fc_local_port *localport; | 
|  | struct list_head lport_list; | 
|  | struct completion unreg_done; | 
|  | }; | 
|  |  | 
|  | struct fcloop_lport_priv { | 
|  | struct fcloop_lport *lport; | 
|  | }; | 
|  |  | 
|  | struct fcloop_rport { | 
|  | struct nvme_fc_remote_port *remoteport; | 
|  | struct nvmet_fc_target_port *targetport; | 
|  | struct fcloop_nport *nport; | 
|  | struct fcloop_lport *lport; | 
|  | }; | 
|  |  | 
|  | struct fcloop_tport { | 
|  | struct nvmet_fc_target_port *targetport; | 
|  | struct nvme_fc_remote_port *remoteport; | 
|  | struct fcloop_nport *nport; | 
|  | struct fcloop_lport *lport; | 
|  | }; | 
|  |  | 
|  | struct fcloop_nport { | 
|  | struct fcloop_rport *rport; | 
|  | struct fcloop_tport *tport; | 
|  | struct fcloop_lport *lport; | 
|  | struct list_head nport_list; | 
|  | struct kref ref; | 
|  | u64 node_name; | 
|  | u64 port_name; | 
|  | u32 port_role; | 
|  | u32 port_id; | 
|  | }; | 
|  |  | 
|  | struct fcloop_lsreq { | 
|  | struct fcloop_tport		*tport; | 
|  | struct nvmefc_ls_req		*lsreq; | 
|  | struct work_struct		work; | 
|  | struct nvmefc_tgt_ls_req	tgt_ls_req; | 
|  | int				status; | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | INI_IO_START		= 0, | 
|  | INI_IO_ACTIVE		= 1, | 
|  | INI_IO_ABORTED		= 2, | 
|  | INI_IO_COMPLETED	= 3, | 
|  | }; | 
|  |  | 
|  | struct fcloop_fcpreq { | 
|  | struct fcloop_tport		*tport; | 
|  | struct nvmefc_fcp_req		*fcpreq; | 
|  | spinlock_t			reqlock; | 
|  | u16				status; | 
|  | u32				inistate; | 
|  | bool				active; | 
|  | bool				aborted; | 
|  | struct kref			ref; | 
|  | struct work_struct		fcp_rcv_work; | 
|  | struct work_struct		abort_rcv_work; | 
|  | struct work_struct		tio_done_work; | 
|  | struct nvmefc_tgt_fcp_req	tgt_fcp_req; | 
|  | }; | 
|  |  | 
|  | struct fcloop_ini_fcpreq { | 
|  | struct nvmefc_fcp_req		*fcpreq; | 
|  | struct fcloop_fcpreq		*tfcp_req; | 
|  | spinlock_t			inilock; | 
|  | }; | 
|  |  | 
|  | static inline struct fcloop_lsreq * | 
|  | tgt_ls_req_to_lsreq(struct nvmefc_tgt_ls_req *tgt_lsreq) | 
|  | { | 
|  | return container_of(tgt_lsreq, struct fcloop_lsreq, tgt_ls_req); | 
|  | } | 
|  |  | 
|  | static inline struct fcloop_fcpreq * | 
|  | tgt_fcp_req_to_fcpreq(struct nvmefc_tgt_fcp_req *tgt_fcpreq) | 
|  | { | 
|  | return container_of(tgt_fcpreq, struct fcloop_fcpreq, tgt_fcp_req); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | fcloop_create_queue(struct nvme_fc_local_port *localport, | 
|  | unsigned int qidx, u16 qsize, | 
|  | void **handle) | 
|  | { | 
|  | *handle = localport; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_delete_queue(struct nvme_fc_local_port *localport, | 
|  | unsigned int idx, void *handle) | 
|  | { | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Transmit of LS RSP done (e.g. buffers all set). call back up | 
|  | * initiator "done" flows. | 
|  | */ | 
|  | static void | 
|  | fcloop_tgt_lsrqst_done_work(struct work_struct *work) | 
|  | { | 
|  | struct fcloop_lsreq *tls_req = | 
|  | container_of(work, struct fcloop_lsreq, work); | 
|  | struct fcloop_tport *tport = tls_req->tport; | 
|  | struct nvmefc_ls_req *lsreq = tls_req->lsreq; | 
|  |  | 
|  | if (!tport || tport->remoteport) | 
|  | lsreq->done(lsreq, tls_req->status); | 
|  | } | 
|  |  | 
|  | static int | 
|  | fcloop_ls_req(struct nvme_fc_local_port *localport, | 
|  | struct nvme_fc_remote_port *remoteport, | 
|  | struct nvmefc_ls_req *lsreq) | 
|  | { | 
|  | struct fcloop_lsreq *tls_req = lsreq->private; | 
|  | struct fcloop_rport *rport = remoteport->private; | 
|  | int ret = 0; | 
|  |  | 
|  | tls_req->lsreq = lsreq; | 
|  | INIT_WORK(&tls_req->work, fcloop_tgt_lsrqst_done_work); | 
|  |  | 
|  | if (!rport->targetport) { | 
|  | tls_req->status = -ECONNREFUSED; | 
|  | tls_req->tport = NULL; | 
|  | schedule_work(&tls_req->work); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | tls_req->status = 0; | 
|  | tls_req->tport = rport->targetport->private; | 
|  | ret = nvmet_fc_rcv_ls_req(rport->targetport, &tls_req->tgt_ls_req, | 
|  | lsreq->rqstaddr, lsreq->rqstlen); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int | 
|  | fcloop_xmt_ls_rsp(struct nvmet_fc_target_port *tport, | 
|  | struct nvmefc_tgt_ls_req *tgt_lsreq) | 
|  | { | 
|  | struct fcloop_lsreq *tls_req = tgt_ls_req_to_lsreq(tgt_lsreq); | 
|  | struct nvmefc_ls_req *lsreq = tls_req->lsreq; | 
|  |  | 
|  | memcpy(lsreq->rspaddr, tgt_lsreq->rspbuf, | 
|  | ((lsreq->rsplen < tgt_lsreq->rsplen) ? | 
|  | lsreq->rsplen : tgt_lsreq->rsplen)); | 
|  | tgt_lsreq->done(tgt_lsreq); | 
|  |  | 
|  | schedule_work(&tls_req->work); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_tfcp_req_free(struct kref *ref) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = | 
|  | container_of(ref, struct fcloop_fcpreq, ref); | 
|  |  | 
|  | kfree(tfcp_req); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_tfcp_req_put(struct fcloop_fcpreq *tfcp_req) | 
|  | { | 
|  | kref_put(&tfcp_req->ref, fcloop_tfcp_req_free); | 
|  | } | 
|  |  | 
|  | static int | 
|  | fcloop_tfcp_req_get(struct fcloop_fcpreq *tfcp_req) | 
|  | { | 
|  | return kref_get_unless_zero(&tfcp_req->ref); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_call_host_done(struct nvmefc_fcp_req *fcpreq, | 
|  | struct fcloop_fcpreq *tfcp_req, int status) | 
|  | { | 
|  | struct fcloop_ini_fcpreq *inireq = NULL; | 
|  |  | 
|  | if (fcpreq) { | 
|  | inireq = fcpreq->private; | 
|  | spin_lock(&inireq->inilock); | 
|  | inireq->tfcp_req = NULL; | 
|  | spin_unlock(&inireq->inilock); | 
|  |  | 
|  | fcpreq->status = status; | 
|  | fcpreq->done(fcpreq); | 
|  | } | 
|  |  | 
|  | /* release original io reference on tgt struct */ | 
|  | fcloop_tfcp_req_put(tfcp_req); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_fcp_recv_work(struct work_struct *work) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = | 
|  | container_of(work, struct fcloop_fcpreq, fcp_rcv_work); | 
|  | struct nvmefc_fcp_req *fcpreq = tfcp_req->fcpreq; | 
|  | int ret = 0; | 
|  | bool aborted = false; | 
|  |  | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | switch (tfcp_req->inistate) { | 
|  | case INI_IO_START: | 
|  | tfcp_req->inistate = INI_IO_ACTIVE; | 
|  | break; | 
|  | case INI_IO_ABORTED: | 
|  | aborted = true; | 
|  | break; | 
|  | default: | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  | WARN_ON(1); | 
|  | return; | 
|  | } | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | if (unlikely(aborted)) | 
|  | ret = -ECANCELED; | 
|  | else | 
|  | ret = nvmet_fc_rcv_fcp_req(tfcp_req->tport->targetport, | 
|  | &tfcp_req->tgt_fcp_req, | 
|  | fcpreq->cmdaddr, fcpreq->cmdlen); | 
|  | if (ret) | 
|  | fcloop_call_host_done(fcpreq, tfcp_req, ret); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_fcp_abort_recv_work(struct work_struct *work) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = | 
|  | container_of(work, struct fcloop_fcpreq, abort_rcv_work); | 
|  | struct nvmefc_fcp_req *fcpreq; | 
|  | bool completed = false; | 
|  |  | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | fcpreq = tfcp_req->fcpreq; | 
|  | switch (tfcp_req->inistate) { | 
|  | case INI_IO_ABORTED: | 
|  | break; | 
|  | case INI_IO_COMPLETED: | 
|  | completed = true; | 
|  | break; | 
|  | default: | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  | WARN_ON(1); | 
|  | return; | 
|  | } | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | if (unlikely(completed)) { | 
|  | /* remove reference taken in original abort downcall */ | 
|  | fcloop_tfcp_req_put(tfcp_req); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (tfcp_req->tport->targetport) | 
|  | nvmet_fc_rcv_fcp_abort(tfcp_req->tport->targetport, | 
|  | &tfcp_req->tgt_fcp_req); | 
|  |  | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | tfcp_req->fcpreq = NULL; | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | fcloop_call_host_done(fcpreq, tfcp_req, -ECANCELED); | 
|  | /* call_host_done releases reference for abort downcall */ | 
|  | } | 
|  |  | 
|  | /* | 
|  | * FCP IO operation done by target completion. | 
|  | * call back up initiator "done" flows. | 
|  | */ | 
|  | static void | 
|  | fcloop_tgt_fcprqst_done_work(struct work_struct *work) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = | 
|  | container_of(work, struct fcloop_fcpreq, tio_done_work); | 
|  | struct nvmefc_fcp_req *fcpreq; | 
|  |  | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | fcpreq = tfcp_req->fcpreq; | 
|  | tfcp_req->inistate = INI_IO_COMPLETED; | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | fcloop_call_host_done(fcpreq, tfcp_req, tfcp_req->status); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | fcloop_fcp_req(struct nvme_fc_local_port *localport, | 
|  | struct nvme_fc_remote_port *remoteport, | 
|  | void *hw_queue_handle, | 
|  | struct nvmefc_fcp_req *fcpreq) | 
|  | { | 
|  | struct fcloop_rport *rport = remoteport->private; | 
|  | struct fcloop_ini_fcpreq *inireq = fcpreq->private; | 
|  | struct fcloop_fcpreq *tfcp_req; | 
|  |  | 
|  | if (!rport->targetport) | 
|  | return -ECONNREFUSED; | 
|  |  | 
|  | tfcp_req = kzalloc(sizeof(*tfcp_req), GFP_KERNEL); | 
|  | if (!tfcp_req) | 
|  | return -ENOMEM; | 
|  |  | 
|  | inireq->fcpreq = fcpreq; | 
|  | inireq->tfcp_req = tfcp_req; | 
|  | spin_lock_init(&inireq->inilock); | 
|  |  | 
|  | tfcp_req->fcpreq = fcpreq; | 
|  | tfcp_req->tport = rport->targetport->private; | 
|  | tfcp_req->inistate = INI_IO_START; | 
|  | spin_lock_init(&tfcp_req->reqlock); | 
|  | INIT_WORK(&tfcp_req->fcp_rcv_work, fcloop_fcp_recv_work); | 
|  | INIT_WORK(&tfcp_req->abort_rcv_work, fcloop_fcp_abort_recv_work); | 
|  | INIT_WORK(&tfcp_req->tio_done_work, fcloop_tgt_fcprqst_done_work); | 
|  | kref_init(&tfcp_req->ref); | 
|  |  | 
|  | schedule_work(&tfcp_req->fcp_rcv_work); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_fcp_copy_data(u8 op, struct scatterlist *data_sg, | 
|  | struct scatterlist *io_sg, u32 offset, u32 length) | 
|  | { | 
|  | void *data_p, *io_p; | 
|  | u32 data_len, io_len, tlen; | 
|  |  | 
|  | io_p = sg_virt(io_sg); | 
|  | io_len = io_sg->length; | 
|  |  | 
|  | for ( ; offset; ) { | 
|  | tlen = min_t(u32, offset, io_len); | 
|  | offset -= tlen; | 
|  | io_len -= tlen; | 
|  | if (!io_len) { | 
|  | io_sg = sg_next(io_sg); | 
|  | io_p = sg_virt(io_sg); | 
|  | io_len = io_sg->length; | 
|  | } else | 
|  | io_p += tlen; | 
|  | } | 
|  |  | 
|  | data_p = sg_virt(data_sg); | 
|  | data_len = data_sg->length; | 
|  |  | 
|  | for ( ; length; ) { | 
|  | tlen = min_t(u32, io_len, data_len); | 
|  | tlen = min_t(u32, tlen, length); | 
|  |  | 
|  | if (op == NVMET_FCOP_WRITEDATA) | 
|  | memcpy(data_p, io_p, tlen); | 
|  | else | 
|  | memcpy(io_p, data_p, tlen); | 
|  |  | 
|  | length -= tlen; | 
|  |  | 
|  | io_len -= tlen; | 
|  | if ((!io_len) && (length)) { | 
|  | io_sg = sg_next(io_sg); | 
|  | io_p = sg_virt(io_sg); | 
|  | io_len = io_sg->length; | 
|  | } else | 
|  | io_p += tlen; | 
|  |  | 
|  | data_len -= tlen; | 
|  | if ((!data_len) && (length)) { | 
|  | data_sg = sg_next(data_sg); | 
|  | data_p = sg_virt(data_sg); | 
|  | data_len = data_sg->length; | 
|  | } else | 
|  | data_p += tlen; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | fcloop_fcp_op(struct nvmet_fc_target_port *tgtport, | 
|  | struct nvmefc_tgt_fcp_req *tgt_fcpreq) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = tgt_fcp_req_to_fcpreq(tgt_fcpreq); | 
|  | struct nvmefc_fcp_req *fcpreq; | 
|  | u32 rsplen = 0, xfrlen = 0; | 
|  | int fcp_err = 0, active, aborted; | 
|  | u8 op = tgt_fcpreq->op; | 
|  |  | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | fcpreq = tfcp_req->fcpreq; | 
|  | active = tfcp_req->active; | 
|  | aborted = tfcp_req->aborted; | 
|  | tfcp_req->active = true; | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | if (unlikely(active)) | 
|  | /* illegal - call while i/o active */ | 
|  | return -EALREADY; | 
|  |  | 
|  | if (unlikely(aborted)) { | 
|  | /* target transport has aborted i/o prior */ | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | tfcp_req->active = false; | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  | tgt_fcpreq->transferred_length = 0; | 
|  | tgt_fcpreq->fcp_error = -ECANCELED; | 
|  | tgt_fcpreq->done(tgt_fcpreq); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * if fcpreq is NULL, the I/O has been aborted (from | 
|  | * initiator side). For the target side, act as if all is well | 
|  | * but don't actually move data. | 
|  | */ | 
|  |  | 
|  | switch (op) { | 
|  | case NVMET_FCOP_WRITEDATA: | 
|  | xfrlen = tgt_fcpreq->transfer_length; | 
|  | if (fcpreq) { | 
|  | fcloop_fcp_copy_data(op, tgt_fcpreq->sg, | 
|  | fcpreq->first_sgl, tgt_fcpreq->offset, | 
|  | xfrlen); | 
|  | fcpreq->transferred_length += xfrlen; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case NVMET_FCOP_READDATA: | 
|  | case NVMET_FCOP_READDATA_RSP: | 
|  | xfrlen = tgt_fcpreq->transfer_length; | 
|  | if (fcpreq) { | 
|  | fcloop_fcp_copy_data(op, tgt_fcpreq->sg, | 
|  | fcpreq->first_sgl, tgt_fcpreq->offset, | 
|  | xfrlen); | 
|  | fcpreq->transferred_length += xfrlen; | 
|  | } | 
|  | if (op == NVMET_FCOP_READDATA) | 
|  | break; | 
|  |  | 
|  | /* Fall-Thru to RSP handling */ | 
|  | /* FALLTHRU */ | 
|  |  | 
|  | case NVMET_FCOP_RSP: | 
|  | if (fcpreq) { | 
|  | rsplen = ((fcpreq->rsplen < tgt_fcpreq->rsplen) ? | 
|  | fcpreq->rsplen : tgt_fcpreq->rsplen); | 
|  | memcpy(fcpreq->rspaddr, tgt_fcpreq->rspaddr, rsplen); | 
|  | if (rsplen < tgt_fcpreq->rsplen) | 
|  | fcp_err = -E2BIG; | 
|  | fcpreq->rcv_rsplen = rsplen; | 
|  | fcpreq->status = 0; | 
|  | } | 
|  | tfcp_req->status = 0; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | fcp_err = -EINVAL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | tfcp_req->active = false; | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | tgt_fcpreq->transferred_length = xfrlen; | 
|  | tgt_fcpreq->fcp_error = fcp_err; | 
|  | tgt_fcpreq->done(tgt_fcpreq); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_tgt_fcp_abort(struct nvmet_fc_target_port *tgtport, | 
|  | struct nvmefc_tgt_fcp_req *tgt_fcpreq) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = tgt_fcp_req_to_fcpreq(tgt_fcpreq); | 
|  |  | 
|  | /* | 
|  | * mark aborted only in case there were 2 threads in transport | 
|  | * (one doing io, other doing abort) and only kills ops posted | 
|  | * after the abort request | 
|  | */ | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | tfcp_req->aborted = true; | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | tfcp_req->status = NVME_SC_INTERNAL; | 
|  |  | 
|  | /* | 
|  | * nothing more to do. If io wasn't active, the transport should | 
|  | * immediately call the req_release. If it was active, the op | 
|  | * will complete, and the lldd should call req_release. | 
|  | */ | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_fcp_req_release(struct nvmet_fc_target_port *tgtport, | 
|  | struct nvmefc_tgt_fcp_req *tgt_fcpreq) | 
|  | { | 
|  | struct fcloop_fcpreq *tfcp_req = tgt_fcp_req_to_fcpreq(tgt_fcpreq); | 
|  |  | 
|  | schedule_work(&tfcp_req->tio_done_work); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_ls_abort(struct nvme_fc_local_port *localport, | 
|  | struct nvme_fc_remote_port *remoteport, | 
|  | struct nvmefc_ls_req *lsreq) | 
|  | { | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_fcp_abort(struct nvme_fc_local_port *localport, | 
|  | struct nvme_fc_remote_port *remoteport, | 
|  | void *hw_queue_handle, | 
|  | struct nvmefc_fcp_req *fcpreq) | 
|  | { | 
|  | struct fcloop_ini_fcpreq *inireq = fcpreq->private; | 
|  | struct fcloop_fcpreq *tfcp_req; | 
|  | bool abortio = true; | 
|  |  | 
|  | spin_lock(&inireq->inilock); | 
|  | tfcp_req = inireq->tfcp_req; | 
|  | if (tfcp_req) | 
|  | fcloop_tfcp_req_get(tfcp_req); | 
|  | spin_unlock(&inireq->inilock); | 
|  |  | 
|  | if (!tfcp_req) | 
|  | /* abort has already been called */ | 
|  | return; | 
|  |  | 
|  | /* break initiator/target relationship for io */ | 
|  | spin_lock(&tfcp_req->reqlock); | 
|  | switch (tfcp_req->inistate) { | 
|  | case INI_IO_START: | 
|  | case INI_IO_ACTIVE: | 
|  | tfcp_req->inistate = INI_IO_ABORTED; | 
|  | break; | 
|  | case INI_IO_COMPLETED: | 
|  | abortio = false; | 
|  | break; | 
|  | default: | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  | WARN_ON(1); | 
|  | return; | 
|  | } | 
|  | spin_unlock(&tfcp_req->reqlock); | 
|  |  | 
|  | if (abortio) | 
|  | /* leave the reference while the work item is scheduled */ | 
|  | WARN_ON(!schedule_work(&tfcp_req->abort_rcv_work)); | 
|  | else  { | 
|  | /* | 
|  | * as the io has already had the done callback made, | 
|  | * nothing more to do. So release the reference taken above | 
|  | */ | 
|  | fcloop_tfcp_req_put(tfcp_req); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_nport_free(struct kref *ref) | 
|  | { | 
|  | struct fcloop_nport *nport = | 
|  | container_of(ref, struct fcloop_nport, ref); | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  | list_del(&nport->nport_list); | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | kfree(nport); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_nport_put(struct fcloop_nport *nport) | 
|  | { | 
|  | kref_put(&nport->ref, fcloop_nport_free); | 
|  | } | 
|  |  | 
|  | static int | 
|  | fcloop_nport_get(struct fcloop_nport *nport) | 
|  | { | 
|  | return kref_get_unless_zero(&nport->ref); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_localport_delete(struct nvme_fc_local_port *localport) | 
|  | { | 
|  | struct fcloop_lport_priv *lport_priv = localport->private; | 
|  | struct fcloop_lport *lport = lport_priv->lport; | 
|  |  | 
|  | /* release any threads waiting for the unreg to complete */ | 
|  | complete(&lport->unreg_done); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_remoteport_delete(struct nvme_fc_remote_port *remoteport) | 
|  | { | 
|  | struct fcloop_rport *rport = remoteport->private; | 
|  |  | 
|  | fcloop_nport_put(rport->nport); | 
|  | } | 
|  |  | 
|  | static void | 
|  | fcloop_targetport_delete(struct nvmet_fc_target_port *targetport) | 
|  | { | 
|  | struct fcloop_tport *tport = targetport->private; | 
|  |  | 
|  | fcloop_nport_put(tport->nport); | 
|  | } | 
|  |  | 
|  | #define	FCLOOP_HW_QUEUES		4 | 
|  | #define	FCLOOP_SGL_SEGS			256 | 
|  | #define FCLOOP_DMABOUND_4G		0xFFFFFFFF | 
|  |  | 
|  | static struct nvme_fc_port_template fctemplate = { | 
|  | .module			= THIS_MODULE, | 
|  | .localport_delete	= fcloop_localport_delete, | 
|  | .remoteport_delete	= fcloop_remoteport_delete, | 
|  | .create_queue		= fcloop_create_queue, | 
|  | .delete_queue		= fcloop_delete_queue, | 
|  | .ls_req			= fcloop_ls_req, | 
|  | .fcp_io			= fcloop_fcp_req, | 
|  | .ls_abort		= fcloop_ls_abort, | 
|  | .fcp_abort		= fcloop_fcp_abort, | 
|  | .max_hw_queues		= FCLOOP_HW_QUEUES, | 
|  | .max_sgl_segments	= FCLOOP_SGL_SEGS, | 
|  | .max_dif_sgl_segments	= FCLOOP_SGL_SEGS, | 
|  | .dma_boundary		= FCLOOP_DMABOUND_4G, | 
|  | /* sizes of additional private data for data structures */ | 
|  | .local_priv_sz		= sizeof(struct fcloop_lport_priv), | 
|  | .remote_priv_sz		= sizeof(struct fcloop_rport), | 
|  | .lsrqst_priv_sz		= sizeof(struct fcloop_lsreq), | 
|  | .fcprqst_priv_sz	= sizeof(struct fcloop_ini_fcpreq), | 
|  | }; | 
|  |  | 
|  | static struct nvmet_fc_target_template tgttemplate = { | 
|  | .targetport_delete	= fcloop_targetport_delete, | 
|  | .xmt_ls_rsp		= fcloop_xmt_ls_rsp, | 
|  | .fcp_op			= fcloop_fcp_op, | 
|  | .fcp_abort		= fcloop_tgt_fcp_abort, | 
|  | .fcp_req_release	= fcloop_fcp_req_release, | 
|  | .max_hw_queues		= FCLOOP_HW_QUEUES, | 
|  | .max_sgl_segments	= FCLOOP_SGL_SEGS, | 
|  | .max_dif_sgl_segments	= FCLOOP_SGL_SEGS, | 
|  | .dma_boundary		= FCLOOP_DMABOUND_4G, | 
|  | /* optional features */ | 
|  | .target_features	= 0, | 
|  | /* sizes of additional private data for data structures */ | 
|  | .target_priv_sz		= sizeof(struct fcloop_tport), | 
|  | }; | 
|  |  | 
|  | static ssize_t | 
|  | fcloop_create_local_port(struct device *dev, struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct nvme_fc_port_info pinfo; | 
|  | struct fcloop_ctrl_options *opts; | 
|  | struct nvme_fc_local_port *localport; | 
|  | struct fcloop_lport *lport; | 
|  | struct fcloop_lport_priv *lport_priv; | 
|  | unsigned long flags; | 
|  | int ret = -ENOMEM; | 
|  |  | 
|  | lport = kzalloc(sizeof(*lport), GFP_KERNEL); | 
|  | if (!lport) | 
|  | return -ENOMEM; | 
|  |  | 
|  | opts = kzalloc(sizeof(*opts), GFP_KERNEL); | 
|  | if (!opts) | 
|  | goto out_free_lport; | 
|  |  | 
|  | ret = fcloop_parse_options(opts, buf); | 
|  | if (ret) | 
|  | goto out_free_opts; | 
|  |  | 
|  | /* everything there ? */ | 
|  | if ((opts->mask & LPORT_OPTS) != LPORT_OPTS) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_opts; | 
|  | } | 
|  |  | 
|  | memset(&pinfo, 0, sizeof(pinfo)); | 
|  | pinfo.node_name = opts->wwnn; | 
|  | pinfo.port_name = opts->wwpn; | 
|  | pinfo.port_role = opts->roles; | 
|  | pinfo.port_id = opts->fcaddr; | 
|  |  | 
|  | ret = nvme_fc_register_localport(&pinfo, &fctemplate, NULL, &localport); | 
|  | if (!ret) { | 
|  | /* success */ | 
|  | lport_priv = localport->private; | 
|  | lport_priv->lport = lport; | 
|  |  | 
|  | lport->localport = localport; | 
|  | INIT_LIST_HEAD(&lport->lport_list); | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  | list_add_tail(&lport->lport_list, &fcloop_lports); | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  | } | 
|  |  | 
|  | out_free_opts: | 
|  | kfree(opts); | 
|  | out_free_lport: | 
|  | /* free only if we're going to fail */ | 
|  | if (ret) | 
|  | kfree(lport); | 
|  |  | 
|  | return ret ? ret : count; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | __unlink_local_port(struct fcloop_lport *lport) | 
|  | { | 
|  | list_del(&lport->lport_list); | 
|  | } | 
|  |  | 
|  | static int | 
|  | __wait_localport_unreg(struct fcloop_lport *lport) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | init_completion(&lport->unreg_done); | 
|  |  | 
|  | ret = nvme_fc_unregister_localport(lport->localport); | 
|  |  | 
|  | wait_for_completion(&lport->unreg_done); | 
|  |  | 
|  | kfree(lport); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ssize_t | 
|  | fcloop_delete_local_port(struct device *dev, struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct fcloop_lport *tlport, *lport = NULL; | 
|  | u64 nodename, portname; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  |  | 
|  | ret = fcloop_parse_nm_options(dev, &nodename, &portname, buf); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  |  | 
|  | list_for_each_entry(tlport, &fcloop_lports, lport_list) { | 
|  | if (tlport->localport->node_name == nodename && | 
|  | tlport->localport->port_name == portname) { | 
|  | lport = tlport; | 
|  | __unlink_local_port(lport); | 
|  | break; | 
|  | } | 
|  | } | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | if (!lport) | 
|  | return -ENOENT; | 
|  |  | 
|  | ret = __wait_localport_unreg(lport); | 
|  |  | 
|  | return ret ? ret : count; | 
|  | } | 
|  |  | 
|  | static struct fcloop_nport * | 
|  | fcloop_alloc_nport(const char *buf, size_t count, bool remoteport) | 
|  | { | 
|  | struct fcloop_nport *newnport, *nport = NULL; | 
|  | struct fcloop_lport *tmplport, *lport = NULL; | 
|  | struct fcloop_ctrl_options *opts; | 
|  | unsigned long flags; | 
|  | u32 opts_mask = (remoteport) ? RPORT_OPTS : TGTPORT_OPTS; | 
|  | int ret; | 
|  |  | 
|  | opts = kzalloc(sizeof(*opts), GFP_KERNEL); | 
|  | if (!opts) | 
|  | return NULL; | 
|  |  | 
|  | ret = fcloop_parse_options(opts, buf); | 
|  | if (ret) | 
|  | goto out_free_opts; | 
|  |  | 
|  | /* everything there ? */ | 
|  | if ((opts->mask & opts_mask) != opts_mask) { | 
|  | ret = -EINVAL; | 
|  | goto out_free_opts; | 
|  | } | 
|  |  | 
|  | newnport = kzalloc(sizeof(*newnport), GFP_KERNEL); | 
|  | if (!newnport) | 
|  | goto out_free_opts; | 
|  |  | 
|  | INIT_LIST_HEAD(&newnport->nport_list); | 
|  | newnport->node_name = opts->wwnn; | 
|  | newnport->port_name = opts->wwpn; | 
|  | if (opts->mask & NVMF_OPT_ROLES) | 
|  | newnport->port_role = opts->roles; | 
|  | if (opts->mask & NVMF_OPT_FCADDR) | 
|  | newnport->port_id = opts->fcaddr; | 
|  | kref_init(&newnport->ref); | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  |  | 
|  | list_for_each_entry(tmplport, &fcloop_lports, lport_list) { | 
|  | if (tmplport->localport->node_name == opts->wwnn && | 
|  | tmplport->localport->port_name == opts->wwpn) | 
|  | goto out_invalid_opts; | 
|  |  | 
|  | if (tmplport->localport->node_name == opts->lpwwnn && | 
|  | tmplport->localport->port_name == opts->lpwwpn) | 
|  | lport = tmplport; | 
|  | } | 
|  |  | 
|  | if (remoteport) { | 
|  | if (!lport) | 
|  | goto out_invalid_opts; | 
|  | newnport->lport = lport; | 
|  | } | 
|  |  | 
|  | list_for_each_entry(nport, &fcloop_nports, nport_list) { | 
|  | if (nport->node_name == opts->wwnn && | 
|  | nport->port_name == opts->wwpn) { | 
|  | if ((remoteport && nport->rport) || | 
|  | (!remoteport && nport->tport)) { | 
|  | nport = NULL; | 
|  | goto out_invalid_opts; | 
|  | } | 
|  |  | 
|  | fcloop_nport_get(nport); | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | if (remoteport) | 
|  | nport->lport = lport; | 
|  | if (opts->mask & NVMF_OPT_ROLES) | 
|  | nport->port_role = opts->roles; | 
|  | if (opts->mask & NVMF_OPT_FCADDR) | 
|  | nport->port_id = opts->fcaddr; | 
|  | goto out_free_newnport; | 
|  | } | 
|  | } | 
|  |  | 
|  | list_add_tail(&newnport->nport_list, &fcloop_nports); | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | kfree(opts); | 
|  | return newnport; | 
|  |  | 
|  | out_invalid_opts: | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  | out_free_newnport: | 
|  | kfree(newnport); | 
|  | out_free_opts: | 
|  | kfree(opts); | 
|  | return nport; | 
|  | } | 
|  |  | 
|  | static ssize_t | 
|  | fcloop_create_remote_port(struct device *dev, struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct nvme_fc_remote_port *remoteport; | 
|  | struct fcloop_nport *nport; | 
|  | struct fcloop_rport *rport; | 
|  | struct nvme_fc_port_info pinfo; | 
|  | int ret; | 
|  |  | 
|  | nport = fcloop_alloc_nport(buf, count, true); | 
|  | if (!nport) | 
|  | return -EIO; | 
|  |  | 
|  | memset(&pinfo, 0, sizeof(pinfo)); | 
|  | pinfo.node_name = nport->node_name; | 
|  | pinfo.port_name = nport->port_name; | 
|  | pinfo.port_role = nport->port_role; | 
|  | pinfo.port_id = nport->port_id; | 
|  |  | 
|  | ret = nvme_fc_register_remoteport(nport->lport->localport, | 
|  | &pinfo, &remoteport); | 
|  | if (ret || !remoteport) { | 
|  | fcloop_nport_put(nport); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* success */ | 
|  | rport = remoteport->private; | 
|  | rport->remoteport = remoteport; | 
|  | rport->targetport = (nport->tport) ?  nport->tport->targetport : NULL; | 
|  | if (nport->tport) { | 
|  | nport->tport->remoteport = remoteport; | 
|  | nport->tport->lport = nport->lport; | 
|  | } | 
|  | rport->nport = nport; | 
|  | rport->lport = nport->lport; | 
|  | nport->rport = rport; | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct fcloop_rport * | 
|  | __unlink_remote_port(struct fcloop_nport *nport) | 
|  | { | 
|  | struct fcloop_rport *rport = nport->rport; | 
|  |  | 
|  | if (rport && nport->tport) | 
|  | nport->tport->remoteport = NULL; | 
|  | nport->rport = NULL; | 
|  |  | 
|  | return rport; | 
|  | } | 
|  |  | 
|  | static int | 
|  | __remoteport_unreg(struct fcloop_nport *nport, struct fcloop_rport *rport) | 
|  | { | 
|  | if (!rport) | 
|  | return -EALREADY; | 
|  |  | 
|  | return nvme_fc_unregister_remoteport(rport->remoteport); | 
|  | } | 
|  |  | 
|  | static ssize_t | 
|  | fcloop_delete_remote_port(struct device *dev, struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct fcloop_nport *nport = NULL, *tmpport; | 
|  | static struct fcloop_rport *rport; | 
|  | u64 nodename, portname; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  |  | 
|  | ret = fcloop_parse_nm_options(dev, &nodename, &portname, buf); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  |  | 
|  | list_for_each_entry(tmpport, &fcloop_nports, nport_list) { | 
|  | if (tmpport->node_name == nodename && | 
|  | tmpport->port_name == portname && tmpport->rport) { | 
|  | nport = tmpport; | 
|  | rport = __unlink_remote_port(nport); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | if (!nport) | 
|  | return -ENOENT; | 
|  |  | 
|  | ret = __remoteport_unreg(nport, rport); | 
|  |  | 
|  | return ret ? ret : count; | 
|  | } | 
|  |  | 
|  | static ssize_t | 
|  | fcloop_create_target_port(struct device *dev, struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct nvmet_fc_target_port *targetport; | 
|  | struct fcloop_nport *nport; | 
|  | struct fcloop_tport *tport; | 
|  | struct nvmet_fc_port_info tinfo; | 
|  | int ret; | 
|  |  | 
|  | nport = fcloop_alloc_nport(buf, count, false); | 
|  | if (!nport) | 
|  | return -EIO; | 
|  |  | 
|  | tinfo.node_name = nport->node_name; | 
|  | tinfo.port_name = nport->port_name; | 
|  | tinfo.port_id = nport->port_id; | 
|  |  | 
|  | ret = nvmet_fc_register_targetport(&tinfo, &tgttemplate, NULL, | 
|  | &targetport); | 
|  | if (ret) { | 
|  | fcloop_nport_put(nport); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* success */ | 
|  | tport = targetport->private; | 
|  | tport->targetport = targetport; | 
|  | tport->remoteport = (nport->rport) ?  nport->rport->remoteport : NULL; | 
|  | if (nport->rport) | 
|  | nport->rport->targetport = targetport; | 
|  | tport->nport = nport; | 
|  | tport->lport = nport->lport; | 
|  | nport->tport = tport; | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct fcloop_tport * | 
|  | __unlink_target_port(struct fcloop_nport *nport) | 
|  | { | 
|  | struct fcloop_tport *tport = nport->tport; | 
|  |  | 
|  | if (tport && nport->rport) | 
|  | nport->rport->targetport = NULL; | 
|  | nport->tport = NULL; | 
|  |  | 
|  | return tport; | 
|  | } | 
|  |  | 
|  | static int | 
|  | __targetport_unreg(struct fcloop_nport *nport, struct fcloop_tport *tport) | 
|  | { | 
|  | if (!tport) | 
|  | return -EALREADY; | 
|  |  | 
|  | return nvmet_fc_unregister_targetport(tport->targetport); | 
|  | } | 
|  |  | 
|  | static ssize_t | 
|  | fcloop_delete_target_port(struct device *dev, struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct fcloop_nport *nport = NULL, *tmpport; | 
|  | struct fcloop_tport *tport = NULL; | 
|  | u64 nodename, portname; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  |  | 
|  | ret = fcloop_parse_nm_options(dev, &nodename, &portname, buf); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  |  | 
|  | list_for_each_entry(tmpport, &fcloop_nports, nport_list) { | 
|  | if (tmpport->node_name == nodename && | 
|  | tmpport->port_name == portname && tmpport->tport) { | 
|  | nport = tmpport; | 
|  | tport = __unlink_target_port(nport); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | if (!nport) | 
|  | return -ENOENT; | 
|  |  | 
|  | ret = __targetport_unreg(nport, tport); | 
|  |  | 
|  | return ret ? ret : count; | 
|  | } | 
|  |  | 
|  |  | 
|  | static DEVICE_ATTR(add_local_port, 0200, NULL, fcloop_create_local_port); | 
|  | static DEVICE_ATTR(del_local_port, 0200, NULL, fcloop_delete_local_port); | 
|  | static DEVICE_ATTR(add_remote_port, 0200, NULL, fcloop_create_remote_port); | 
|  | static DEVICE_ATTR(del_remote_port, 0200, NULL, fcloop_delete_remote_port); | 
|  | static DEVICE_ATTR(add_target_port, 0200, NULL, fcloop_create_target_port); | 
|  | static DEVICE_ATTR(del_target_port, 0200, NULL, fcloop_delete_target_port); | 
|  |  | 
|  | static struct attribute *fcloop_dev_attrs[] = { | 
|  | &dev_attr_add_local_port.attr, | 
|  | &dev_attr_del_local_port.attr, | 
|  | &dev_attr_add_remote_port.attr, | 
|  | &dev_attr_del_remote_port.attr, | 
|  | &dev_attr_add_target_port.attr, | 
|  | &dev_attr_del_target_port.attr, | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static struct attribute_group fclopp_dev_attrs_group = { | 
|  | .attrs		= fcloop_dev_attrs, | 
|  | }; | 
|  |  | 
|  | static const struct attribute_group *fcloop_dev_attr_groups[] = { | 
|  | &fclopp_dev_attrs_group, | 
|  | NULL, | 
|  | }; | 
|  |  | 
|  | static struct class *fcloop_class; | 
|  | static struct device *fcloop_device; | 
|  |  | 
|  |  | 
|  | static int __init fcloop_init(void) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | fcloop_class = class_create(THIS_MODULE, "fcloop"); | 
|  | if (IS_ERR(fcloop_class)) { | 
|  | pr_err("couldn't register class fcloop\n"); | 
|  | ret = PTR_ERR(fcloop_class); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | fcloop_device = device_create_with_groups( | 
|  | fcloop_class, NULL, MKDEV(0, 0), NULL, | 
|  | fcloop_dev_attr_groups, "ctl"); | 
|  | if (IS_ERR(fcloop_device)) { | 
|  | pr_err("couldn't create ctl device!\n"); | 
|  | ret = PTR_ERR(fcloop_device); | 
|  | goto out_destroy_class; | 
|  | } | 
|  |  | 
|  | get_device(fcloop_device); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_destroy_class: | 
|  | class_destroy(fcloop_class); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void __exit fcloop_exit(void) | 
|  | { | 
|  | struct fcloop_lport *lport; | 
|  | struct fcloop_nport *nport; | 
|  | struct fcloop_tport *tport; | 
|  | struct fcloop_rport *rport; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  |  | 
|  | for (;;) { | 
|  | nport = list_first_entry_or_null(&fcloop_nports, | 
|  | typeof(*nport), nport_list); | 
|  | if (!nport) | 
|  | break; | 
|  |  | 
|  | tport = __unlink_target_port(nport); | 
|  | rport = __unlink_remote_port(nport); | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | ret = __targetport_unreg(nport, tport); | 
|  | if (ret) | 
|  | pr_warn("%s: Failed deleting target port\n", __func__); | 
|  |  | 
|  | ret = __remoteport_unreg(nport, rport); | 
|  | if (ret) | 
|  | pr_warn("%s: Failed deleting remote port\n", __func__); | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  | } | 
|  |  | 
|  | for (;;) { | 
|  | lport = list_first_entry_or_null(&fcloop_lports, | 
|  | typeof(*lport), lport_list); | 
|  | if (!lport) | 
|  | break; | 
|  |  | 
|  | __unlink_local_port(lport); | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | ret = __wait_localport_unreg(lport); | 
|  | if (ret) | 
|  | pr_warn("%s: Failed deleting local port\n", __func__); | 
|  |  | 
|  | spin_lock_irqsave(&fcloop_lock, flags); | 
|  | } | 
|  |  | 
|  | spin_unlock_irqrestore(&fcloop_lock, flags); | 
|  |  | 
|  | put_device(fcloop_device); | 
|  |  | 
|  | device_destroy(fcloop_class, MKDEV(0, 0)); | 
|  | class_destroy(fcloop_class); | 
|  | } | 
|  |  | 
|  | module_init(fcloop_init); | 
|  | module_exit(fcloop_exit); | 
|  |  | 
|  | MODULE_LICENSE("GPL v2"); |