cregit-Linux how code gets into the kernel

Release 4.14 drivers/nvme/target/fcloop.c

/*
 * 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; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart316100.00%1100.00%
Total316100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart239100.00%1100.00%
Total239100.00%1100.00%

#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_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; }; struct fcloop_fcpreq { struct fcloop_tport *tport; struct nvmefc_fcp_req *fcpreq; spinlock_t reqlock; u16 status; bool active; bool aborted; struct work_struct work; struct nvmefc_tgt_fcp_req tgt_fcp_req; }; struct fcloop_ini_fcpreq { struct nvmefc_fcp_req *fcpreq; struct fcloop_fcpreq *tfcp_req; struct work_struct iniwork; };
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); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart25100.00%1100.00%
Total25100.00%1100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart25100.00%1100.00%
Total25100.00%1100.00%


static int fcloop_create_queue(struct nvme_fc_local_port *localport, unsigned int qidx, u16 qsize, void **handle) { *handle = localport; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart31100.00%1100.00%
Total31100.00%1100.00%


static void fcloop_delete_queue(struct nvme_fc_local_port *localport, unsigned int idx, void *handle) { }

Contributors

PersonTokensPropCommitsCommitProp
James Smart18100.00%1100.00%
Total18100.00%1100.00%

/* * 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->remoteport) lsreq->done(lsreq, tls_req->status); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart61100.00%1100.00%
Total61100.00%1100.00%


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; 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; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart128100.00%1100.00%
Total128100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart84100.00%1100.00%
Total84100.00%1100.00%

/* * FCP IO operation done by initiator abort. * call back up initiator "done" flows. */
static void fcloop_tgt_fcprqst_ini_done_work(struct work_struct *work) { struct fcloop_ini_fcpreq *inireq = container_of(work, struct fcloop_ini_fcpreq, iniwork); inireq->fcpreq->done(inireq->fcpreq); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart37100.00%1100.00%
Total37100.00%1100.00%

/* * 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, work); struct fcloop_tport *tport = tfcp_req->tport; struct nvmefc_fcp_req *fcpreq; spin_lock(&tfcp_req->reqlock); fcpreq = tfcp_req->fcpreq; spin_unlock(&tfcp_req->reqlock); if (tport->remoteport && fcpreq) { fcpreq->status = tfcp_req->status; fcpreq->done(fcpreq); } kfree(tfcp_req); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart92100.00%3100.00%
Total92100.00%3100.00%


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; int ret = 0; 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; INIT_WORK(&inireq->iniwork, fcloop_tgt_fcprqst_ini_done_work); tfcp_req->fcpreq = fcpreq; tfcp_req->tport = rport->targetport->private; spin_lock_init(&tfcp_req->reqlock); INIT_WORK(&tfcp_req->work, fcloop_tgt_fcprqst_done_work); ret = nvmet_fc_rcv_fcp_req(rport->targetport, &tfcp_req->tgt_fcp_req, fcpreq->cmdaddr, fcpreq->cmdlen); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart167100.00%3100.00%
Total167100.00%3100.00%


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; } }

Contributors

PersonTokensPropCommitsCommitProp
James Smart267100.00%1100.00%
Total267100.00%1100.00%


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 */ 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; }

Contributors

PersonTokensPropCommitsCommitProp
James Smart389100.00%3100.00%
Total389100.00%3100.00%


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. */ }

Contributors

PersonTokensPropCommitsCommitProp
James Smart56100.00%3100.00%
Total56100.00%3100.00%


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->work); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart34100.00%3100.00%
Total34100.00%3100.00%


static void fcloop_ls_abort(struct nvme_fc_local_port *localport, struct nvme_fc_remote_port *remoteport, struct nvmefc_ls_req *lsreq) { }

Contributors

PersonTokensPropCommitsCommitProp
James Smart20100.00%1100.00%
Total20100.00%1100.00%


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_rport *rport = remoteport->private; struct fcloop_ini_fcpreq *inireq = fcpreq->private; struct fcloop_fcpreq *tfcp_req = inireq->tfcp_req; if (!tfcp_req) /* abort has already been called */ return; if (rport->targetport) nvmet_fc_rcv_fcp_abort(rport->targetport, &tfcp_req->tgt_fcp_req); /* break initiator/target relationship for io */ spin_lock(&tfcp_req->reqlock); inireq->tfcp_req = NULL; tfcp_req->fcpreq = NULL; spin_unlock(&tfcp_req->reqlock); /* post the aborted io completion */ fcpreq->status = -ECANCELED; schedule_work(&inireq->iniwork); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart122100.00%2100.00%
Total122100.00%2100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart59100.00%2100.00%
Total59100.00%2100.00%


static void fcloop_nport_put(struct fcloop_nport *nport) { kref_put(&nport->ref, fcloop_nport_free); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart21100.00%1100.00%
Total21100.00%1100.00%


static int fcloop_nport_get(struct fcloop_nport *nport) { return kref_get_unless_zero(&nport->ref); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart20100.00%1100.00%
Total20100.00%1100.00%


static void fcloop_localport_delete(struct nvme_fc_local_port *localport) { struct fcloop_lport *lport = localport->private; /* release any threads waiting for the unreg to complete */ complete(&lport->unreg_done); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart29100.00%2100.00%
Total29100.00%2100.00%


static void fcloop_remoteport_delete(struct nvme_fc_remote_port *remoteport) { struct fcloop_rport *rport = remoteport->private; fcloop_nport_put(rport->nport); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart27100.00%2100.00%
Total27100.00%2100.00%


static void fcloop_targetport_delete(struct nvmet_fc_target_port *targetport) { struct fcloop_tport *tport = targetport->private; fcloop_nport_put(tport->nport); }

Contributors

PersonTokensPropCommitsCommitProp
James Smart27100.00%2100.00%
Total27100.00%2100.00%

#define FCLOOP_HW_QUEUES 4 #define FCLOOP_SGL_SEGS 256 #define FCLOOP_DMABOUND_4G 0xFFFFFFFF static struct nvme_fc_port_template fctemplate = { .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), .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 = NVMET_FCTGTFEAT_CMD_IN_ISR | NVMET_FCTGTFEAT_OPDONE_IN_ISR, /* 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; int ret; opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return -ENOMEM; ret = fcloop_parse_options(opts, buf); if (ret