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

Quelle  qla_target.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  qla_target.c SCSI LLD infrastructure for QLogic 22xx/23xx/24xx/25xx
 *
 *  based on qla2x00t.c code:
 *
 *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst@vlnb.net>
 *  Copyright (C) 2004 - 2005 Leonid Stoljar
 *  Copyright (C) 2006 Nathaniel Clark <nate@misrule.us>
 *  Copyright (C) 2006 - 2010 ID7 Ltd.
 *
 *  Forward port and refactoring to modern qla2xxx and target/configfs
 *
 *  Copyright (C) 2010-2013 Nicholas A. Bellinger <nab@kernel.org>
 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/blkdev.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/unaligned.h>
#include <scsi/scsi.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_tcq.h>

#include "qla_def.h"
#include "qla_target.h"

static int ql2xtgt_tape_enable;
module_param(ql2xtgt_tape_enable, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(ql2xtgt_tape_enable,
  "Enables Sequence level error recovery (aka FC Tape). Default is 0 - no SLER. 1 - Enable SLER.");

static char *qlini_mode = QLA2XXX_INI_MODE_STR_ENABLED;
module_param(qlini_mode, charp, S_IRUGO);
MODULE_PARM_DESC(qlini_mode,
 "Determines when initiator mode will be enabled. Possible values: "
 "\"exclusive\" - initiator mode will be enabled on load, "
 "disabled on enabling target mode and then on disabling target mode "
 "enabled back; "
 "\"disabled\" - initiator mode will never be enabled; "
 "\"dual\" - Initiator Modes will be enabled. Target Mode can be activated "
 "when ready "
 "\"enabled\" (default) - initiator mode will always stay enabled.");

int ql2xuctrlirq = 1;
module_param(ql2xuctrlirq, int, 0644);
MODULE_PARM_DESC(ql2xuctrlirq,
    "User to control IRQ placement via smp_affinity."
    "Valid with qlini_mode=disabled."
    "1(default): enable");

int ql2x_ini_mode = QLA2XXX_INI_MODE_EXCLUSIVE;

static int qla_sam_status = SAM_STAT_BUSY;
static int tc_sam_status = SAM_STAT_TASK_SET_FULL; /* target core */

/*
 * From scsi/fc/fc_fcp.h
 */

enum fcp_resp_rsp_codes {
 FCP_TMF_CMPL = 0,
 FCP_DATA_LEN_INVALID = 1,
 FCP_CMND_FIELDS_INVALID = 2,
 FCP_DATA_PARAM_MISMATCH = 3,
 FCP_TMF_REJECTED = 4,
 FCP_TMF_FAILED = 5,
 FCP_TMF_INVALID_LUN = 9,
};

/*
 * fc_pri_ta from scsi/fc/fc_fcp.h
 */

#define FCP_PTA_SIMPLE      0   /* simple task attribute */
#define FCP_PTA_HEADQ       1   /* head of queue task attribute */
#define FCP_PTA_ORDERED     2   /* ordered task attribute */
#define FCP_PTA_ACA         4   /* auto. contingent allegiance */
#define FCP_PTA_MASK        7   /* mask for task attribute field */
#define FCP_PRI_SHIFT       3   /* priority field starts in bit 3 */
#define FCP_PRI_RESVD_MASK  0x80        /* reserved bits in priority field */

/*
 * This driver calls qla2x00_alloc_iocbs() and qla2x00_issue_marker(), which
 * must be called under HW lock and could unlock/lock it inside.
 * It isn't an issue, since in the current implementation on the time when
 * those functions are called:
 *
 *   - Either context is IRQ and only IRQ handler can modify HW data,
 *     including rings related fields,
 *
 *   - Or access to target mode variables from struct qla_tgt doesn't
 *     cross those functions boundaries, except tgt_stop, which
 *     additionally protected by irq_cmd_count.
 */

/* Predefs for callbacks handed to qla2xxx LLD */
static void qlt_24xx_atio_pkt(struct scsi_qla_host *ha,
 struct atio_from_isp *pkt, uint8_t);
static void qlt_response_pkt(struct scsi_qla_host *ha, struct rsp_que *rsp,
 response_t *pkt);
static int qlt_issue_task_mgmt(struct fc_port *sess, u64 lun,
 int fn, void *iocb, int flags);
static void qlt_send_term_exchange(struct qla_qpair *, struct qla_tgt_cmd
 *cmd, struct atio_from_isp *atio, int ha_locked, int ul_abort);
static void qlt_alloc_qfull_cmd(struct scsi_qla_host *vha,
 struct atio_from_isp *atio, uint16_t status, int qfull);
static void qlt_disable_vha(struct scsi_qla_host *vha);
static void qlt_clear_tgt_db(struct qla_tgt *tgt);
static void qlt_send_notify_ack(struct qla_qpair *qpair,
 struct imm_ntfy_from_isp *ntfy,
 uint32_t add_flags, uint16_t resp_code, int resp_code_valid,
 uint16_t srr_flags, uint16_t srr_reject_code, uint8_t srr_explan);
static void qlt_send_term_imm_notif(struct scsi_qla_host *vha,
 struct imm_ntfy_from_isp *imm, int ha_locked);
static struct fc_port *qlt_create_sess(struct scsi_qla_host *vha,
 fc_port_t *fcport, bool local);
void qlt_unreg_sess(struct fc_port *sess);
static void qlt_24xx_handle_abts(struct scsi_qla_host *,
 struct abts_recv_from_24xx *);
static void qlt_send_busy(struct qla_qpair *, struct atio_from_isp *,
    uint16_t);
static int qlt_check_reserve_free_req(struct qla_qpair *qpair, uint32_t);
static inline uint32_t qlt_make_handle(struct qla_qpair *);

/*
 * Global Variables
 */

static struct kmem_cache *qla_tgt_mgmt_cmd_cachep;
struct kmem_cache *qla_tgt_plogi_cachep;
static mempool_t *qla_tgt_mgmt_cmd_mempool;
static struct workqueue_struct *qla_tgt_wq;
static DEFINE_MUTEX(qla_tgt_mutex);
static LIST_HEAD(qla_tgt_glist);

static const char *prot_op_str(u32 prot_op)
{
 switch (prot_op) {
 case TARGET_PROT_NORMAL: return "NORMAL";
 case TARGET_PROT_DIN_INSERT: return "DIN_INSERT";
 case TARGET_PROT_DOUT_INSERT: return "DOUT_INSERT";
 case TARGET_PROT_DIN_STRIP: return "DIN_STRIP";
 case TARGET_PROT_DOUT_STRIP: return "DOUT_STRIP";
 case TARGET_PROT_DIN_PASS: return "DIN_PASS";
 case TARGET_PROT_DOUT_PASS: return "DOUT_PASS";
 default:   return "UNKNOWN";
 }
}

/* This API intentionally takes dest as a parameter, rather than returning
 * int value to avoid caller forgetting to issue wmb() after the store */

void qlt_do_generation_tick(struct scsi_qla_host *vha, int *dest)
{
 scsi_qla_host_t *base_vha = pci_get_drvdata(vha->hw->pdev);
 *dest = atomic_inc_return(&base_vha->generation_tick);
 /* memory barrier */
 wmb();
}

/* Might release hw lock, then reaquire!! */
static inline int qlt_issue_marker(struct scsi_qla_host *vha, int vha_locked)
{
 /* Send marker if required */
 if (unlikely(vha->marker_needed != 0)) {
  int rc = qla2x00_issue_marker(vha, vha_locked);

  if (rc != QLA_SUCCESS) {
   ql_dbg(ql_dbg_tgt, vha, 0xe03d,
       "qla_target(%d): issue_marker() failed\n",
       vha->vp_idx);
  }
  return rc;
 }
 return QLA_SUCCESS;
}

struct scsi_qla_host *qla_find_host_by_d_id(struct scsi_qla_host *vha,
         be_id_t d_id)
{
 struct scsi_qla_host *host;
 uint32_t key;

 if (vha->d_id.b.area == d_id.area &&
     vha->d_id.b.domain == d_id.domain &&
     vha->d_id.b.al_pa == d_id.al_pa)
  return vha;

 key = be_to_port_id(d_id).b24;

 host = btree_lookup32(&vha->hw->host_map, key);
 if (!host)
  ql_dbg(ql_dbg_tgt_mgt + ql_dbg_verbose, vha, 0xf005,
      "Unable to find host %06x\n", key);

 return host;
}

static inline void qlt_incr_num_pend_cmds(struct scsi_qla_host *vha)
{
 unsigned long flags;

 spin_lock_irqsave(&vha->hw->tgt.q_full_lock, flags);

 vha->hw->tgt.num_pend_cmds++;
 if (vha->hw->tgt.num_pend_cmds > vha->qla_stats.stat_max_pend_cmds)
  vha->qla_stats.stat_max_pend_cmds =
   vha->hw->tgt.num_pend_cmds;
 spin_unlock_irqrestore(&vha->hw->tgt.q_full_lock, flags);
}
static inline void qlt_decr_num_pend_cmds(struct scsi_qla_host *vha)
{
 unsigned long flags;

 spin_lock_irqsave(&vha->hw->tgt.q_full_lock, flags);
 vha->hw->tgt.num_pend_cmds--;
 spin_unlock_irqrestore(&vha->hw->tgt.q_full_lock, flags);
}


static void qlt_queue_unknown_atio(scsi_qla_host_t *vha,
 struct atio_from_isp *atio, uint8_t ha_locked)
{
 struct qla_tgt_sess_op *u;
 struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
 unsigned long flags;

 if (tgt->tgt_stop) {
  ql_dbg(ql_dbg_async, vha, 0x502c,
      "qla_target(%d): dropping unknown ATIO_TYPE7, because tgt is being stopped",
      vha->vp_idx);
  goto out_term;
 }

 u = kzalloc(sizeof(*u), GFP_ATOMIC);
 if (u == NULL)
  goto out_term;

 u->vha = vha;
 memcpy(&u->atio, atio, sizeof(*atio));
 INIT_LIST_HEAD(&u->cmd_list);

 spin_lock_irqsave(&vha->cmd_list_lock, flags);
 list_add_tail(&u->cmd_list, &vha->unknown_atio_list);
 spin_unlock_irqrestore(&vha->cmd_list_lock, flags);

 schedule_delayed_work(&vha->unknown_atio_work, 1);

out:
 return;

out_term:
 qlt_send_term_exchange(vha->hw->base_qpair, NULL, atio, ha_locked, 0);
 goto out;
}

static void qlt_try_to_dequeue_unknown_atios(struct scsi_qla_host *vha,
 uint8_t ha_locked)
{
 struct qla_tgt_sess_op *u, *t;
 scsi_qla_host_t *host;
 struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
 unsigned long flags;
 uint8_t queued = 0;

 list_for_each_entry_safe(u, t, &vha->unknown_atio_list, cmd_list) {
  if (u->aborted) {
   ql_dbg(ql_dbg_async, vha, 0x502e,
       "Freeing unknown %s %p, because of Abort\n",
       "ATIO_TYPE7", u);
   qlt_send_term_exchange(vha->hw->base_qpair, NULL,
       &u->atio, ha_locked, 0);
   goto abort;
  }

  host = qla_find_host_by_d_id(vha, u->atio.u.isp24.fcp_hdr.d_id);
  if (host != NULL) {
   ql_dbg(ql_dbg_async + ql_dbg_verbose, vha, 0x502f,
       "Requeuing unknown ATIO_TYPE7 %p\n", u);
   qlt_24xx_atio_pkt(host, &u->atio, ha_locked);
  } else if (tgt->tgt_stop) {
   ql_dbg(ql_dbg_async + ql_dbg_verbose, vha, 0x503a,
       "Freeing unknown %s %p, because tgt is being stopped\n",
       "ATIO_TYPE7", u);
   qlt_send_term_exchange(vha->hw->base_qpair, NULL,
       &u->atio, ha_locked, 0);
  } else {
   ql_dbg(ql_dbg_async + ql_dbg_verbose, vha, 0x503d,
       "Reschedule u %p, vha %p, host %p\n", u, vha, host);
   if (!queued) {
    queued = 1;
    schedule_delayed_work(&vha->unknown_atio_work,
        1);
   }
   continue;
  }

abort:
  spin_lock_irqsave(&vha->cmd_list_lock, flags);
  list_del(&u->cmd_list);
  spin_unlock_irqrestore(&vha->cmd_list_lock, flags);
  kfree(u);
 }
}

void qlt_unknown_atio_work_fn(struct work_struct *work)
{
 struct scsi_qla_host *vha = container_of(to_delayed_work(work),
     struct scsi_qla_host, unknown_atio_work);

 qlt_try_to_dequeue_unknown_atios(vha, 0);
}

static bool qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha,
 struct atio_from_isp *atio, uint8_t ha_locked)
{
 ql_dbg(ql_dbg_tgt, vha, 0xe072,
  "%s: qla_target(%d): type %x ox_id %04x\n",
  __func__, vha->vp_idx, atio->u.raw.entry_type,
  be16_to_cpu(atio->u.isp24.fcp_hdr.ox_id));

 switch (atio->u.raw.entry_type) {
 case ATIO_TYPE7:
 {
  struct scsi_qla_host *host = qla_find_host_by_d_id(vha,
      atio->u.isp24.fcp_hdr.d_id);
  if (unlikely(NULL == host)) {
   ql_dbg(ql_dbg_tgt, vha, 0xe03e,
       "qla_target(%d): Received ATIO_TYPE7 "
       "with unknown d_id %x:%x:%x\n", vha->vp_idx,
       atio->u.isp24.fcp_hdr.d_id.domain,
       atio->u.isp24.fcp_hdr.d_id.area,
       atio->u.isp24.fcp_hdr.d_id.al_pa);


   qlt_queue_unknown_atio(vha, atio, ha_locked);
   break;
  }
  if (unlikely(!list_empty(&vha->unknown_atio_list)))
   qlt_try_to_dequeue_unknown_atios(vha, ha_locked);

  qlt_24xx_atio_pkt(host, atio, ha_locked);
  break;
 }

 case IMMED_NOTIFY_TYPE:
 {
  struct scsi_qla_host *host = vha;
  struct imm_ntfy_from_isp *entry =
      (struct imm_ntfy_from_isp *)atio;

  qlt_issue_marker(vha, ha_locked);

  if ((entry->u.isp24.vp_index != 0xFF) &&
      (entry->u.isp24.nport_handle != cpu_to_le16(0xFFFF))) {
   host = qla_find_host_by_vp_idx(vha,
       entry->u.isp24.vp_index);
   if (unlikely(!host)) {
    ql_dbg(ql_dbg_tgt, vha, 0xe03f,
        "qla_target(%d): Received "
        "ATIO (IMMED_NOTIFY_TYPE) "
        "with unknown vp_index %d\n",
        vha->vp_idx, entry->u.isp24.vp_index);
    break;
   }
  }
  qlt_24xx_atio_pkt(host, atio, ha_locked);
  break;
 }

 case VP_RPT_ID_IOCB_TYPE:
  qla24xx_report_id_acquisition(vha,
   (struct vp_rpt_id_entry_24xx *)atio);
  break;

 case ABTS_RECV_24XX:
 {
  struct abts_recv_from_24xx *entry =
   (struct abts_recv_from_24xx *)atio;
  struct scsi_qla_host *host = qla_find_host_by_vp_idx(vha,
   entry->vp_index);
  unsigned long flags;

  if (unlikely(!host)) {
   ql_dbg(ql_dbg_tgt, vha, 0xe00a,
       "qla_target(%d): Response pkt (ABTS_RECV_24XX) "
       "received, with unknown vp_index %d\n",
       vha->vp_idx, entry->vp_index);
   break;
  }
  if (!ha_locked)
   spin_lock_irqsave(&host->hw->hardware_lock, flags);
  qlt_24xx_handle_abts(host, (struct abts_recv_from_24xx *)atio);
  if (!ha_locked)
   spin_unlock_irqrestore(&host->hw->hardware_lock, flags);
  break;
 }

 /* case PUREX_IOCB_TYPE: ql2xmvasynctoatio */

 default:
  ql_dbg(ql_dbg_tgt, vha, 0xe040,
      "qla_target(%d): Received unknown ATIO atio "
      "type %x\n", vha->vp_idx, atio->u.raw.entry_type);
  break;
 }

 return false;
}

void qlt_response_pkt_all_vps(struct scsi_qla_host *vha,
 struct rsp_que *rsp, response_t *pkt)
{
 switch (pkt->entry_type) {
 case CTIO_CRC2:
  ql_dbg(ql_dbg_tgt, vha, 0xe073,
   "qla_target(%d):%s: CRC2 Response pkt\n",
   vha->vp_idx, __func__);
  fallthrough;
 case CTIO_TYPE7:
 {
  struct ctio7_from_24xx *entry = (struct ctio7_from_24xx *)pkt;
  struct scsi_qla_host *host = qla_find_host_by_vp_idx(vha,
      entry->vp_index);
  if (unlikely(!host)) {
   ql_dbg(ql_dbg_tgt, vha, 0xe041,
       "qla_target(%d): Response pkt (CTIO_TYPE7) "
       "received, with unknown vp_index %d\n",
       vha->vp_idx, entry->vp_index);
   break;
  }
  qlt_response_pkt(host, rsp, pkt);
  break;
 }

 case IMMED_NOTIFY_TYPE:
 {
  struct scsi_qla_host *host;
  struct imm_ntfy_from_isp *entry =
      (struct imm_ntfy_from_isp *)pkt;

  host = qla_find_host_by_vp_idx(vha, entry->u.isp24.vp_index);
  if (unlikely(!host)) {
   ql_dbg(ql_dbg_tgt, vha, 0xe042,
       "qla_target(%d): Response pkt (IMMED_NOTIFY_TYPE) "
       "received, with unknown vp_index %d\n",
       vha->vp_idx, entry->u.isp24.vp_index);
   break;
  }
  qlt_response_pkt(host, rsp, pkt);
  break;
 }

 case NOTIFY_ACK_TYPE:
 {
  struct scsi_qla_host *host = vha;
  struct nack_to_isp *entry = (struct nack_to_isp *)pkt;

  if (0xFF != entry->u.isp24.vp_index) {
   host = qla_find_host_by_vp_idx(vha,
       entry->u.isp24.vp_index);
   if (unlikely(!host)) {
    ql_dbg(ql_dbg_tgt, vha, 0xe043,
        "qla_target(%d): Response "
        "pkt (NOTIFY_ACK_TYPE) "
        "received, with unknown "
        "vp_index %d\n", vha->vp_idx,
        entry->u.isp24.vp_index);
    break;
   }
  }
  qlt_response_pkt(host, rsp, pkt);
  break;
 }

 case ABTS_RECV_24XX:
 {
  struct abts_recv_from_24xx *entry =
      (struct abts_recv_from_24xx *)pkt;
  struct scsi_qla_host *host = qla_find_host_by_vp_idx(vha,
      entry->vp_index);
  if (unlikely(!host)) {
   ql_dbg(ql_dbg_tgt, vha, 0xe044,
       "qla_target(%d): Response pkt "
       "(ABTS_RECV_24XX) received, with unknown "
       "vp_index %d\n", vha->vp_idx, entry->vp_index);
   break;
  }
  qlt_response_pkt(host, rsp, pkt);
  break;
 }

 case ABTS_RESP_24XX:
 {
  struct abts_resp_to_24xx *entry =
      (struct abts_resp_to_24xx *)pkt;
  struct scsi_qla_host *host = qla_find_host_by_vp_idx(vha,
      entry->vp_index);
  if (unlikely(!host)) {
   ql_dbg(ql_dbg_tgt, vha, 0xe045,
       "qla_target(%d): Response pkt "
       "(ABTS_RECV_24XX) received, with unknown "
       "vp_index %d\n", vha->vp_idx, entry->vp_index);
   break;
  }
  qlt_response_pkt(host, rsp, pkt);
  break;
 }
 default:
  qlt_response_pkt(vha, rsp, pkt);
  break;
 }

}

/*
 * All qlt_plogi_ack_t operations are protected by hardware_lock
 */

static int qla24xx_post_nack_work(struct scsi_qla_host *vha, fc_port_t *fcport,
 struct imm_ntfy_from_isp *ntfy, int type)
{
 struct qla_work_evt *e;

 e = qla2x00_alloc_work(vha, QLA_EVT_NACK);
 if (!e)
  return QLA_FUNCTION_FAILED;

 e->u.nack.fcport = fcport;
 e->u.nack.type = type;
 memcpy(e->u.nack.iocb, ntfy, sizeof(struct imm_ntfy_from_isp));
 return qla2x00_post_work(vha, e);
}

static void qla2x00_async_nack_sp_done(srb_t *sp, int res)
{
 struct scsi_qla_host *vha = sp->vha;
 unsigned long flags;

 ql_dbg(ql_dbg_disc, vha, 0x20f2,
     "Async done-%s res %x %8phC type %d\n",
     sp->name, res, sp->fcport->port_name, sp->type);

 spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags);
 sp->fcport->flags &= ~FCF_ASYNC_SENT;
 sp->fcport->chip_reset = vha->hw->base_qpair->chip_reset;

 switch (sp->type) {
 case SRB_NACK_PLOGI:
  sp->fcport->login_gen++;
  sp->fcport->fw_login_state = DSC_LS_PLOGI_COMP;
  sp->fcport->logout_on_delete = 1;
  sp->fcport->plogi_nack_done_deadline = jiffies + HZ;
  sp->fcport->send_els_logo = 0;

  if (sp->fcport->flags & FCF_FCSP_DEVICE) {
   ql_dbg(ql_dbg_edif, vha, 0x20ef,
       "%s %8phC edif: PLOGI- AUTH WAIT\n", __func__,
       sp->fcport->port_name);
   qla2x00_set_fcport_disc_state(sp->fcport,
       DSC_LOGIN_AUTH_PEND);
   qla2x00_post_aen_work(vha, FCH_EVT_PORT_ONLINE,
       sp->fcport->d_id.b24);
   qla_edb_eventcreate(vha, VND_CMD_AUTH_STATE_NEEDED, sp->fcport->d_id.b24,
       0, sp->fcport);
  }
  break;

 case SRB_NACK_PRLI:
  sp->fcport->fw_login_state = DSC_LS_PRLI_COMP;
  sp->fcport->deleted = 0;
  sp->fcport->send_els_logo = 0;

  if (!sp->fcport->login_succ &&
      !IS_SW_RESV_ADDR(sp->fcport->d_id)) {
   sp->fcport->login_succ = 1;

   vha->fcport_count++;
   spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags);
   qla24xx_sched_upd_fcport(sp->fcport);
   spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags);
  } else {
   sp->fcport->login_retry = 0;
   qla2x00_set_fcport_disc_state(sp->fcport,
       DSC_LOGIN_COMPLETE);
   sp->fcport->deleted = 0;
   sp->fcport->logout_on_delete = 1;
  }
  break;

 case SRB_NACK_LOGO:
  sp->fcport->login_gen++;
  sp->fcport->fw_login_state = DSC_LS_PORT_UNAVAIL;
  qlt_logo_completion_handler(sp->fcport, MBS_COMMAND_COMPLETE);
  break;
 }
 spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags);

 kref_put(&sp->cmd_kref, qla2x00_sp_release);
}

int qla24xx_async_notify_ack(scsi_qla_host_t *vha, fc_port_t *fcport,
 struct imm_ntfy_from_isp *ntfy, int type)
{
 int rval = QLA_FUNCTION_FAILED;
 srb_t *sp;
 char *c = NULL;

 fcport->flags |= FCF_ASYNC_SENT;
 switch (type) {
 case SRB_NACK_PLOGI:
  fcport->fw_login_state = DSC_LS_PLOGI_PEND;
  c = "PLOGI";
  if (vha->hw->flags.edif_enabled &&
      (le16_to_cpu(ntfy->u.isp24.flags) & NOTIFY24XX_FLAGS_FCSP))
   fcport->flags |= FCF_FCSP_DEVICE;
  break;
 case SRB_NACK_PRLI:
  fcport->fw_login_state = DSC_LS_PRLI_PEND;
  fcport->deleted = 0;
  c = "PRLI";
  break;
 case SRB_NACK_LOGO:
  fcport->fw_login_state = DSC_LS_LOGO_PEND;
  c = "LOGO";
  break;
 }

 sp = qla2x00_get_sp(vha, fcport, GFP_ATOMIC);
 if (!sp)
  goto done;

 sp->type = type;
 sp->name = "nack";
 qla2x00_init_async_sp(sp, qla2x00_get_async_timeout(vha) + 2,
         qla2x00_async_nack_sp_done);

 sp->u.iocb_cmd.u.nack.ntfy = ntfy;

 ql_dbg(ql_dbg_disc, vha, 0x20f4,
     "Async-%s %8phC hndl %x %s\n",
     sp->name, fcport->port_name, sp->handle, c);

 rval = qla2x00_start_sp(sp);
 if (rval != QLA_SUCCESS)
  goto done_free_sp;

 return rval;

done_free_sp:
 kref_put(&sp->cmd_kref, qla2x00_sp_release);
done:
 fcport->flags &= ~FCF_ASYNC_SENT;
 return rval;
}

void qla24xx_do_nack_work(struct scsi_qla_host *vha, struct qla_work_evt *e)
{
 fc_port_t *t;

 switch (e->u.nack.type) {
 case SRB_NACK_PRLI:
  t = e->u.nack.fcport;
  flush_work(&t->del_work);
  flush_work(&t->free_work);
  mutex_lock(&vha->vha_tgt.tgt_mutex);
  t = qlt_create_sess(vha, e->u.nack.fcport, 0);
  mutex_unlock(&vha->vha_tgt.tgt_mutex);
  if (t) {
   ql_log(ql_log_info, vha, 0xd034,
       "%s create sess success %p", __func__, t);
   /* create sess has an extra kref */
   vha->hw->tgt.tgt_ops->put_sess(e->u.nack.fcport);
  }
  break;
 }
 qla24xx_async_notify_ack(vha, e->u.nack.fcport,
     (struct imm_ntfy_from_isp *)e->u.nack.iocb, e->u.nack.type);
}

void qla24xx_delete_sess_fn(struct work_struct *work)
{
 fc_port_t *fcport = container_of(work, struct fc_port, del_work);
 struct qla_hw_data *ha = NULL;

 if (!fcport || !fcport->vha || !fcport->vha->hw)
  return;

 ha = fcport->vha->hw;

 if (fcport->se_sess) {
  ha->tgt.tgt_ops->shutdown_sess(fcport);
  ha->tgt.tgt_ops->put_sess(fcport);
 } else {
  qlt_unreg_sess(fcport);
 }
}

/*
 * Called from qla2x00_reg_remote_port()
 */

void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport)
{
 struct qla_hw_data *ha = vha->hw;
 struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
 struct fc_port *sess = fcport;
 unsigned long flags;

 if (!vha->hw->tgt.tgt_ops)
  return;

 spin_lock_irqsave(&ha->tgt.sess_lock, flags);
 if (tgt->tgt_stop) {
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
  return;
 }

 if (fcport->disc_state == DSC_DELETE_PEND) {
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
  return;
 }

 if (!sess->se_sess) {
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);

  mutex_lock(&vha->vha_tgt.tgt_mutex);
  sess = qlt_create_sess(vha, fcport, false);
  mutex_unlock(&vha->vha_tgt.tgt_mutex);

  spin_lock_irqsave(&ha->tgt.sess_lock, flags);
 } else {
  if (fcport->fw_login_state == DSC_LS_PRLI_COMP) {
   spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
   return;
  }

  if (!kref_get_unless_zero(&sess->sess_kref)) {
   ql_dbg(ql_dbg_disc, vha, 0x2107,
       "%s: kref_get fail sess %8phC \n",
       __func__, sess->port_name);
   spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
   return;
  }

  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04c,
      "qla_target(%u): %ssession for port %8phC "
      "(loop ID %d) reappeared\n", vha->vp_idx,
      sess->local ? "local " : "", sess->port_name, sess->loop_id);

  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf007,
      "Reappeared sess %p\n", sess);

  ha->tgt.tgt_ops->update_sess(sess, fcport->d_id,
      fcport->loop_id,
      (fcport->flags & FCF_CONF_COMP_SUPPORTED));
 }

 if (sess && sess->local) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04d,
      "qla_target(%u): local session for "
      "port %8phC (loop ID %d) became global\n", vha->vp_idx,
      fcport->port_name, sess->loop_id);
  sess->local = 0;
 }
 spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);

 ha->tgt.tgt_ops->put_sess(sess);
}

/*
 * This is a zero-base ref-counting solution, since hardware_lock
 * guarantees that ref_count is not modified concurrently.
 * Upon successful return content of iocb is undefined
 */

static struct qlt_plogi_ack_t *
qlt_plogi_ack_find_add(struct scsi_qla_host *vha, port_id_t *id,
         struct imm_ntfy_from_isp *iocb)
{
 struct qlt_plogi_ack_t *pla;

 lockdep_assert_held(&vha->hw->hardware_lock);

 list_for_each_entry(pla, &vha->plogi_ack_list, list) {
  if (pla->id.b24 == id->b24) {
   ql_dbg(ql_dbg_disc + ql_dbg_verbose, vha, 0x210d,
       "%s %d %8phC Term INOT due to new INOT",
       __func__, __LINE__,
       pla->iocb.u.isp24.port_name);
   qlt_send_term_imm_notif(vha, &pla->iocb, 1);
   memcpy(&pla->iocb, iocb, sizeof(pla->iocb));
   return pla;
  }
 }

 pla = kmem_cache_zalloc(qla_tgt_plogi_cachep, GFP_ATOMIC);
 if (!pla) {
  ql_dbg(ql_dbg_async, vha, 0x5088,
         "qla_target(%d): Allocation of plogi_ack failed\n",
         vha->vp_idx);
  return NULL;
 }

 memcpy(&pla->iocb, iocb, sizeof(pla->iocb));
 pla->id = *id;
 list_add_tail(&pla->list, &vha->plogi_ack_list);

 return pla;
}

void qlt_plogi_ack_unref(struct scsi_qla_host *vha,
    struct qlt_plogi_ack_t *pla)
{
 struct imm_ntfy_from_isp *iocb = &pla->iocb;
 port_id_t port_id;
 uint16_t loop_id;
 fc_port_t *fcport = pla->fcport;

 BUG_ON(!pla->ref_count);
 pla->ref_count--;

 if (pla->ref_count)
  return;

 ql_dbg(ql_dbg_disc, vha, 0x5089,
     "Sending PLOGI ACK to wwn %8phC s_id %02x:%02x:%02x loop_id %#04x"
     " exch %#x ox_id %#x\n", iocb->u.isp24.port_name,
     iocb->u.isp24.port_id[2], iocb->u.isp24.port_id[1],
     iocb->u.isp24.port_id[0],
     le16_to_cpu(iocb->u.isp24.nport_handle),
     iocb->u.isp24.exchange_address, iocb->ox_id);

 port_id.b.domain = iocb->u.isp24.port_id[2];
 port_id.b.area   = iocb->u.isp24.port_id[1];
 port_id.b.al_pa  = iocb->u.isp24.port_id[0];
 port_id.b.rsvd_1 = 0;

 loop_id = le16_to_cpu(iocb->u.isp24.nport_handle);

 fcport->loop_id = loop_id;
 fcport->d_id = port_id;
 if (iocb->u.isp24.status_subcode == ELS_PLOGI)
  qla24xx_post_nack_work(vha, fcport, iocb, SRB_NACK_PLOGI);
 else
  qla24xx_post_nack_work(vha, fcport, iocb, SRB_NACK_PRLI);

 list_for_each_entry(fcport, &vha->vp_fcports, list) {
  if (fcport->plogi_link[QLT_PLOGI_LINK_SAME_WWN] == pla)
   fcport->plogi_link[QLT_PLOGI_LINK_SAME_WWN] = NULL;
  if (fcport->plogi_link[QLT_PLOGI_LINK_CONFLICT] == pla)
   fcport->plogi_link[QLT_PLOGI_LINK_CONFLICT] = NULL;
 }

 list_del(&pla->list);
 kmem_cache_free(qla_tgt_plogi_cachep, pla);
}

void
qlt_plogi_ack_link(struct scsi_qla_host *vha, struct qlt_plogi_ack_t *pla,
    struct fc_port *sess, enum qlt_plogi_link_t link)
{
 struct imm_ntfy_from_isp *iocb = &pla->iocb;
 /* Inc ref_count first because link might already be pointing at pla */
 pla->ref_count++;

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf097,
  "Linking sess %p [%d] wwn %8phC with PLOGI ACK to wwn %8phC"
  " s_id %02x:%02x:%02x, ref=%d pla %p link %d\n",
  sess, link, sess->port_name,
  iocb->u.isp24.port_name, iocb->u.isp24.port_id[2],
  iocb->u.isp24.port_id[1], iocb->u.isp24.port_id[0],
  pla->ref_count, pla, link);

 if (link == QLT_PLOGI_LINK_CONFLICT) {
  switch (sess->disc_state) {
  case DSC_DELETED:
  case DSC_DELETE_PEND:
   pla->ref_count--;
   return;
  default:
   break;
  }
 }

 if (sess->plogi_link[link])
  qlt_plogi_ack_unref(vha, sess->plogi_link[link]);

 if (link == QLT_PLOGI_LINK_SAME_WWN)
  pla->fcport = sess;

 sess->plogi_link[link] = pla;
}

typedef struct {
 /* These fields must be initialized by the caller */
 port_id_t id;
 /*
 * number of cmds dropped while we were waiting for
 * initiator to ack LOGO initialize to 1 if LOGO is
 * triggered by a command, otherwise, to 0
 */

 int cmd_count;

 /* These fields are used by callee */
 struct list_head list;
} qlt_port_logo_t;

static void
qlt_send_first_logo(struct scsi_qla_host *vha, qlt_port_logo_t *logo)
{
 qlt_port_logo_t *tmp;
 int res;

 if (test_bit(PFLG_DRIVER_REMOVING, &vha->pci_flags)) {
  res = 0;
  goto out;
 }

 mutex_lock(&vha->vha_tgt.tgt_mutex);

 list_for_each_entry(tmp, &vha->logo_list, list) {
  if (tmp->id.b24 == logo->id.b24) {
   tmp->cmd_count += logo->cmd_count;
   mutex_unlock(&vha->vha_tgt.tgt_mutex);
   return;
  }
 }

 list_add_tail(&logo->list, &vha->logo_list);

 mutex_unlock(&vha->vha_tgt.tgt_mutex);

 res = qla24xx_els_dcmd_iocb(vha, ELS_DCMD_LOGO, logo->id);

 mutex_lock(&vha->vha_tgt.tgt_mutex);
 list_del(&logo->list);
 mutex_unlock(&vha->vha_tgt.tgt_mutex);

out:
 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf098,
     "Finished LOGO to %02x:%02x:%02x, dropped %d cmds, res = %#x\n",
     logo->id.b.domain, logo->id.b.area, logo->id.b.al_pa,
     logo->cmd_count, res);
}

void qlt_free_session_done(struct work_struct *work)
{
 struct fc_port *sess = container_of(work, struct fc_port,
     free_work);
 struct qla_tgt *tgt = sess->tgt;
 struct scsi_qla_host *vha = sess->vha;
 struct qla_hw_data *ha = vha->hw;
 unsigned long flags;
 bool logout_started = false;
 scsi_qla_host_t *base_vha = pci_get_drvdata(ha->pdev);
 struct qlt_plogi_ack_t *own =
  sess->plogi_link[QLT_PLOGI_LINK_SAME_WWN];

 ql_dbg(ql_dbg_disc, vha, 0xf084,
  "%s: se_sess %p / sess %p from port %8phC loop_id %#04x"
  " s_id %02x:%02x:%02x logout %d keep %d els_logo %d\n",
  __func__, sess->se_sess, sess, sess->port_name, sess->loop_id,
  sess->d_id.b.domain, sess->d_id.b.area, sess->d_id.b.al_pa,
  sess->logout_on_delete, sess->keep_nport_handle,
  sess->send_els_logo);

 if (!IS_SW_RESV_ADDR(sess->d_id)) {
  qla2x00_mark_device_lost(vha, sess, 0);

  if (sess->send_els_logo) {
   qlt_port_logo_t logo;

   logo.id = sess->d_id;
   logo.cmd_count = 0;
   INIT_LIST_HEAD(&logo.list);
   if (!own)
    qlt_send_first_logo(vha, &logo);
   sess->send_els_logo = 0;
  }

  if (sess->logout_on_delete && sess->loop_id != FC_NO_LOOP_ID) {
   int rc;

   if (!own ||
        (own->iocb.u.isp24.status_subcode == ELS_PLOGI)) {
    sess->logout_completed = 0;
    rc = qla2x00_post_async_logout_work(vha, sess,
        NULL);
    if (rc != QLA_SUCCESS)
     ql_log(ql_log_warn, vha, 0xf085,
         "Schedule logo failed sess %p rc %d\n",
         sess, rc);
    else
     logout_started = true;
   } else if (own && (own->iocb.u.isp24.status_subcode ==
    ELS_PRLI) && ha->flags.rida_fmt2) {
    rc = qla2x00_post_async_prlo_work(vha, sess,
        NULL);
    if (rc != QLA_SUCCESS)
     ql_log(ql_log_warn, vha, 0xf085,
         "Schedule PRLO failed sess %p rc %d\n",
         sess, rc);
    else
     logout_started = true;
   }
  } /* if sess->logout_on_delete */

  if (sess->nvme_flag & NVME_FLAG_REGISTERED &&
      !(sess->nvme_flag & NVME_FLAG_DELETING)) {
   sess->nvme_flag |= NVME_FLAG_DELETING;
   qla_nvme_unregister_remote_port(sess);
  }

  if (ha->flags.edif_enabled &&
    (!own || own->iocb.u.isp24.status_subcode == ELS_PLOGI)) {
   sess->edif.authok = 0;
   if (!ha->flags.host_shutting_down) {
    ql_dbg(ql_dbg_edif, vha, 0x911e,
           "%s wwpn %8phC calling qla2x00_release_all_sadb\n",
           __func__, sess->port_name);
    qla2x00_release_all_sadb(vha, sess);
   } else {
    ql_dbg(ql_dbg_edif, vha, 0x911e,
           "%s bypassing release_all_sadb\n",
           __func__);
   }

   qla_edif_clear_appdata(vha, sess);
   qla_edif_sess_down(vha, sess);
  }
 }

 /*
 * Release the target session for FC Nexus from fabric module code.
 */

 if (sess->se_sess != NULL)
  ha->tgt.tgt_ops->free_session(sess);

 if (logout_started) {
  bool traced = false;
  u16 cnt = 0;

  while (!READ_ONCE(sess->logout_completed)) {
   if (!traced) {
    ql_dbg(ql_dbg_disc, vha, 0xf086,
     "%s: waiting for sess %p logout\n",
     __func__, sess);
    traced = true;
   }
   msleep(100);
   cnt++;
   /*
 * Driver timeout is set to 22 Sec, update count value to loop
 * long enough for log-out to complete before advancing. Otherwise,
 * straddling logout can interfere with re-login attempt.
 */

   if (cnt > 230)
    break;
  }

  ql_dbg(ql_dbg_disc, vha, 0xf087,
      "%s: sess %p logout completed\n", __func__, sess);
 }

 /* check for any straggling io left behind */
 if (!(sess->flags & FCF_FCP2_DEVICE) &&
     qla2x00_eh_wait_for_pending_commands(sess->vha, sess->d_id.b24, 0, WAIT_TARGET)) {
  ql_log(ql_log_warn, vha, 0x3027,
      "IO not return. Resetting.\n");
  set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
  qla2xxx_wake_dpc(vha);
  qla2x00_wait_for_chip_reset(vha);
 }

 if (sess->logo_ack_needed) {
  sess->logo_ack_needed = 0;
  qla24xx_async_notify_ack(vha, sess,
   (struct imm_ntfy_from_isp *)sess->iocb, SRB_NACK_LOGO);
 }

 spin_lock_irqsave(&ha->tgt.sess_lock, flags);
 if (sess->se_sess) {
  sess->se_sess = NULL;
  if (tgt && !IS_SW_RESV_ADDR(sess->d_id))
   tgt->sess_count--;
 }

 qla2x00_set_fcport_disc_state(sess, DSC_DELETED);
 sess->fw_login_state = DSC_LS_PORT_UNAVAIL;

 if (sess->login_succ && !IS_SW_RESV_ADDR(sess->d_id)) {
  vha->fcport_count--;
  sess->login_succ = 0;
 }

 qla2x00_clear_loop_id(sess);

 if (sess->conflict) {
  sess->conflict->login_pause = 0;
  sess->conflict = NULL;
  if (!test_bit(UNLOADING, &vha->dpc_flags))
   set_bit(RELOGIN_NEEDED, &vha->dpc_flags);
 }

 {
  struct qlt_plogi_ack_t *con =
      sess->plogi_link[QLT_PLOGI_LINK_CONFLICT];
  struct imm_ntfy_from_isp *iocb;

  own = sess->plogi_link[QLT_PLOGI_LINK_SAME_WWN];

  if (con) {
   iocb = &con->iocb;
   ql_dbg(ql_dbg_tgt_mgt, vha, 0xf099,
     "se_sess %p / sess %p port %8phC is gone,"
     " %s (ref=%d), releasing PLOGI for %8phC (ref=%d)\n",
     sess->se_sess, sess, sess->port_name,
     own ? "releasing own PLOGI" : "no own PLOGI pending",
     own ? own->ref_count : -1,
     iocb->u.isp24.port_name, con->ref_count);
   qlt_plogi_ack_unref(vha, con);
   sess->plogi_link[QLT_PLOGI_LINK_CONFLICT] = NULL;
  } else {
   ql_dbg(ql_dbg_tgt_mgt, vha, 0xf09a,
       "se_sess %p / sess %p port %8phC is gone, %s (ref=%d)\n",
       sess->se_sess, sess, sess->port_name,
       own ? "releasing own PLOGI" :
       "no own PLOGI pending",
       own ? own->ref_count : -1);
  }

  if (own) {
   sess->fw_login_state = DSC_LS_PLOGI_PEND;
   qlt_plogi_ack_unref(vha, own);
   sess->plogi_link[QLT_PLOGI_LINK_SAME_WWN] = NULL;
  }
 }

 sess->explicit_logout = 0;
 spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);

 qla2x00_dfs_remove_rport(vha, sess);

 spin_lock_irqsave(&vha->work_lock, flags);
 sess->flags &= ~FCF_ASYNC_SENT;
 sess->deleted = QLA_SESS_DELETED;
 sess->free_pending = 0;
 spin_unlock_irqrestore(&vha->work_lock, flags);

 ql_dbg(ql_dbg_disc, vha, 0xf001,
     "Unregistration of sess %p %8phC finished fcp_cnt %d\n",
  sess, sess->port_name, vha->fcport_count);

 if (tgt && (tgt->sess_count == 0))
  wake_up_all(&tgt->waitQ);

 if (!test_bit(PFLG_DRIVER_REMOVING, &base_vha->pci_flags) &&
     !(vha->vp_idx && test_bit(VPORT_DELETE, &vha->dpc_flags)) &&
     (!tgt || !tgt->tgt_stop) && !LOOP_TRANSITION(vha)) {
  switch (vha->host->active_mode) {
  case MODE_INITIATOR:
  case MODE_DUAL:
   set_bit(RELOGIN_NEEDED, &vha->dpc_flags);
   qla2xxx_wake_dpc(vha);
   break;
  case MODE_TARGET:
  default:
   /* no-op */
   break;
  }
 }

 if (vha->fcport_count == 0)
  wake_up_all(&vha->fcport_waitQ);
}

/* ha->tgt.sess_lock supposed to be held on entry */
void qlt_unreg_sess(struct fc_port *sess)
{
 struct scsi_qla_host *vha = sess->vha;
 unsigned long flags;

 ql_dbg(ql_dbg_disc, sess->vha, 0x210a,
     "%s sess %p for deletion %8phC\n",
     __func__, sess, sess->port_name);

 spin_lock_irqsave(&sess->vha->work_lock, flags);
 if (sess->free_pending) {
  spin_unlock_irqrestore(&sess->vha->work_lock, flags);
  return;
 }
 sess->free_pending = 1;
 /*
 * Use FCF_ASYNC_SENT flag to block other cmds used in sess
 * management from being sent.
 */

 sess->flags |= FCF_ASYNC_SENT;
 sess->deleted = QLA_SESS_DELETION_IN_PROGRESS;
 spin_unlock_irqrestore(&sess->vha->work_lock, flags);

 if (sess->se_sess)
  vha->hw->tgt.tgt_ops->clear_nacl_from_fcport_map(sess);

 qla2x00_set_fcport_disc_state(sess, DSC_DELETE_PEND);
 sess->last_rscn_gen = sess->rscn_gen;
 sess->last_login_gen = sess->login_gen;

 queue_work(sess->vha->hw->wq, &sess->free_work);
}
EXPORT_SYMBOL(qlt_unreg_sess);

static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd)
{
 struct qla_hw_data *ha = vha->hw;
 struct fc_port *sess = NULL;
 uint16_t loop_id;
 int res = 0;
 struct imm_ntfy_from_isp *n = (struct imm_ntfy_from_isp *)iocb;
 unsigned long flags;

 loop_id = le16_to_cpu(n->u.isp24.nport_handle);
 if (loop_id == 0xFFFF) {
  /* Global event */
  atomic_inc(&vha->vha_tgt.qla_tgt->tgt_global_resets_count);
  spin_lock_irqsave(&ha->tgt.sess_lock, flags);
  qlt_clear_tgt_db(vha->vha_tgt.qla_tgt);
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
 } else {
  spin_lock_irqsave(&ha->tgt.sess_lock, flags);
  sess = ha->tgt.tgt_ops->find_sess_by_loop_id(vha, loop_id);
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
 }

 ql_dbg(ql_dbg_tgt, vha, 0xe000,
     "Using sess for qla_tgt_reset: %p\n", sess);
 if (!sess) {
  res = -ESRCH;
  return res;
 }

 ql_dbg(ql_dbg_tgt, vha, 0xe047,
     "scsi(%ld): resetting (session %p from port %8phC mcmd %x, "
     "loop_id %d)\n", vha->host_no, sess, sess->port_name,
     mcmd, loop_id);

 return qlt_issue_task_mgmt(sess, 0, mcmd, iocb, QLA24XX_MGMT_SEND_NACK);
}

static void qla24xx_chk_fcp_state(struct fc_port *sess)
{
 if (sess->chip_reset != sess->vha->hw->base_qpair->chip_reset) {
  sess->logout_on_delete = 0;
  sess->logo_ack_needed = 0;
  sess->fw_login_state = DSC_LS_PORT_UNAVAIL;
 }
}

void qlt_schedule_sess_for_deletion(struct fc_port *sess)
{
 struct qla_tgt *tgt = sess->tgt;
 unsigned long flags;
 u16 sec;

 switch (sess->disc_state) {
 case DSC_DELETE_PEND:
  return;
 case DSC_DELETED:
  if (!sess->plogi_link[QLT_PLOGI_LINK_SAME_WWN] &&
   !sess->plogi_link[QLT_PLOGI_LINK_CONFLICT]) {
   if (tgt && tgt->tgt_stop && tgt->sess_count == 0)
    wake_up_all(&tgt->waitQ);

   if (sess->vha->fcport_count == 0)
    wake_up_all(&sess->vha->fcport_waitQ);
   return;
  }
  break;
 case DSC_UPD_FCPORT:
  /*
 * This port is not done reporting to upper layer.
 * let it finish
 */

  sess->next_disc_state = DSC_DELETE_PEND;
  sec = jiffies_to_msecs(jiffies -
      sess->jiffies_at_registration)/1000;
  if (sess->sec_since_registration < sec && sec && !(sec % 5)) {
   sess->sec_since_registration = sec;
   ql_dbg(ql_dbg_disc, sess->vha, 0xffff,
       "%s %8phC : Slow Rport registration(%d Sec)\n",
       __func__, sess->port_name, sec);
  }
  return;
 default:
  break;
 }

 spin_lock_irqsave(&sess->vha->work_lock, flags);
 if (sess->deleted == QLA_SESS_DELETION_IN_PROGRESS) {
  spin_unlock_irqrestore(&sess->vha->work_lock, flags);
  return;
 }
 sess->deleted = QLA_SESS_DELETION_IN_PROGRESS;
 spin_unlock_irqrestore(&sess->vha->work_lock, flags);

 sess->prli_pend_timer = 0;
 qla2x00_set_fcport_disc_state(sess, DSC_DELETE_PEND);

 qla24xx_chk_fcp_state(sess);

 ql_dbg(ql_log_warn, sess->vha, 0xe001,
     "Scheduling sess %p for deletion %8phC fc4_type %x\n",
     sess, sess->port_name, sess->fc4_type);

 WARN_ON(!queue_work(sess->vha->hw->wq, &sess->del_work));
}

static void qlt_clear_tgt_db(struct qla_tgt *tgt)
{
 struct fc_port *sess;
 scsi_qla_host_t *vha = tgt->vha;

 list_for_each_entry(sess, &vha->vp_fcports, list) {
  if (sess->se_sess)
   qlt_schedule_sess_for_deletion(sess);
 }

 /* At this point tgt could be already dead */
}

static int qla24xx_get_loop_id(struct scsi_qla_host *vha, be_id_t s_id,
 uint16_t *loop_id)
{
 struct qla_hw_data *ha = vha->hw;
 dma_addr_t gid_list_dma;
 struct gid_list_info *gid_list, *gid;
 int res, rc, i;
 uint16_t entries;

 gid_list = dma_alloc_coherent(&ha->pdev->dev, qla2x00_gid_list_size(ha),
     &gid_list_dma, GFP_KERNEL);
 if (!gid_list) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf044,
      "qla_target(%d): DMA Alloc failed of %u\n",
      vha->vp_idx, qla2x00_gid_list_size(ha));
  return -ENOMEM;
 }

 /* Get list of logged in devices */
 rc = qla24xx_gidlist_wait(vha, gid_list, gid_list_dma, &entries);
 if (rc != QLA_SUCCESS) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf045,
      "qla_target(%d): get_id_list() failed: %x\n",
      vha->vp_idx, rc);
  res = -EBUSY;
  goto out_free_id_list;
 }

 gid = gid_list;
 res = -ENOENT;
 for (i = 0; i < entries; i++) {
  if (gid->al_pa == s_id.al_pa &&
      gid->area == s_id.area &&
      gid->domain == s_id.domain) {
   *loop_id = le16_to_cpu(gid->loop_id);
   res = 0;
   break;
  }
  gid = (void *)gid + ha->gid_list_info_size;
 }

out_free_id_list:
 dma_free_coherent(&ha->pdev->dev, qla2x00_gid_list_size(ha),
     gid_list, gid_list_dma);
 return res;
}

/*
 * Adds an extra ref to allow to drop hw lock after adding sess to the list.
 * Caller must put it.
 */

static struct fc_port *qlt_create_sess(
 struct scsi_qla_host *vha,
 fc_port_t *fcport,
 bool local)
{
 struct qla_hw_data *ha = vha->hw;
 struct fc_port *sess = fcport;
 unsigned long flags;

 if (vha->vha_tgt.qla_tgt->tgt_stop)
  return NULL;

 if (fcport->se_sess) {
  if (!kref_get_unless_zero(&sess->sess_kref)) {
   ql_dbg(ql_dbg_disc, vha, 0x20f6,
       "%s: kref_get_unless_zero failed for %8phC\n",
       __func__, sess->port_name);
   return NULL;
  }
  return fcport;
 }
 sess->tgt = vha->vha_tgt.qla_tgt;
 sess->local = local;

 /*
 * Under normal circumstances we want to logout from firmware when
 * session eventually ends and release corresponding nport handle.
 * In the exception cases (e.g. when new PLOGI is waiting) corresponding
 * code will adjust these flags as necessary.
 */

 sess->logout_on_delete = 1;
 sess->keep_nport_handle = 0;
 sess->logout_completed = 0;

 if (ha->tgt.tgt_ops->check_initiator_node_acl(vha,
     &fcport->port_name[0], sess) < 0) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf015,
      "(%d) %8phC check_initiator_node_acl failed\n",
      vha->vp_idx, fcport->port_name);
  return NULL;
 } else {
  kref_init(&fcport->sess_kref);
  /*
 * Take an extra reference to ->sess_kref here to handle
 * fc_port access across ->tgt.sess_lock reaquire.
 */

  if (!kref_get_unless_zero(&sess->sess_kref)) {
   ql_dbg(ql_dbg_disc, vha, 0x20f7,
       "%s: kref_get_unless_zero failed for %8phC\n",
       __func__, sess->port_name);
   return NULL;
  }

  spin_lock_irqsave(&ha->tgt.sess_lock, flags);
  if (!IS_SW_RESV_ADDR(sess->d_id))
   vha->vha_tgt.qla_tgt->sess_count++;

  qlt_do_generation_tick(vha, &sess->generation);
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
 }

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf006,
     "Adding sess %p se_sess %p to tgt %p sess_count %d\n",
     sess, sess->se_sess, vha->vha_tgt.qla_tgt,
     vha->vha_tgt.qla_tgt->sess_count);

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04b,
     "qla_target(%d): %ssession for wwn %8phC (loop_id %d, "
     "s_id %x:%x:%x, confirmed completion %ssupported) added\n",
     vha->vp_idx, local ?  "local " : "", fcport->port_name,
     fcport->loop_id, sess->d_id.b.domain, sess->d_id.b.area,
     sess->d_id.b.al_pa, sess->conf_compl_supported ?  "" : "not ");

 return sess;
}

static inline int test_tgt_sess_count(struct qla_tgt *tgt)
{
 struct qla_hw_data *ha = tgt->ha;
 unsigned long flags;
 int res;
 /*
 * We need to protect against race, when tgt is freed before or
 * inside wake_up()
 */

 spin_lock_irqsave(&ha->tgt.sess_lock, flags);
 ql_dbg(ql_dbg_tgt, tgt->vha, 0xe002,
     "tgt %p, sess_count=%d\n",
     tgt, tgt->sess_count);
 res = (tgt->sess_count == 0);
 spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);

 return res;
}

/* Called by tcm_qla2xxx configfs code */
int qlt_stop_phase1(struct qla_tgt *tgt)
{
 struct scsi_qla_host *vha = tgt->vha;
 struct qla_hw_data *ha = tgt->ha;
 unsigned long flags;

 mutex_lock(&ha->optrom_mutex);
 mutex_lock(&qla_tgt_mutex);

 if (tgt->tgt_stop || tgt->tgt_stopped) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04e,
      "Already in tgt->tgt_stop or tgt_stopped state\n");
  mutex_unlock(&qla_tgt_mutex);
  mutex_unlock(&ha->optrom_mutex);
  return -EPERM;
 }

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xe003, "Stopping target for host %ld(%p)\n",
     vha->host_no, vha);
 /*
 * Mutex needed to sync with qla_tgt_fc_port_[added,deleted].
 * Lock is needed, because we still can get an incoming packet.
 */

 mutex_lock(&vha->vha_tgt.tgt_mutex);
 tgt->tgt_stop = 1;
 qlt_clear_tgt_db(tgt);
 mutex_unlock(&vha->vha_tgt.tgt_mutex);
 mutex_unlock(&qla_tgt_mutex);

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf009,
     "Waiting for sess works (tgt %p)", tgt);
 spin_lock_irqsave(&tgt->sess_work_lock, flags);
 do {
  spin_unlock_irqrestore(&tgt->sess_work_lock, flags);
  flush_work(&tgt->sess_work);
  spin_lock_irqsave(&tgt->sess_work_lock, flags);
 } while (!list_empty(&tgt->sess_works_list));
 spin_unlock_irqrestore(&tgt->sess_work_lock, flags);

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00a,
     "Waiting for tgt %p: sess_count=%d\n", tgt, tgt->sess_count);

 wait_event_timeout(tgt->waitQ, test_tgt_sess_count(tgt), 10*HZ);

 /* Big hammer */
 if (!ha->flags.host_shutting_down &&
     (qla_tgt_mode_enabled(vha) || qla_dual_mode_enabled(vha)))
  qlt_disable_vha(vha);

 /* Wait for sessions to clear out (just in case) */
 wait_event_timeout(tgt->waitQ, test_tgt_sess_count(tgt), 10*HZ);
 mutex_unlock(&ha->optrom_mutex);

 return 0;
}
EXPORT_SYMBOL(qlt_stop_phase1);

/* Called by tcm_qla2xxx configfs code */
void qlt_stop_phase2(struct qla_tgt *tgt)
{
 scsi_qla_host_t *vha = tgt->vha;

 if (tgt->tgt_stopped) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04f,
      "Already in tgt->tgt_stopped state\n");
  dump_stack();
  return;
 }
 if (!tgt->tgt_stop) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00b,
      "%s: phase1 stop is not completed\n", __func__);
  dump_stack();
  return;
 }

 mutex_lock(&tgt->ha->optrom_mutex);
 mutex_lock(&vha->vha_tgt.tgt_mutex);
 tgt->tgt_stop = 0;
 tgt->tgt_stopped = 1;
 mutex_unlock(&vha->vha_tgt.tgt_mutex);
 mutex_unlock(&tgt->ha->optrom_mutex);

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00c, "Stop of tgt %p finished\n",
     tgt);

 switch (vha->qlini_mode) {
 case QLA2XXX_INI_MODE_EXCLUSIVE:
  vha->flags.online = 1;
  set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags);
  break;
 default:
  break;
 }
}
EXPORT_SYMBOL(qlt_stop_phase2);

/* Called from qlt_remove_target() -> qla2x00_remove_one() */
static void qlt_release(struct qla_tgt *tgt)
{
 scsi_qla_host_t *vha = tgt->vha;
 void *node;
 u64 key = 0;
 u16 i;
 struct qla_qpair_hint *h;
 struct qla_hw_data *ha = vha->hw;

 if (!tgt->tgt_stop && !tgt->tgt_stopped)
  qlt_stop_phase1(tgt);

 if (!tgt->tgt_stopped)
  qlt_stop_phase2(tgt);

 for (i = 0; i < vha->hw->max_qpairs + 1; i++) {
  unsigned long flags;

  h = &tgt->qphints[i];
  if (h->qpair) {
   spin_lock_irqsave(h->qpair->qp_lock_ptr, flags);
   list_del(&h->hint_elem);
   spin_unlock_irqrestore(h->qpair->qp_lock_ptr, flags);
   h->qpair = NULL;
  }
 }
 kfree(tgt->qphints);
 mutex_lock(&qla_tgt_mutex);
 list_del(&vha->vha_tgt.qla_tgt->tgt_list_entry);
 mutex_unlock(&qla_tgt_mutex);

 btree_for_each_safe64(&tgt->lun_qpair_map, key, node)
  btree_remove64(&tgt->lun_qpair_map, key);

 btree_destroy64(&tgt->lun_qpair_map);

 if (vha->vp_idx)
  if (ha->tgt.tgt_ops &&
      ha->tgt.tgt_ops->remove_target &&
      vha->vha_tgt.target_lport_ptr)
   ha->tgt.tgt_ops->remove_target(vha);

 vha->vha_tgt.qla_tgt = NULL;

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00d,
     "Release of tgt %p finished\n", tgt);

 kfree(tgt);
}

/* ha->hardware_lock supposed to be held on entry */
static int qlt_sched_sess_work(struct qla_tgt *tgt, int type,
 const void *param, unsigned int param_size)
{
 struct qla_tgt_sess_work_param *prm;
 unsigned long flags;

 prm = kzalloc(sizeof(*prm), GFP_ATOMIC);
 if (!prm) {
  ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf050,
      "qla_target(%d): Unable to create session "
      "work, command will be refused", 0);
  return -ENOMEM;
 }

 ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf00e,
     "Scheduling work (type %d, prm %p)"
     " to find session for param %p (size %d, tgt %p)\n",
     type, prm, param, param_size, tgt);

 prm->type = type;
 memcpy(&prm->tm_iocb, param, param_size);

 spin_lock_irqsave(&tgt->sess_work_lock, flags);
 list_add_tail(&prm->sess_works_list_entry, &tgt->sess_works_list);
 spin_unlock_irqrestore(&tgt->sess_work_lock, flags);

 schedule_work(&tgt->sess_work);

 return 0;
}

/*
 * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
 */

static void qlt_send_notify_ack(struct qla_qpair *qpair,
 struct imm_ntfy_from_isp *ntfy,
 uint32_t add_flags, uint16_t resp_code, int resp_code_valid,
 uint16_t srr_flags, uint16_t srr_reject_code, uint8_t srr_explan)
{
 struct scsi_qla_host *vha = qpair->vha;
 struct qla_hw_data *ha = vha->hw;
 request_t *pkt;
 struct nack_to_isp *nack;

 if (!ha->flags.fw_started)
  return;

 ql_dbg(ql_dbg_tgt, vha, 0xe004, "Sending NOTIFY_ACK (ha=%p)\n", ha);

 pkt = (request_t *)__qla2x00_alloc_iocbs(qpair, NULL);
 if (!pkt) {
  ql_dbg(ql_dbg_tgt, vha, 0xe049,
      "qla_target(%d): %s failed: unable to allocate "
      "request packet\n", vha->vp_idx, __func__);
  return;
 }

 if (vha->vha_tgt.qla_tgt != NULL)
  vha->vha_tgt.qla_tgt->notify_ack_expected++;

 pkt->entry_type = NOTIFY_ACK_TYPE;
 pkt->entry_count = 1;

 nack = (struct nack_to_isp *)pkt;
 nack->ox_id = ntfy->ox_id;

 nack->u.isp24.handle = QLA_TGT_SKIP_HANDLE;
 nack->u.isp24.nport_handle = ntfy->u.isp24.nport_handle;
 if (le16_to_cpu(ntfy->u.isp24.status) == IMM_NTFY_ELS) {
  nack->u.isp24.flags = ntfy->u.isp24.flags &
   cpu_to_le16(NOTIFY24XX_FLAGS_PUREX_IOCB);
 }
 nack->u.isp24.srr_rx_id = ntfy->u.isp24.srr_rx_id;
 nack->u.isp24.status = ntfy->u.isp24.status;
 nack->u.isp24.status_subcode = ntfy->u.isp24.status_subcode;
 nack->u.isp24.fw_handle = ntfy->u.isp24.fw_handle;
 nack->u.isp24.exchange_address = ntfy->u.isp24.exchange_address;
 nack->u.isp24.srr_rel_offs = ntfy->u.isp24.srr_rel_offs;
 nack->u.isp24.srr_ui = ntfy->u.isp24.srr_ui;
 nack->u.isp24.srr_flags = cpu_to_le16(srr_flags);
 nack->u.isp24.srr_reject_code = srr_reject_code;
 nack->u.isp24.srr_reject_code_expl = srr_explan;
 nack->u.isp24.vp_index = ntfy->u.isp24.vp_index;

 /* TODO qualify this with EDIF enable */
 if (ntfy->u.isp24.status_subcode == ELS_PLOGI &&
     (le16_to_cpu(ntfy->u.isp24.flags) & NOTIFY24XX_FLAGS_FCSP)) {
  nack->u.isp24.flags |= cpu_to_le16(NOTIFY_ACK_FLAGS_FCSP);
 }

 ql_dbg(ql_dbg_tgt, vha, 0xe005,
     "qla_target(%d): Sending 24xx Notify Ack %d\n",
     vha->vp_idx, nack->u.isp24.status);

 /* Memory Barrier */
 wmb();
 qla2x00_start_iocbs(vha, qpair->req);
}

static int qlt_build_abts_resp_iocb(struct qla_tgt_mgmt_cmd *mcmd)
{
 struct scsi_qla_host *vha = mcmd->vha;
 struct qla_hw_data *ha = vha->hw;
 struct abts_resp_to_24xx *resp;
 __le32 f_ctl;
 uint32_t h;
 uint8_t *p;
 int rc;
 struct abts_recv_from_24xx *abts = &mcmd->orig_iocb.abts;
 struct qla_qpair *qpair = mcmd->qpair;

 ql_dbg(ql_dbg_tgt, vha, 0xe006,
     "Sending task mgmt ABTS response (ha=%p, status=%x)\n",
     ha, mcmd->fc_tm_rsp);

 rc = qlt_check_reserve_free_req(qpair, 1);
 if (rc) {
  ql_dbg(ql_dbg_tgt, vha, 0xe04a,
      "qla_target(%d): %s failed: unable to allocate request packet\n",
      vha->vp_idx, __func__);
  return -EAGAIN;
 }

 resp = (struct abts_resp_to_24xx *)qpair->req->ring_ptr;
 memset(resp, 0, sizeof(*resp));

 h = qlt_make_handle(qpair);
 if (unlikely(h == QLA_TGT_NULL_HANDLE)) {
  /*
 * CTIO type 7 from the firmware doesn't provide a way to
 * know the initiator's LOOP ID, hence we can't find
 * the session and, so, the command.
 */

  return -EAGAIN;
 } else {
  qpair->req->outstanding_cmds[h] = (srb_t *)mcmd;
 }

 resp->handle = make_handle(qpair->req->id, h);
 resp->entry_type = ABTS_RESP_24XX;
 resp->entry_count = 1;
 resp->nport_handle = abts->nport_handle;
 resp->vp_index = vha->vp_idx;
 resp->sof_type = abts->sof_type;
 resp->exchange_address = abts->exchange_address;
 resp->fcp_hdr_le = abts->fcp_hdr_le;
 f_ctl = cpu_to_le32(F_CTL_EXCH_CONTEXT_RESP |
     F_CTL_LAST_SEQ | F_CTL_END_SEQ |
     F_CTL_SEQ_INITIATIVE);
 p = (uint8_t *)&f_ctl;
 resp->fcp_hdr_le.f_ctl[0] = *p++;
 resp->fcp_hdr_le.f_ctl[1] = *p++;
 resp->fcp_hdr_le.f_ctl[2] = *p;

 resp->fcp_hdr_le.d_id = abts->fcp_hdr_le.s_id;
 resp->fcp_hdr_le.s_id = abts->fcp_hdr_le.d_id;

 resp->exchange_addr_to_abort = abts->exchange_addr_to_abort;
 if (mcmd->fc_tm_rsp == FCP_TMF_CMPL) {
  resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_ACC;
  resp->payload.ba_acct.seq_id_valid = SEQ_ID_INVALID;
  resp->payload.ba_acct.low_seq_cnt = 0x0000;
  resp->payload.ba_acct.high_seq_cnt = cpu_to_le16(0xFFFF);
  resp->payload.ba_acct.ox_id = abts->fcp_hdr_le.ox_id;
  resp->payload.ba_acct.rx_id = abts->fcp_hdr_le.rx_id;
 } else {
  resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_RJT;
  resp->payload.ba_rjt.reason_code =
   BA_RJT_REASON_CODE_UNABLE_TO_PERFORM;
  /* Other bytes are zero */
 }

 vha->vha_tgt.qla_tgt->abts_resp_expected++;

 /* Memory Barrier */
 wmb();
 if (qpair->reqq_start_iocbs)
  qpair->reqq_start_iocbs(qpair);
 else
  qla2x00_start_iocbs(vha, qpair->req);

 return rc;
}

/*
 * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
 */

static void qlt_24xx_send_abts_resp(struct qla_qpair *qpair,
 struct abts_recv_from_24xx *abts, uint32_t status,
 bool ids_reversed)
{
 struct scsi_qla_host *vha = qpair->vha;
 struct qla_hw_data *ha = vha->hw;
 struct abts_resp_to_24xx *resp;
 __le32 f_ctl;
 uint8_t *p;

 ql_dbg(ql_dbg_tgt, vha, 0xe006,
     "Sending task mgmt ABTS response (ha=%p, atio=%p, status=%x\n",
     ha, abts, status);

 resp = (struct abts_resp_to_24xx *)qla2x00_alloc_iocbs_ready(qpair,
     NULL);
 if (!resp) {
  ql_dbg(ql_dbg_tgt, vha, 0xe04a,
      "qla_target(%d): %s failed: unable to allocate "
      "request packet", vha->vp_idx, __func__);
  return;
 }

 resp->entry_type = ABTS_RESP_24XX;
 resp->handle = QLA_TGT_SKIP_HANDLE;
 resp->entry_count = 1;
 resp->nport_handle = abts->nport_handle;
 resp->vp_index = vha->vp_idx;
 resp->sof_type = abts->sof_type;
 resp->exchange_address = abts->exchange_address;
 resp->fcp_hdr_le = abts->fcp_hdr_le;
 f_ctl = cpu_to_le32(F_CTL_EXCH_CONTEXT_RESP |
     F_CTL_LAST_SEQ | F_CTL_END_SEQ |
     F_CTL_SEQ_INITIATIVE);
 p = (uint8_t *)&f_ctl;
 resp->fcp_hdr_le.f_ctl[0] = *p++;
 resp->fcp_hdr_le.f_ctl[1] = *p++;
 resp->fcp_hdr_le.f_ctl[2] = *p;
 if (ids_reversed) {
  resp->fcp_hdr_le.d_id = abts->fcp_hdr_le.d_id;
  resp->fcp_hdr_le.s_id = abts->fcp_hdr_le.s_id;
 } else {
  resp->fcp_hdr_le.d_id = abts->fcp_hdr_le.s_id;
  resp->fcp_hdr_le.s_id = abts->fcp_hdr_le.d_id;
 }
 resp->exchange_addr_to_abort = abts->exchange_addr_to_abort;
 if (status == FCP_TMF_CMPL) {
  resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_ACC;
  resp->payload.ba_acct.seq_id_valid = SEQ_ID_INVALID;
  resp->payload.ba_acct.low_seq_cnt = 0x0000;
  resp->payload.ba_acct.high_seq_cnt = cpu_to_le16(0xFFFF);
  resp->payload.ba_acct.ox_id = abts->fcp_hdr_le.ox_id;
  resp->payload.ba_acct.rx_id = abts->fcp_hdr_le.rx_id;
 } else {
  resp->fcp_hdr_le.r_ctl = R_CTL_BASIC_LINK_SERV | R_CTL_B_RJT;
  resp->payload.ba_rjt.reason_code =
   BA_RJT_REASON_CODE_UNABLE_TO_PERFORM;
  /* Other bytes are zero */
 }

 vha->vha_tgt.qla_tgt->abts_resp_expected++;

 /* Memory Barrier */
 wmb();
 if (qpair->reqq_start_iocbs)
  qpair->reqq_start_iocbs(qpair);
 else
  qla2x00_start_iocbs(vha, qpair->req);
}

/*
 * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
 */

static void qlt_24xx_retry_term_exchange(struct scsi_qla_host *vha,
    struct qla_qpair *qpair, response_t *pkt, struct qla_tgt_mgmt_cmd *mcmd)
{
 struct ctio7_to_24xx *ctio;
 u16 tmp;
 struct abts_recv_from_24xx *entry;

 ctio = (struct ctio7_to_24xx *)qla2x00_alloc_iocbs_ready(qpair, NULL);
 if (ctio == NULL) {
  ql_dbg(ql_dbg_tgt, vha, 0xe04b,
      "qla_target(%d): %s failed: unable to allocate "
      "request packet\n", vha->vp_idx, __func__);
  return;
 }

 if (mcmd)
  /* abts from remote port */
  entry = &mcmd->orig_iocb.abts;
 else
  /* abts from this driver.  */
  entry = (struct abts_recv_from_24xx *)pkt;

 /*
 * We've got on entrance firmware's response on by us generated
 * ABTS response. So, in it ID fields are reversed.
 */


 ctio->entry_type = CTIO_TYPE7;
 ctio->entry_count = 1;
 ctio->nport_handle = entry->nport_handle;
 ctio->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
 ctio->timeout = cpu_to_le16(QLA_TGT_TIMEOUT);
 ctio->vp_index = vha->vp_idx;
 ctio->exchange_addr = entry->exchange_addr_to_abort;
 tmp = (CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_TERMINATE);

 if (mcmd) {
  ctio->initiator_id = entry->fcp_hdr_le.s_id;

  if (mcmd->flags & QLA24XX_MGMT_ABORT_IO_ATTR_VALID)
   tmp |= (mcmd->abort_io_attr << 9);
  else if (qpair->retry_term_cnt & 1)
   tmp |= (0x4 << 9);
 } else {
  ctio->initiator_id = entry->fcp_hdr_le.d_id;

  if (qpair->retry_term_cnt & 1)
   tmp |= (0x4 << 9);
 }
 ctio->u.status1.flags = cpu_to_le16(tmp);
 ctio->u.status1.ox_id = entry->fcp_hdr_le.ox_id;

 ql_dbg(ql_dbg_tgt, vha, 0xe007,
     "Sending retry TERM EXCH CTIO7 flags %04xh oxid %04xh attr valid %x\n",
     le16_to_cpu(ctio->u.status1.flags),
     le16_to_cpu(ctio->u.status1.ox_id),
     (mcmd && mcmd->flags & QLA24XX_MGMT_ABORT_IO_ATTR_VALID) ? 1 : 0);

 /* Memory Barrier */
 wmb();
 if (qpair->reqq_start_iocbs)
  qpair->reqq_start_iocbs(qpair);
 else
  qla2x00_start_iocbs(vha, qpair->req);

 if (mcmd)
  qlt_build_abts_resp_iocb(mcmd);
 else
  qlt_24xx_send_abts_resp(qpair,
      (struct abts_recv_from_24xx *)entry, FCP_TMF_CMPL, true);

}

/* drop cmds for the given lun
 * XXX only looks for cmds on the port through which lun reset was recieved
 * XXX does not go through the list of other port (which may have cmds
 *     for the same lun)
 */

static void abort_cmds_for_lun(struct scsi_qla_host *vha, u64 lun, be_id_t s_id)
{
 struct qla_tgt_sess_op *op;
 struct qla_tgt_cmd *cmd;
 uint32_t key;
 unsigned long flags;

 key = sid_to_key(s_id);
 spin_lock_irqsave(&vha->cmd_list_lock, flags);
 list_for_each_entry(op, &vha->unknown_atio_list, cmd_list) {
  uint32_t op_key;
  u64 op_lun;

  op_key = sid_to_key(op->atio.u.isp24.fcp_hdr.s_id);
  op_lun = scsilun_to_int(
   (struct scsi_lun *)&op->atio.u.isp24.fcp_cmnd.lun);
  if (op_key == key && op_lun == lun)
   op->aborted = true;
 }

 list_for_each_entry(cmd, &vha->qla_cmd_list, cmd_list) {
  uint32_t cmd_key;
  u64 cmd_lun;

  cmd_key = sid_to_key(cmd->atio.u.isp24.fcp_hdr.s_id);
  cmd_lun = scsilun_to_int(
   (struct scsi_lun *)&cmd->atio.u.isp24.fcp_cmnd.lun);
  if (cmd_key == key && cmd_lun == lun)
   cmd->aborted = 1;
 }
 spin_unlock_irqrestore(&vha->cmd_list_lock, flags);
}

static struct qla_qpair_hint *qlt_find_qphint(struct scsi_qla_host *vha,
    uint64_t unpacked_lun)
{
 struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
 struct qla_qpair_hint *h = NULL;

 if (vha->flags.qpairs_available) {
  h = btree_lookup64(&tgt->lun_qpair_map, unpacked_lun);
  if (!h)
   h = &tgt->qphints[0];
 } else {
  h = &tgt->qphints[0];
 }

 return h;
}

static void qlt_do_tmr_work(struct work_struct *work)
{
 struct qla_tgt_mgmt_cmd *mcmd =
  container_of(work, struct qla_tgt_mgmt_cmd, work);
 struct qla_hw_data *ha = mcmd->vha->hw;
 int rc;
 uint32_t tag;
 unsigned long flags;

 switch (mcmd->tmr_func) {
 case QLA_TGT_ABTS:
  tag = le32_to_cpu(mcmd->orig_iocb.abts.exchange_addr_to_abort);
  break;
 default:
  tag = 0;
  break;
 }

 rc = ha->tgt.tgt_ops->handle_tmr(mcmd, mcmd->unpacked_lun,
     mcmd->tmr_func, tag);

 if (rc != 0) {
  spin_lock_irqsave(mcmd->qpair->qp_lock_ptr, flags);
  switch (mcmd->tmr_func) {
  case QLA_TGT_ABTS:
   mcmd->fc_tm_rsp = FCP_TMF_REJECTED;
   qlt_build_abts_resp_iocb(mcmd);
   break;
  case QLA_TGT_LUN_RESET:
  case QLA_TGT_CLEAR_TS:
  case QLA_TGT_ABORT_TS:
  case QLA_TGT_CLEAR_ACA:
  case QLA_TGT_TARGET_RESET:
   qlt_send_busy(mcmd->qpair, &mcmd->orig_iocb.atio,
       qla_sam_status);
   break;

  case QLA_TGT_ABORT_ALL:
  case QLA_TGT_NEXUS_LOSS_SESS:
  case QLA_TGT_NEXUS_LOSS:
   qlt_send_notify_ack(mcmd->qpair,
       &mcmd->orig_iocb.imm_ntfy, 0, 0, 0, 0, 0, 0);
   break;
  }
  spin_unlock_irqrestore(mcmd->qpair->qp_lock_ptr, flags);

  ql_dbg(ql_dbg_tgt_mgt, mcmd->vha, 0xf052,
      "qla_target(%d): tgt_ops->handle_tmr() failed: %d\n",
      mcmd->vha->vp_idx, rc);
  mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool);
 }
}

/* ha->hardware_lock supposed to be held on entry */
static int __qlt_24xx_handle_abts(struct scsi_qla_host *vha,
 struct abts_recv_from_24xx *abts, struct fc_port *sess)
{
 struct qla_hw_data *ha = vha->hw;
 struct qla_tgt_mgmt_cmd *mcmd;
 struct qla_qpair_hint *h = &vha->vha_tgt.qla_tgt->qphints[0];
 struct qla_tgt_cmd *abort_cmd;

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00f,
     "qla_target(%d): task abort (tag=%d)\n",
     vha->vp_idx, abts->exchange_addr_to_abort);

 mcmd = mempool_alloc(qla_tgt_mgmt_cmd_mempool, GFP_ATOMIC);
 if (mcmd == NULL) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf051,
      "qla_target(%d): %s: Allocation of ABORT cmd failed",
      vha->vp_idx, __func__);
  return -ENOMEM;
 }
 memset(mcmd, 0, sizeof(*mcmd));
 mcmd->cmd_type = TYPE_TGT_TMCMD;
 mcmd->sess = sess;
 memcpy(&mcmd->orig_iocb.abts, abts, sizeof(mcmd->orig_iocb.abts));
 mcmd->reset_count = ha->base_qpair->chip_reset;
 mcmd->tmr_func = QLA_TGT_ABTS;
 mcmd->qpair = h->qpair;
 mcmd->vha = vha;

 /*
 * LUN is looked up by target-core internally based on the passed
 * abts->exchange_addr_to_abort tag.
 */

 mcmd->se_cmd.cpuid = h->cpuid;

 abort_cmd = ha->tgt.tgt_ops->find_cmd_by_tag(sess,
    le32_to_cpu(abts->exchange_addr_to_abort));
 if (!abort_cmd) {
  mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool);
  return -EIO;
 }
 mcmd->unpacked_lun = abort_cmd->se_cmd.orig_fe_lun;

 if (abort_cmd->qpair) {
  mcmd->qpair = abort_cmd->qpair;
  mcmd->se_cmd.cpuid = abort_cmd->se_cmd.cpuid;
  mcmd->abort_io_attr = abort_cmd->atio.u.isp24.attr;
  mcmd->flags = QLA24XX_MGMT_ABORT_IO_ATTR_VALID;
 }

 INIT_WORK(&mcmd->work, qlt_do_tmr_work);
 queue_work_on(mcmd->se_cmd.cpuid, qla_tgt_wq, &mcmd->work);

 return 0;
}

/*
 * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
 */

static void qlt_24xx_handle_abts(struct scsi_qla_host *vha,
 struct abts_recv_from_24xx *abts)
{
 struct qla_hw_data *ha = vha->hw;
 struct fc_port *sess;
 uint32_t tag = le32_to_cpu(abts->exchange_addr_to_abort);
 be_id_t s_id;
 int rc;
 unsigned long flags;

 if (le32_to_cpu(abts->fcp_hdr_le.parameter) & ABTS_PARAM_ABORT_SEQ) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf053,
      "qla_target(%d): ABTS: Abort Sequence not "
      "supported\n", vha->vp_idx);
  qlt_24xx_send_abts_resp(ha->base_qpair, abts, FCP_TMF_REJECTED,
      false);
  return;
 }

 if (tag == ATIO_EXCHANGE_ADDRESS_UNKNOWN) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf010,
      "qla_target(%d): ABTS: Unknown Exchange "
      "Address received\n", vha->vp_idx);
  qlt_24xx_send_abts_resp(ha->base_qpair, abts, FCP_TMF_REJECTED,
      false);
  return;
 }

 ql_dbg(ql_dbg_tgt_mgt, vha, 0xf011,
     "qla_target(%d): task abort (s_id=%x:%x:%x, "
     "tag=%d, param=%x)\n", vha->vp_idx, abts->fcp_hdr_le.s_id.domain,
     abts->fcp_hdr_le.s_id.area, abts->fcp_hdr_le.s_id.al_pa, tag,
     le32_to_cpu(abts->fcp_hdr_le.parameter));

 s_id = le_id_to_be(abts->fcp_hdr_le.s_id);

 spin_lock_irqsave(&ha->tgt.sess_lock, flags);
 sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, s_id);
 if (!sess) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf012,
      "qla_target(%d): task abort for non-existent session\n",
      vha->vp_idx);
  spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);

  qlt_24xx_send_abts_resp(ha->base_qpair, abts, FCP_TMF_REJECTED,
       false);
  return;
 }
 spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);


 if (sess->deleted) {
  qlt_24xx_send_abts_resp(ha->base_qpair, abts, FCP_TMF_REJECTED,
      false);
  return;
 }

 rc = __qlt_24xx_handle_abts(vha, abts, sess);
 if (rc != 0) {
  ql_dbg(ql_dbg_tgt_mgt, vha, 0xf054,
      "qla_target(%d): __qlt_24xx_handle_abts() failed: %d\n",
      vha->vp_idx, rc);
  qlt_24xx_send_abts_resp(ha->base_qpair, abts, FCP_TMF_REJECTED,
      false);
  return;
 }
}

/*
 * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
 */

static void qlt_24xx_send_task_mgmt_ctio(struct qla_qpair *qpair,
 struct qla_tgt_mgmt_cmd *mcmd, uint32_t resp_code)
{
 struct scsi_qla_host *ha = mcmd->vha;
 struct atio_from_isp *atio = &mcmd->orig_iocb.atio;
 struct ctio7_to_24xx *ctio;
 uint16_t temp;

 ql_dbg(ql_dbg_tgt, ha, 0xe008,
     "Sending task mgmt CTIO7 (ha=%p, atio=%p, resp_code=%x\n",
     ha, atio, resp_code);


 ctio = (struct ctio7_to_24xx *)__qla2x00_alloc_iocbs(qpair, NULL);
 if (ctio == NULL) {
  ql_dbg(ql_dbg_tgt, ha, 0xe04c,
      "qla_target(%d): %s failed: unable to allocate "
      "request packet\n", ha->vp_idx, __func__);
  return;
 }

 ctio->entry_type = CTIO_TYPE7;
 ctio->entry_count = 1;
 ctio->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK;
 ctio->nport_handle = cpu_to_le16(mcmd->sess->loop_id);
 ctio->timeout = cpu_to_le16(QLA_TGT_TIMEOUT);
 ctio->vp_index = ha->vp_idx;
 ctio->initiator_id = be_id_to_le(atio->u.isp24.fcp_hdr.s_id);
 ctio->exchange_addr = atio->u.isp24.exchange_addr;
 temp = (atio->u.isp24.attr << 9)|
  CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_SEND_STATUS;
 ctio->u.status1.flags = cpu_to_le16(temp);
 temp = be16_to_cpu(atio->u.isp24.fcp_hdr.ox_id);
 ctio->u.status1.ox_id = cpu_to_le16(temp);
 ctio->u.status1.scsi_status =
     cpu_to_le16(SS_RESPONSE_INFO_LEN_VALID);
 ctio->u.status1.response_len = cpu_to_le16(8);
 ctio->u.status1.sense_data[0] = resp_code;

 /* Memory Barrier */
 wmb();
 if (qpair->reqq_start_iocbs)
  qpair->reqq_start_iocbs(qpair);
 else
  qla2x00_start_iocbs(ha, qpair->req);
}

void qlt_free_mcmd(struct qla_tgt_mgmt_cmd *mcmd)
{
 mempool_free(mcmd, qla_tgt_mgmt_cmd_mempool);
}
EXPORT_SYMBOL(qlt_free_mcmd);

/*
 * ha->hardware_lock supposed to be held on entry. Might drop it, then
 * reacquire
 */

void qlt_send_resp_ctio(struct qla_qpair *qpair, struct qla_tgt_cmd *cmd,
    uint8_t scsi_status, uint8_t sense_key, uint8_t asc, uint8_t ascq)
{
 struct atio_from_isp *atio = &cmd->atio;
 struct ctio7_to_24xx *ctio;
 uint16_t temp;
 struct scsi_qla_host *vha = cmd->vha;

 ql_dbg(ql_dbg_tgt_dif, vha, 0x3066,
     "Sending response CTIO7 (vha=%p, atio=%p, scsi_status=%02x, "
     "sense_key=%02x, asc=%02x, ascq=%02x",
     vha, atio, scsi_status, sense_key, asc, ascq);

 ctio = (struct ctio7_to_24xx *)qla2x00_alloc_iocbs(vha, NULL);
 if (!ctio) {
  ql_dbg(ql_dbg_async, vha, 0x3067,
      "qla2x00t(%ld): %s failed: unable to allocate request packet",
      vha->host_no, __func__);
  goto out;
 }

 ctio->entry_type = CTIO_TYPE7;
 ctio->entry_count = 1;
 ctio->handle = QLA_TGT_SKIP_HANDLE;
 ctio->nport_handle = cpu_to_le16(cmd->sess->loop_id);
 ctio->timeout = cpu_to_le16(QLA_TGT_TIMEOUT);
 ctio->vp_index = vha->vp_idx;
 ctio->initiator_id = be_id_to_le(atio->u.isp24.fcp_hdr.s_id);
 ctio->exchange_addr = atio->u.isp24.exchange_addr;
 temp = (atio->u.isp24.attr << 9) |
     CTIO7_FLAGS_STATUS_MODE_1 | CTIO7_FLAGS_SEND_STATUS;
 ctio->u.status1.flags = cpu_to_le16(temp);
 temp = be16_to_cpu(atio->u.isp24.fcp_hdr.ox_id);
 ctio->u.status1.ox_id = cpu_to_le16(temp);
 ctio->u.status1.scsi_status =
     cpu_to_le16(SS_RESPONSE_INFO_LEN_VALID | scsi_status);
 ctio->u.status1.response_len = cpu_to_le16(18);
 ctio->u.status1.residual = cpu_to_le32(get_datalen_for_atio(atio));

 if (ctio->u.status1.residual != 0)
  ctio->u.status1.scsi_status |=
      cpu_to_le16(SS_RESIDUAL_UNDER);

 /* Fixed format sense data. */
 ctio->u.status1.sense_data[0] = 0x70;
 ctio->u.status1.sense_data[2] = sense_key;
--> --------------------

--> maximum size reached

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

Messung V0.5
C=97 H=97 G=96

¤ Dauer der Verarbeitung: 0.23 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.