1379 lines
31 KiB
C
1379 lines
31 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2016 Avago Technologies. All rights reserved.
|
|
*/
|
|
#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 = {
|
|
.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");
|