Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/nvme/host/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 106 kB image not shown  

Quelle  fc.c   Sprache: 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 <uapi/scsi/fc/fc_els.h>
#include <linux/delay.h>
#include <linux/overflow.h>
#include <linux/blk-cgroup.h>
#include "nvme.h"
#include "fabrics.h"
#include <linux/nvme-fc-driver.h>
#include <linux/nvme-fc.h>
#include "fc.h"
#include <scsi/scsi_transport_fc.h>

/* *************************** Data Structures/Defines ****************** */


enum nvme_fc_queue_flags {
 NVME_FC_Q_CONNECTED = 0,
 NVME_FC_Q_LIVE,
};

#define NVME_FC_DEFAULT_DEV_LOSS_TMO 60 /* seconds */
#define NVME_FC_DEFAULT_RECONNECT_TMO 2 /* delay between reconnects
 * when connected and a
 * connection failure.
 */


struct nvme_fc_queue {
 struct nvme_fc_ctrl *ctrl;
 struct device  *dev;
 struct blk_mq_hw_ctx *hctx;
 void   *lldd_handle;
 size_t   cmnd_capsule_len;
 u32   qnum;
 u32   rqcnt;
 u32   seqno;

 u64   connection_id;
 atomic_t  csn;

 unsigned long  flags;
} __aligned(sizeof(u64)); /* alignment for other things alloc'd with */

enum nvme_fcop_flags {
 FCOP_FLAGS_TERMIO = (1 << 0),
 FCOP_FLAGS_AEN  = (1 << 1),
};

struct nvmefc_ls_req_op {
 struct nvmefc_ls_req ls_req;

 struct nvme_fc_rport *rport;
 struct nvme_fc_queue *queue;
 struct request  *rq;
 u32   flags;

 int   ls_error;
 struct completion ls_done;
 struct list_head lsreq_list; /* rport->ls_req_list */
 bool   req_queued;
};

struct nvmefc_ls_rcv_op {
 struct nvme_fc_rport  *rport;
 struct nvmefc_ls_rsp  *lsrsp;
 union nvmefc_ls_requests *rqstbuf;
 union nvmefc_ls_responses *rspbuf;
 u16    rqstdatalen;
 bool    handled;
 dma_addr_t   rspdma;
 struct list_head  lsrcv_list; /* rport->ls_rcv_list */
} __aligned(sizeof(u64)); /* alignment for other things alloc'd with */

enum nvme_fcpop_state {
 FCPOP_STATE_UNINIT = 0,
 FCPOP_STATE_IDLE = 1,
 FCPOP_STATE_ACTIVE = 2,
 FCPOP_STATE_ABORTED = 3,
 FCPOP_STATE_COMPLETE = 4,
};

struct nvme_fc_fcp_op {
 struct nvme_request nreq;  /*
 * nvme/host/core.c
 * requires this to be
 * the 1st element in the
 * private structure
 * associated with the
 * request.
 */

 struct nvmefc_fcp_req fcp_req;

 struct nvme_fc_ctrl *ctrl;
 struct nvme_fc_queue *queue;
 struct request  *rq;

 atomic_t  state;
 u32   flags;
 u32   rqno;
 u32   nents;

 struct nvme_fc_cmd_iu cmd_iu;
 struct nvme_fc_ersp_iu rsp_iu;
};

struct nvme_fcp_op_w_sgl {
 struct nvme_fc_fcp_op op;
 struct scatterlist sgl[NVME_INLINE_SG_CNT];
 uint8_t   priv[];
};

struct nvme_fc_lport {
 struct nvme_fc_local_port localport;

 struct ida   endp_cnt;
 struct list_head  port_list; /* nvme_fc_port_list */
 struct list_head  endp_list;
 struct device   *dev; /* physical device for dma */
 struct nvme_fc_port_template *ops;
 struct kref   ref;
 atomic_t                        act_rport_cnt;
} __aligned(sizeof(u64)); /* alignment for other things alloc'd with */

struct nvme_fc_rport {
 struct nvme_fc_remote_port remoteport;

 struct list_head  endp_list; /* for lport->endp_list */
 struct list_head  ctrl_list;
 struct list_head  ls_req_list;
 struct list_head  ls_rcv_list;
 struct list_head  disc_list;
 struct device   *dev; /* physical device for dma */
 struct nvme_fc_lport  *lport;
 spinlock_t   lock;
 struct kref   ref;
 atomic_t                        act_ctrl_cnt;
 unsigned long   dev_loss_end;
 struct work_struct  lsrcv_work;
} __aligned(sizeof(u64)); /* alignment for other things alloc'd with */

/* fc_ctrl flags values - specified as bit positions */
#define ASSOC_ACTIVE  0
#define ASSOC_FAILED  1
#define FCCTRL_TERMIO  2

struct nvme_fc_ctrl {
 spinlock_t  lock;
 struct nvme_fc_queue *queues;
 struct device  *dev;
 struct nvme_fc_lport *lport;
 struct nvme_fc_rport *rport;
 u32   cnum;

 bool   ioq_live;
 u64   association_id;
 struct nvmefc_ls_rcv_op *rcv_disconn;

 struct list_head ctrl_list; /* rport->ctrl_list */

 struct blk_mq_tag_set admin_tag_set;
 struct blk_mq_tag_set tag_set;

 struct work_struct ioerr_work;
 struct delayed_work connect_work;

 struct kref  ref;
 unsigned long  flags;
 u32   iocnt;
 wait_queue_head_t ioabort_wait;

 struct nvme_fc_fcp_op aen_ops[NVME_NR_AEN_COMMANDS];

 struct nvme_ctrl ctrl;
};

static inline struct nvme_fc_ctrl *
to_fc_ctrl(struct nvme_ctrl *ctrl)
{
 return container_of(ctrl, struct nvme_fc_ctrl, ctrl);
}

static inline struct nvme_fc_lport *
localport_to_lport(struct nvme_fc_local_port *portptr)
{
 return container_of(portptr, struct nvme_fc_lport, localport);
}

static inline struct nvme_fc_rport *
remoteport_to_rport(struct nvme_fc_remote_port *portptr)
{
 return container_of(portptr, struct nvme_fc_rport, remoteport);
}

static inline struct nvmefc_ls_req_op *
ls_req_to_lsop(struct nvmefc_ls_req *lsreq)
{
 return container_of(lsreq, struct nvmefc_ls_req_op, ls_req);
}

static inline struct nvme_fc_fcp_op *
fcp_req_to_fcp_op(struct nvmefc_fcp_req *fcpreq)
{
 return container_of(fcpreq, struct nvme_fc_fcp_op, fcp_req);
}



/* *************************** Globals **************************** */


static DEFINE_SPINLOCK(nvme_fc_lock);

static LIST_HEAD(nvme_fc_lport_list);
static DEFINE_IDA(nvme_fc_local_port_cnt);
static DEFINE_IDA(nvme_fc_ctrl_cnt);

/*
 * These items are short-term. They will eventually be moved into
 * a generic FC class. See comments in module init.
 */

static struct device *fc_udev_device;

static void nvme_fc_complete_rq(struct request *rq);

/* *********************** FC-NVME Port Management ************************ */

static void __nvme_fc_delete_hw_queue(struct nvme_fc_ctrl *,
   struct nvme_fc_queue *, unsigned int);

static void nvme_fc_handle_ls_rqst_work(struct work_struct *work);


static void
nvme_fc_free_lport(struct kref *ref)
{
 struct nvme_fc_lport *lport =
  container_of(ref, struct nvme_fc_lport, ref);
 unsigned long flags;

 WARN_ON(lport->localport.port_state != FC_OBJSTATE_DELETED);
 WARN_ON(!list_empty(&lport->endp_list));

 /* remove from transport list */
 spin_lock_irqsave(&nvme_fc_lock, flags);
 list_del(&lport->port_list);
 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 ida_free(&nvme_fc_local_port_cnt, lport->localport.port_num);
 ida_destroy(&lport->endp_cnt);

 put_device(lport->dev);

 kfree(lport);
}

static void
nvme_fc_lport_put(struct nvme_fc_lport *lport)
{
 kref_put(&lport->ref, nvme_fc_free_lport);
}

static int
nvme_fc_lport_get(struct nvme_fc_lport *lport)
{
 return kref_get_unless_zero(&lport->ref);
}


static struct nvme_fc_lport *
nvme_fc_attach_to_unreg_lport(struct nvme_fc_port_info *pinfo,
   struct nvme_fc_port_template *ops,
   struct device *dev)
{
 struct nvme_fc_lport *lport;
 unsigned long flags;

 spin_lock_irqsave(&nvme_fc_lock, flags);

 list_for_each_entry(lport, &nvme_fc_lport_list, port_list) {
  if (lport->localport.node_name != pinfo->node_name ||
      lport->localport.port_name != pinfo->port_name)
   continue;

  if (lport->dev != dev) {
   lport = ERR_PTR(-EXDEV);
   goto out_done;
  }

  if (lport->localport.port_state != FC_OBJSTATE_DELETED) {
   lport = ERR_PTR(-EEXIST);
   goto out_done;
  }

  if (!nvme_fc_lport_get(lport)) {
   /*
 * fails if ref cnt already 0. If so,
 * act as if lport already deleted
 */

   lport = NULL;
   goto out_done;
  }

  /* resume the lport */

  lport->ops = ops;
  lport->localport.port_role = pinfo->port_role;
  lport->localport.port_id = pinfo->port_id;
  lport->localport.port_state = FC_OBJSTATE_ONLINE;

  spin_unlock_irqrestore(&nvme_fc_lock, flags);

  return lport;
 }

 lport = NULL;

out_done:
 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 return lport;
}

/**
 * nvme_fc_register_localport - transport entry point called by an
 *                              LLDD to register the existence of a NVME
 *                              host FC port.
 * @pinfo:     pointer to information about the port to be registered
 * @template:  LLDD entrypoints and operational parameters for the port
 * @dev:       physical hardware device node port corresponds to. Will be
 *             used for DMA mappings
 * @portptr:   pointer to a local port pointer. Upon success, the routine
 *             will allocate a nvme_fc_local_port structure and place its
 *             address in the local port pointer. Upon failure, local port
 *             pointer will be set to 0.
 *
 * Returns:
 * a completion status. Must be 0 upon success; a negative errno
 * (ex: -ENXIO) upon failure.
 */

int
nvme_fc_register_localport(struct nvme_fc_port_info *pinfo,
   struct nvme_fc_port_template *template,
   struct device *dev,
   struct nvme_fc_local_port **portptr)
{
 struct nvme_fc_lport *newrec;
 unsigned long flags;
 int ret, idx;

 if (!template->localport_delete || !template->remoteport_delete ||
     !template->ls_req || !template->fcp_io ||
     !template->ls_abort || !template->fcp_abort ||
     !template->max_hw_queues || !template->max_sgl_segments ||
     !template->max_dif_sgl_segments || !template->dma_boundary) {
  ret = -EINVAL;
  goto out_reghost_failed;
 }

 /*
 * look to see if there is already a localport that had been
 * deregistered and in the process of waiting for all the
 * references to fully be removed.  If the references haven't
 * expired, we can simply re-enable the localport. Remoteports
 * and controller reconnections should resume naturally.
 */

 newrec = nvme_fc_attach_to_unreg_lport(pinfo, template, dev);

 /* found an lport, but something about its state is bad */
 if (IS_ERR(newrec)) {
  ret = PTR_ERR(newrec);
  goto out_reghost_failed;

 /* found existing lport, which was resumed */
 } else if (newrec) {
  *portptr = &newrec->localport;
  return 0;
 }

 /* nothing found - allocate a new localport struct */

 newrec = kmalloc((sizeof(*newrec) + template->local_priv_sz),
    GFP_KERNEL);
 if (!newrec) {
  ret = -ENOMEM;
  goto out_reghost_failed;
 }

 idx = ida_alloc(&nvme_fc_local_port_cnt, GFP_KERNEL);
 if (idx < 0) {
  ret = -ENOSPC;
  goto out_fail_kfree;
 }

 if (!get_device(dev) && dev) {
  ret = -ENODEV;
  goto out_ida_put;
 }

 INIT_LIST_HEAD(&newrec->port_list);
 INIT_LIST_HEAD(&newrec->endp_list);
 kref_init(&newrec->ref);
 atomic_set(&newrec->act_rport_cnt, 0);
 newrec->ops = template;
 newrec->dev = dev;
 ida_init(&newrec->endp_cnt);
 if (template->local_priv_sz)
  newrec->localport.private = &newrec[1];
 else
  newrec->localport.private = NULL;
 newrec->localport.node_name = pinfo->node_name;
 newrec->localport.port_name = pinfo->port_name;
 newrec->localport.port_role = pinfo->port_role;
 newrec->localport.port_id = pinfo->port_id;
 newrec->localport.port_state = FC_OBJSTATE_ONLINE;
 newrec->localport.port_num = idx;

 spin_lock_irqsave(&nvme_fc_lock, flags);
 list_add_tail(&newrec->port_list, &nvme_fc_lport_list);
 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 if (dev)
  dma_set_seg_boundary(dev, template->dma_boundary);

 *portptr = &newrec->localport;
 return 0;

out_ida_put:
 ida_free(&nvme_fc_local_port_cnt, idx);
out_fail_kfree:
 kfree(newrec);
out_reghost_failed:
 *portptr = NULL;

 return ret;
}
EXPORT_SYMBOL_GPL(nvme_fc_register_localport);

/**
 * nvme_fc_unregister_localport - transport entry point called by an
 *                              LLDD to deregister/remove a previously
 *                              registered a NVME host FC port.
 * @portptr: pointer to the (registered) local port that is to be deregistered.
 *
 * Returns:
 * a completion status. Must be 0 upon success; a negative errno
 * (ex: -ENXIO) upon failure.
 */

int
nvme_fc_unregister_localport(struct nvme_fc_local_port *portptr)
{
 struct nvme_fc_lport *lport = localport_to_lport(portptr);
 unsigned long flags;

 if (!portptr)
  return -EINVAL;

 spin_lock_irqsave(&nvme_fc_lock, flags);

 if (portptr->port_state != FC_OBJSTATE_ONLINE) {
  spin_unlock_irqrestore(&nvme_fc_lock, flags);
  return -EINVAL;
 }
 portptr->port_state = FC_OBJSTATE_DELETED;

 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 if (atomic_read(&lport->act_rport_cnt) == 0)
  lport->ops->localport_delete(&lport->localport);

 nvme_fc_lport_put(lport);

 return 0;
}
EXPORT_SYMBOL_GPL(nvme_fc_unregister_localport);

/*
 * TRADDR strings, per FC-NVME are fixed format:
 *   "nn-0x<16hexdigits>:pn-0x<16hexdigits>" - 43 characters
 * udev event will only differ by prefix of what field is
 * being specified:
 *    "NVMEFC_HOST_TRADDR=" or "NVMEFC_TRADDR=" - 19 max characters
 *  19 + 43 + null_fudge = 64 characters
 */

#define FCNVME_TRADDR_LENGTH  64

static void
nvme_fc_signal_discovery_scan(struct nvme_fc_lport *lport,
  struct nvme_fc_rport *rport)
{
 char hostaddr[FCNVME_TRADDR_LENGTH]; /* NVMEFC_HOST_TRADDR=...*/
 char tgtaddr[FCNVME_TRADDR_LENGTH]; /* NVMEFC_TRADDR=...*/
 char *envp[4] = { "FC_EVENT=nvmediscovery", hostaddr, tgtaddr, NULL };

 if (!(rport->remoteport.port_role & FC_PORT_ROLE_NVME_DISCOVERY))
  return;

 snprintf(hostaddr, sizeof(hostaddr),
  "NVMEFC_HOST_TRADDR=nn-0x%016llx:pn-0x%016llx",
  lport->localport.node_name, lport->localport.port_name);
 snprintf(tgtaddr, sizeof(tgtaddr),
  "NVMEFC_TRADDR=nn-0x%016llx:pn-0x%016llx",
  rport->remoteport.node_name, rport->remoteport.port_name);
 kobject_uevent_env(&fc_udev_device->kobj, KOBJ_CHANGE, envp);
}

static void
nvme_fc_free_rport(struct kref *ref)
{
 struct nvme_fc_rport *rport =
  container_of(ref, struct nvme_fc_rport, ref);
 struct nvme_fc_lport *lport =
   localport_to_lport(rport->remoteport.localport);
 unsigned long flags;

 WARN_ON(rport->remoteport.port_state != FC_OBJSTATE_DELETED);
 WARN_ON(!list_empty(&rport->ctrl_list));

 /* remove from lport list */
 spin_lock_irqsave(&nvme_fc_lock, flags);
 list_del(&rport->endp_list);
 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 WARN_ON(!list_empty(&rport->disc_list));
 ida_free(&lport->endp_cnt, rport->remoteport.port_num);

 kfree(rport);

 nvme_fc_lport_put(lport);
}

static void
nvme_fc_rport_put(struct nvme_fc_rport *rport)
{
 kref_put(&rport->ref, nvme_fc_free_rport);
}

static int
nvme_fc_rport_get(struct nvme_fc_rport *rport)
{
 return kref_get_unless_zero(&rport->ref);
}

static void
nvme_fc_resume_controller(struct nvme_fc_ctrl *ctrl)
{
 switch (nvme_ctrl_state(&ctrl->ctrl)) {
 case NVME_CTRL_NEW:
 case NVME_CTRL_CONNECTING:
  /*
 * As all reconnects were suppressed, schedule a
 * connect.
 */

  dev_info(ctrl->ctrl.device,
   "NVME-FC{%d}: connectivity re-established. "
   "Attempting reconnect\n", ctrl->cnum);

  queue_delayed_work(nvme_wq, &ctrl->connect_work, 0);
  break;

 case NVME_CTRL_RESETTING:
  /*
 * Controller is already in the process of terminating the
 * association. No need to do anything further. The reconnect
 * step will naturally occur after the reset completes.
 */

  break;

 default:
  /* no action to take - let it delete */
  break;
 }
}

static struct nvme_fc_rport *
nvme_fc_attach_to_suspended_rport(struct nvme_fc_lport *lport,
    struct nvme_fc_port_info *pinfo)
{
 struct nvme_fc_rport *rport;
 struct nvme_fc_ctrl *ctrl;
 unsigned long flags;

 spin_lock_irqsave(&nvme_fc_lock, flags);

 list_for_each_entry(rport, &lport->endp_list, endp_list) {
  if (rport->remoteport.node_name != pinfo->node_name ||
      rport->remoteport.port_name != pinfo->port_name)
   continue;

  if (!nvme_fc_rport_get(rport)) {
   rport = ERR_PTR(-ENOLCK);
   goto out_done;
  }

  spin_unlock_irqrestore(&nvme_fc_lock, flags);

  spin_lock_irqsave(&rport->lock, flags);

  /* has it been unregistered */
  if (rport->remoteport.port_state != FC_OBJSTATE_DELETED) {
   /* means lldd called us twice */
   spin_unlock_irqrestore(&rport->lock, flags);
   nvme_fc_rport_put(rport);
   return ERR_PTR(-ESTALE);
  }

  rport->remoteport.port_role = pinfo->port_role;
  rport->remoteport.port_id = pinfo->port_id;
  rport->remoteport.port_state = FC_OBJSTATE_ONLINE;
  rport->dev_loss_end = 0;

  /*
 * kick off a reconnect attempt on all associations to the
 * remote port. A successful reconnects will resume i/o.
 */

  list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list)
   nvme_fc_resume_controller(ctrl);

  spin_unlock_irqrestore(&rport->lock, flags);

  return rport;
 }

 rport = NULL;

out_done:
 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 return rport;
}

static inline void
__nvme_fc_set_dev_loss_tmo(struct nvme_fc_rport *rport,
   struct nvme_fc_port_info *pinfo)
{
 if (pinfo->dev_loss_tmo)
  rport->remoteport.dev_loss_tmo = pinfo->dev_loss_tmo;
 else
  rport->remoteport.dev_loss_tmo = NVME_FC_DEFAULT_DEV_LOSS_TMO;
}

/**
 * nvme_fc_register_remoteport - transport entry point called by an
 *                              LLDD to register the existence of a NVME
 *                              subsystem FC port on its fabric.
 * @localport: pointer to the (registered) local port that the remote
 *             subsystem port is connected to.
 * @pinfo:     pointer to information about the port to be registered
 * @portptr:   pointer to a remote port pointer. Upon success, the routine
 *             will allocate a nvme_fc_remote_port structure and place its
 *             address in the remote port pointer. Upon failure, remote port
 *             pointer will be set to 0.
 *
 * Returns:
 * a completion status. Must be 0 upon success; a negative errno
 * (ex: -ENXIO) upon failure.
 */

int
nvme_fc_register_remoteport(struct nvme_fc_local_port *localport,
    struct nvme_fc_port_info *pinfo,
    struct nvme_fc_remote_port **portptr)
{
 struct nvme_fc_lport *lport = localport_to_lport(localport);
 struct nvme_fc_rport *newrec;
 unsigned long flags;
 int ret, idx;

 if (!nvme_fc_lport_get(lport)) {
  ret = -ESHUTDOWN;
  goto out_reghost_failed;
 }

 /*
 * look to see if there is already a remoteport that is waiting
 * for a reconnect (within dev_loss_tmo) with the same WWN's.
 * If so, transition to it and reconnect.
 */

 newrec = nvme_fc_attach_to_suspended_rport(lport, pinfo);

 /* found an rport, but something about its state is bad */
 if (IS_ERR(newrec)) {
  ret = PTR_ERR(newrec);
  goto out_lport_put;

 /* found existing rport, which was resumed */
 } else if (newrec) {
  nvme_fc_lport_put(lport);
  __nvme_fc_set_dev_loss_tmo(newrec, pinfo);
  nvme_fc_signal_discovery_scan(lport, newrec);
  *portptr = &newrec->remoteport;
  return 0;
 }

 /* nothing found - allocate a new remoteport struct */

 newrec = kmalloc((sizeof(*newrec) + lport->ops->remote_priv_sz),
    GFP_KERNEL);
 if (!newrec) {
  ret = -ENOMEM;
  goto out_lport_put;
 }

 idx = ida_alloc(&lport->endp_cnt, GFP_KERNEL);
 if (idx < 0) {
  ret = -ENOSPC;
  goto out_kfree_rport;
 }

 INIT_LIST_HEAD(&newrec->endp_list);
 INIT_LIST_HEAD(&newrec->ctrl_list);
 INIT_LIST_HEAD(&newrec->ls_req_list);
 INIT_LIST_HEAD(&newrec->disc_list);
 kref_init(&newrec->ref);
 atomic_set(&newrec->act_ctrl_cnt, 0);
 spin_lock_init(&newrec->lock);
 newrec->remoteport.localport = &lport->localport;
 INIT_LIST_HEAD(&newrec->ls_rcv_list);
 newrec->dev = lport->dev;
 newrec->lport = lport;
 if (lport->ops->remote_priv_sz)
  newrec->remoteport.private = &newrec[1];
 else
  newrec->remoteport.private = NULL;
 newrec->remoteport.port_role = pinfo->port_role;
 newrec->remoteport.node_name = pinfo->node_name;
 newrec->remoteport.port_name = pinfo->port_name;
 newrec->remoteport.port_id = pinfo->port_id;
 newrec->remoteport.port_state = FC_OBJSTATE_ONLINE;
 newrec->remoteport.port_num = idx;
 __nvme_fc_set_dev_loss_tmo(newrec, pinfo);
 INIT_WORK(&newrec->lsrcv_work, nvme_fc_handle_ls_rqst_work);

 spin_lock_irqsave(&nvme_fc_lock, flags);
 list_add_tail(&newrec->endp_list, &lport->endp_list);
 spin_unlock_irqrestore(&nvme_fc_lock, flags);

 nvme_fc_signal_discovery_scan(lport, newrec);

 *portptr = &newrec->remoteport;
 return 0;

out_kfree_rport:
 kfree(newrec);
out_lport_put:
 nvme_fc_lport_put(lport);
out_reghost_failed:
 *portptr = NULL;
 return ret;
}
EXPORT_SYMBOL_GPL(nvme_fc_register_remoteport);

static int
nvme_fc_abort_lsops(struct nvme_fc_rport *rport)
{
 struct nvmefc_ls_req_op *lsop;
 unsigned long flags;

restart:
 spin_lock_irqsave(&rport->lock, flags);

 list_for_each_entry(lsop, &rport->ls_req_list, lsreq_list) {
  if (!(lsop->flags & FCOP_FLAGS_TERMIO)) {
   lsop->flags |= FCOP_FLAGS_TERMIO;
   spin_unlock_irqrestore(&rport->lock, flags);
   rport->lport->ops->ls_abort(&rport->lport->localport,
      &rport->remoteport,
      &lsop->ls_req);
   goto restart;
  }
 }
 spin_unlock_irqrestore(&rport->lock, flags);

 return 0;
}

static void
nvme_fc_ctrl_connectivity_loss(struct nvme_fc_ctrl *ctrl)
{
 dev_info(ctrl->ctrl.device,
  "NVME-FC{%d}: controller connectivity lost. Awaiting "
  "Reconnect", ctrl->cnum);

 set_bit(ASSOC_FAILED, &ctrl->flags);
 nvme_reset_ctrl(&ctrl->ctrl);
}

/**
 * nvme_fc_unregister_remoteport - transport entry point called by an
 *                              LLDD to deregister/remove a previously
 *                              registered a NVME subsystem FC port.
 * @portptr: pointer to the (registered) remote port that is to be
 *           deregistered.
 *
 * Returns:
 * a completion status. Must be 0 upon success; a negative errno
 * (ex: -ENXIO) upon failure.
 */

int
nvme_fc_unregister_remoteport(struct nvme_fc_remote_port *portptr)
{
 struct nvme_fc_rport *rport = remoteport_to_rport(portptr);
 struct nvme_fc_ctrl *ctrl;
 unsigned long flags;

 if (!portptr)
  return -EINVAL;

 spin_lock_irqsave(&rport->lock, flags);

 if (portptr->port_state != FC_OBJSTATE_ONLINE) {
  spin_unlock_irqrestore(&rport->lock, flags);
  return -EINVAL;
 }
 portptr->port_state = FC_OBJSTATE_DELETED;

 rport->dev_loss_end = jiffies + (portptr->dev_loss_tmo * HZ);

 list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list) {
  /* if dev_loss_tmo==0, dev loss is immediate */
  if (!portptr->dev_loss_tmo) {
   dev_warn(ctrl->ctrl.device,
    "NVME-FC{%d}: controller connectivity lost.\n",
    ctrl->cnum);
   nvme_delete_ctrl(&ctrl->ctrl);
  } else
   nvme_fc_ctrl_connectivity_loss(ctrl);
 }

 spin_unlock_irqrestore(&rport->lock, flags);

 nvme_fc_abort_lsops(rport);

 if (atomic_read(&rport->act_ctrl_cnt) == 0)
  rport->lport->ops->remoteport_delete(portptr);

 /*
 * release the reference, which will allow, if all controllers
 * go away, which should only occur after dev_loss_tmo occurs,
 * for the rport to be torn down.
 */

 nvme_fc_rport_put(rport);

 return 0;
}
EXPORT_SYMBOL_GPL(nvme_fc_unregister_remoteport);

/**
 * nvme_fc_rescan_remoteport - transport entry point called by an
 *                              LLDD to request a nvme device rescan.
 * @remoteport: pointer to the (registered) remote port that is to be
 *              rescanned.
 *
 * Returns: N/A
 */

void
nvme_fc_rescan_remoteport(struct nvme_fc_remote_port *remoteport)
{
 struct nvme_fc_rport *rport = remoteport_to_rport(remoteport);

 nvme_fc_signal_discovery_scan(rport->lport, rport);
}
EXPORT_SYMBOL_GPL(nvme_fc_rescan_remoteport);

int
nvme_fc_set_remoteport_devloss(struct nvme_fc_remote_port *portptr,
   u32 dev_loss_tmo)
{
 struct nvme_fc_rport *rport = remoteport_to_rport(portptr);
 unsigned long flags;

 spin_lock_irqsave(&rport->lock, flags);

 if (portptr->port_state != FC_OBJSTATE_ONLINE) {
  spin_unlock_irqrestore(&rport->lock, flags);
  return -EINVAL;
 }

 /* a dev_loss_tmo of 0 (immediate) is allowed to be set */
 rport->remoteport.dev_loss_tmo = dev_loss_tmo;

 spin_unlock_irqrestore(&rport->lock, flags);

 return 0;
}
EXPORT_SYMBOL_GPL(nvme_fc_set_remoteport_devloss);


/* *********************** FC-NVME DMA Handling **************************** */

/*
 * The fcloop device passes in a NULL device pointer. Real LLD's will
 * pass in a valid device pointer. If NULL is passed to the dma mapping
 * routines, depending on the platform, it may or may not succeed, and
 * may crash.
 *
 * As such:
 * Wrap all the dma routines and check the dev pointer.
 *
 * If simple mappings (return just a dma address, we'll noop them,
 * returning a dma address of 0.
 *
 * On more complex mappings (dma_map_sg), a pseudo routine fills
 * in the scatter list, setting all dma addresses to 0.
 */


static inline dma_addr_t
fc_dma_map_single(struct device *dev, void *ptr, size_t size,
  enum dma_data_direction dir)
{
 return dev ? dma_map_single(dev, ptr, size, dir) : (dma_addr_t)0L;
}

static inline int
fc_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
 return dev ? dma_mapping_error(dev, dma_addr) : 0;
}

static inline void
fc_dma_unmap_single(struct device *dev, dma_addr_t addr, size_t size,
 enum dma_data_direction dir)
{
 if (dev)
  dma_unmap_single(dev, addr, size, dir);
}

static inline void
fc_dma_sync_single_for_cpu(struct device *dev, dma_addr_t addr, size_t size,
  enum dma_data_direction dir)
{
 if (dev)
  dma_sync_single_for_cpu(dev, addr, size, dir);
}

static inline void
fc_dma_sync_single_for_device(struct device *dev, dma_addr_t addr, size_t size,
  enum dma_data_direction dir)
{
 if (dev)
  dma_sync_single_for_device(dev, addr, size, dir);
}

/* pseudo dma_map_sg call */
static int
fc_map_sg(struct scatterlist *sg, int nents)
{
 struct scatterlist *s;
 int i;

 WARN_ON(nents == 0 || sg[0].length == 0);

 for_each_sg(sg, s, nents, i) {
  s->dma_address = 0L;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
  s->dma_length = s->length;
#endif
 }
 return nents;
}

static inline int
fc_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
  enum dma_data_direction dir)
{
 return dev ? dma_map_sg(dev, sg, nents, dir) : fc_map_sg(sg, nents);
}

static inline void
fc_dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
  enum dma_data_direction dir)
{
 if (dev)
  dma_unmap_sg(dev, sg, nents, dir);
}

/* *********************** FC-NVME LS Handling **************************** */

static void nvme_fc_ctrl_put(struct nvme_fc_ctrl *);
static int nvme_fc_ctrl_get(struct nvme_fc_ctrl *);

static void nvme_fc_error_recovery(struct nvme_fc_ctrl *ctrl, char *errmsg);

static void
__nvme_fc_finish_ls_req(struct nvmefc_ls_req_op *lsop)
{
 struct nvme_fc_rport *rport = lsop->rport;
 struct nvmefc_ls_req *lsreq = &lsop->ls_req;
 unsigned long flags;

 spin_lock_irqsave(&rport->lock, flags);

 if (!lsop->req_queued) {
  spin_unlock_irqrestore(&rport->lock, flags);
  return;
 }

 list_del(&lsop->lsreq_list);

 lsop->req_queued = false;

 spin_unlock_irqrestore(&rport->lock, flags);

 fc_dma_unmap_single(rport->dev, lsreq->rqstdma,
      (lsreq->rqstlen + lsreq->rsplen),
      DMA_BIDIRECTIONAL);

 nvme_fc_rport_put(rport);
}

static int
__nvme_fc_send_ls_req(struct nvme_fc_rport *rport,
  struct nvmefc_ls_req_op *lsop,
  void (*done)(struct nvmefc_ls_req *req, int status))
{
 struct nvmefc_ls_req *lsreq = &lsop->ls_req;
 unsigned long flags;
 int ret = 0;

 if (rport->remoteport.port_state != FC_OBJSTATE_ONLINE)
  return -ECONNREFUSED;

 if (!nvme_fc_rport_get(rport))
  return -ESHUTDOWN;

 lsreq->done = done;
 lsop->rport = rport;
 lsop->req_queued = false;
 INIT_LIST_HEAD(&lsop->lsreq_list);
 init_completion(&lsop->ls_done);

 lsreq->rqstdma = fc_dma_map_single(rport->dev, lsreq->rqstaddr,
      lsreq->rqstlen + lsreq->rsplen,
      DMA_BIDIRECTIONAL);
 if (fc_dma_mapping_error(rport->dev, lsreq->rqstdma)) {
  ret = -EFAULT;
  goto out_putrport;
 }
 lsreq->rspdma = lsreq->rqstdma + lsreq->rqstlen;

 spin_lock_irqsave(&rport->lock, flags);

 list_add_tail(&lsop->lsreq_list, &rport->ls_req_list);

 lsop->req_queued = true;

 spin_unlock_irqrestore(&rport->lock, flags);

 ret = rport->lport->ops->ls_req(&rport->lport->localport,
     &rport->remoteport, lsreq);
 if (ret)
  goto out_unlink;

 return 0;

out_unlink:
 lsop->ls_error = ret;
 spin_lock_irqsave(&rport->lock, flags);
 lsop->req_queued = false;
 list_del(&lsop->lsreq_list);
 spin_unlock_irqrestore(&rport->lock, flags);
 fc_dma_unmap_single(rport->dev, lsreq->rqstdma,
      (lsreq->rqstlen + lsreq->rsplen),
      DMA_BIDIRECTIONAL);
out_putrport:
 nvme_fc_rport_put(rport);

 return ret;
}

static void
nvme_fc_send_ls_req_done(struct nvmefc_ls_req *lsreq, int status)
{
 struct nvmefc_ls_req_op *lsop = ls_req_to_lsop(lsreq);

 lsop->ls_error = status;
 complete(&lsop->ls_done);
}

static int
nvme_fc_send_ls_req(struct nvme_fc_rport *rport, struct nvmefc_ls_req_op *lsop)
{
 struct nvmefc_ls_req *lsreq = &lsop->ls_req;
 struct fcnvme_ls_rjt *rjt = lsreq->rspaddr;
 int ret;

 ret = __nvme_fc_send_ls_req(rport, lsop, nvme_fc_send_ls_req_done);

 if (!ret) {
  /*
 * No timeout/not interruptible as we need the struct
 * to exist until the lldd calls us back. Thus mandate
 * wait until driver calls back. lldd responsible for
 * the timeout action
 */

  wait_for_completion(&lsop->ls_done);

  __nvme_fc_finish_ls_req(lsop);

  ret = lsop->ls_error;
 }

 if (ret)
  return ret;

 /* ACC or RJT payload ? */
 if (rjt->w0.ls_cmd == FCNVME_LS_RJT)
  return -ENXIO;

 return 0;
}

static int
nvme_fc_send_ls_req_async(struct nvme_fc_rport *rport,
  struct nvmefc_ls_req_op *lsop,
  void (*done)(struct nvmefc_ls_req *req, int status))
{
 /* don't wait for completion */

 return __nvme_fc_send_ls_req(rport, lsop, done);
}

static int
nvme_fc_connect_admin_queue(struct nvme_fc_ctrl *ctrl,
 struct nvme_fc_queue *queue, u16 qsize, u16 ersp_ratio)
{
 struct nvmefc_ls_req_op *lsop;
 struct nvmefc_ls_req *lsreq;
 struct fcnvme_ls_cr_assoc_rqst *assoc_rqst;
 struct fcnvme_ls_cr_assoc_acc *assoc_acc;
 unsigned long flags;
 int ret, fcret = 0;

 lsop = kzalloc((sizeof(*lsop) +
    sizeof(*assoc_rqst) + sizeof(*assoc_acc) +
    ctrl->lport->ops->lsrqst_priv_sz), GFP_KERNEL);
 if (!lsop) {
  dev_info(ctrl->ctrl.device,
   "NVME-FC{%d}: send Create Association failed: ENOMEM\n",
   ctrl->cnum);
  ret = -ENOMEM;
  goto out_no_memory;
 }

 assoc_rqst = (struct fcnvme_ls_cr_assoc_rqst *)&lsop[1];
 assoc_acc = (struct fcnvme_ls_cr_assoc_acc *)&assoc_rqst[1];
 lsreq = &lsop->ls_req;
 if (ctrl->lport->ops->lsrqst_priv_sz)
  lsreq->private = &assoc_acc[1];
 else
  lsreq->private = NULL;

 assoc_rqst->w0.ls_cmd = FCNVME_LS_CREATE_ASSOCIATION;
 assoc_rqst->desc_list_len =
   cpu_to_be32(sizeof(struct fcnvme_lsdesc_cr_assoc_cmd));

 assoc_rqst->assoc_cmd.desc_tag =
   cpu_to_be32(FCNVME_LSDESC_CREATE_ASSOC_CMD);
 assoc_rqst->assoc_cmd.desc_len =
   fcnvme_lsdesc_len(
    sizeof(struct fcnvme_lsdesc_cr_assoc_cmd));

 assoc_rqst->assoc_cmd.ersp_ratio = cpu_to_be16(ersp_ratio);
 assoc_rqst->assoc_cmd.sqsize = cpu_to_be16(qsize - 1);
 /* Linux supports only Dynamic controllers */
 assoc_rqst->assoc_cmd.cntlid = cpu_to_be16(0xffff);
 uuid_copy(&assoc_rqst->assoc_cmd.hostid, &ctrl->ctrl.opts->host->id);
 strscpy(assoc_rqst->assoc_cmd.hostnqn, ctrl->ctrl.opts->host->nqn,
  sizeof(assoc_rqst->assoc_cmd.hostnqn));
 strscpy(assoc_rqst->assoc_cmd.subnqn, ctrl->ctrl.opts->subsysnqn,
  sizeof(assoc_rqst->assoc_cmd.subnqn));

 lsop->queue = queue;
 lsreq->rqstaddr = assoc_rqst;
 lsreq->rqstlen = sizeof(*assoc_rqst);
 lsreq->rspaddr = assoc_acc;
 lsreq->rsplen = sizeof(*assoc_acc);
 lsreq->timeout = NVME_FC_LS_TIMEOUT_SEC;

 ret = nvme_fc_send_ls_req(ctrl->rport, lsop);
 if (ret)
  goto out_free_buffer;

 /* process connect LS completion */

 /* validate the ACC response */
 if (assoc_acc->hdr.w0.ls_cmd != FCNVME_LS_ACC)
  fcret = VERR_LSACC;
 else if (assoc_acc->hdr.desc_list_len !=
   fcnvme_lsdesc_len(
    sizeof(struct fcnvme_ls_cr_assoc_acc)))
  fcret = VERR_CR_ASSOC_ACC_LEN;
 else if (assoc_acc->hdr.rqst.desc_tag !=
   cpu_to_be32(FCNVME_LSDESC_RQST))
  fcret = VERR_LSDESC_RQST;
 else if (assoc_acc->hdr.rqst.desc_len !=
   fcnvme_lsdesc_len(sizeof(struct fcnvme_lsdesc_rqst)))
  fcret = VERR_LSDESC_RQST_LEN;
 else if (assoc_acc->hdr.rqst.w0.ls_cmd != FCNVME_LS_CREATE_ASSOCIATION)
  fcret = VERR_CR_ASSOC;
 else if (assoc_acc->associd.desc_tag !=
   cpu_to_be32(FCNVME_LSDESC_ASSOC_ID))
  fcret = VERR_ASSOC_ID;
 else if (assoc_acc->associd.desc_len !=
   fcnvme_lsdesc_len(
    sizeof(struct fcnvme_lsdesc_assoc_id)))
  fcret = VERR_ASSOC_ID_LEN;
 else if (assoc_acc->connectid.desc_tag !=
   cpu_to_be32(FCNVME_LSDESC_CONN_ID))
  fcret = VERR_CONN_ID;
 else if (assoc_acc->connectid.desc_len !=
   fcnvme_lsdesc_len(sizeof(struct fcnvme_lsdesc_conn_id)))
  fcret = VERR_CONN_ID_LEN;

 if (fcret) {
  ret = -EBADF;
  dev_err(ctrl->dev,
   "q %d Create Association LS failed: %s\n",
   queue->qnum, validation_errors[fcret]);
 } else {
  spin_lock_irqsave(&ctrl->lock, flags);
  ctrl->association_id =
   be64_to_cpu(assoc_acc->associd.association_id);
  queue->connection_id =
   be64_to_cpu(assoc_acc->connectid.connection_id);
  set_bit(NVME_FC_Q_CONNECTED, &queue->flags);
  spin_unlock_irqrestore(&ctrl->lock, flags);
 }

out_free_buffer:
 kfree(lsop);
out_no_memory:
 if (ret)
  dev_err(ctrl->dev,
   "queue %d connect admin queue failed (%d).\n",
   queue->qnum, ret);
 return ret;
}

static int
nvme_fc_connect_queue(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
   u16 qsize, u16 ersp_ratio)
{
 struct nvmefc_ls_req_op *lsop;
 struct nvmefc_ls_req *lsreq;
 struct fcnvme_ls_cr_conn_rqst *conn_rqst;
 struct fcnvme_ls_cr_conn_acc *conn_acc;
 int ret, fcret = 0;

 lsop = kzalloc((sizeof(*lsop) +
    sizeof(*conn_rqst) + sizeof(*conn_acc) +
    ctrl->lport->ops->lsrqst_priv_sz), GFP_KERNEL);
 if (!lsop) {
  dev_info(ctrl->ctrl.device,
   "NVME-FC{%d}: send Create Connection failed: ENOMEM\n",
   ctrl->cnum);
  ret = -ENOMEM;
  goto out_no_memory;
 }

 conn_rqst = (struct fcnvme_ls_cr_conn_rqst *)&lsop[1];
 conn_acc = (struct fcnvme_ls_cr_conn_acc *)&conn_rqst[1];
 lsreq = &lsop->ls_req;
 if (ctrl->lport->ops->lsrqst_priv_sz)
  lsreq->private = (void *)&conn_acc[1];
 else
  lsreq->private = NULL;

 conn_rqst->w0.ls_cmd = FCNVME_LS_CREATE_CONNECTION;
 conn_rqst->desc_list_len = cpu_to_be32(
    sizeof(struct fcnvme_lsdesc_assoc_id) +
    sizeof(struct fcnvme_lsdesc_cr_conn_cmd));

 conn_rqst->associd.desc_tag = cpu_to_be32(FCNVME_LSDESC_ASSOC_ID);
 conn_rqst->associd.desc_len =
   fcnvme_lsdesc_len(
    sizeof(struct fcnvme_lsdesc_assoc_id));
 conn_rqst->associd.association_id = cpu_to_be64(ctrl->association_id);
 conn_rqst->connect_cmd.desc_tag =
   cpu_to_be32(FCNVME_LSDESC_CREATE_CONN_CMD);
 conn_rqst->connect_cmd.desc_len =
   fcnvme_lsdesc_len(
    sizeof(struct fcnvme_lsdesc_cr_conn_cmd));
 conn_rqst->connect_cmd.ersp_ratio = cpu_to_be16(ersp_ratio);
 conn_rqst->connect_cmd.qid  = cpu_to_be16(queue->qnum);
 conn_rqst->connect_cmd.sqsize = cpu_to_be16(qsize - 1);

 lsop->queue = queue;
 lsreq->rqstaddr = conn_rqst;
 lsreq->rqstlen = sizeof(*conn_rqst);
 lsreq->rspaddr = conn_acc;
 lsreq->rsplen = sizeof(*conn_acc);
 lsreq->timeout = NVME_FC_LS_TIMEOUT_SEC;

 ret = nvme_fc_send_ls_req(ctrl->rport, lsop);
 if (ret)
  goto out_free_buffer;

 /* process connect LS completion */

 /* validate the ACC response */
 if (conn_acc->hdr.w0.ls_cmd != FCNVME_LS_ACC)
  fcret = VERR_LSACC;
 else if (conn_acc->hdr.desc_list_len !=
   fcnvme_lsdesc_len(sizeof(struct fcnvme_ls_cr_conn_acc)))
  fcret = VERR_CR_CONN_ACC_LEN;
 else if (conn_acc->hdr.rqst.desc_tag != cpu_to_be32(FCNVME_LSDESC_RQST))
  fcret = VERR_LSDESC_RQST;
 else if (conn_acc->hdr.rqst.desc_len !=
   fcnvme_lsdesc_len(sizeof(struct fcnvme_lsdesc_rqst)))
  fcret = VERR_LSDESC_RQST_LEN;
 else if (conn_acc->hdr.rqst.w0.ls_cmd != FCNVME_LS_CREATE_CONNECTION)
  fcret = VERR_CR_CONN;
 else if (conn_acc->connectid.desc_tag !=
   cpu_to_be32(FCNVME_LSDESC_CONN_ID))
  fcret = VERR_CONN_ID;
 else if (conn_acc->connectid.desc_len !=
   fcnvme_lsdesc_len(sizeof(struct fcnvme_lsdesc_conn_id)))
  fcret = VERR_CONN_ID_LEN;

 if (fcret) {
  ret = -EBADF;
  dev_err(ctrl->dev,
   "q %d Create I/O Connection LS failed: %s\n",
   queue->qnum, validation_errors[fcret]);
 } else {
  queue->connection_id =
   be64_to_cpu(conn_acc->connectid.connection_id);
  set_bit(NVME_FC_Q_CONNECTED, &queue->flags);
 }

out_free_buffer:
 kfree(lsop);
out_no_memory:
 if (ret)
  dev_err(ctrl->dev,
   "queue %d connect I/O queue failed (%d).\n",
   queue->qnum, ret);
 return ret;
}

static void
nvme_fc_disconnect_assoc_done(struct nvmefc_ls_req *lsreq, int status)
{
 struct nvmefc_ls_req_op *lsop = ls_req_to_lsop(lsreq);

 __nvme_fc_finish_ls_req(lsop);

 /* fc-nvme initiator doesn't care about success or failure of cmd */

 kfree(lsop);
}

/*
 * This routine sends a FC-NVME LS to disconnect (aka terminate)
 * the FC-NVME Association.  Terminating the association also
 * terminates the FC-NVME connections (per queue, both admin and io
 * queues) that are part of the association. E.g. things are torn
 * down, and the related FC-NVME Association ID and Connection IDs
 * become invalid.
 *
 * The behavior of the fc-nvme initiator is such that its
 * understanding of the association and connections will implicitly
 * be torn down. The action is implicit as it may be due to a loss of
 * connectivity with the fc-nvme target, so you may never get a
 * response even if you tried.  As such, the action of this routine
 * is to asynchronously send the LS, ignore any results of the LS, and
 * continue on with terminating the association. If the fc-nvme target
 * is present and receives the LS, it too can tear down.
 */

static void
nvme_fc_xmt_disconnect_assoc(struct nvme_fc_ctrl *ctrl)
{
 struct fcnvme_ls_disconnect_assoc_rqst *discon_rqst;
 struct fcnvme_ls_disconnect_assoc_acc *discon_acc;
 struct nvmefc_ls_req_op *lsop;
 struct nvmefc_ls_req *lsreq;
 int ret;

 lsop = kzalloc((sizeof(*lsop) +
   sizeof(*discon_rqst) + sizeof(*discon_acc) +
   ctrl->lport->ops->lsrqst_priv_sz), GFP_KERNEL);
 if (!lsop) {
  dev_info(ctrl->ctrl.device,
   "NVME-FC{%d}: send Disconnect Association "
   "failed: ENOMEM\n",
   ctrl->cnum);
  return;
 }

 discon_rqst = (struct fcnvme_ls_disconnect_assoc_rqst *)&lsop[1];
 discon_acc = (struct fcnvme_ls_disconnect_assoc_acc *)&discon_rqst[1];
 lsreq = &lsop->ls_req;
 if (ctrl->lport->ops->lsrqst_priv_sz)
  lsreq->private = (void *)&discon_acc[1];
 else
  lsreq->private = NULL;

 nvmefc_fmt_lsreq_discon_assoc(lsreq, discon_rqst, discon_acc,
    ctrl->association_id);

 ret = nvme_fc_send_ls_req_async(ctrl->rport, lsop,
    nvme_fc_disconnect_assoc_done);
 if (ret)
  kfree(lsop);
}

static void
nvme_fc_xmt_ls_rsp_free(struct nvmefc_ls_rcv_op *lsop)
{
 struct nvme_fc_rport *rport = lsop->rport;
 struct nvme_fc_lport *lport = rport->lport;
 unsigned long flags;

 spin_lock_irqsave(&rport->lock, flags);
 list_del(&lsop->lsrcv_list);
 spin_unlock_irqrestore(&rport->lock, flags);

 fc_dma_sync_single_for_cpu(lport->dev, lsop->rspdma,
    sizeof(*lsop->rspbuf), DMA_TO_DEVICE);
 fc_dma_unmap_single(lport->dev, lsop->rspdma,
   sizeof(*lsop->rspbuf), DMA_TO_DEVICE);

 kfree(lsop->rspbuf);
 kfree(lsop->rqstbuf);
 kfree(lsop);

 nvme_fc_rport_put(rport);
}

static void
nvme_fc_xmt_ls_rsp_done(struct nvmefc_ls_rsp *lsrsp)
{
 struct nvmefc_ls_rcv_op *lsop = lsrsp->nvme_fc_private;

 nvme_fc_xmt_ls_rsp_free(lsop);
}

static void
nvme_fc_xmt_ls_rsp(struct nvmefc_ls_rcv_op *lsop)
{
 struct nvme_fc_rport *rport = lsop->rport;
 struct nvme_fc_lport *lport = rport->lport;
 struct fcnvme_ls_rqst_w0 *w0 = &lsop->rqstbuf->w0;
 int ret;

 fc_dma_sync_single_for_device(lport->dev, lsop->rspdma,
      sizeof(*lsop->rspbuf), DMA_TO_DEVICE);

 ret = lport->ops->xmt_ls_rsp(&lport->localport, &rport->remoteport,
         lsop->lsrsp);
 if (ret) {
  dev_warn(lport->dev,
   "LLDD rejected LS RSP xmt: LS %d status %d\n",
   w0->ls_cmd, ret);
  nvme_fc_xmt_ls_rsp_free(lsop);
  return;
 }
}

static struct nvme_fc_ctrl *
nvme_fc_match_disconn_ls(struct nvme_fc_rport *rport,
        struct nvmefc_ls_rcv_op *lsop)
{
 struct fcnvme_ls_disconnect_assoc_rqst *rqst =
     &lsop->rqstbuf->rq_dis_assoc;
 struct nvme_fc_ctrl *ctrl, *ret = NULL;
 struct nvmefc_ls_rcv_op *oldls = NULL;
 u64 association_id = be64_to_cpu(rqst->associd.association_id);
 unsigned long flags;

 spin_lock_irqsave(&rport->lock, flags);

 list_for_each_entry(ctrl, &rport->ctrl_list, ctrl_list) {
  if (!nvme_fc_ctrl_get(ctrl))
   continue;
  spin_lock(&ctrl->lock);
  if (association_id == ctrl->association_id) {
   oldls = ctrl->rcv_disconn;
   ctrl->rcv_disconn = lsop;
   ret = ctrl;
  }
  spin_unlock(&ctrl->lock);
  if (ret)
   /* leave the ctrl get reference */
   break;
  nvme_fc_ctrl_put(ctrl);
 }

 spin_unlock_irqrestore(&rport->lock, flags);

 /* transmit a response for anything that was pending */
 if (oldls) {
  dev_info(rport->lport->dev,
   "NVME-FC{%d}: Multiple Disconnect Association "
   "LS's received\n", ctrl->cnum);
  /* overwrite good response with bogus failure */
  oldls->lsrsp->rsplen = nvme_fc_format_rjt(oldls->rspbuf,
      sizeof(*oldls->rspbuf),
      rqst->w0.ls_cmd,
      FCNVME_RJT_RC_UNAB,
      FCNVME_RJT_EXP_NONE, 0);
  nvme_fc_xmt_ls_rsp(oldls);
 }

 return ret;
}

/*
 * returns true to mean LS handled and ls_rsp can be sent
 * returns false to defer ls_rsp xmt (will be done as part of
 *     association termination)
 */

static bool
nvme_fc_ls_disconnect_assoc(struct nvmefc_ls_rcv_op *lsop)
{
 struct nvme_fc_rport *rport = lsop->rport;
 struct fcnvme_ls_disconnect_assoc_rqst *rqst =
     &lsop->rqstbuf->rq_dis_assoc;
 struct fcnvme_ls_disconnect_assoc_acc *acc =
     &lsop->rspbuf->rsp_dis_assoc;
 struct nvme_fc_ctrl *ctrl = NULL;
 int ret = 0;

 memset(acc, 0, sizeof(*acc));

 ret = nvmefc_vldt_lsreq_discon_assoc(lsop->rqstdatalen, rqst);
 if (!ret) {
  /* match an active association */
  ctrl = nvme_fc_match_disconn_ls(rport, lsop);
  if (!ctrl)
   ret = VERR_NO_ASSOC;
 }

 if (ret) {
  dev_info(rport->lport->dev,
   "Disconnect LS failed: %s\n",
   validation_errors[ret]);
  lsop->lsrsp->rsplen = nvme_fc_format_rjt(acc,
     sizeof(*acc), rqst->w0.ls_cmd,
     (ret == VERR_NO_ASSOC) ?
      FCNVME_RJT_RC_INV_ASSOC :
      FCNVME_RJT_RC_LOGIC,
     FCNVME_RJT_EXP_NONE, 0);
  return true;
 }

 /* format an ACCept response */

 lsop->lsrsp->rsplen = sizeof(*acc);

 nvme_fc_format_rsp_hdr(acc, FCNVME_LS_ACC,
   fcnvme_lsdesc_len(
    sizeof(struct fcnvme_ls_disconnect_assoc_acc)),
   FCNVME_LS_DISCONNECT_ASSOC);

 /*
 * the transmit of the response will occur after the exchanges
 * for the association have been ABTS'd by
 * nvme_fc_delete_association().
 */


 /* fail the association */
 nvme_fc_error_recovery(ctrl, "Disconnect Association LS received");

 /* release the reference taken by nvme_fc_match_disconn_ls() */
 nvme_fc_ctrl_put(ctrl);

 return false;
}

/*
 * Actual Processing routine for received FC-NVME LS Requests from the LLD
 * returns true if a response should be sent afterward, false if rsp will
 * be sent asynchronously.
 */

static bool
nvme_fc_handle_ls_rqst(struct nvmefc_ls_rcv_op *lsop)
{
 struct fcnvme_ls_rqst_w0 *w0 = &lsop->rqstbuf->w0;
 bool ret = true;

 lsop->lsrsp->nvme_fc_private = lsop;
 lsop->lsrsp->rspbuf = lsop->rspbuf;
 lsop->lsrsp->rspdma = lsop->rspdma;
 lsop->lsrsp->done = nvme_fc_xmt_ls_rsp_done;
 /* Be preventative. handlers will later set to valid length */
 lsop->lsrsp->rsplen = 0;

 /*
 * handlers:
 *   parse request input, execute the request, and format the
 *   LS response
 */

 switch (w0->ls_cmd) {
 case FCNVME_LS_DISCONNECT_ASSOC:
  ret = nvme_fc_ls_disconnect_assoc(lsop);
  break;
 case FCNVME_LS_DISCONNECT_CONN:
  lsop->lsrsp->rsplen = nvme_fc_format_rjt(lsop->rspbuf,
    sizeof(*lsop->rspbuf), w0->ls_cmd,
    FCNVME_RJT_RC_UNSUP, FCNVME_RJT_EXP_NONE, 0);
  break;
 case FCNVME_LS_CREATE_ASSOCIATION:
 case FCNVME_LS_CREATE_CONNECTION:
  lsop->lsrsp->rsplen = nvme_fc_format_rjt(lsop->rspbuf,
    sizeof(*lsop->rspbuf), w0->ls_cmd,
    FCNVME_RJT_RC_LOGIC, FCNVME_RJT_EXP_NONE, 0);
  break;
 default:
  lsop->lsrsp->rsplen = nvme_fc_format_rjt(lsop->rspbuf,
    sizeof(*lsop->rspbuf), w0->ls_cmd,
    FCNVME_RJT_RC_INVAL, FCNVME_RJT_EXP_NONE, 0);
  break;
 }

 return(ret);
}

static void
nvme_fc_handle_ls_rqst_work(struct work_struct *work)
{
 struct nvme_fc_rport *rport =
  container_of(work, struct nvme_fc_rport, lsrcv_work);
 struct fcnvme_ls_rqst_w0 *w0;
 struct nvmefc_ls_rcv_op *lsop;
 unsigned long flags;
 bool sendrsp;

restart:
 sendrsp = true;
 spin_lock_irqsave(&rport->lock, flags);
 list_for_each_entry(lsop, &rport->ls_rcv_list, lsrcv_list) {
  if (lsop->handled)
   continue;

  lsop->handled = true;
  if (rport->remoteport.port_state == FC_OBJSTATE_ONLINE) {
   spin_unlock_irqrestore(&rport->lock, flags);
   sendrsp = nvme_fc_handle_ls_rqst(lsop);
  } else {
   spin_unlock_irqrestore(&rport->lock, flags);
   w0 = &lsop->rqstbuf->w0;
   lsop->lsrsp->rsplen = nvme_fc_format_rjt(
      lsop->rspbuf,
      sizeof(*lsop->rspbuf),
      w0->ls_cmd,
      FCNVME_RJT_RC_UNAB,
      FCNVME_RJT_EXP_NONE, 0);
  }
  if (sendrsp)
   nvme_fc_xmt_ls_rsp(lsop);
  goto restart;
 }
 spin_unlock_irqrestore(&rport->lock, flags);
}

static
void nvme_fc_rcv_ls_req_err_msg(struct nvme_fc_lport *lport,
    struct fcnvme_ls_rqst_w0 *w0)
{
 dev_info(lport->dev, "RCV %s LS failed: No memory\n",
  (w0->ls_cmd <= NVME_FC_LAST_LS_CMD_VALUE) ?
   nvmefc_ls_names[w0->ls_cmd] : "");
}

/**
 * nvme_fc_rcv_ls_req - transport entry point called by an LLDD
 *                       upon the reception of a NVME LS request.
 *
 * The nvme-fc layer will copy payload to an internal structure for
 * processing.  As such, upon completion of the routine, the LLDD may
 * immediately free/reuse the LS request buffer passed in the call.
 *
 * If this routine returns error, the LLDD should abort the exchange.
 *
 * @portptr:    pointer to the (registered) remote port that the LS
 *              was received from. The remoteport is associated with
 *              a specific localport.
 * @lsrsp:      pointer to a nvmefc_ls_rsp response structure to be
 *              used to reference the exchange corresponding to the LS
 *              when issuing an ls response.
 * @lsreqbuf:   pointer to the buffer containing the LS Request
 * @lsreqbuf_len: length, in bytes, of the received LS request
 */

int
nvme_fc_rcv_ls_req(struct nvme_fc_remote_port *portptr,
   struct nvmefc_ls_rsp *lsrsp,
   void *lsreqbuf, u32 lsreqbuf_len)
{
 struct nvme_fc_rport *rport = remoteport_to_rport(portptr);
 struct nvme_fc_lport *lport = rport->lport;
 struct fcnvme_ls_rqst_w0 *w0 = (struct fcnvme_ls_rqst_w0 *)lsreqbuf;
 struct nvmefc_ls_rcv_op *lsop;
 unsigned long flags;
 int ret;

 nvme_fc_rport_get(rport);

 /* validate there's a routine to transmit a response */
 if (!lport->ops->xmt_ls_rsp) {
  dev_info(lport->dev,
   "RCV %s LS failed: no LLDD xmt_ls_rsp\n",
   (w0->ls_cmd <= NVME_FC_LAST_LS_CMD_VALUE) ?
    nvmefc_ls_names[w0->ls_cmd] : "");
  ret = -EINVAL;
  goto out_put;
 }

 if (lsreqbuf_len > sizeof(union nvmefc_ls_requests)) {
  dev_info(lport->dev,
   "RCV %s LS failed: payload too large\n",
   (w0->ls_cmd <= NVME_FC_LAST_LS_CMD_VALUE) ?
    nvmefc_ls_names[w0->ls_cmd] : "");
  ret = -E2BIG;
  goto out_put;
 }

 lsop = kzalloc(sizeof(*lsop), GFP_KERNEL);
 if (!lsop) {
  nvme_fc_rcv_ls_req_err_msg(lport, w0);
  ret = -ENOMEM;
  goto out_put;
 }

 lsop->rqstbuf = kzalloc(sizeof(*lsop->rqstbuf), GFP_KERNEL);
 lsop->rspbuf = kzalloc(sizeof(*lsop->rspbuf), GFP_KERNEL);
 if (!lsop->rqstbuf || !lsop->rspbuf) {
  nvme_fc_rcv_ls_req_err_msg(lport, w0);
  ret = -ENOMEM;
  goto out_free;
 }

 lsop->rspdma = fc_dma_map_single(lport->dev, lsop->rspbuf,
     sizeof(*lsop->rspbuf),
     DMA_TO_DEVICE);
 if (fc_dma_mapping_error(lport->dev, lsop->rspdma)) {
  dev_info(lport->dev,
   "RCV %s LS failed: DMA mapping failure\n",
   (w0->ls_cmd <= NVME_FC_LAST_LS_CMD_VALUE) ?
    nvmefc_ls_names[w0->ls_cmd] : "");
  ret = -EFAULT;
  goto out_free;
 }

 lsop->rport = rport;
 lsop->lsrsp = lsrsp;

 memcpy(lsop->rqstbuf, lsreqbuf, lsreqbuf_len);
 lsop->rqstdatalen = lsreqbuf_len;

 spin_lock_irqsave(&rport->lock, flags);
 if (rport->remoteport.port_state != FC_OBJSTATE_ONLINE) {
  spin_unlock_irqrestore(&rport->lock, flags);
  ret = -ENOTCONN;
  goto out_unmap;
 }
 list_add_tail(&lsop->lsrcv_list, &rport->ls_rcv_list);
 spin_unlock_irqrestore(&rport->lock, flags);

 schedule_work(&rport->lsrcv_work);

 return 0;

out_unmap:
 fc_dma_unmap_single(lport->dev, lsop->rspdma,
   sizeof(*lsop->rspbuf), DMA_TO_DEVICE);
out_free:
 kfree(lsop->rspbuf);
 kfree(lsop->rqstbuf);
 kfree(lsop);
out_put:
 nvme_fc_rport_put(rport);
 return ret;
}
EXPORT_SYMBOL_GPL(nvme_fc_rcv_ls_req);


/* *********************** NVME Ctrl Routines **************************** */

static void
__nvme_fc_exit_request(struct nvme_fc_ctrl *ctrl,
  struct nvme_fc_fcp_op *op)
{
 fc_dma_unmap_single(ctrl->lport->dev, op->fcp_req.rspdma,
    sizeof(op->rsp_iu), DMA_FROM_DEVICE);
 fc_dma_unmap_single(ctrl->lport->dev, op->fcp_req.cmddma,
    sizeof(op->cmd_iu), DMA_TO_DEVICE);

 atomic_set(&op->state, FCPOP_STATE_UNINIT);
}

static void
nvme_fc_exit_request(struct blk_mq_tag_set *set, struct request *rq,
  unsigned int hctx_idx)
{
 struct nvme_fc_fcp_op *op = blk_mq_rq_to_pdu(rq);

 return __nvme_fc_exit_request(to_fc_ctrl(set->driver_data), op);
}

static int
__nvme_fc_abort_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_fcp_op *op)
{
 unsigned long flags;
 int opstate;

 spin_lock_irqsave(&ctrl->lock, flags);
 opstate = atomic_xchg(&op->state, FCPOP_STATE_ABORTED);
 if (opstate != FCPOP_STATE_ACTIVE)
  atomic_set(&op->state, opstate);
 else if (test_bit(FCCTRL_TERMIO, &ctrl->flags)) {
  op->flags |= FCOP_FLAGS_TERMIO;
  ctrl->iocnt++;
 }
 spin_unlock_irqrestore(&ctrl->lock, flags);

 if (opstate != FCPOP_STATE_ACTIVE)
  return -ECANCELED;

 ctrl->lport->ops->fcp_abort(&ctrl->lport->localport,
     &ctrl->rport->remoteport,
     op->queue->lldd_handle,
     &op->fcp_req);

 return 0;
}

static void
nvme_fc_abort_aen_ops(struct nvme_fc_ctrl *ctrl)
{
 struct nvme_fc_fcp_op *aen_op = ctrl->aen_ops;
 int i;

 /* ensure we've initialized the ops once */
 if (!(aen_op->flags & FCOP_FLAGS_AEN))
  return;

 for (i = 0; i < NVME_NR_AEN_COMMANDS; i++, aen_op++)
  __nvme_fc_abort_op(ctrl, aen_op);
}

static inline void
__nvme_fc_fcpop_chk_teardowns(struct nvme_fc_ctrl *ctrl,
  struct nvme_fc_fcp_op *op, int opstate)
{
 unsigned long flags;

 if (opstate == FCPOP_STATE_ABORTED) {
  spin_lock_irqsave(&ctrl->lock, flags);
  if (test_bit(FCCTRL_TERMIO, &ctrl->flags) &&
      op->flags & FCOP_FLAGS_TERMIO) {
   if (!--ctrl->iocnt)
    wake_up(&ctrl->ioabort_wait);
  }
  spin_unlock_irqrestore(&ctrl->lock, flags);
 }
}

static void
nvme_fc_ctrl_ioerr_work(struct work_struct *work)
{
 struct nvme_fc_ctrl *ctrl =
   container_of(work, struct nvme_fc_ctrl, ioerr_work);

 nvme_fc_error_recovery(ctrl, "transport detected io error");
}

/*
 * nvme_fc_io_getuuid - Routine called to get the appid field
 * associated with request by the lldd
 * @req:IO request from nvme fc to driver
 * Returns: UUID if there is an appid associated with VM or
 * NULL if the user/libvirt has not set the appid to VM
 */

char *nvme_fc_io_getuuid(struct nvmefc_fcp_req *req)
{
 struct nvme_fc_fcp_op *op = fcp_req_to_fcp_op(req);
 struct request *rq = op->rq;

 if (!IS_ENABLED(CONFIG_BLK_CGROUP_FC_APPID) || !rq || !rq->bio)
  return NULL;
 return blkcg_get_fc_appid(rq->bio);
}
EXPORT_SYMBOL_GPL(nvme_fc_io_getuuid);

static void
nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
{
 struct nvme_fc_fcp_op *op = fcp_req_to_fcp_op(req);
 struct request *rq = op->rq;
 struct nvmefc_fcp_req *freq = &op->fcp_req;
 struct nvme_fc_ctrl *ctrl = op->ctrl;
 struct nvme_fc_queue *queue = op->queue;
 struct nvme_completion *cqe = &op->rsp_iu.cqe;
 struct nvme_command *sqe = &op->cmd_iu.sqe;
 __le16 status = cpu_to_le16(NVME_SC_SUCCESS << 1);
 union nvme_result result;
 bool terminate_assoc = true;
 int opstate;

 /*
 * WARNING:
 * The current linux implementation of a nvme controller
 * allocates a single tag set for all io queues and sizes
 * the io queues to fully hold all possible tags. Thus, the
 * implementation does not reference or care about the sqhd
 * value as it never needs to use the sqhd/sqtail pointers
 * for submission pacing.
 *
 * This affects the FC-NVME implementation in two ways:
 * 1) As the value doesn't matter, we don't need to waste
 *    cycles extracting it from ERSPs and stamping it in the
 *    cases where the transport fabricates CQEs on successful
 *    completions.
 * 2) The FC-NVME implementation requires that delivery of
 *    ERSP completions are to go back to the nvme layer in order
 *    relative to the rsn, such that the sqhd value will always
 *    be "in order" for the nvme layer. As the nvme layer in
 *    linux doesn't care about sqhd, there's no need to return
 *    them in order.
 *
 * Additionally:
 * As the core nvme layer in linux currently does not look at
 * every field in the cqe - in cases where the FC transport must
 * fabricate a CQE, the following fields will not be set as they
 * are not referenced:
 *      cqe.sqid,  cqe.sqhd,  cqe.command_id
 *
 * Failure or error of an individual i/o, in a transport
 * detected fashion unrelated to the nvme completion status,
 * potentially cause the initiator and target sides to get out
 * of sync on SQ head/tail (aka outstanding io count allowed).
 * Per FC-NVME spec, failure of an individual command requires
 * the connection to be terminated, which in turn requires the
 * association to be terminated.
 */


 opstate = atomic_xchg(&op->state, FCPOP_STATE_COMPLETE);

 fc_dma_sync_single_for_cpu(ctrl->lport->dev, op->fcp_req.rspdma,
    sizeof(op->rsp_iu), DMA_FROM_DEVICE);

 if (opstate == FCPOP_STATE_ABORTED)
  status = cpu_to_le16(NVME_SC_HOST_ABORTED_CMD << 1);
 else if (freq->status) {
  status = cpu_to_le16(NVME_SC_HOST_PATH_ERROR << 1);
  dev_info(ctrl->ctrl.device,
   "NVME-FC{%d}: io failed due to lldd error %d\n",
   ctrl->cnum, freq->status);
 }

 /*
 * For the linux implementation, if we have an unsuccessful
 * status, the blk-mq layer can typically be called with the
 * non-zero status and the content of the cqe isn't important.
 */

 if (status)
  goto done;

 /*
 * command completed successfully relative to the wire
 * protocol. However, validate anything received and
 * extract the status and result from the cqe (create it
 * where necessary).
 */


 switch (freq->rcv_rsplen) {

 case 0:
 case NVME_FC_SIZEOF_ZEROS_RSP:
  /*
 * No response payload or 12 bytes of payload (which
 * should all be zeros) are considered successful and
 * no payload in the CQE by the transport.
 */

  if (freq->transferred_length !=
      be32_to_cpu(op->cmd_iu.data_len)) {
   status = cpu_to_le16(NVME_SC_HOST_PATH_ERROR << 1);
   dev_info(ctrl->ctrl.device,
    "NVME-FC{%d}: io failed due to bad transfer "
    "length: %d vs expected %d\n",
    ctrl->cnum, freq->transferred_length,
    be32_to_cpu(op->cmd_iu.data_len));
   goto done;
  }
  result.u64 = 0;
  break;

 case sizeof(struct nvme_fc_ersp_iu):
  /*
 * The ERSP IU contains a full completion with CQE.
 * Validate ERSP IU and look at cqe.
 */

  if (unlikely(be16_to_cpu(op->rsp_iu.iu_len) !=
     (freq->rcv_rsplen / 4) ||
        be32_to_cpu(op->rsp_iu.xfrd_len) !=
     freq->transferred_length ||
        op->rsp_iu.ersp_result ||
        sqe->common.command_id != cqe->command_id)) {
   status = cpu_to_le16(NVME_SC_HOST_PATH_ERROR << 1);
   dev_info(ctrl->ctrl.device,
    "NVME-FC{%d}: io failed due to bad NVMe_ERSP: "
    "iu len %d, xfr len %d vs %d, status code "
    "%d, cmdid %d vs %d\n",
    ctrl->cnum, be16_to_cpu(op->rsp_iu.iu_len),
    be32_to_cpu(op->rsp_iu.xfrd_len),
    freq->transferred_length,
    op->rsp_iu.ersp_result,
    sqe->common.command_id,
    cqe->command_id);
   goto done;
  }
  result = cqe->result;
  status = cqe->status;
  break;

 default:
  status = cpu_to_le16(NVME_SC_HOST_PATH_ERROR << 1);
  dev_info(ctrl->ctrl.device,
   "NVME-FC{%d}: io failed due to odd NVMe_xRSP iu "
   "len %d\n",
   ctrl->cnum, freq->rcv_rsplen);
  goto done;
 }

 terminate_assoc = false;

done:
 if (op->flags & FCOP_FLAGS_AEN) {
  nvme_complete_async_event(&queue->ctrl->ctrl, status, &result);
  __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
  atomic_set(&op->state, FCPOP_STATE_IDLE);
  op->flags = FCOP_FLAGS_AEN; /* clear other flags */
  nvme_fc_ctrl_put(ctrl);
  goto check_error;
 }

 __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
 if (!nvme_try_complete_req(rq, status, result))
  nvme_fc_complete_rq(rq);

check_error:
 if (terminate_assoc &&
     nvme_ctrl_state(&ctrl->ctrl) != NVME_CTRL_RESETTING)
  queue_work(nvme_reset_wq, &ctrl->ioerr_work);
}

static int
__nvme_fc_init_request(struct nvme_fc_ctrl *ctrl,
  struct nvme_fc_queue *queue, struct nvme_fc_fcp_op *op,
  struct request *rq, u32 rqno)
{
 struct nvme_fcp_op_w_sgl *op_w_sgl =
  container_of(op, typeof(*op_w_sgl), op);
 struct nvme_fc_cmd_iu *cmdiu = &op->cmd_iu;
 int ret = 0;

 memset(op, 0, sizeof(*op));
 op->fcp_req.cmdaddr = &op->cmd_iu;
 op->fcp_req.cmdlen = sizeof(op->cmd_iu);
 op->fcp_req.rspaddr = &op->rsp_iu;
 op->fcp_req.rsplen = sizeof(op->rsp_iu);
 op->fcp_req.done = nvme_fc_fcpio_done;
 op->ctrl = ctrl;
 op->queue = queue;
 op->rq = rq;
 op->rqno = rqno;

 cmdiu->format_id = NVME_CMD_FORMAT_ID;
 cmdiu->fc_id = NVME_CMD_FC_ID;
 cmdiu->iu_len = cpu_to_be16(sizeof(*cmdiu) / sizeof(u32));
 if (queue->qnum)
  cmdiu->rsv_cat = fccmnd_set_cat_css(0,
     (NVME_CC_CSS_NVM >> NVME_CC_CSS_SHIFT));
 else
  cmdiu->rsv_cat = fccmnd_set_cat_admin(0);

 op->fcp_req.cmddma = fc_dma_map_single(ctrl->lport->dev,
    &op->cmd_iu, sizeof(op->cmd_iu), DMA_TO_DEVICE);
 if (fc_dma_mapping_error(ctrl->lport->dev, op->fcp_req.cmddma)) {
  dev_err(ctrl->dev,
   "FCP Op failed - cmdiu dma mapping failed.\n");
  ret = -EFAULT;
  goto out_on_error;
 }

 op->fcp_req.rspdma = fc_dma_map_single(ctrl->lport->dev,
    &op->rsp_iu, sizeof(op->rsp_iu),
    DMA_FROM_DEVICE);
 if (fc_dma_mapping_error(ctrl->lport->dev, op->fcp_req.rspdma)) {
  dev_err(ctrl->dev,
   "FCP Op failed - rspiu dma mapping failed.\n");
  ret = -EFAULT;
 }

 atomic_set(&op->state, FCPOP_STATE_IDLE);
out_on_error:
 return ret;
}

static int
nvme_fc_init_request(struct blk_mq_tag_set *set, struct request *rq,
  unsigned int hctx_idx, unsigned int numa_node)
{
 struct nvme_fc_ctrl *ctrl = to_fc_ctrl(set->driver_data);
 struct nvme_fcp_op_w_sgl *op = blk_mq_rq_to_pdu(rq);
 int queue_idx = (set == &ctrl->tag_set) ? hctx_idx + 1 : 0;
 struct nvme_fc_queue *queue = &ctrl->queues[queue_idx];
 int res;

 res = __nvme_fc_init_request(ctrl, queue, &op->op, rq, queue->rqcnt++);
 if (res)
  return res;
 op->op.fcp_req.first_sgl = op->sgl;
 op->op.fcp_req.private = &op->priv[0];
 nvme_req(rq)->ctrl = &ctrl->ctrl;
 nvme_req(rq)->cmd = &op->op.cmd_iu.sqe;
 return res;
}

static int
nvme_fc_init_aen_ops(struct nvme_fc_ctrl *ctrl)
{
 struct nvme_fc_fcp_op *aen_op;
 struct nvme_fc_cmd_iu *cmdiu;
 struct nvme_command *sqe;
 void *private = NULL;
 int i, ret;

 aen_op = ctrl->aen_ops;
 for (i = 0; i < NVME_NR_AEN_COMMANDS; i++, aen_op++) {
  if (ctrl->lport->ops->fcprqst_priv_sz) {
   private = kzalloc(ctrl->lport->ops->fcprqst_priv_sz,
      GFP_KERNEL);
   if (!private)
    return -ENOMEM;
  }

  cmdiu = &aen_op->cmd_iu;
  sqe = &cmdiu->sqe;
  ret = __nvme_fc_init_request(ctrl, &ctrl->queues[0],
    aen_op, (struct request *)NULL,
    (NVME_AQ_BLK_MQ_DEPTH + i));
  if (ret) {
   kfree(private);
   return ret;
  }

  aen_op->flags = FCOP_FLAGS_AEN;
  aen_op->fcp_req.private = private;

  memset(sqe, 0, sizeof(*sqe));
  sqe->common.opcode = nvme_admin_async_event;
  /* Note: core layer may overwrite the sqe.command_id value */
  sqe->common.command_id = NVME_AQ_BLK_MQ_DEPTH + i;
 }
 return 0;
}

static void
nvme_fc_term_aen_ops(struct nvme_fc_ctrl *ctrl)
{
 struct nvme_fc_fcp_op *aen_op;
 int i;

 cancel_work_sync(&ctrl->ctrl.async_event_work);
 aen_op = ctrl->aen_ops;
 for (i = 0; i < NVME_NR_AEN_COMMANDS; i++, aen_op++) {
  __nvme_fc_exit_request(ctrl, aen_op);

  kfree(aen_op->fcp_req.private);
  aen_op->fcp_req.private = NULL;
 }
}

static inline int
__nvme_fc_init_hctx(struct blk_mq_hw_ctx *hctx, void *data, unsigned int qidx)
{
 struct nvme_fc_ctrl *ctrl = to_fc_ctrl(data);
 struct nvme_fc_queue *queue = &ctrl->queues[qidx];

 hctx->driver_data = queue;
 queue->hctx = hctx;
 return 0;
}

static int
nvme_fc_init_hctx(struct blk_mq_hw_ctx *hctx, void *data, unsigned int hctx_idx)
{
 return __nvme_fc_init_hctx(hctx, data, hctx_idx + 1);
}

static int
nvme_fc_init_admin_hctx(struct blk_mq_hw_ctx *hctx, void *data,
  unsigned int hctx_idx)
{
 return __nvme_fc_init_hctx(hctx, data, hctx_idx);
}

static void
nvme_fc_init_queue(struct nvme_fc_ctrl *ctrl, int idx)
{
 struct nvme_fc_queue *queue;

 queue = &ctrl->queues[idx];
 memset(queue, 0, sizeof(*queue));
 queue->ctrl = ctrl;
 queue->qnum = idx;
 atomic_set(&queue->csn, 0);
 queue->dev = ctrl->dev;

 if (idx > 0)
  queue->cmnd_capsule_len = ctrl->ctrl.ioccsz * 16;
 else
  queue->cmnd_capsule_len = sizeof(struct nvme_command);

 /*
 * Considered whether we should allocate buffers for all SQEs
 * and CQEs and dma map them - mapping their respective entries
 * into the request structures (kernel vm addr and dma address)
 * thus the driver could use the buffers/mappings directly.
 * It only makes sense if the LLDD would use them for its
 * messaging api. It's very unlikely most adapter api's would use
 * a native NVME sqe/cqe. More reasonable if FC-NVME IU payload
 * structures were used instead.
 */

}

/*
 * This routine terminates a queue at the transport level.
 * The transport has already ensured that all outstanding ios on
 * the queue have been terminated.
 * The transport will send a Disconnect LS request to terminate
 * the queue's connection. Termination of the admin queue will also
 * terminate the association at the target.
 */

static void
nvme_fc_free_queue(struct nvme_fc_queue *queue)
{
 if (!test_and_clear_bit(NVME_FC_Q_CONNECTED, &queue->flags))
  return;

 clear_bit(NVME_FC_Q_LIVE, &queue->flags);
 /*
 * Current implementation never disconnects a single queue.
 * It always terminates a whole association. So there is never
 * a disconnect(queue) LS sent to the target.
 */


 queue->connection_id = 0;
 atomic_set(&queue->csn, 0);
}

static void
__nvme_fc_delete_hw_queue(struct nvme_fc_ctrl *ctrl,
 struct nvme_fc_queue *queue, unsigned int qidx)
{
 if (ctrl->lport->ops->delete_queue)
  ctrl->lport->ops->delete_queue(&ctrl->lport->localport, qidx,
    queue->lldd_handle);
 queue->lldd_handle = NULL;
}

static void
nvme_fc_free_io_queues(struct nvme_fc_ctrl *ctrl)
{
 int i;

 for (i = 1; i < ctrl->ctrl.queue_count; i++)
  nvme_fc_free_queue(&ctrl->queues[i]);
}

static int
__nvme_fc_create_hw_queue(struct nvme_fc_ctrl *ctrl,
 struct nvme_fc_queue *queue, unsigned int qidx, u16 qsize)
{
 int ret = 0;

 queue->lldd_handle = NULL;
 if (ctrl->lport->ops->create_queue)
  ret = ctrl->lport->ops->create_queue(&ctrl->lport->localport,
    qidx, qsize, &queue->lldd_handle);

 return ret;
}

static void
nvme_fc_delete_hw_io_queues(struct nvme_fc_ctrl *ctrl)
{
 struct nvme_fc_queue *queue = &ctrl->queues[ctrl->ctrl.queue_count - 1];
 int i;

 for (i = ctrl->ctrl.queue_count - 1; i >= 1; i--, queue--)
  __nvme_fc_delete_hw_queue(ctrl, queue, i);
}

static int
nvme_fc_create_hw_io_queues(struct nvme_fc_ctrl *ctrl, u16 qsize)
{
 struct nvme_fc_queue *queue = &ctrl->queues[1];
 int i, ret;

 for (i = 1; i < ctrl->ctrl.queue_count; i++, queue++) {
  ret = __nvme_fc_create_hw_queue(ctrl, queue, i, qsize);
  if (ret)
   goto delete_queues;
 }

 return 0;

delete_queues:
 for (; i > 0; i--)
  __nvme_fc_delete_hw_queue(ctrl, &ctrl->queues[i], i);
 return ret;
}

static int
nvme_fc_connect_io_queues(struct nvme_fc_ctrl *ctrl, u16 qsize)
{
 int i, ret = 0;

 for (i = 1; i < ctrl->ctrl.queue_count; i++) {
  ret = nvme_fc_connect_queue(ctrl, &ctrl->queues[i], qsize,
     (qsize / 5));
  if (ret)
   break;
  ret = nvmf_connect_io_queue(&ctrl->ctrl, i);
  if (ret)
   break;

  set_bit(NVME_FC_Q_LIVE, &ctrl->queues[i].flags);
 }

 return ret;
}

static void
nvme_fc_init_io_queues(struct nvme_fc_ctrl *ctrl)
{
 int i;

 for (i = 1; i < ctrl->ctrl.queue_count; i++)
  nvme_fc_init_queue(ctrl, i);
}

static void
nvme_fc_ctrl_free(struct kref *ref)
{
 struct nvme_fc_ctrl *ctrl =
  container_of(ref, struct nvme_fc_ctrl, ref);
 unsigned long flags;

 if (ctrl->ctrl.tagset)
  nvme_remove_io_tag_set(&ctrl->ctrl);

 /* remove from rport list */
 spin_lock_irqsave(&ctrl->rport->lock, flags);
 list_del(&ctrl->ctrl_list);
 spin_unlock_irqrestore(&ctrl->rport->lock, flags);

 nvme_unquiesce_admin_queue(&ctrl->ctrl);
 nvme_remove_admin_tag_set(&ctrl->ctrl);

 kfree(ctrl->queues);

 put_device(ctrl->dev);
 nvme_fc_rport_put(ctrl->rport);

 ida_free(&nvme_fc_ctrl_cnt, ctrl->cnum);
 if (ctrl->ctrl.opts)
  nvmf_free_options(ctrl->ctrl.opts);
 kfree(ctrl);
}

static void
nvme_fc_ctrl_put(struct nvme_fc_ctrl *ctrl)
{
 kref_put(&ctrl->ref, nvme_fc_ctrl_free);
}

static int
nvme_fc_ctrl_get(struct nvme_fc_ctrl *ctrl)
{
 return kref_get_unless_zero(&ctrl->ref);
}

/*
 * All accesses from nvme core layer done - can now free the
 * controller. Called after last nvme_put_ctrl() call
 */

static void
nvme_fc_free_ctrl(struct nvme_ctrl *nctrl)
{
 struct nvme_fc_ctrl *ctrl = to_fc_ctrl(nctrl);

 WARN_ON(nctrl != &ctrl->ctrl);

 nvme_fc_ctrl_put(ctrl);
}

/*
 * This routine is used by the transport when it needs to find active
 * io on a queue that is to be terminated. The transport uses
 * blk_mq_tagset_busy_itr() to find the busy requests, which then invoke
 * this routine to kill them on a 1 by 1 basis.
 *
 * As FC allocates FC exchange for each io, the transport must contact
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=93 G=93

¤ Dauer der Verarbeitung: 0.57 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.