--- /dev/null
+From: Hannes Reinecke <hare@suse.de>
+Date: Wed, 17 Sep 2008 16:24:37 +0200
+Subject: libfc: a modular software Fibre Channel implementation
+References: FATE#303913
+
+Signed-off-by: Robert Love <robert.w.love@intel.com>
+Signed-off-by: Chris Leech <christopher.leech@intel.com>
+Signed-off-by: Vasu Dev <vasu.dev@intel.com>
+Signed-off-by: Yi Zou <yi.zou@intel.com>
+Signed-off-by: Steve Ma <steve.ma@intel.com>
+Signed-off-by: Hannes Reinecke <hare@suse.de>
+---
+ drivers/scsi/Kconfig | 6 +
+ drivers/scsi/Makefile | 1 +
+ drivers/scsi/libfc/Makefile | 12 +
+ drivers/scsi/libfc/fc_attr.c | 129 +++
+ drivers/scsi/libfc/fc_exch.c | 2028 ++++++++++++++++++++++++++++++++++++++
+ drivers/scsi/libfc/fc_fcp.c | 2173 +++++++++++++++++++++++++++++++++++++++++
+ drivers/scsi/libfc/fc_frame.c | 88 ++
+ drivers/scsi/libfc/fc_lport.c | 926 ++++++++++++++++++
+ drivers/scsi/libfc/fc_ns.c | 1283 ++++++++++++++++++++++++
+ drivers/scsi/libfc/fc_rport.c | 1301 ++++++++++++++++++++++++
+ 10 files changed, 7947 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/scsi/libfc/Makefile
+ create mode 100644 drivers/scsi/libfc/fc_attr.c
+ create mode 100644 drivers/scsi/libfc/fc_exch.c
+ create mode 100644 drivers/scsi/libfc/fc_fcp.c
+ create mode 100644 drivers/scsi/libfc/fc_frame.c
+ create mode 100644 drivers/scsi/libfc/fc_lport.c
+ create mode 100644 drivers/scsi/libfc/fc_ns.c
+ create mode 100644 drivers/scsi/libfc/fc_rport.c
+
+diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
+index 4e0322b..bd480d2 100644
+--- a/drivers/scsi/Kconfig
++++ b/drivers/scsi/Kconfig
+@@ -328,6 +328,12 @@ menuconfig SCSI_LOWLEVEL
+
+ if SCSI_LOWLEVEL && SCSI
+
++config LIBFC
++ tristate "LibFC module"
++ depends on SCSI && SCSI_FC_ATTRS
++ ---help---
++ Fibre Channel library module
++
+ config ISCSI_TCP
+ tristate "iSCSI Initiator over TCP/IP"
+ depends on SCSI && INET
+diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
+index 72fd504..9158dc6 100644
+--- a/drivers/scsi/Makefile
++++ b/drivers/scsi/Makefile
+@@ -36,6 +36,7 @@ obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas/
+ obj-$(CONFIG_SCSI_SRP_ATTRS) += scsi_transport_srp.o
+ obj-$(CONFIG_SCSI_DH) += device_handler/
+
++obj-$(CONFIG_LIBFC) += libfc/
+ obj-$(CONFIG_ISCSI_TCP) += libiscsi.o iscsi_tcp.o
+ obj-$(CONFIG_INFINIBAND_ISER) += libiscsi.o
+ obj-$(CONFIG_SCSI_A4000T) += 53c700.o a4000t.o
+diff --git a/drivers/scsi/libfc/Makefile b/drivers/scsi/libfc/Makefile
+new file mode 100644
+index 0000000..0a31ca2
+--- /dev/null
++++ b/drivers/scsi/libfc/Makefile
+@@ -0,0 +1,12 @@
++# $Id: Makefile
++
++obj-$(CONFIG_LIBFC) += libfc.o
++
++libfc-objs := \
++ fc_ns.o \
++ fc_exch.o \
++ fc_frame.o \
++ fc_lport.o \
++ fc_rport.o \
++ fc_attr.o \
++ fc_fcp.o
+diff --git a/drivers/scsi/libfc/fc_attr.c b/drivers/scsi/libfc/fc_attr.c
+new file mode 100644
+index 0000000..d73f39e
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_attr.c
+@@ -0,0 +1,129 @@
++/*
++ * Copyright(c) 2007 Intel Corporation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++#include <linux/kernel.h>
++#include <linux/types.h>
++
++#include <scsi/scsi_host.h>
++
++#include <scsi/libfc/libfc.h>
++
++MODULE_AUTHOR("Open-FCoE.org");
++MODULE_DESCRIPTION("libfc");
++MODULE_LICENSE("GPL");
++
++void fc_get_host_port_id(struct Scsi_Host *shost)
++{
++ struct fc_lport *lp = shost_priv(shost);
++
++ fc_host_port_id(shost) = fc_lport_get_fid(lp);
++}
++EXPORT_SYMBOL(fc_get_host_port_id);
++
++void fc_get_host_speed(struct Scsi_Host *shost)
++{
++ /*
++ * should be obtain from DEC or Enet Driver
++ */
++ fc_host_speed(shost) = 1; /* for now it is 1g */
++}
++EXPORT_SYMBOL(fc_get_host_speed);
++
++void fc_get_host_port_type(struct Scsi_Host *shost)
++{
++ fc_host_port_type(shost) = FC_PORTTYPE_NPORT;
++}
++EXPORT_SYMBOL(fc_get_host_port_type);
++
++void fc_get_host_fabric_name(struct Scsi_Host *shost)
++{
++ struct fc_lport *lp = shost_priv(shost);
++
++ fc_host_fabric_name(shost) = lp->wwnn;
++}
++EXPORT_SYMBOL(fc_get_host_fabric_name);
++
++void fc_attr_init(struct fc_lport *lp)
++{
++ fc_host_node_name(lp->host) = lp->wwnn;
++ fc_host_port_name(lp->host) = lp->wwpn;
++ fc_host_supported_classes(lp->host) = FC_COS_CLASS3;
++ memset(fc_host_supported_fc4s(lp->host), 0,
++ sizeof(fc_host_supported_fc4s(lp->host)));
++ fc_host_supported_fc4s(lp->host)[2] = 1;
++ fc_host_supported_fc4s(lp->host)[7] = 1;
++ /* This value is also unchanging */
++ memset(fc_host_active_fc4s(lp->host), 0,
++ sizeof(fc_host_active_fc4s(lp->host)));
++ fc_host_active_fc4s(lp->host)[2] = 1;
++ fc_host_active_fc4s(lp->host)[7] = 1;
++ fc_host_maxframe_size(lp->host) = lp->mfs;
++}
++EXPORT_SYMBOL(fc_attr_init);
++
++void fc_set_rport_loss_tmo(struct fc_rport *rport, u32 timeout)
++{
++ if (timeout)
++ rport->dev_loss_tmo = timeout + 5;
++ else
++ rport->dev_loss_tmo = 30;
++
++}
++EXPORT_SYMBOL(fc_set_rport_loss_tmo);
++
++struct fc_host_statistics *fc_get_host_stats(struct Scsi_Host *shost)
++{
++ int i;
++ struct fc_host_statistics *fcoe_stats;
++ struct fc_lport *lp = shost_priv(shost);
++ struct timespec v0, v1;
++
++ fcoe_stats = &lp->host_stats;
++ memset(fcoe_stats, 0, sizeof(struct fc_host_statistics));
++
++ jiffies_to_timespec(jiffies, &v0);
++ jiffies_to_timespec(lp->boot_time, &v1);
++ fcoe_stats->seconds_since_last_reset = (v0.tv_sec - v1.tv_sec);
++
++ for_each_online_cpu(i) {
++ struct fcoe_dev_stats *stats = lp->dev_stats[i];
++ if (stats == NULL)
++ continue;
++ fcoe_stats->tx_frames += stats->TxFrames;
++ fcoe_stats->tx_words += stats->TxWords;
++ fcoe_stats->rx_frames += stats->RxFrames;
++ fcoe_stats->rx_words += stats->RxWords;
++ fcoe_stats->error_frames += stats->ErrorFrames;
++ fcoe_stats->invalid_crc_count += stats->InvalidCRCCount;
++ fcoe_stats->fcp_input_requests += stats->InputRequests;
++ fcoe_stats->fcp_output_requests += stats->OutputRequests;
++ fcoe_stats->fcp_control_requests += stats->ControlRequests;
++ fcoe_stats->fcp_input_megabytes += stats->InputMegabytes;
++ fcoe_stats->fcp_output_megabytes += stats->OutputMegabytes;
++ fcoe_stats->link_failure_count += stats->LinkFailureCount;
++ }
++ fcoe_stats->lip_count = -1;
++ fcoe_stats->nos_count = -1;
++ fcoe_stats->loss_of_sync_count = -1;
++ fcoe_stats->loss_of_signal_count = -1;
++ fcoe_stats->prim_seq_protocol_err_count = -1;
++ fcoe_stats->dumped_frames = -1;
++ return fcoe_stats;
++}
++EXPORT_SYMBOL(fc_get_host_stats);
+diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c
+new file mode 100644
+index 0000000..11a03bd
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_exch.c
+@@ -0,0 +1,2028 @@
++/*
++ * Copyright(c) 2007 Intel Corporation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++/*
++ * Fibre Channel exchange and sequence handling.
++ */
++
++#include <linux/timer.h>
++#include <linux/gfp.h>
++#include <linux/err.h>
++
++#include <scsi/fc/fc_fc2.h>
++
++#include <scsi/libfc/libfc.h>
++
++#define FC_DEF_R_A_TOV (10 * 1000) /* resource allocation timeout */
++
++/*
++ * fc_exch_debug can be set in debugger or at compile time to get more logs.
++ */
++static int fc_exch_debug;
++static struct kmem_cache *fc_em_cachep; /* cache for exchanges */
++
++/*
++ * Structure and function definitions for managing Fibre Channel Exchanges
++ * and Sequences.
++ *
++ * The three primary structures used here are fc_exch_mgr, fc_exch, and fc_seq.
++ *
++ * fc_exch_mgr holds the exchange state for an N port
++ *
++ * fc_exch holds state for one exchange and links to its active sequence.
++ *
++ * fc_seq holds the state for an individual sequence.
++ */
++
++/*
++ * Sequence.
++ */
++struct fc_seq {
++ u8 id; /* seq ID */
++ u16 ssb_stat; /* status flags for sequence status block */
++ u16 cnt; /* frames sent so far on sequence */
++ u32 f_ctl; /* F_CTL flags for frames */
++ u32 rec_data; /* FC-4 value for REC */
++};
++
++struct fc_exch;
++
++#define FC_EX_DONE (1 << 0) /* ep is completed */
++#define FC_EX_RST_CLEANUP (1 << 1) /* reset is forcing completion */
++
++/*
++ * Exchange.
++ *
++ * Locking notes: The ex_lock protects changes to the following fields:
++ * esb_stat, f_ctl, seq.ssb_stat, seq.f_ctl.
++ * seq_id
++ * sequence allocation
++ */
++struct fc_exch {
++ struct fc_exch_mgr *em; /* exchange manager */
++ u32 state; /* internal driver state */
++ u16 xid; /* our exchange ID */
++ struct list_head ex_list; /* free or busy list linkage */
++ spinlock_t ex_lock; /* lock covering exchange state */
++ atomic_t ex_refcnt; /* reference counter */
++ struct timer_list ex_timer; /* timer for upper level protocols */
++ struct fc_lport *lp; /* fc device instance */
++ u16 oxid; /* originator's exchange ID */
++ u16 rxid; /* responder's exchange ID */
++ u32 oid; /* originator's FCID */
++ u32 sid; /* source FCID */
++ u32 did; /* destination FCID */
++ u32 esb_stat; /* exchange status for ESB */
++ u32 r_a_tov; /* r_a_tov from rport (msec) */
++ u8 seq_id; /* next sequence ID to use */
++ u32 f_ctl; /* F_CTL flags for sequences */
++ u8 fh_type; /* frame type */
++ enum fc_class class; /* class of service */
++ struct fc_seq seq; /* single sequence */
++ struct fc_exch *aborted_ep; /* ref to ep rrq is cleaning up */
++
++ /*
++ * Handler for responses to this current exchange.
++ */
++ void (*resp)(struct fc_seq *, struct fc_frame *, void *);
++ void *resp_arg; /* 3rd arg for exchange resp handler */
++};
++
++/*
++ * Exchange manager.
++ *
++ * This structure is the center for creating exchanges and sequences.
++ * It manages the allocation of exchange IDs.
++ */
++struct fc_exch_mgr {
++ enum fc_class class; /* default class for sequences */
++ spinlock_t em_lock; /* exchange manager lock */
++ u16 last_xid; /* last allocated exchange ID */
++ u16 min_xid; /* min exchange ID */
++ u16 max_xid; /* max exchange ID */
++ u32 total_exches; /* total allocated exchanges */
++ struct list_head ex_list; /* allocated exchanges list */
++ struct fc_lport *lp; /* fc device instance */
++ mempool_t *ep_pool; /* reserve ep's */
++
++ /*
++ * currently exchange mgr stats are updated but not used.
++ * either stats can be expose via sysfs or remove them
++ * all together if not used XXX
++ */
++ struct {
++ atomic_t no_free_exch;
++ atomic_t no_free_exch_xid;
++ atomic_t xid_not_found;
++ atomic_t xid_busy;
++ atomic_t seq_not_found;
++ atomic_t non_bls_resp;
++ } stats;
++ struct fc_exch **exches; /* for exch pointers indexed by xid */
++};
++
++#define fc_seq_exch(sp) container_of(sp, struct fc_exch, seq)
++#define fc_exch_next_xid(mp, id) ((id == mp->max_xid) ? mp->min_xid : id + 1)
++
++static void fc_exch_rrq(struct fc_exch *);
++static void fc_seq_ls_acc(struct fc_seq *);
++static void fc_seq_ls_rjt(struct fc_seq *, enum fc_els_rjt_reason,
++ enum fc_els_rjt_explan);
++static void fc_exch_els_rec(struct fc_seq *, struct fc_frame *);
++static void fc_exch_els_rrq(struct fc_seq *, struct fc_frame *);
++static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp);
++
++/*
++ * Internal implementation notes.
++ *
++ * The exchange manager is one by default in libfc but LLD may choose
++ * to have one per CPU. The sequence manager is one per exchange manager
++ * and currently never separated.
++ *
++ * Section 9.8 in FC-FS-2 specifies: "The SEQ_ID is a one-byte field
++ * assigned by the Sequence Initiator that shall be unique for a specific
++ * D_ID and S_ID pair while the Sequence is open." Note that it isn't
++ * qualified by exchange ID, which one might think it would be.
++ * In practice this limits the number of open sequences and exchanges to 256
++ * per session. For most targets we could treat this limit as per exchange.
++ *
++ * The exchange and its sequence are freed when the last sequence is received.
++ * It's possible for the remote port to leave an exchange open without
++ * sending any sequences.
++ *
++ * Notes on reference counts:
++ *
++ * Exchanges are reference counted and exchange gets freed when the reference
++ * count becomes zero.
++ *
++ * Timeouts:
++ * Sequences are timed out for E_D_TOV and R_A_TOV.
++ *
++ * Sequence event handling:
++ *
++ * The following events may occur on initiator sequences:
++ *
++ * Send.
++ * For now, the whole thing is sent.
++ * Receive ACK
++ * This applies only to class F.
++ * The sequence is marked complete.
++ * ULP completion.
++ * The upper layer calls fc_exch_done() when done
++ * with exchange and sequence tuple.
++ * RX-inferred completion.
++ * When we receive the next sequence on the same exchange, we can
++ * retire the previous sequence ID. (XXX not implemented).
++ * Timeout.
++ * R_A_TOV frees the sequence ID. If we're waiting for ACK,
++ * E_D_TOV causes abort and calls upper layer response handler
++ * with FC_EX_TIMEOUT error.
++ * Receive RJT
++ * XXX defer.
++ * Send ABTS
++ * On timeout.
++ *
++ * The following events may occur on recipient sequences:
++ *
++ * Receive
++ * Allocate sequence for first frame received.
++ * Hold during receive handler.
++ * Release when final frame received.
++ * Keep status of last N of these for the ELS RES command. XXX TBD.
++ * Receive ABTS
++ * Deallocate sequence
++ * Send RJT
++ * Deallocate
++ *
++ * For now, we neglect conditions where only part of a sequence was
++ * received or transmitted, or where out-of-order receipt is detected.
++ */
++
++/*
++ * Locking notes:
++ *
++ * The EM code run in a per-CPU worker thread.
++ *
++ * To protect against concurrency between a worker thread code and timers,
++ * sequence allocation and deallocation must be locked.
++ * - exchange refcnt can be done atomicly without locks.
++ * - sequence allocation must be locked by exch lock.
++ */
++
++/*
++ * opcode names for debugging.
++ */
++static char *fc_exch_rctl_names[] = FC_RCTL_NAMES_INIT;
++
++#define FC_TABLE_SIZE(x) (sizeof(x) / sizeof(x[0]))
++
++static inline const char *fc_exch_name_lookup(unsigned int op, char **table,
++ unsigned int max_index)
++{
++ const char *name = NULL;
++
++ if (op < max_index)
++ name = table[op];
++ if (!name)
++ name = "unknown";
++ return name;
++}
++
++static const char *fc_exch_rctl_name(unsigned int op)
++{
++ return fc_exch_name_lookup(op, fc_exch_rctl_names,
++ FC_TABLE_SIZE(fc_exch_rctl_names));
++}
++
++/*
++ * Hold an exchange - keep it from being freed.
++ */
++static void fc_exch_hold(struct fc_exch *ep)
++{
++ atomic_inc(&ep->ex_refcnt);
++}
++
++/*
++ * Fill in frame header.
++ *
++ * The following fields are the responsibility of this routine:
++ * d_id, s_id, df_ctl, oxid, rxid, cs_ctl, seq_id
++ *
++ * The following fields are handled by the caller.
++ * r_ctl, type, f_ctl, seq_cnt, parm_offset
++ *
++ * That should be a complete list.
++ *
++ * We may be the originator or responder to the sequence.
++ */
++static void fc_seq_fill_hdr(struct fc_seq *sp, struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ struct fc_exch *ep;
++
++ ep = fc_seq_exch(sp);
++
++ hton24(fh->fh_s_id, ep->sid);
++ hton24(fh->fh_d_id, ep->did);
++ fh->fh_ox_id = htons(ep->oxid);
++ fh->fh_rx_id = htons(ep->rxid);
++ fh->fh_seq_id = sp->id;
++ fh->fh_cs_ctl = 0;
++ fh->fh_df_ctl = 0;
++}
++
++/*
++ * Release a reference to an exchange.
++ * If the refcnt goes to zero and the exchange is complete, it is freed.
++ */
++static void fc_exch_release(struct fc_exch *ep)
++{
++ struct fc_exch_mgr *mp;
++
++ if (atomic_dec_and_test(&ep->ex_refcnt)) {
++ mp = ep->em;
++ if (ep->lp->tt.exch_put)
++ ep->lp->tt.exch_put(ep->lp, mp, ep->xid);
++ WARN_ON(!ep->esb_stat & ESB_ST_COMPLETE);
++ WARN_ON(timer_pending(&ep->ex_timer));
++ mempool_free(ep, mp->ep_pool);
++ }
++}
++
++static int fc_exch_done_locked(struct fc_exch *ep)
++{
++ int rc = 1;
++
++ /*
++ * We must check for completion in case there are two threads
++ * tyring to complete this. But the rrq code will reuse the
++ * ep, and in that case we only clear the resp and set it as
++ * complete, so it can be reused by the timer to send the rrq.
++ */
++ ep->resp = NULL;
++ if (ep->state & FC_EX_DONE)
++ return rc;
++ ep->esb_stat |= ESB_ST_COMPLETE;
++
++ if (!(ep->esb_stat & ESB_ST_REC_QUAL)) {
++ ep->state |= FC_EX_DONE;
++ if (del_timer(&ep->ex_timer))
++ atomic_dec(&ep->ex_refcnt); /* drop hold for timer */
++ atomic_dec(&ep->ex_refcnt); /* drop hold from alloc */
++ rc = 0;
++ }
++ return rc;
++}
++
++static void fc_exch_mgr_delete_ep(struct fc_exch *ep)
++{
++ struct fc_exch_mgr *mp;
++
++ mp = ep->em;
++ spin_lock_bh(&mp->em_lock);
++ WARN_ON(mp->total_exches <= 0);
++ mp->total_exches--;
++ mp->exches[ep->xid - mp->min_xid] = NULL;
++ list_del(&ep->ex_list);
++ spin_unlock_bh(&mp->em_lock);
++}
++
++/*
++ * Internal version of fc_exch_timer_set - used with lock held.
++ */
++static inline void fc_exch_timer_set_locked(struct fc_exch *ep,
++ unsigned int timer_msec)
++{
++ if (ep->state & (FC_EX_RST_CLEANUP | FC_EX_DONE))
++ return;
++
++ if (!mod_timer(&ep->ex_timer, jiffies + msecs_to_jiffies(timer_msec)))
++ fc_exch_hold(ep); /* hold for timer */
++}
++
++/*
++ * Set timer for an exchange.
++ * The time is a minimum delay in milliseconds until the timer fires.
++ * Used for upper level protocols to time out the exchange.
++ * The timer is cancelled when it fires or when the exchange completes.
++ * Returns non-zero if a timer couldn't be allocated.
++ */
++static void fc_exch_timer_set(struct fc_exch *ep, unsigned int timer_msec)
++{
++ spin_lock_bh(&ep->ex_lock);
++ fc_exch_timer_set_locked(ep, timer_msec);
++ spin_unlock_bh(&ep->ex_lock);
++}
++
++int fc_seq_exch_abort(const struct fc_seq *req_sp, unsigned int timer_msec)
++{
++ struct fc_seq *sp;
++ struct fc_exch *ep;
++ struct fc_frame *fp;
++ int error;
++
++ ep = fc_seq_exch(req_sp);
++
++ spin_lock_bh(&ep->ex_lock);
++ if (ep->esb_stat & (ESB_ST_COMPLETE | ESB_ST_ABNORMAL) ||
++ ep->state & (FC_EX_DONE | FC_EX_RST_CLEANUP)) {
++ spin_unlock_bh(&ep->ex_lock);
++ return -ENXIO;
++ }
++
++ /*
++ * Send the abort on a new sequence if possible.
++ */
++ sp = fc_seq_start_next_locked(&ep->seq);
++ if (!sp) {
++ spin_unlock_bh(&ep->ex_lock);
++ return -ENOMEM;
++ }
++
++ sp->f_ctl |= FC_FC_SEQ_INIT;
++ ep->esb_stat |= ESB_ST_SEQ_INIT | ESB_ST_ABNORMAL;
++ if (timer_msec)
++ fc_exch_timer_set_locked(ep, timer_msec);
++ spin_unlock_bh(&ep->ex_lock);
++
++ /*
++ * If not logged into the fabric, don't send ABTS but leave
++ * sequence active until next timeout.
++ */
++ if (!ep->sid)
++ return 0;
++
++ /*
++ * Send an abort for the sequence that timed out.
++ */
++ fp = fc_frame_alloc(ep->lp, 0);
++ if (fp) {
++ fc_frame_setup(fp, FC_RCTL_BA_ABTS, FC_TYPE_BLS);
++ error = fc_seq_send(ep->lp, sp, fp, FC_FC_END_SEQ);
++ } else
++ error = -ENOBUFS;
++ return error;
++}
++EXPORT_SYMBOL(fc_seq_exch_abort);
++
++/*
++ * Exchange timeout - handle exchange timer expiration.
++ * The timer will have been cancelled before this is called.
++ */
++static void fc_exch_timeout(unsigned long ep_arg)
++{
++ struct fc_exch *ep = (struct fc_exch *)ep_arg;
++ struct fc_seq *sp = &ep->seq;
++ void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
++ void *arg;
++ u32 e_stat;
++ int rc = 1;
++
++ spin_lock_bh(&ep->ex_lock);
++ if (ep->state & (FC_EX_RST_CLEANUP | FC_EX_DONE))
++ goto unlock;
++
++ e_stat = ep->esb_stat;
++ if (e_stat & ESB_ST_COMPLETE) {
++ ep->esb_stat = e_stat & ~ESB_ST_REC_QUAL;
++ spin_unlock_bh(&ep->ex_lock);
++ if (e_stat & ESB_ST_REC_QUAL)
++ fc_exch_rrq(ep);
++ goto done;
++ } else {
++ resp = ep->resp;
++ arg = ep->resp_arg;
++ ep->resp = NULL;
++ if (e_stat & ESB_ST_ABNORMAL)
++ rc = fc_exch_done_locked(ep);
++ spin_unlock_bh(&ep->ex_lock);
++ if (!rc)
++ fc_exch_mgr_delete_ep(ep);
++ if (resp)
++ resp(sp, ERR_PTR(-FC_EX_TIMEOUT), arg);
++ fc_seq_exch_abort(sp, 2 * ep->r_a_tov);
++ goto done;
++ }
++unlock:
++ spin_unlock_bh(&ep->ex_lock);
++done:
++ /*
++ * This release matches the hold taken when the timer was set.
++ */
++ fc_exch_release(ep);
++}
++
++/*
++ * Allocate a sequence.
++ *
++ * We don't support multiple originated sequences on the same exchange.
++ * By implication, any previously originated sequence on this exchange
++ * is complete, and we reallocate the same sequence.
++ */
++static struct fc_seq *fc_seq_alloc(struct fc_exch *ep, u8 seq_id)
++{
++ struct fc_seq *sp;
++
++ sp = &ep->seq;
++ sp->ssb_stat = 0;
++ sp->f_ctl = 0;
++ sp->cnt = 0;
++ sp->id = seq_id;
++ return sp;
++}
++
++/*
++ * Allocate an exchange.
++ *
++ * if xid is supplied zero then assign next free exchange ID
++ * from exchange manager, otherwise use supplied xid.
++ */
++struct fc_exch *fc_exch_alloc(struct fc_exch_mgr *mp, u16 xid)
++{
++ struct fc_exch *ep = NULL;
++ u16 min_xid, max_xid;
++
++ min_xid = mp->min_xid;
++ max_xid = mp->max_xid;
++ /*
++ * if xid is supplied then verify its xid range
++ */
++ if (xid) {
++ if (unlikely((xid < min_xid) || (xid > max_xid))) {
++ FC_DBG("Invalid xid 0x:%x\n", xid);
++ goto out;
++ }
++ if (unlikely(mp->exches[xid - min_xid] != NULL)) {
++ FC_DBG("xid 0x:%x is already in use\n", xid);
++ goto out;
++ }
++ }
++
++ /*
++ * Allocate new exchange
++ */
++ ep = mempool_alloc(mp->ep_pool, GFP_ATOMIC);
++ if (!ep) {
++ atomic_inc(&mp->stats.no_free_exch);
++ goto out;
++ }
++ memset(ep, 0, sizeof(*ep));
++
++ spin_lock_bh(&mp->em_lock);
++
++ /*
++ * if xid is zero then assign next free exchange ID
++ */
++ if (!xid) {
++ xid = fc_exch_next_xid(mp, mp->last_xid);
++ /*
++ * find next free xid using linear search
++ */
++ while (mp->exches[xid - min_xid] != NULL) {
++ if (xid == mp->last_xid)
++ break;
++ xid = fc_exch_next_xid(mp, xid);
++ }
++
++ if (likely(mp->exches[xid - min_xid] == NULL)) {
++ mp->last_xid = xid;
++ } else {
++ spin_unlock_bh(&mp->em_lock);
++ atomic_inc(&mp->stats.no_free_exch_xid);
++ mempool_free(ep, mp->ep_pool);
++ goto out;
++ }
++ }
++
++ mp->exches[xid - min_xid] = ep;
++ list_add_tail(&ep->ex_list, &mp->ex_list);
++ fc_seq_alloc(ep, ep->seq_id++);
++ mp->total_exches++;
++ spin_unlock_bh(&mp->em_lock);
++
++ /*
++ * update exchange
++ */
++ ep->oxid = ep->xid = xid;
++ ep->em = mp;
++ ep->lp = mp->lp;
++ ep->f_ctl = FC_FC_FIRST_SEQ; /* next seq is first seq */
++ ep->rxid = FC_XID_UNKNOWN;
++ ep->class = mp->class;
++
++ spin_lock_init(&ep->ex_lock);
++ setup_timer(&ep->ex_timer, fc_exch_timeout, (unsigned long)ep);
++
++ fc_exch_hold(ep); /* hold for caller */
++out:
++ return ep;
++}
++EXPORT_SYMBOL(fc_exch_alloc);
++
++/*
++ * Lookup and hold an exchange.
++ */
++static struct fc_exch *fc_exch_find(struct fc_exch_mgr *mp, u16 xid)
++{
++ struct fc_exch *ep = NULL;
++
++ if ((xid >= mp->min_xid) && (xid <= mp->max_xid)) {
++ spin_lock_bh(&mp->em_lock);
++ ep = mp->exches[xid - mp->min_xid];
++ if (ep) {
++ fc_exch_hold(ep);
++ WARN_ON(ep->xid != xid);
++ }
++ spin_unlock_bh(&mp->em_lock);
++ }
++ return ep;
++}
++
++void fc_exch_done(struct fc_seq *sp)
++{
++ struct fc_exch *ep = fc_seq_exch(sp);
++ int rc;
++
++ spin_lock_bh(&ep->ex_lock);
++ rc = fc_exch_done_locked(ep);
++ spin_unlock_bh(&ep->ex_lock);
++ if (!rc)
++ fc_exch_mgr_delete_ep(ep);
++}
++EXPORT_SYMBOL(fc_exch_done);
++
++/*
++ * Allocate a new exchange as responder.
++ * Sets the responder ID in the frame header.
++ */
++static struct fc_exch *fc_exch_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
++{
++ struct fc_exch *ep;
++ struct fc_frame_header *fh;
++ u16 rxid;
++
++ ep = mp->lp->tt.exch_get(mp->lp, fp);
++ if (ep) {
++ ep->class = fc_frame_class(fp);
++
++ /*
++ * Set EX_CTX indicating we're responding on this exchange.
++ */
++ ep->f_ctl |= FC_FC_EX_CTX; /* we're responding */
++ ep->f_ctl &= ~FC_FC_FIRST_SEQ; /* not new */
++ fh = fc_frame_header_get(fp);
++ ep->sid = ntoh24(fh->fh_d_id);
++ ep->did = ntoh24(fh->fh_s_id);
++ ep->oid = ep->did;
++
++ /*
++ * Allocated exchange has placed the XID in the
++ * originator field. Move it to the responder field,
++ * and set the originator XID from the frame.
++ */
++ ep->rxid = ep->xid;
++ ep->oxid = ntohs(fh->fh_ox_id);
++ ep->esb_stat |= ESB_ST_RESP | ESB_ST_SEQ_INIT;
++ if ((ntoh24(fh->fh_f_ctl) & FC_FC_SEQ_INIT) == 0)
++ ep->esb_stat &= ~ESB_ST_SEQ_INIT;
++
++ /*
++ * Set the responder ID in the frame header.
++ * The old one should've been 0xffff.
++ * If it isn't, don't assign one.
++ * Incoming basic link service frames may specify
++ * a referenced RX_ID.
++ */
++ if (fh->fh_type != FC_TYPE_BLS) {
++ rxid = ntohs(fh->fh_rx_id);
++ WARN_ON(rxid != FC_XID_UNKNOWN);
++ fh->fh_rx_id = htons(ep->rxid);
++ }
++ }
++ return ep;
++}
++
++/*
++ * Find a sequence for receive where the other end is originating the sequence.
++ * If fc_pf_rjt_reason is FC_RJT_NONE then this function will have a hold
++ * on the ep that should be released by the caller.
++ */
++static enum fc_pf_rjt_reason
++fc_seq_lookup_recip(struct fc_exch_mgr *mp, struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ struct fc_exch *ep = NULL, *new_ep = NULL;
++ struct fc_seq *sp = NULL;
++ enum fc_pf_rjt_reason reject = FC_RJT_NONE;
++ u32 f_ctl;
++ u16 xid;
++
++ f_ctl = ntoh24(fh->fh_f_ctl);
++ WARN_ON((f_ctl & FC_FC_SEQ_CTX) != 0);
++
++ /*
++ * Lookup or create the exchange if we will be creating the sequence.
++ */
++ if (f_ctl & FC_FC_EX_CTX) {
++ xid = ntohs(fh->fh_ox_id); /* we originated exch */
++ ep = fc_exch_find(mp, xid);
++ if (!ep) {
++ atomic_inc(&mp->stats.xid_not_found);
++ reject = FC_RJT_OX_ID;
++ goto out;
++ }
++ if (ep->rxid == FC_XID_UNKNOWN)
++ ep->rxid = ntohs(fh->fh_rx_id);
++ else if (ep->rxid != ntohs(fh->fh_rx_id)) {
++ reject = FC_RJT_OX_ID;
++ goto rel;
++ }
++ } else {
++ xid = ntohs(fh->fh_rx_id); /* we are the responder */
++
++ /*
++ * Special case for MDS issuing an ELS TEST with a
++ * bad rxid of 0.
++ * XXX take this out once we do the proper reject.
++ */
++ if (xid == 0 && fh->fh_r_ctl == FC_RCTL_ELS_REQ &&
++ fc_frame_payload_op(fp) == ELS_TEST) {
++ fh->fh_rx_id = htons(FC_XID_UNKNOWN);
++ xid = FC_XID_UNKNOWN;
++ }
++
++ /*
++ * new sequence - find the exchange
++ */
++ ep = fc_exch_find(mp, xid);
++ if ((f_ctl & FC_FC_FIRST_SEQ) && fc_sof_is_init(fr_sof(fp))) {
++ if (ep) {
++ atomic_inc(&mp->stats.xid_busy);
++ reject = FC_RJT_RX_ID;
++ goto rel;
++ }
++ new_ep = ep = fc_exch_resp(mp, fp);
++ if (!ep) {
++ reject = FC_RJT_EXCH_EST; /* XXX */
++ goto out;
++ }
++ fc_exch_hold(ep); /* Additional hold for caller */
++ xid = ep->xid; /* get our XID */
++ } else if (!ep) {
++ atomic_inc(&mp->stats.xid_not_found);
++ reject = FC_RJT_RX_ID; /* XID not found */
++ goto out;
++ }
++ }
++
++ /*
++ * At this point, we have the exchange held.
++ * Find or create the sequence.
++ */
++ if (fc_sof_is_init(fr_sof(fp))) {
++ sp = fc_seq_start_next(&ep->seq);
++ if (!sp) {
++ reject = FC_RJT_SEQ_XS; /* exchange shortage */
++ goto rel;
++ }
++ sp->id = fh->fh_seq_id;
++ sp->ssb_stat |= SSB_ST_RESP;
++ } else {
++ sp = &ep->seq;
++ if (sp->id != fh->fh_seq_id) {
++ atomic_inc(&mp->stats.seq_not_found);
++ reject = FC_RJT_SEQ_ID; /* sequence/exch should exist */
++ goto rel;
++ }
++ }
++ WARN_ON(ep != fc_seq_exch(sp));
++
++ if (f_ctl & FC_FC_SEQ_INIT)
++ ep->esb_stat |= ESB_ST_SEQ_INIT;
++
++ fr_seq(fp) = sp;
++out:
++ return reject;
++rel:
++ fc_exch_release(ep);
++ if (new_ep)
++ fc_exch_release(new_ep);
++ return reject;
++}
++
++/*
++ * Find the sequence for a frame being received.
++ * We originated the sequence, so it should be found.
++ * We may or may not have originated the exchange.
++ * Does not hold the sequence for the caller.
++ */
++static struct fc_seq *fc_seq_lookup_orig(struct fc_exch_mgr *mp,
++ struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ struct fc_exch *ep;
++ struct fc_seq *sp = NULL;
++ u32 f_ctl;
++ u16 xid;
++
++ f_ctl = ntoh24(fh->fh_f_ctl);
++ WARN_ON((f_ctl & FC_FC_SEQ_CTX) != FC_FC_SEQ_CTX);
++ xid = ntohs((f_ctl & FC_FC_EX_CTX) ? fh->fh_ox_id : fh->fh_rx_id);
++ ep = fc_exch_find(mp, xid);
++ if (!ep)
++ return NULL;
++ if (ep->seq.id == fh->fh_seq_id) {
++ /*
++ * Save the RX_ID if we didn't previously know it.
++ */
++ sp = &ep->seq;
++ if ((f_ctl & FC_FC_EX_CTX) != 0 &&
++ ep->rxid == FC_XID_UNKNOWN) {
++ ep->rxid = ntohs(fh->fh_rx_id);
++ }
++ }
++ fc_exch_release(ep);
++ return sp;
++}
++
++/*
++ * Set addresses for an exchange.
++ * Note this must be done before the first sequence of the exchange is sent.
++ */
++static void fc_exch_set_addr(struct fc_exch *ep,
++ u32 orig_id, u32 resp_id)
++{
++ ep->oid = orig_id;
++ if (ep->esb_stat & ESB_ST_RESP) {
++ ep->sid = resp_id;
++ ep->did = orig_id;
++ } else {
++ ep->sid = orig_id;
++ ep->did = resp_id;
++ }
++}
++
++static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp)
++{
++ struct fc_exch *ep = fc_seq_exch(sp);
++
++ sp = fc_seq_alloc(ep, ep->seq_id++);
++ if (fc_exch_debug)
++ FC_DBG("exch %4x f_ctl %6x seq %2x f_ctl %6x\n",
++ ep->xid, ep->f_ctl, sp->id, sp->f_ctl);
++ return sp;
++}
++/*
++ * Allocate a new sequence on the same exchange as the supplied sequence.
++ * This will never return NULL.
++ */
++struct fc_seq *fc_seq_start_next(struct fc_seq *sp)
++{
++ struct fc_exch *ep = fc_seq_exch(sp);
++
++ spin_lock_bh(&ep->ex_lock);
++ WARN_ON((ep->esb_stat & ESB_ST_COMPLETE) != 0);
++ sp = fc_seq_start_next_locked(sp);
++ spin_unlock_bh(&ep->ex_lock);
++
++ return sp;
++}
++EXPORT_SYMBOL(fc_seq_start_next);
++
++int fc_seq_send(struct fc_lport *lp, struct fc_seq *sp,
++ struct fc_frame *fp, u32 f_ctl)
++{
++ struct fc_exch *ep;
++ struct fc_frame_header *fh;
++ enum fc_class class;
++ u16 fill = 0;
++ int error;
++
++ ep = fc_seq_exch(sp);
++ WARN_ON((ep->esb_stat & ESB_ST_SEQ_INIT) != ESB_ST_SEQ_INIT);
++
++ fc_seq_fill_hdr(sp, fp);
++ fh = fc_frame_header_get(fp);
++ class = ep->class;
++ fr_sof(fp) = class;
++ if (sp->cnt)
++ fr_sof(fp) = fc_sof_normal(class);
++
++ if (f_ctl & FC_FC_END_SEQ) {
++ fr_eof(fp) = FC_EOF_T;
++ if (fc_sof_needs_ack(class))
++ fr_eof(fp) = FC_EOF_N;
++ /*
++ * Form f_ctl.
++ * The number of fill bytes to make the length a 4-byte
++ * multiple is the low order 2-bits of the f_ctl.
++ * The fill itself will have been cleared by the frame
++ * allocation.
++ * After this, the length will be even, as expected by
++ * the transport. Don't include the fill in the f_ctl
++ * saved in the sequence.
++ */
++ fill = fr_len(fp) & 3;
++ if (fill) {
++ fill = 4 - fill;
++ /* TODO, this may be a problem with fragmented skb */
++ skb_put(fp_skb(fp), fill);
++ }
++ f_ctl |= sp->f_ctl | ep->f_ctl;
++ } else {
++ WARN_ON(fr_len(fp) % 4 != 0); /* no pad to non last frame */
++ f_ctl |= sp->f_ctl | ep->f_ctl;
++ f_ctl &= ~FC_FC_SEQ_INIT;
++ fr_eof(fp) = FC_EOF_N;
++ }
++
++ hton24(fh->fh_f_ctl, f_ctl | fill);
++ fh->fh_seq_cnt = htons(sp->cnt++);
++
++ /*
++ * Send the frame.
++ */
++ error = lp->tt.frame_send(lp, fp);
++
++ /*
++ * Update the exchange and sequence flags,
++ * assuming all frames for the sequence have been sent.
++ * We can only be called to send once for each sequence.
++ */
++ spin_lock_bh(&ep->ex_lock);
++ sp->f_ctl = f_ctl; /* save for possible abort */
++ ep->f_ctl &= ~FC_FC_FIRST_SEQ; /* not first seq */
++ if (f_ctl & FC_FC_END_SEQ) {
++ if (f_ctl & FC_FC_SEQ_INIT)
++ ep->esb_stat &= ~ESB_ST_SEQ_INIT;
++ }
++ spin_unlock_bh(&ep->ex_lock);
++ return error;
++}
++EXPORT_SYMBOL(fc_seq_send);
++
++void fc_seq_els_rsp_send(struct fc_seq *sp, enum fc_els_cmd els_cmd,
++ struct fc_seq_els_data *els_data)
++{
++ switch (els_cmd) {
++ case ELS_LS_RJT:
++ fc_seq_ls_rjt(sp, els_data->reason, els_data->explan);
++ break;
++ case ELS_LS_ACC:
++ fc_seq_ls_acc(sp);
++ break;
++ case ELS_RRQ:
++ fc_exch_els_rrq(sp, els_data->fp);
++ break;
++ case ELS_REC:
++ fc_exch_els_rec(sp, els_data->fp);
++ break;
++ default:
++ FC_DBG("Invalid ELS CMD:%x\n", els_cmd);
++ }
++}
++EXPORT_SYMBOL(fc_seq_els_rsp_send);
++
++/*
++ * Send a sequence, which is also the last sequence in the exchange.
++ */
++static void fc_seq_send_last(struct fc_seq *sp, struct fc_frame *fp,
++ enum fc_rctl rctl, enum fc_fh_type fh_type)
++{
++ u32 f_ctl;
++
++ fc_frame_setup(fp, rctl, fh_type);
++ f_ctl = FC_FC_SEQ_INIT | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ fc_seq_send(fc_seq_exch(sp)->lp, sp, fp, f_ctl);
++}
++
++/*
++ * Send ACK_1 (or equiv.) indicating we received something.
++ * The frame we're acking is supplied.
++ */
++static void fc_seq_send_ack(struct fc_seq *sp, const struct fc_frame *rx_fp)
++{
++ struct fc_frame *fp;
++ struct fc_frame_header *rx_fh;
++ struct fc_frame_header *fh;
++ struct fc_lport *lp = fc_seq_exch(sp)->lp;
++ unsigned int f_ctl;
++
++ /*
++ * Don't send ACKs for class 3.
++ */
++ if (fc_sof_needs_ack(fr_sof(rx_fp))) {
++ fp = fc_frame_alloc(lp, 0);
++ BUG_ON(!fp);
++ if (!fp)
++ return;
++
++ fc_seq_fill_hdr(sp, fp);
++ fh = fc_frame_header_get(fp);
++ fh->fh_r_ctl = FC_RCTL_ACK_1;
++ fh->fh_type = FC_TYPE_BLS;
++
++ /*
++ * Form f_ctl by inverting EX_CTX and SEQ_CTX (bits 23, 22).
++ * Echo FIRST_SEQ, LAST_SEQ, END_SEQ, END_CONN, SEQ_INIT.
++ * Bits 9-8 are meaningful (retransmitted or unidirectional).
++ * Last ACK uses bits 7-6 (continue sequence),
++ * bits 5-4 are meaningful (what kind of ACK to use).
++ */
++ rx_fh = fc_frame_header_get(rx_fp);
++ f_ctl = ntoh24(rx_fh->fh_f_ctl);
++ f_ctl &= FC_FC_EX_CTX | FC_FC_SEQ_CTX |
++ FC_FC_FIRST_SEQ | FC_FC_LAST_SEQ |
++ FC_FC_END_SEQ | FC_FC_END_CONN | FC_FC_SEQ_INIT |
++ FC_FC_RETX_SEQ | FC_FC_UNI_TX;
++ f_ctl ^= FC_FC_EX_CTX | FC_FC_SEQ_CTX;
++ hton24(fh->fh_f_ctl, f_ctl);
++
++ fh->fh_seq_id = rx_fh->fh_seq_id;
++ fh->fh_seq_cnt = rx_fh->fh_seq_cnt;
++ fh->fh_parm_offset = htonl(1); /* ack single frame */
++
++ fr_sof(fp) = fr_sof(rx_fp);
++ if (f_ctl & FC_FC_END_SEQ)
++ fr_eof(fp) = FC_EOF_T;
++ else
++ fr_eof(fp) = FC_EOF_N;
++
++ (void) lp->tt.frame_send(lp, fp);
++ }
++}
++
++/*
++ * Send BLS Reject.
++ * This is for rejecting BA_ABTS only.
++ */
++static void
++fc_exch_send_ba_rjt(struct fc_frame *rx_fp, enum fc_ba_rjt_reason reason,
++ enum fc_ba_rjt_explan explan)
++{
++ struct fc_frame *fp;
++ struct fc_frame_header *rx_fh;
++ struct fc_frame_header *fh;
++ struct fc_ba_rjt *rp;
++ struct fc_lport *lp;
++ unsigned int f_ctl;
++
++ lp = fr_dev(rx_fp);
++ fp = fc_frame_alloc(lp, sizeof(*rp));
++ if (!fp)
++ return;
++ fh = fc_frame_header_get(fp);
++ rx_fh = fc_frame_header_get(rx_fp);
++
++ memset(fh, 0, sizeof(*fh) + sizeof(*rp));
++
++ rp = fc_frame_payload_get(fp, sizeof(*rp));
++ rp->br_reason = reason;
++ rp->br_explan = explan;
++
++ /*
++ * seq_id, cs_ctl, df_ctl and param/offset are zero.
++ */
++ memcpy(fh->fh_s_id, rx_fh->fh_d_id, 3);
++ memcpy(fh->fh_d_id, rx_fh->fh_s_id, 3);
++ fh->fh_ox_id = rx_fh->fh_rx_id;
++ fh->fh_rx_id = rx_fh->fh_ox_id;
++ fh->fh_seq_cnt = rx_fh->fh_seq_cnt;
++ fh->fh_r_ctl = FC_RCTL_BA_RJT;
++ fh->fh_type = FC_TYPE_BLS;
++
++ /*
++ * Form f_ctl by inverting EX_CTX and SEQ_CTX (bits 23, 22).
++ * Echo FIRST_SEQ, LAST_SEQ, END_SEQ, END_CONN, SEQ_INIT.
++ * Bits 9-8 are meaningful (retransmitted or unidirectional).
++ * Last ACK uses bits 7-6 (continue sequence),
++ * bits 5-4 are meaningful (what kind of ACK to use).
++ * Always set LAST_SEQ, END_SEQ.
++ */
++ f_ctl = ntoh24(rx_fh->fh_f_ctl);
++ f_ctl &= FC_FC_EX_CTX | FC_FC_SEQ_CTX |
++ FC_FC_END_CONN | FC_FC_SEQ_INIT |
++ FC_FC_RETX_SEQ | FC_FC_UNI_TX;
++ f_ctl ^= FC_FC_EX_CTX | FC_FC_SEQ_CTX;
++ f_ctl |= FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ f_ctl &= ~FC_FC_FIRST_SEQ;
++ hton24(fh->fh_f_ctl, f_ctl);
++
++ fr_sof(fp) = fc_sof_class(fr_sof(rx_fp));
++ fr_eof(fp) = FC_EOF_T;
++ if (fc_sof_needs_ack(fr_sof(fp)))
++ fr_eof(fp) = FC_EOF_N;
++
++ (void) lp->tt.frame_send(lp, fp);
++}
++
++/*
++ * Handle an incoming ABTS. This would be for target mode usually,
++ * but could be due to lost FCP transfer ready, confirm or RRQ.
++ * We always handle this as an exchange abort, ignoring the parameter.
++ */
++static void fc_exch_recv_abts(struct fc_exch *ep, struct fc_frame *rx_fp)
++{
++ struct fc_frame *fp;
++ struct fc_ba_acc *ap;
++ struct fc_frame_header *fh;
++ struct fc_seq *sp;
++
++ if (!ep)
++ goto reject;
++ spin_lock_bh(&ep->ex_lock);
++ if (ep->esb_stat & ESB_ST_COMPLETE) {
++ spin_unlock_bh(&ep->ex_lock);
++ goto reject;
++ }
++ if (!(ep->esb_stat & ESB_ST_REC_QUAL))
++ fc_exch_hold(ep); /* hold for REC_QUAL */
++ ep->esb_stat |= ESB_ST_ABNORMAL | ESB_ST_REC_QUAL;
++ fc_exch_timer_set_locked(ep, ep->r_a_tov);
++
++ fp = fc_frame_alloc(ep->lp, sizeof(*ap));
++ if (!fp) {
++ spin_unlock_bh(&ep->ex_lock);
++ goto free;
++ }
++ fh = fc_frame_header_get(fp);
++ ap = fc_frame_payload_get(fp, sizeof(*ap));
++ memset(ap, 0, sizeof(*ap));
++ sp = &ep->seq;
++ ap->ba_high_seq_cnt = htons(0xffff);
++ if (sp->ssb_stat & SSB_ST_RESP) {
++ ap->ba_seq_id = sp->id;
++ ap->ba_seq_id_val = FC_BA_SEQ_ID_VAL;
++ ap->ba_high_seq_cnt = fh->fh_seq_cnt;
++ ap->ba_low_seq_cnt = htons(sp->cnt);
++ }
++ sp = fc_seq_start_next(sp);
++ spin_unlock_bh(&ep->ex_lock);
++ fc_seq_send_last(sp, fp, FC_RCTL_BA_ACC, FC_TYPE_BLS);
++ fc_frame_free(rx_fp);
++ return;
++
++reject:
++ fc_exch_send_ba_rjt(rx_fp, FC_BA_RJT_UNABLE, FC_BA_RJT_INV_XID);
++free:
++ fc_frame_free(rx_fp);
++}
++
++/*
++ * Handle receive where the other end is originating the sequence.
++ */
++static void fc_exch_recv_req(struct fc_lport *lp, struct fc_exch_mgr *mp,
++ struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ struct fc_seq *sp = NULL;
++ struct fc_exch *ep = NULL;
++ enum fc_sof sof;
++ enum fc_eof eof;
++ u32 f_ctl;
++ enum fc_pf_rjt_reason reject;
++
++ fr_seq(fp) = NULL;
++ reject = fc_seq_lookup_recip(mp, fp);
++ if (reject == FC_RJT_NONE) {
++ sp = fr_seq(fp); /* sequence will be held */
++ ep = fc_seq_exch(sp);
++ sof = fr_sof(fp);
++ eof = fr_eof(fp);
++ f_ctl = ntoh24(fh->fh_f_ctl);
++ fc_seq_send_ack(sp, fp);
++
++ /*
++ * Call the receive function.
++ *
++ * The receive function may allocate a new sequence
++ * over the old one, so we shouldn't change the
++ * sequence after this.
++ *
++ * The frame will be freed by the receive function.
++ * If new exch resp handler is valid then call that
++ * first.
++ */
++ if (ep->resp)
++ ep->resp(sp, fp, ep->resp_arg);
++ else
++ lp->tt.lport_recv(lp, sp, fp);
++ fc_exch_release(ep); /* release from lookup */
++ } else {
++ if (fc_exch_debug)
++ FC_DBG("exch/seq lookup failed: reject %x\n", reject);
++ fc_frame_free(fp);
++ }
++}
++
++/*
++ * Handle receive where the other end is originating the sequence in
++ * response to our exchange.
++ */
++static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ struct fc_seq *sp;
++ struct fc_exch *ep;
++ enum fc_sof sof;
++ u32 f_ctl;
++ void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
++ void *ex_resp_arg;
++ int rc;
++
++ ep = fc_exch_find(mp, ntohs(fh->fh_ox_id));
++ if (!ep) {
++ atomic_inc(&mp->stats.xid_not_found);
++ goto out;
++ }
++ if (ep->rxid == FC_XID_UNKNOWN)
++ ep->rxid = ntohs(fh->fh_rx_id);
++ if (ep->sid != 0 && ep->sid != ntoh24(fh->fh_d_id)) {
++ atomic_inc(&mp->stats.xid_not_found);
++ goto rel;
++ }
++ if (ep->did != ntoh24(fh->fh_s_id) &&
++ ep->did != FC_FID_FLOGI) {
++ atomic_inc(&mp->stats.xid_not_found);
++ goto rel;
++ }
++ sof = fr_sof(fp);
++ if (fc_sof_is_init(sof)) {
++ sp = fc_seq_start_next(&ep->seq);
++ sp->id = fh->fh_seq_id;
++ sp->ssb_stat |= SSB_ST_RESP;
++ } else {
++ sp = &ep->seq;
++ if (sp->id != fh->fh_seq_id) {
++ atomic_inc(&mp->stats.seq_not_found);
++ goto rel;
++ }
++ }
++ f_ctl = ntoh24(fh->fh_f_ctl);
++ fr_seq(fp) = sp;
++ if (f_ctl & FC_FC_SEQ_INIT)
++ ep->esb_stat |= ESB_ST_SEQ_INIT;
++
++ if (fc_sof_needs_ack(sof))
++ fc_seq_send_ack(sp, fp);
++ resp = ep->resp;
++ ex_resp_arg = ep->resp_arg;
++
++ if (fh->fh_type != FC_TYPE_FCP && fr_eof(fp) == FC_EOF_T &&
++ (f_ctl & (FC_FC_LAST_SEQ | FC_FC_END_SEQ)) ==
++ (FC_FC_LAST_SEQ | FC_FC_END_SEQ)) {
++ spin_lock_bh(&ep->ex_lock);
++ rc = fc_exch_done_locked(ep);
++ WARN_ON(fc_seq_exch(sp) != ep);
++ spin_unlock_bh(&ep->ex_lock);
++ if (!rc)
++ fc_exch_mgr_delete_ep(ep);
++ }
++
++ /*
++ * Call the receive function.
++ * The sequence is held (has a refcnt) for us,
++ * but not for the receive function.
++ *
++ * The receive function may allocate a new sequence
++ * over the old one, so we shouldn't change the
++ * sequence after this.
++ *
++ * The frame will be freed by the receive function.
++ * If new exch resp handler is valid then call that
++ * first.
++ */
++ if (resp)
++ resp(sp, fp, ex_resp_arg);
++ else
++ fc_frame_free(fp);
++ fc_exch_release(ep);
++ return;
++rel:
++ fc_exch_release(ep);
++out:
++ fc_frame_free(fp);
++}
++
++/*
++ * Handle receive for a sequence where other end is responding to our sequence.
++ */
++static void fc_exch_recv_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
++{
++ struct fc_seq *sp;
++
++ sp = fc_seq_lookup_orig(mp, fp); /* doesn't hold sequence */
++ if (!sp) {
++ atomic_inc(&mp->stats.xid_not_found);
++ if (fc_exch_debug)
++ FC_DBG("seq lookup failed\n");
++ } else {
++ atomic_inc(&mp->stats.non_bls_resp);
++ if (fc_exch_debug)
++ FC_DBG("non-BLS response to sequence");
++ }
++ fc_frame_free(fp);
++}
++
++/*
++ * Handle the response to an ABTS for exchange or sequence.
++ * This can be BA_ACC or BA_RJT.
++ */
++static void fc_exch_abts_resp(struct fc_exch *ep, struct fc_frame *fp)
++{
++ void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
++ void *ex_resp_arg;
++ struct fc_frame_header *fh;
++ struct fc_ba_acc *ap;
++ struct fc_seq *sp;
++ u16 low;
++ u16 high;
++ int rc = 1, has_rec = 0;
++
++ fh = fc_frame_header_get(fp);
++ if (fc_exch_debug)
++ FC_DBG("exch: BLS rctl %x - %s\n",
++ fh->fh_r_ctl, fc_exch_rctl_name(fh->fh_r_ctl));
++
++ if (del_timer_sync(&ep->ex_timer))
++ fc_exch_release(ep); /* release from pending timer hold */
++
++ spin_lock_bh(&ep->ex_lock);
++ switch (fh->fh_r_ctl) {
++ case FC_RCTL_BA_ACC:
++ ap = fc_frame_payload_get(fp, sizeof(*ap));
++ if (!ap)
++ break;
++
++ /*
++ * Decide whether to establish a Recovery Qualifier.
++ * We do this if there is a non-empty SEQ_CNT range and
++ * SEQ_ID is the same as the one we aborted.
++ */
++ low = ntohs(ap->ba_low_seq_cnt);
++ high = ntohs(ap->ba_high_seq_cnt);
++ if ((ep->esb_stat & ESB_ST_REC_QUAL) == 0 &&
++ (ap->ba_seq_id_val != FC_BA_SEQ_ID_VAL ||
++ ap->ba_seq_id == ep->seq_id) && low != high) {
++ ep->esb_stat |= ESB_ST_REC_QUAL;
++ fc_exch_hold(ep); /* hold for recovery qualifier */
++ has_rec = 1;
++ }
++ break;
++ case FC_RCTL_BA_RJT:
++ break;
++ default:
++ break;
++ }
++
++ resp = ep->resp;
++ ex_resp_arg = ep->resp_arg;
++
++ /* do we need to do some other checks here. Can we reuse more of
++ * fc_exch_recv_seq_resp
++ */
++ sp = &ep->seq;
++ /*
++ * do we want to check END_SEQ as well as LAST_SEQ here?
++ */
++ if (fh->fh_type != FC_TYPE_FCP &&
++ ntoh24(fh->fh_f_ctl) & FC_FC_LAST_SEQ)
++ rc = fc_exch_done_locked(ep);
++ spin_unlock_bh(&ep->ex_lock);
++ if (!rc)
++ fc_exch_mgr_delete_ep(ep);
++
++ if (resp)
++ resp(sp, fp, ex_resp_arg);
++ else
++ fc_frame_free(fp);
++
++ if (has_rec)
++ fc_exch_timer_set(ep, ep->r_a_tov);
++
++}
++
++/*
++ * Receive BLS sequence.
++ * This is always a sequence initiated by the remote side.
++ * We may be either the originator or recipient of the exchange.
++ */
++static void fc_exch_recv_bls(struct fc_exch_mgr *mp, struct fc_frame *fp)
++{
++ struct fc_frame_header *fh;
++ struct fc_exch *ep;
++ u32 f_ctl;
++
++ fh = fc_frame_header_get(fp);
++ f_ctl = ntoh24(fh->fh_f_ctl);
++ fr_seq(fp) = NULL;
++
++ ep = fc_exch_find(mp, (f_ctl & FC_FC_EX_CTX) ?
++ ntohs(fh->fh_ox_id) : ntohs(fh->fh_rx_id));
++ if (ep && (f_ctl & FC_FC_SEQ_INIT)) {
++ spin_lock_bh(&ep->ex_lock);
++ ep->esb_stat |= ESB_ST_SEQ_INIT;
++ spin_unlock_bh(&ep->ex_lock);
++ }
++ if (f_ctl & FC_FC_SEQ_CTX) {
++ /*
++ * A response to a sequence we initiated.
++ * This should only be ACKs for class 2 or F.
++ */
++ switch (fh->fh_r_ctl) {
++ case FC_RCTL_ACK_1:
++ case FC_RCTL_ACK_0:
++ break;
++ default:
++ if (fc_exch_debug)
++ FC_DBG("BLS rctl %x - %s received",
++ fh->fh_r_ctl,
++ fc_exch_rctl_name(fh->fh_r_ctl));
++ break;
++ }
++ fc_frame_free(fp);
++ } else {
++ switch (fh->fh_r_ctl) {
++ case FC_RCTL_BA_RJT:
++ case FC_RCTL_BA_ACC:
++ if (ep)
++ fc_exch_abts_resp(ep, fp);
++ else
++ fc_frame_free(fp);
++ break;
++ case FC_RCTL_BA_ABTS:
++ fc_exch_recv_abts(ep, fp);
++ break;
++ default: /* ignore junk */
++ fc_frame_free(fp);
++ break;
++ }
++ }
++ if (ep)
++ fc_exch_release(ep); /* release hold taken by fc_exch_find */
++}
++
++/*
++ * Accept sequence with LS_ACC.
++ * If this fails due to allocation or transmit congestion, assume the
++ * originator will repeat the sequence.
++ */
++static void fc_seq_ls_acc(struct fc_seq *req_sp)
++{
++ struct fc_seq *sp;
++ struct fc_els_ls_acc *acc;
++ struct fc_frame *fp;
++
++ sp = fc_seq_start_next(req_sp);
++ fp = fc_frame_alloc(fc_seq_exch(sp)->lp, sizeof(*acc));
++ if (fp) {
++ acc = fc_frame_payload_get(fp, sizeof(*acc));
++ memset(acc, 0, sizeof(*acc));
++ acc->la_cmd = ELS_LS_ACC;
++ fc_seq_send_last(sp, fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ }
++}
++
++/*
++ * Reject sequence with ELS LS_RJT.
++ * If this fails due to allocation or transmit congestion, assume the
++ * originator will repeat the sequence.
++ */
++static void fc_seq_ls_rjt(struct fc_seq *req_sp, enum fc_els_rjt_reason reason,
++ enum fc_els_rjt_explan explan)
++{
++ struct fc_seq *sp;
++ struct fc_els_ls_rjt *rjt;
++ struct fc_frame *fp;
++
++ sp = fc_seq_start_next(req_sp);
++ fp = fc_frame_alloc(fc_seq_exch(sp)->lp, sizeof(*rjt));
++ if (fp) {
++ rjt = fc_frame_payload_get(fp, sizeof(*rjt));
++ memset(rjt, 0, sizeof(*rjt));
++ rjt->er_cmd = ELS_LS_RJT;
++ rjt->er_reason = reason;
++ rjt->er_explan = explan;
++ fc_seq_send_last(sp, fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ }
++}
++
++static void fc_exch_reset(struct fc_exch *ep)
++{
++ struct fc_seq *sp;
++ void (*resp)(struct fc_seq *, struct fc_frame *, void *);
++ void *arg;
++ int rc = 1;
++
++ spin_lock_bh(&ep->ex_lock);
++ ep->state |= FC_EX_RST_CLEANUP;
++ /*
++ * we really want to call del_timer_sync, but cannot due
++ * to the lport calling with the lport lock held (some resp
++ * functions can also grab the lport lock which could cause
++ * a deadlock).
++ */
++ if (del_timer(&ep->ex_timer))
++ atomic_dec(&ep->ex_refcnt); /* drop hold for timer */
++ resp = ep->resp;
++ ep->resp = NULL;
++ if (ep->esb_stat & ESB_ST_REC_QUAL)
++ atomic_dec(&ep->ex_refcnt); /* drop hold for rec_qual */
++ ep->esb_stat &= ~ESB_ST_REC_QUAL;
++ arg = ep->resp_arg;
++ sp = &ep->seq;
++
++ if (ep->fh_type != FC_TYPE_FCP)
++ rc = fc_exch_done_locked(ep);
++ spin_unlock_bh(&ep->ex_lock);
++ if (!rc)
++ fc_exch_mgr_delete_ep(ep);
++
++ if (resp)
++ resp(sp, ERR_PTR(-FC_EX_CLOSED), arg);
++}
++
++/*
++ * Reset an exchange manager, releasing all sequences and exchanges.
++ * If sid is non-zero, reset only exchanges we source from that FID.
++ * If did is non-zero, reset only exchanges destined to that FID.
++ */
++void fc_exch_mgr_reset(struct fc_exch_mgr *mp, u32 sid, u32 did)
++{
++ struct fc_exch *ep;
++ struct fc_exch *next;
++
++ spin_lock_bh(&mp->em_lock);
++restart:
++ list_for_each_entry_safe(ep, next, &mp->ex_list, ex_list) {
++ if ((sid == 0 || sid == ep->sid) &&
++ (did == 0 || did == ep->did)) {
++ fc_exch_hold(ep);
++ spin_unlock_bh(&mp->em_lock);
++
++ fc_exch_reset(ep);
++
++ fc_exch_release(ep);
++ spin_lock_bh(&mp->em_lock);
++
++ /*
++ * must restart loop incase while lock was down
++ * multiple eps were released.
++ */
++ goto restart;
++ }
++ }
++ spin_unlock_bh(&mp->em_lock);
++}
++EXPORT_SYMBOL(fc_exch_mgr_reset);
++
++void fc_seq_get_xids(struct fc_seq *sp, u16 *oxid, u16 *rxid)
++{
++ struct fc_exch *ep;
++
++ ep = fc_seq_exch(sp);
++ *oxid = ep->oxid;
++ *rxid = ep->rxid;
++}
++EXPORT_SYMBOL(fc_seq_get_xids);
++
++void fc_seq_set_rec_data(struct fc_seq *sp, u32 rec_data)
++{
++ sp->rec_data = rec_data;
++}
++EXPORT_SYMBOL(fc_seq_set_rec_data);
++
++/*
++ * Handle incoming ELS REC - Read Exchange Concise.
++ * Note that the requesting port may be different than the S_ID in the request.
++ */
++static void fc_exch_els_rec(struct fc_seq *sp, struct fc_frame *rfp)
++{
++ struct fc_frame *fp;
++ struct fc_exch *ep;
++ struct fc_exch_mgr *em;
++ struct fc_els_rec *rp;
++ struct fc_els_rec_acc *acc;
++ enum fc_els_rjt_reason reason = ELS_RJT_LOGIC;
++ enum fc_els_rjt_explan explan;
++ u32 sid;
++ u16 rxid;
++ u16 oxid;
++
++ rp = fc_frame_payload_get(rfp, sizeof(*rp));
++ explan = ELS_EXPL_INV_LEN;
++ if (!rp)
++ goto reject;
++ sid = ntoh24(rp->rec_s_id);
++ rxid = ntohs(rp->rec_rx_id);
++ oxid = ntohs(rp->rec_ox_id);
++
++ /*
++ * Currently it's hard to find the local S_ID from the exchange
++ * manager. This will eventually be fixed, but for now it's easier
++ * to lookup the subject exchange twice, once as if we were
++ * the initiator, and then again if we weren't.
++ */
++ em = fc_seq_exch(sp)->em;
++ ep = fc_exch_find(em, oxid);
++ explan = ELS_EXPL_OXID_RXID;
++ if (ep && ep->oid == sid) {
++ if (ep->rxid != FC_XID_UNKNOWN &&
++ rxid != FC_XID_UNKNOWN &&
++ ep->rxid != rxid)
++ goto rel;
++ } else {
++ if (ep)
++ fc_exch_release(ep);
++ ep = NULL;
++ if (rxid != FC_XID_UNKNOWN)
++ ep = fc_exch_find(em, rxid);
++ if (!ep)
++ goto reject;
++ }
++
++ fp = fc_frame_alloc(fc_seq_exch(sp)->lp, sizeof(*acc));
++ if (!fp) {
++ fc_exch_done(sp);
++ goto out;
++ }
++ sp = fc_seq_start_next(sp);
++ acc = fc_frame_payload_get(fp, sizeof(*acc));
++ memset(acc, 0, sizeof(*acc));
++ acc->reca_cmd = ELS_LS_ACC;
++ acc->reca_ox_id = rp->rec_ox_id;
++ memcpy(acc->reca_ofid, rp->rec_s_id, 3);
++ acc->reca_rx_id = htons(ep->rxid);
++ if (ep->sid == ep->oid)
++ hton24(acc->reca_rfid, ep->did);
++ else
++ hton24(acc->reca_rfid, ep->sid);
++ acc->reca_fc4value = htonl(ep->seq.rec_data);
++ acc->reca_e_stat = htonl(ep->esb_stat & (ESB_ST_RESP |
++ ESB_ST_SEQ_INIT |
++ ESB_ST_COMPLETE));
++ sp = fc_seq_start_next(sp);
++ fc_seq_send_last(sp, fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++out:
++ fc_exch_release(ep);
++ fc_frame_free(rfp);
++ return;
++
++rel:
++ fc_exch_release(ep);
++reject:
++ fc_seq_ls_rjt(sp, reason, explan);
++ fc_frame_free(rfp);
++}
++
++/*
++ * Handle response from RRQ.
++ * Not much to do here, really.
++ * Should report errors.
++ *
++ * TODO: fix error handler.
++ */
++static void fc_exch_rrq_resp(struct fc_seq *sp, struct fc_frame *fp, void *arg)
++{
++ struct fc_exch *ep = fc_seq_exch(sp);
++ struct fc_exch *aborted_ep;
++
++ unsigned int op;
++
++ if (IS_ERR(fp)) {
++ int err = PTR_ERR(fp);
++
++ if (err == -FC_EX_CLOSED)
++ goto cleanup;
++ FC_DBG("Cannot process RRQ, because of frame error %d\n", err);
++ return;
++ }
++
++ op = fc_frame_payload_op(fp);
++ fc_frame_free(fp);
++
++ switch (op) {
++ case ELS_LS_RJT:
++ FC_DBG("LS_RJT for RRQ");
++ /* fall through */
++ case ELS_LS_ACC:
++ goto cleanup;
++ default:
++ FC_DBG("unexpected response op %x for RRQ", op);
++ return;
++ }
++
++cleanup:
++ spin_lock_bh(&ep->ex_lock);
++ aborted_ep = ep->aborted_ep;
++ ep->aborted_ep = NULL;
++ spin_unlock_bh(&ep->ex_lock);
++
++ if (aborted_ep) {
++ fc_exch_done(&aborted_ep->seq);
++ /* drop hold for rec qual */
++ fc_exch_release(aborted_ep);
++ }
++}
++
++/*
++ * Send ELS RRQ - Reinstate Recovery Qualifier.
++ * This tells the remote port to stop blocking the use of
++ * the exchange and the seq_cnt range.
++ */
++static void fc_exch_rrq(struct fc_exch *ep)
++{
++ struct fc_lport *lp;
++ struct fc_els_rrq *rrq;
++ struct fc_frame *fp;
++ struct fc_seq *rrq_sp;
++ struct fc_exch *rrq_ep;
++ u32 did;
++
++ lp = ep->lp;
++
++ fp = fc_frame_alloc(lp, sizeof(*rrq));
++ if (!fp)
++ return;
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ rrq = fc_frame_payload_get(fp, sizeof(*rrq));
++ memset(rrq, 0, sizeof(*rrq));
++ rrq->rrq_cmd = ELS_RRQ;
++ hton24(rrq->rrq_s_id, ep->sid);
++ rrq->rrq_ox_id = htons(ep->oxid);
++ rrq->rrq_rx_id = htons(ep->rxid);
++
++ did = ep->did;
++ if (ep->esb_stat & ESB_ST_RESP)
++ did = ep->sid;
++ rrq_sp = fc_exch_seq_send(lp, fp, fc_exch_rrq_resp, ep, lp->e_d_tov,
++ lp->fid, did, FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++ if (!rrq_sp) {
++ spin_lock_bh(&ep->ex_lock);
++ ep->esb_stat |= ESB_ST_REC_QUAL;
++ fc_exch_timer_set_locked(ep, ep->r_a_tov);
++ spin_unlock_bh(&ep->ex_lock);
++ return;
++ }
++
++ rrq_ep = fc_seq_exch(rrq_sp);
++ rrq_ep->aborted_ep = ep;
++}
++
++
++/*
++ * Handle incoming ELS RRQ - Reset Recovery Qualifier.
++ */
++static void fc_exch_els_rrq(struct fc_seq *sp, struct fc_frame *fp)
++{
++ struct fc_exch *ep; /* request or subject exchange */
++ struct fc_els_rrq *rp;
++ u32 sid;
++ u16 xid;
++ enum fc_els_rjt_explan explan;
++
++ rp = fc_frame_payload_get(fp, sizeof(*rp));
++ explan = ELS_EXPL_INV_LEN;
++ if (!rp)
++ goto reject;
++
++ /*
++ * lookup subject exchange.
++ */
++ ep = fc_seq_exch(sp);
++ sid = ntoh24(rp->rrq_s_id); /* subject source */
++ xid = ep->did == sid ? ntohs(rp->rrq_ox_id) : ntohs(rp->rrq_rx_id);
++ ep = fc_exch_find(ep->em, xid);
++
++ explan = ELS_EXPL_OXID_RXID;
++ if (!ep)
++ goto reject;
++ spin_lock_bh(&ep->ex_lock);
++ if (ep->oxid != ntohs(rp->rrq_ox_id))
++ goto unlock_reject;
++ if (ep->rxid != ntohs(rp->rrq_rx_id) &&
++ ep->rxid != FC_XID_UNKNOWN)
++ goto unlock_reject;
++ explan = ELS_EXPL_SID;
++ if (ep->sid != sid)
++ goto unlock_reject;
++
++ /*
++ * Clear Recovery Qualifier state, and cancel timer if complete.
++ */
++ if (ep->esb_stat & ESB_ST_REC_QUAL) {
++ ep->esb_stat &= ~ESB_ST_REC_QUAL;
++ atomic_dec(&ep->ex_refcnt); /* drop hold for rec qual */
++ }
++ if ((ep->esb_stat & ESB_ST_COMPLETE) && (del_timer(&ep->ex_timer)))
++ atomic_dec(&ep->ex_refcnt); /* drop hold for timer */
++
++ spin_unlock_bh(&ep->ex_lock);
++
++ /*
++ * Send LS_ACC.
++ */
++ fc_seq_ls_acc(sp);
++ fc_frame_free(fp);
++ return;
++
++unlock_reject:
++ spin_unlock_bh(&ep->ex_lock);
++ fc_exch_release(ep); /* drop hold from fc_exch_find */
++reject:
++ fc_seq_ls_rjt(sp, ELS_RJT_LOGIC, explan);
++ fc_frame_free(fp);
++}
++
++struct fc_exch_mgr *fc_exch_mgr_alloc(struct fc_lport *lp,
++ enum fc_class class,
++ u16 min_xid, u16 max_xid)
++{
++ struct fc_exch_mgr *mp;
++ size_t len;
++
++ if (max_xid <= min_xid || min_xid == 0 || max_xid == FC_XID_UNKNOWN) {
++ FC_DBG("Invalid min_xid 0x:%x and max_xid 0x:%x\n",
++ min_xid, max_xid);
++ return NULL;
++ }
++
++ /*
++ * Memory need for EM
++ */
++ len = (max_xid - min_xid + 1) * (sizeof(struct fc_exch *));
++ len += sizeof(struct fc_exch_mgr);
++
++ mp = kzalloc(len, GFP_ATOMIC);
++ if (mp) {
++ mp->class = class;
++ mp->total_exches = 0;
++ mp->exches = (struct fc_exch **)(mp + 1);
++ mp->last_xid = min_xid - 1;
++ mp->min_xid = min_xid;
++ mp->max_xid = max_xid;
++ mp->lp = lp;
++ INIT_LIST_HEAD(&mp->ex_list);
++ spin_lock_init(&mp->em_lock);
++ }
++
++ mp->ep_pool = mempool_create_slab_pool(2, fc_em_cachep);
++ if (!mp->ep_pool)
++ goto free_mp;
++
++ return mp;
++
++free_mp:
++ kfree(mp);
++ return NULL;
++}
++EXPORT_SYMBOL(fc_exch_mgr_alloc);
++
++void fc_exch_mgr_free(struct fc_exch_mgr *mp)
++{
++ WARN_ON(!mp);
++ /*
++ * The total exch count must be zero
++ * before freeing exchange manager.
++ */
++ WARN_ON(mp->total_exches != 0);
++ mempool_destroy(mp->ep_pool);
++ kfree(mp);
++}
++EXPORT_SYMBOL(fc_exch_mgr_free);
++
++struct fc_exch *fc_exch_get(struct fc_lport *lp, struct fc_frame *fp)
++{
++ if (!lp || !lp->emp)
++ return NULL;
++ return fc_exch_alloc(lp->emp, 0);
++}
++EXPORT_SYMBOL(fc_exch_get);
++
++struct fc_seq *fc_exch_seq_send(struct fc_lport *lp,
++ struct fc_frame *fp,
++ void (*resp)(struct fc_seq *,
++ struct fc_frame *fp,
++ void *arg),
++ void *resp_arg, u32 timer_msec,
++ u32 sid, u32 did, u32 f_ctl)
++{
++ struct fc_exch *ep;
++ struct fc_seq *sp = NULL;
++ struct fc_frame_header *fh;
++ u16 fill;
++
++ ep = lp->tt.exch_get(lp, fp);
++ if (!ep) {
++ fc_frame_free(fp);
++ return NULL;
++ }
++ ep->esb_stat |= ESB_ST_SEQ_INIT;
++ fc_exch_set_addr(ep, sid, did);
++ ep->resp = resp;
++ ep->resp_arg = resp_arg;
++ ep->r_a_tov = FC_DEF_R_A_TOV;
++ ep->lp = lp;
++ sp = &ep->seq;
++ WARN_ON((sp->f_ctl & FC_FC_END_SEQ) != 0);
++
++ fr_sof(fp) = ep->class;
++ if (sp->cnt)
++ fr_sof(fp) = fc_sof_normal(ep->class);
++ fr_eof(fp) = FC_EOF_T;
++ if (fc_sof_needs_ack(ep->class))
++ fr_eof(fp) = FC_EOF_N;
++
++ fc_seq_fill_hdr(sp, fp);
++ /*
++ * Form f_ctl.
++ * The number of fill bytes to make the length a 4-byte multiple is
++ * the low order 2-bits of the f_ctl. The fill itself will have been
++ * cleared by the frame allocation.
++ * After this, the length will be even, as expected by the transport.
++ * Don't include the fill in the f_ctl saved in the sequence.
++ */
++ fill = fr_len(fp) & 3;
++ if (fill) {
++ fill = 4 - fill;
++ /* TODO, this may be a problem with fragmented skb */
++ skb_put(fp_skb(fp), fill);
++ }
++ f_ctl |= ep->f_ctl;
++ fh = fc_frame_header_get(fp);
++ hton24(fh->fh_f_ctl, f_ctl | fill);
++ fh->fh_seq_cnt = htons(sp->cnt++);
++
++ if (unlikely(lp->tt.frame_send(lp, fp)))
++ goto err;
++
++ spin_lock_bh(&ep->ex_lock);
++ if (timer_msec)
++ fc_exch_timer_set_locked(ep, timer_msec);
++ sp->f_ctl = f_ctl; /* save for possible abort */
++ ep->f_ctl &= ~FC_FC_FIRST_SEQ; /* not first seq */
++ ep->fh_type = fh->fh_type; /* save for possbile timeout handling */
++
++ if (f_ctl & FC_FC_SEQ_INIT)
++ ep->esb_stat &= ~ESB_ST_SEQ_INIT;
++ spin_unlock_bh(&ep->ex_lock);
++ return sp;
++err:
++ fc_exch_done(sp);
++ return NULL;
++}
++EXPORT_SYMBOL(fc_exch_seq_send);
++
++/*
++ * Receive a frame
++ */
++void fc_exch_recv(struct fc_lport *lp, struct fc_exch_mgr *mp,
++ struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ u32 f_ctl;
++
++ if (!lp || !mp || (lp->state == LPORT_ST_NONE)) {
++ FC_DBG("fc_lport or EM is not allocated and configured");
++ fc_frame_free(fp);
++ return;
++ }
++
++ /*
++ * If frame is marked invalid, just drop it.
++ */
++ f_ctl = ntoh24(fh->fh_f_ctl);
++ switch (fr_eof(fp)) {
++ case FC_EOF_T:
++ if (f_ctl & FC_FC_END_SEQ)
++ skb_trim(fp_skb(fp), fr_len(fp) - FC_FC_FILL(f_ctl));
++ /* fall through */
++ case FC_EOF_N:
++ if (fh->fh_type == FC_TYPE_BLS)
++ fc_exch_recv_bls(mp, fp);
++ else if ((f_ctl & (FC_FC_EX_CTX | FC_FC_SEQ_CTX)) ==
++ FC_FC_EX_CTX)
++ fc_exch_recv_seq_resp(mp, fp);
++ else if (f_ctl & FC_FC_SEQ_CTX)
++ fc_exch_recv_resp(mp, fp);
++ else
++ fc_exch_recv_req(lp, mp, fp);
++ break;
++ default:
++ FC_DBG("dropping invalid frame (eof %x)", fr_eof(fp));
++ fc_frame_free(fp);
++ break;
++ }
++}
++EXPORT_SYMBOL(fc_exch_recv);
++
++int fc_exch_init(struct fc_lport *lp)
++{
++ if (!lp->tt.exch_get) {
++ /*
++ * exch_put() should be NULL if
++ * exch_get() is NULL
++ */
++ WARN_ON(lp->tt.exch_put);
++ lp->tt.exch_get = fc_exch_get;
++ }
++
++ if (!lp->tt.seq_start_next)
++ lp->tt.seq_start_next = fc_seq_start_next;
++
++ if (!lp->tt.exch_seq_send)
++ lp->tt.exch_seq_send = fc_exch_seq_send;
++
++ if (!lp->tt.seq_send)
++ lp->tt.seq_send = fc_seq_send;
++
++ if (!lp->tt.seq_els_rsp_send)
++ lp->tt.seq_els_rsp_send = fc_seq_els_rsp_send;
++
++ if (!lp->tt.exch_done)
++ lp->tt.exch_done = fc_exch_done;
++
++ if (!lp->tt.exch_mgr_reset)
++ lp->tt.exch_mgr_reset = fc_exch_mgr_reset;
++
++ if (!lp->tt.seq_exch_abort)
++ lp->tt.seq_exch_abort = fc_seq_exch_abort;
++
++ if (!lp->tt.seq_get_xids)
++ lp->tt.seq_get_xids = fc_seq_get_xids;
++
++ if (!lp->tt.seq_set_rec_data)
++ lp->tt.seq_set_rec_data = fc_seq_set_rec_data;
++ return 0;
++}
++EXPORT_SYMBOL(fc_exch_init);
++
++int fc_setup_exch_mgr(void)
++{
++ fc_em_cachep = kmem_cache_create("libfc_em", sizeof(struct fc_exch),
++ 0, SLAB_HWCACHE_ALIGN, NULL);
++ if (!fc_em_cachep)
++ return -ENOMEM;
++ return 0;
++}
++
++void fc_destroy_exch_mgr(void)
++{
++ kmem_cache_destroy(fc_em_cachep);
++}
+diff --git a/drivers/scsi/libfc/fc_fcp.c b/drivers/scsi/libfc/fc_fcp.c
+new file mode 100644
+index 0000000..9402eba
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_fcp.c
+@@ -0,0 +1,2174 @@
++/*
++ * Copyright(c) 2007 Intel Corporation. All rights reserved.
++ * Copyright(c) 2008 Red Hat, Inc. All rights reserved.
++ * Copyright(c) 2008 Mike Christie
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <linux/spinlock.h>
++#include <linux/scatterlist.h>
++#include <linux/err.h>
++#include <linux/crc32.h>
++#include <linux/delay.h>
++
++#include <scsi/scsi_tcq.h>
++#include <scsi/scsi.h>
++#include <scsi/scsi_host.h>
++#include <scsi/scsi_device.h>
++#include <scsi/scsi_cmnd.h>
++
++#include <scsi/fc/fc_fc2.h>
++
++#include <scsi/libfc/libfc.h>
++
++int fc_fcp_debug;
++static struct kmem_cache *scsi_pkt_cachep;
++
++/* SRB state definitions */
++#define FC_SRB_FREE 0 /* cmd is free */
++#define FC_SRB_CMD_SENT (1 << 0) /* cmd has been sent */
++#define FC_SRB_RCV_STATUS (1 << 1) /* response has arrived */
++#define FC_SRB_ABORT_PENDING (1 << 2) /* cmd abort sent to device */
++#define FC_SRB_ABORTED (1 << 3) /* abort acknowleged */
++#define FC_SRB_DISCONTIG (1 << 4) /* non-sequential data recvd */
++#define FC_SRB_COMPL (1 << 5) /* fc_io_compl has been run */
++#define FC_SRB_FCP_PROCESSING_TMO (1 << 6) /* timer function processing */
++#define FC_SRB_NOMEM (1 << 7) /* dropped to out of mem */
++
++#define FC_SRB_READ (1 << 1)
++#define FC_SRB_WRITE (1 << 0)
++
++/*
++ * scsi request structure, one for each scsi request
++ */
++struct fc_fcp_pkt {
++ /*
++ * housekeeping stuff
++ */
++ struct fc_lport *lp; /* handle to hba struct */
++ u16 state; /* scsi_pkt state state */
++ u16 tgt_flags; /* target flags */
++ atomic_t ref_cnt; /* only used byr REC ELS */
++ spinlock_t scsi_pkt_lock; /* Must be taken before the host lock
++ * if both are held at the same time */
++ /*
++ * SCSI I/O related stuff
++ */
++ struct scsi_cmnd *cmd; /* scsi command pointer. set/clear
++ * under host lock */
++ struct list_head list; /* tracks queued commands. access under
++ * host lock */
++ /*
++ * timeout related stuff
++ */
++ struct timer_list timer; /* command timer */
++ struct completion tm_done;
++ int wait_for_comp;
++ unsigned long start_time; /* start jiffie */
++ unsigned long end_time; /* end jiffie */
++ unsigned long last_pkt_time; /* jiffies of last frame received */
++
++ /*
++ * scsi cmd and data transfer information
++ */
++ u32 data_len;
++ /*
++ * transport related veriables
++ */
++ struct fcp_cmnd cdb_cmd;
++ size_t xfer_len;
++ u32 xfer_contig_end; /* offset of end of contiguous xfer */
++ u16 max_payload; /* max payload size in bytes */
++
++ /*
++ * scsi/fcp return status
++ */
++ u32 io_status; /* SCSI result upper 24 bits */
++ u8 cdb_status;
++ u8 status_code; /* FCP I/O status */
++ /* bit 3 Underrun bit 2: overrun */
++ u8 scsi_comp_flags;
++ u32 req_flags; /* bit 0: read bit:1 write */
++ u32 scsi_resid; /* residule length */
++
++ struct fc_rport *rport; /* remote port pointer */
++ struct fc_seq *seq_ptr; /* current sequence pointer */
++ /*
++ * Error Processing
++ */
++ u8 recov_retry; /* count of recovery retries */
++ struct fc_seq *recov_seq; /* sequence for REC or SRR */
++};
++
++/*
++ * The SCp.ptr should be tested and set under the host lock. NULL indicates
++ * that the command has been retruned to the scsi layer.
++ */
++#define CMD_SP(Cmnd) ((struct fc_fcp_pkt *)(Cmnd)->SCp.ptr)
++#define CMD_ENTRY_STATUS(Cmnd) ((Cmnd)->SCp.have_data_in)
++#define CMD_COMPL_STATUS(Cmnd) ((Cmnd)->SCp.this_residual)
++#define CMD_SCSI_STATUS(Cmnd) ((Cmnd)->SCp.Status)
++#define CMD_RESID_LEN(Cmnd) ((Cmnd)->SCp.buffers_residual)
++
++struct fc_fcp_internal {
++ mempool_t *scsi_pkt_pool;
++ struct list_head scsi_pkt_queue;
++ u8 throttled;
++};
++
++#define fc_get_scsi_internal(x) ((struct fc_fcp_internal *)(x)->scsi_priv)
++
++/*
++ * function prototypes
++ * FC scsi I/O related functions
++ */
++static void fc_fcp_recv_data(struct fc_fcp_pkt *, struct fc_frame *);
++static void fc_fcp_recv(struct fc_seq *, struct fc_frame *, void *);
++static void fc_fcp_resp(struct fc_fcp_pkt *, struct fc_frame *);
++static void fc_fcp_complete(struct fc_fcp_pkt *);
++static void fc_tm_done(struct fc_seq *, struct fc_frame *, void *);
++static void fc_fcp_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp);
++static void fc_timeout_error(struct fc_fcp_pkt *);
++static int fc_fcp_send_cmd(struct fc_fcp_pkt *);
++static void fc_fcp_timeout(unsigned long data);
++static void fc_fcp_rec(struct fc_fcp_pkt *);
++static void fc_fcp_rec_error(struct fc_fcp_pkt *, struct fc_frame *);
++static void fc_fcp_rec_resp(struct fc_seq *, struct fc_frame *, void *);
++static void fc_io_compl(struct fc_fcp_pkt *);
++
++static void fc_fcp_srr(struct fc_fcp_pkt *, enum fc_rctl, u32);
++static void fc_fcp_srr_resp(struct fc_seq *, struct fc_frame *, void *);
++static void fc_fcp_srr_error(struct fc_fcp_pkt *, struct fc_frame *);
++
++/*
++ * command status codes
++ */
++#define FC_COMPLETE 0
++#define FC_CMD_ABORTED 1
++#define FC_CMD_RESET 2
++#define FC_CMD_PLOGO 3
++#define FC_SNS_RCV 4
++#define FC_TRANS_ERR 5
++#define FC_DATA_OVRRUN 6
++#define FC_DATA_UNDRUN 7
++#define FC_ERROR 8
++#define FC_HRD_ERROR 9
++#define FC_CMD_TIME_OUT 10
++
++/*
++ * Error recovery timeout values.
++ */
++#define FC_SCSI_ER_TIMEOUT (10 * HZ)
++#define FC_SCSI_TM_TOV (10 * HZ)
++#define FC_SCSI_REC_TOV (2 * HZ)
++#define FC_HOST_RESET_TIMEOUT (30 * HZ)
++
++#define FC_MAX_ERROR_CNT 5
++#define FC_MAX_RECOV_RETRY 3
++
++#define FC_FCP_DFLT_QUEUE_DEPTH 32
++
++/**
++ * fc_fcp_pkt_alloc - allocation routine for scsi_pkt packet
++ * @lp: fc lport struct
++ * @gfp: gfp flags for allocation
++ *
++ * This is used by upper layer scsi driver.
++ * Return Value : scsi_pkt structure or null on allocation failure.
++ * Context : call from process context. no locking required.
++ */
++static struct fc_fcp_pkt *fc_fcp_pkt_alloc(struct fc_lport *lp, gfp_t gfp)
++{
++ struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
++ struct fc_fcp_pkt *sp;
++
++ sp = mempool_alloc(si->scsi_pkt_pool, gfp);
++ if (sp) {
++ memset(sp, 0, sizeof(*sp));
++ sp->lp = lp;
++ atomic_set(&sp->ref_cnt, 1);
++ init_timer(&sp->timer);
++ INIT_LIST_HEAD(&sp->list);
++ }
++ return sp;
++}
++
++/**
++ * fc_fcp_pkt_release - release hold on scsi_pkt packet
++ * @sp: fcp packet struct
++ *
++ * This is used by upper layer scsi driver.
++ * Context : call from process and interrupt context.
++ * no locking required
++ */
++static void fc_fcp_pkt_release(struct fc_fcp_pkt *sp)
++{
++ if (atomic_dec_and_test(&sp->ref_cnt)) {
++ struct fc_fcp_internal *si = fc_get_scsi_internal(sp->lp);
++
++ mempool_free(sp, si->scsi_pkt_pool);
++ }
++}
++
++static void fc_fcp_pkt_hold(struct fc_fcp_pkt *sp)
++{
++ atomic_inc(&sp->ref_cnt);
++}
++
++/**
++ * fc_fcp_lock_pkt - lock a packet and get a ref to it.
++ * @fsp: fcp packet
++ *
++ * We should only return error if we return a command to scsi-ml before
++ * getting a response. This can happen in cases where we send a abort, but
++ * do not wait for the response and the abort and command can be passing
++ * each other on the wire/network-layer.
++ *
++ * Note: this function locks the packet and gets a reference to allow
++ * callers to call the completion function while the lock is held and
++ * not have to worry about the packets refcount.
++ *
++ * TODO: Maybe we should just have callers grab/release the lock and
++ * have a function that they call to verify the fsp and grab a ref if
++ * needed.
++ */
++static inline int fc_fcp_lock_pkt(struct fc_fcp_pkt *fsp)
++{
++ spin_lock_bh(&fsp->scsi_pkt_lock);
++ if (!fsp->cmd) {
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++ FC_DBG("Invalid scsi cmd pointer on fcp packet.\n");
++ return -EINVAL;
++ }
++
++ fc_fcp_pkt_hold(fsp);
++ return 0;
++}
++
++static inline void fc_fcp_unlock_pkt(struct fc_fcp_pkt *fsp)
++{
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++ fc_fcp_pkt_release(fsp);
++}
++
++static void fc_fcp_timer_set(struct fc_fcp_pkt *fsp, unsigned long delay)
++{
++ if (!(fsp->state & FC_SRB_COMPL))
++ mod_timer(&fsp->timer, jiffies + delay);
++}
++
++static int fc_fcp_send_abort(struct fc_fcp_pkt *fsp)
++{
++ if (!fsp->seq_ptr)
++ return -EINVAL;
++
++ fsp->state |= FC_SRB_ABORT_PENDING;
++ return fsp->lp->tt.seq_exch_abort(fsp->seq_ptr, 0);
++}
++
++/*
++ * Retry command.
++ * An abort isn't needed.
++ */
++static void fc_fcp_retry_cmd(struct fc_fcp_pkt *fsp)
++{
++ if (fsp->seq_ptr) {
++ fsp->lp->tt.exch_done(fsp->seq_ptr);
++ fsp->seq_ptr = NULL;
++ }
++
++ fsp->state &= ~FC_SRB_ABORT_PENDING;
++ fsp->io_status = SUGGEST_RETRY << 24;
++ fsp->status_code = FC_ERROR;
++ fc_fcp_complete(fsp);
++}
++
++/*
++ * Receive SCSI data from target.
++ * Called after receiving solicited data.
++ */
++static void fc_fcp_recv_data(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
++{
++ struct scsi_cmnd *sc = fsp->cmd;
++ struct fc_lport *lp = fsp->lp;
++ struct fcoe_dev_stats *sp;
++ struct fc_frame_header *fh;
++ size_t start_offset;
++ size_t offset;
++ u32 crc;
++ u32 copy_len = 0;
++ size_t len;
++ void *buf;
++ struct scatterlist *sg;
++ size_t remaining;
++
++ fh = fc_frame_header_get(fp);
++ offset = ntohl(fh->fh_parm_offset);
++ start_offset = offset;
++ len = fr_len(fp) - sizeof(*fh);
++ buf = fc_frame_payload_get(fp, 0);
++
++ if (offset + len > fsp->data_len) {
++ /*
++ * this should never happen
++ */
++ if ((fr_flags(fp) & FCPHF_CRC_UNCHECKED) &&
++ fc_frame_crc_check(fp))
++ goto crc_err;
++ if (fc_fcp_debug) {
++ FC_DBG("data received past end. "
++ "len %zx offset %zx "
++ "data_len %x\n", len, offset, fsp->data_len);
++ }
++ fc_fcp_retry_cmd(fsp);
++ return;
++ }
++ if (offset != fsp->xfer_len)
++ fsp->state |= FC_SRB_DISCONTIG;
++
++ crc = 0;
++ if (fr_flags(fp) & FCPHF_CRC_UNCHECKED)
++ crc = crc32(~0, (u8 *) fh, sizeof(*fh));
++
++ sg = scsi_sglist(sc);
++ remaining = len;
++
++ while (remaining > 0 && sg) {
++ size_t off;
++ void *page_addr;
++ size_t sg_bytes;
++
++ if (offset >= sg->length) {
++ offset -= sg->length;
++ sg = sg_next(sg);
++ continue;
++ }
++ sg_bytes = min(remaining, sg->length - offset);
++
++ /*
++ * The scatterlist item may be bigger than PAGE_SIZE,
++ * but we are limited to mapping PAGE_SIZE at a time.
++ */
++ off = offset + sg->offset;
++ sg_bytes = min(sg_bytes, (size_t)
++ (PAGE_SIZE - (off & ~PAGE_MASK)));
++ page_addr = kmap_atomic(sg_page(sg) + (off >> PAGE_SHIFT),
++ KM_SOFTIRQ0);
++ if (!page_addr)
++ break; /* XXX panic? */
++
++ if (fr_flags(fp) & FCPHF_CRC_UNCHECKED)
++ crc = crc32(crc, buf, sg_bytes);
++ memcpy((char *)page_addr + (off & ~PAGE_MASK), buf,
++ sg_bytes);
++
++ kunmap_atomic(page_addr, KM_SOFTIRQ0);
++ buf += sg_bytes;
++ offset += sg_bytes;
++ remaining -= sg_bytes;
++ copy_len += sg_bytes;
++ }
++
++ if (fr_flags(fp) & FCPHF_CRC_UNCHECKED) {
++ buf = fc_frame_payload_get(fp, 0);
++ if (len % 4) {
++ crc = crc32(crc, buf + len, 4 - (len % 4));
++ len += 4 - (len % 4);
++ }
++
++ if (~crc != le32_to_cpu(*(__le32 *)(buf + len))) {
++crc_err:
++ sp = lp->dev_stats[smp_processor_id()];
++ sp->ErrorFrames++;
++ if (sp->InvalidCRCCount++ < 5)
++ FC_DBG("CRC error on data frame\n");
++ /*
++ * Assume the frame is total garbage.
++ * We may have copied it over the good part
++ * of the buffer.
++ * If so, we need to retry the entire operation.
++ * Otherwise, ignore it.
++ */
++ if (fsp->state & FC_SRB_DISCONTIG)
++ fc_fcp_retry_cmd(fsp);
++ return;
++ }
++ }
++
++ if (fsp->xfer_contig_end == start_offset)
++ fsp->xfer_contig_end += copy_len;
++ fsp->xfer_len += copy_len;
++
++ /*
++ * In the very rare event that this data arrived after the response
++ * and completes the transfer, call the completion handler.
++ */
++ if (unlikely(fsp->state & FC_SRB_RCV_STATUS) &&
++ fsp->xfer_len == fsp->data_len - fsp->scsi_resid)
++ fc_fcp_complete(fsp);
++}
++
++/*
++ * Send SCSI data to target.
++ * Called after receiving a Transfer Ready data descriptor.
++ */
++static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *sp,
++ size_t offset, size_t len,
++ struct fc_frame *oldfp, int sg_supp)
++{
++ struct scsi_cmnd *sc;
++ struct scatterlist *sg;
++ struct fc_frame *fp = NULL;
++ struct fc_lport *lp = fsp->lp;
++ size_t remaining;
++ size_t mfs;
++ size_t tlen;
++ size_t sg_bytes;
++ size_t frame_offset;
++ int error;
++ void *data = NULL;
++ void *page_addr;
++ int using_sg = sg_supp;
++ u32 f_ctl;
++
++ if (unlikely(offset + len > fsp->data_len)) {
++ /*
++ * this should never happen
++ */
++ if (fc_fcp_debug) {
++ FC_DBG("xfer-ready past end. len %zx offset %zx\n",
++ len, offset);
++ }
++ fc_fcp_send_abort(fsp);
++ return 0;
++ } else if (offset != fsp->xfer_len) {
++ /*
++ * Out of Order Data Request - no problem, but unexpected.
++ */
++ if (fc_fcp_debug) {
++ FC_DBG("xfer-ready non-contiguous. "
++ "len %zx offset %zx\n", len, offset);
++ }
++ }
++ mfs = fsp->max_payload;
++ WARN_ON(mfs > FC_MAX_PAYLOAD);
++ WARN_ON(mfs < FC_MIN_MAX_PAYLOAD);
++ if (mfs > 512)
++ mfs &= ~(512 - 1); /* round down to block size */
++ WARN_ON(mfs < FC_MIN_MAX_PAYLOAD); /* won't go below 256 */
++ WARN_ON(len <= 0);
++ sc = fsp->cmd;
++
++ remaining = len;
++ frame_offset = offset;
++ tlen = 0;
++ sp = lp->tt.seq_start_next(sp);
++ f_ctl = FC_FC_REL_OFF;
++ WARN_ON(!sp);
++
++ /*
++ * If a get_page()/put_page() will fail, don't use sg lists
++ * in the fc_frame structure.
++ *
++ * The put_page() may be long after the I/O has completed
++ * in the case of FCoE, since the network driver does it
++ * via free_skb(). See the test in free_pages_check().
++ *
++ * Test this case with 'dd </dev/zero >/dev/st0 bs=64k'.
++ */
++ if (using_sg) {
++ for (sg = scsi_sglist(sc); sg; sg = sg_next(sg)) {
++ if (page_count(sg_page(sg)) == 0 ||
++ (sg_page(sg)->flags & (1 << PG_lru |
++ 1 << PG_private |
++ 1 << PG_locked |
++ 1 << PG_active |
++ 1 << PG_slab |
++ 1 << PG_swapcache |
++ 1 << PG_writeback |
++ 1 << PG_reserved |
++ 1 << PG_buddy))) {
++ using_sg = 0;
++ break;
++ }
++ }
++ }
++ sg = scsi_sglist(sc);
++
++ while (remaining > 0 && sg) {
++ if (offset >= sg->length) {
++ offset -= sg->length;
++ sg = sg_next(sg);
++ continue;
++ }
++ if (!fp) {
++ tlen = min(mfs, remaining);
++
++ /*
++ * TODO. Temporary workaround. fc_seq_send() can't
++ * handle odd lengths in non-linear skbs.
++ * This will be the final fragment only.
++ */
++ if (tlen % 4)
++ using_sg = 0;
++ if (using_sg) {
++ fp = _fc_frame_alloc(lp, 0);
++ if (!fp)
++ return -ENOMEM;
++ } else {
++ fp = fc_frame_alloc(lp, tlen);
++ if (!fp)
++ return -ENOMEM;
++
++ data = (void *)(fr_hdr(fp)) +
++ sizeof(struct fc_frame_header);
++ }
++ fc_frame_setup(fp, FC_RCTL_DD_SOL_DATA, FC_TYPE_FCP);
++ fc_frame_set_offset(fp, frame_offset);
++ }
++ sg_bytes = min(tlen, sg->length - offset);
++ if (using_sg) {
++ WARN_ON(skb_shinfo(fp_skb(fp))->nr_frags >
++ FC_FRAME_SG_LEN);
++ get_page(sg_page(sg));
++ skb_fill_page_desc(fp_skb(fp),
++ skb_shinfo(fp_skb(fp))->nr_frags,
++ sg_page(sg), sg->offset + offset,
++ sg_bytes);
++ fp_skb(fp)->data_len += sg_bytes;
++ fr_len(fp) += sg_bytes;
++ fp_skb(fp)->truesize += PAGE_SIZE;
++ } else {
++ size_t off = offset + sg->offset;
++
++ /*
++ * The scatterlist item may be bigger than PAGE_SIZE,
++ * but we must not cross pages inside the kmap.
++ */
++ sg_bytes = min(sg_bytes, (size_t) (PAGE_SIZE -
++ (off & ~PAGE_MASK)));
++ page_addr = kmap_atomic(sg_page(sg) +
++ (off >> PAGE_SHIFT),
++ KM_SOFTIRQ0);
++ memcpy(data, (char *)page_addr + (off & ~PAGE_MASK),
++ sg_bytes);
++ kunmap_atomic(page_addr, KM_SOFTIRQ0);
++ data += sg_bytes;
++ }
++ offset += sg_bytes;
++ frame_offset += sg_bytes;
++ tlen -= sg_bytes;
++ remaining -= sg_bytes;
++
++ if (remaining == 0) {
++ /*
++ * Send a request sequence with
++ * transfer sequence initiative.
++ */
++ f_ctl |= FC_FC_SEQ_INIT | FC_FC_END_SEQ;
++ error = lp->tt.seq_send(lp, sp, fp, f_ctl);
++ } else if (tlen == 0) {
++ /*
++ * send fragment using for a sequence.
++ */
++ error = lp->tt.seq_send(lp, sp, fp, f_ctl);
++ } else {
++ continue;
++ }
++ fp = NULL;
++
++ if (error) {
++ WARN_ON(1); /* send error should be rare */
++ fc_fcp_retry_cmd(fsp);
++ return 0;
++ }
++ }
++ fsp->xfer_len += len; /* premature count? */
++ return 0;
++}
++
++static void fc_fcp_abts_resp(struct fc_fcp_pkt *fsp, struct fc_frame_header *fh)
++{
++ /*
++ * we will let the command timeout and scsi-ml escalate if
++ * the abort was rejected
++ */
++ if (fh->fh_r_ctl == FC_RCTL_BA_ACC) {
++ fsp->state |= FC_SRB_ABORTED;
++ fsp->state &= ~FC_SRB_ABORT_PENDING;
++
++ if (fsp->wait_for_comp)
++ complete(&fsp->tm_done);
++ else
++ fc_fcp_complete(fsp);
++ }
++}
++
++/*
++ * fc_fcp_reduce_can_queue - drop can_queue
++ * @lp: lport to drop queueing for
++ *
++ * If we are getting memory allocation failures, then we may
++ * be trying to execute too many commands. We let the running
++ * commands complete or timeout, then try again with a reduced
++ * can_queue. Eventually we will hit the point where we run
++ * on all reserved structs.
++ */
++static void fc_fcp_reduce_can_queue(struct fc_lport *lp)
++{
++ struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
++ unsigned long flags;
++ int can_queue;
++
++ spin_lock_irqsave(lp->host->host_lock, flags);
++ if (si->throttled)
++ goto done;
++ si->throttled = 1;
++
++ can_queue = lp->host->can_queue;
++ can_queue >>= 1;
++ if (!can_queue)
++ can_queue = 1;
++ lp->host->can_queue = can_queue;
++ shost_printk(KERN_ERR, lp->host, "Could not allocate frame.\n"
++ "Reducing can_queue to %d.\n", can_queue);
++done:
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++}
++
++/*
++ * exch mgr calls this routine to process scsi
++ * exchanges.
++ *
++ * Return : None
++ * Context : called from Soft IRQ context
++ * can not called holding list lock
++ */
++static void fc_fcp_recv(struct fc_seq *sp, struct fc_frame *fp, void *arg)
++{
++ struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)arg;
++ struct fc_lport *lp;
++ struct fc_frame_header *fh;
++ struct fc_data_desc *dd;
++ u8 r_ctl;
++ int rc = 0;
++
++ if (IS_ERR(fp))
++ goto errout;
++
++ fh = fc_frame_header_get(fp);
++ r_ctl = fh->fh_r_ctl;
++ lp = fsp->lp;
++
++ if (!(lp->state & LPORT_ST_READY))
++ goto out;
++ if (fc_fcp_lock_pkt(fsp))
++ goto out;
++ fsp->last_pkt_time = jiffies;
++
++ if (fh->fh_type == FC_TYPE_BLS) {
++ fc_fcp_abts_resp(fsp, fh);
++ goto unlock;
++ }
++
++ if (fsp->state & (FC_SRB_ABORTED | FC_SRB_ABORT_PENDING))
++ goto unlock;
++
++ if (r_ctl == FC_RCTL_DD_DATA_DESC) {
++ /*
++ * received XFER RDY from the target
++ * need to send data to the target
++ */
++ WARN_ON(fr_flags(fp) & FCPHF_CRC_UNCHECKED);
++ dd = fc_frame_payload_get(fp, sizeof(*dd));
++ WARN_ON(!dd);
++
++ rc = fc_fcp_send_data(fsp, sp,
++ (size_t) ntohl(dd->dd_offset),
++ (size_t) ntohl(dd->dd_len), fp,
++ lp->capabilities & TRANS_C_SG);
++ if (!rc)
++ lp->tt.seq_set_rec_data(sp, fsp->xfer_len);
++ else if (rc == -ENOMEM)
++ fsp->state |= FC_SRB_NOMEM;
++ } else if (r_ctl == FC_RCTL_DD_SOL_DATA) {
++ /*
++ * received a DATA frame
++ * next we will copy the data to the system buffer
++ */
++ WARN_ON(fr_len(fp) < sizeof(*fh)); /* len may be 0 */
++ fc_fcp_recv_data(fsp, fp);
++ lp->tt.seq_set_rec_data(sp, fsp->xfer_contig_end);
++ } else if (r_ctl == FC_RCTL_DD_CMD_STATUS) {
++ WARN_ON(fr_flags(fp) & FCPHF_CRC_UNCHECKED);
++
++ fc_fcp_resp(fsp, fp);
++ } else {
++ FC_DBG("unexpected frame. r_ctl %x\n", r_ctl);
++ }
++unlock:
++ fc_fcp_unlock_pkt(fsp);
++out:
++ fc_frame_free(fp);
++errout:
++ if (IS_ERR(fp))
++ fc_fcp_error(fsp, fp);
++ else if (rc == -ENOMEM)
++ fc_fcp_reduce_can_queue(lp);
++}
++
++static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
++{
++ struct fc_frame_header *fh;
++ struct fcp_resp *fc_rp;
++ struct fcp_resp_ext *rp_ex;
++ struct fcp_resp_rsp_info *fc_rp_info;
++ u32 plen;
++ u32 expected_len;
++ u32 respl = 0;
++ u32 snsl = 0;
++ u8 flags = 0;
++
++ plen = fr_len(fp);
++ fh = (struct fc_frame_header *)fr_hdr(fp);
++ if (unlikely(plen < sizeof(*fh) + sizeof(*fc_rp)))
++ goto len_err;
++ plen -= sizeof(*fh);
++ fc_rp = (struct fcp_resp *)(fh + 1);
++ fsp->cdb_status = fc_rp->fr_status;
++ flags = fc_rp->fr_flags;
++ fsp->scsi_comp_flags = flags;
++ expected_len = fsp->data_len;
++
++ if (unlikely((flags & ~FCP_CONF_REQ) || fc_rp->fr_status)) {
++ rp_ex = (void *)(fc_rp + 1);
++ if (flags & (FCP_RSP_LEN_VAL | FCP_SNS_LEN_VAL)) {
++ if (plen < sizeof(*fc_rp) + sizeof(*rp_ex))
++ goto len_err;
++ fc_rp_info = (struct fcp_resp_rsp_info *)(rp_ex + 1);
++ if (flags & FCP_RSP_LEN_VAL) {
++ respl = ntohl(rp_ex->fr_rsp_len);
++ if (respl != sizeof(*fc_rp_info))
++ goto len_err;
++ if (fsp->wait_for_comp) {
++ /* Abuse cdb_status for rsp code */
++ fsp->cdb_status = fc_rp_info->rsp_code;
++ complete(&fsp->tm_done);
++ /*
++ * tmfs will not have any scsi cmd so
++ * exit here
++ */
++ return;
++ } else
++ goto err;
++ }
++ if (flags & FCP_SNS_LEN_VAL) {
++ snsl = ntohl(rp_ex->fr_sns_len);
++ if (snsl > SCSI_SENSE_BUFFERSIZE)
++ snsl = SCSI_SENSE_BUFFERSIZE;
++ memcpy(fsp->cmd->sense_buffer,
++ (char *)fc_rp_info + respl, snsl);
++ }
++ }
++ if (flags & (FCP_RESID_UNDER | FCP_RESID_OVER)) {
++ if (plen < sizeof(*fc_rp) + sizeof(rp_ex->fr_resid))
++ goto len_err;
++ if (flags & FCP_RESID_UNDER) {
++ fsp->scsi_resid = ntohl(rp_ex->fr_resid);
++ /*
++ * The cmnd->underflow is the minimum number of
++ * bytes that must be transfered for this
++ * command. Provided a sense condition is not
++ * present, make sure the actual amount
++ * transferred is at least the underflow value
++ * or fail.
++ */
++ if (!(flags & FCP_SNS_LEN_VAL) &&
++ (fc_rp->fr_status == 0) &&
++ (scsi_bufflen(fsp->cmd) -
++ fsp->scsi_resid) < fsp->cmd->underflow)
++ goto err;
++ expected_len -= fsp->scsi_resid;
++ } else {
++ fsp->status_code = FC_ERROR;
++ }
++ }
++ }
++ fsp->state |= FC_SRB_RCV_STATUS;
++
++ /*
++ * Check for missing or extra data frames.
++ */
++ if (unlikely(fsp->xfer_len != expected_len)) {
++ if (fsp->xfer_len < expected_len) {
++ /*
++ * Some data may be queued locally,
++ * Wait a at least one jiffy to see if it is delivered.
++ * If this expires without data, we may do SRR.
++ */
++ fc_fcp_timer_set(fsp, 2);
++ return;
++ }
++ fsp->status_code = FC_DATA_OVRRUN;
++ FC_DBG("tgt %6x xfer len %zx greater than expected len %x. "
++ "data len %x\n",
++ fsp->rport->port_id,
++ fsp->xfer_len, expected_len, fsp->data_len);
++ }
++ fc_fcp_complete(fsp);
++ return;
++
++len_err:
++ FC_DBG("short FCP response. flags 0x%x len %u respl %u snsl %u\n",
++ flags, fr_len(fp), respl, snsl);
++err:
++ fsp->status_code = FC_ERROR;
++ fc_fcp_complete(fsp);
++}
++
++/**
++ * fc_fcp_complete - complete processing of a fcp packet
++ * @fsp: fcp packet
++ *
++ * This function may sleep if a timer is pending. The packet lock must be
++ * held, and the host lock must not be held.
++ */
++static void fc_fcp_complete(struct fc_fcp_pkt *fsp)
++{
++ struct fc_lport *lp = fsp->lp;
++ struct fc_seq *sp;
++ u32 f_ctl;
++
++ if (fsp->state & FC_SRB_ABORT_PENDING)
++ return;
++
++ if (fsp->state & FC_SRB_ABORTED) {
++ if (!fsp->status_code)
++ fsp->status_code = FC_CMD_ABORTED;
++ } else {
++ /*
++ * Test for transport underrun, independent of response
++ * underrun status.
++ */
++ if (fsp->xfer_len < fsp->data_len && !fsp->io_status &&
++ (!(fsp->scsi_comp_flags & FCP_RESID_UNDER) ||
++ fsp->xfer_len < fsp->data_len - fsp->scsi_resid)) {
++ fsp->status_code = FC_DATA_UNDRUN;
++ fsp->io_status = SUGGEST_RETRY << 24;
++ }
++ }
++
++ sp = fsp->seq_ptr;
++ if (sp) {
++ fsp->seq_ptr = NULL;
++ if (unlikely(fsp->scsi_comp_flags & FCP_CONF_REQ)) {
++ struct fc_frame *conf_frame;
++ struct fc_seq *csp;
++
++ csp = lp->tt.seq_start_next(sp);
++ conf_frame = fc_frame_alloc(fsp->lp, 0);
++ if (conf_frame) {
++ fc_frame_setup(conf_frame,
++ FC_RCTL_DD_SOL_CTL, FC_TYPE_FCP);
++ f_ctl = FC_FC_SEQ_INIT;
++ f_ctl |= FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ lp->tt.seq_send(lp, csp, conf_frame, f_ctl);
++ }
++ }
++ lp->tt.exch_done(sp);
++ }
++ fc_io_compl(fsp);
++}
++
++static void fc_fcp_cleanup_cmd(struct fc_fcp_pkt *fsp, int error)
++{
++ struct fc_lport *lp = fsp->lp;
++
++ if (fsp->seq_ptr) {
++ lp->tt.exch_done(fsp->seq_ptr);
++ fsp->seq_ptr = NULL;
++ }
++ fsp->status_code = error;
++}
++
++/**
++ * fc_fcp_cleanup_each_cmd - run fn on each active command
++ * @lp: logical port
++ * @id: target id
++ * @lun: lun
++ * @error: fsp status code
++ *
++ * If lun or id is -1, they are ignored.
++ */
++static void fc_fcp_cleanup_each_cmd(struct fc_lport *lp, unsigned int id,
++ unsigned int lun, int error)
++{
++ struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
++ struct fc_fcp_pkt *fsp;
++ struct scsi_cmnd *sc_cmd;
++ unsigned long flags;
++
++ spin_lock_irqsave(lp->host->host_lock, flags);
++restart:
++ list_for_each_entry(fsp, &si->scsi_pkt_queue, list) {
++ sc_cmd = fsp->cmd;
++ if (id != -1 && scmd_id(sc_cmd) != id)
++ continue;
++
++ if (lun != -1 && sc_cmd->device->lun != lun)
++ continue;
++
++ fc_fcp_pkt_hold(fsp);
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++
++ if (!fc_fcp_lock_pkt(fsp)) {
++ fc_fcp_cleanup_cmd(fsp, error);
++ fc_io_compl(fsp);
++ fc_fcp_unlock_pkt(fsp);
++ }
++
++ fc_fcp_pkt_release(fsp);
++ spin_lock_irqsave(lp->host->host_lock, flags);
++ /*
++ * while we dropped the lock multiple pkts could
++ * have been released, so we have to start over.
++ */
++ goto restart;
++ }
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++}
++
++static void fc_fcp_abort_io(struct fc_lport *lp)
++{
++ fc_fcp_cleanup_each_cmd(lp, -1, -1, FC_HRD_ERROR);
++}
++
++/**
++ * fc_fcp_pkt_send - send a fcp packet to the lower level.
++ * @lp: fc lport
++ * @fsp: fc packet.
++ *
++ * This is called by upper layer protocol.
++ * Return : zero for success and -1 for failure
++ * Context : called from queuecommand which can be called from process
++ * or scsi soft irq.
++ * Locks : called with the host lock and irqs disabled.
++ */
++static int fc_fcp_pkt_send(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
++{
++ struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
++ int rc;
++
++ fsp->cmd->SCp.ptr = (char *)fsp;
++ fsp->cdb_cmd.fc_dl = htonl(fsp->data_len);
++ fsp->cdb_cmd.fc_flags = fsp->req_flags & ~FCP_CFL_LEN_MASK;
++
++ int_to_scsilun(fsp->cmd->device->lun,
++ (struct scsi_lun *)fsp->cdb_cmd.fc_lun);
++ memcpy(fsp->cdb_cmd.fc_cdb, fsp->cmd->cmnd, fsp->cmd->cmd_len);
++ list_add_tail(&fsp->list, &si->scsi_pkt_queue);
++
++ spin_unlock_irq(lp->host->host_lock);
++ rc = fc_fcp_send_cmd(fsp);
++ spin_lock_irq(lp->host->host_lock);
++ if (rc)
++ list_del(&fsp->list);
++
++ return rc;
++}
++
++static int fc_fcp_send_cmd(struct fc_fcp_pkt *fsp)
++{
++ struct fc_lport *lp;
++ struct fc_frame *fp;
++ struct fc_seq *sp;
++ struct fc_rport *rport;
++ struct fc_rport_libfc_priv *rp;
++ int rc = 0;
++
++ if (fc_fcp_lock_pkt(fsp))
++ return 0;
++
++ if (fsp->state & FC_SRB_COMPL)
++ goto unlock;
++
++ lp = fsp->lp;
++ fp = fc_frame_alloc(lp, sizeof(fsp->cdb_cmd));
++ if (!fp) {
++ rc = -1;
++ goto unlock;
++ }
++
++ memcpy(fc_frame_payload_get(fp, sizeof(fsp->cdb_cmd)),
++ &fsp->cdb_cmd, sizeof(fsp->cdb_cmd));
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CMD, FC_TYPE_FCP);
++ fc_frame_set_offset(fp, 0);
++ rport = fsp->rport;
++ fsp->max_payload = rport->maxframe_size;
++ rp = rport->dd_data;
++ sp = lp->tt.exch_seq_send(lp, fp,
++ fc_fcp_recv,
++ fsp, 0,
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++ if (!sp) {
++ fc_frame_free(fp);
++ rc = -1;
++ goto unlock;
++ }
++ fsp->seq_ptr = sp;
++
++ setup_timer(&fsp->timer, fc_fcp_timeout, (unsigned long)fsp);
++ fc_fcp_timer_set(fsp,
++ (fsp->tgt_flags & FC_RP_FLAGS_REC_SUPPORTED) ?
++ FC_SCSI_REC_TOV : FC_SCSI_ER_TIMEOUT);
++unlock:
++ fc_fcp_unlock_pkt(fsp);
++ return rc;
++}
++
++/*
++ * transport error handler
++ */
++static void fc_fcp_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
++{
++ int error = PTR_ERR(fp);
++
++ if (fc_fcp_lock_pkt(fsp))
++ return;
++
++ switch (error) {
++ case -FC_EX_CLOSED:
++ fc_fcp_retry_cmd(fsp);
++ goto unlock;
++ default:
++ FC_DBG("unknown error %ld\n", PTR_ERR(fp));
++ }
++ /*
++ * clear abort pending, because the lower layer
++ * decided to force completion.
++ */
++ fsp->state &= ~FC_SRB_ABORT_PENDING;
++ fsp->status_code = FC_CMD_PLOGO;
++ fc_fcp_complete(fsp);
++unlock:
++ fc_fcp_unlock_pkt(fsp);
++}
++
++/*
++ * Scsi abort handler- calls to send an abort
++ * and then wait for abort completion
++ */
++static int fc_fcp_pkt_abort(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
++{
++ int rc = FAILED;
++
++ if (fc_fcp_send_abort(fsp))
++ return FAILED;
++
++ init_completion(&fsp->tm_done);
++ fsp->wait_for_comp = 1;
++
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++ rc = wait_for_completion_timeout(&fsp->tm_done, FC_SCSI_TM_TOV);
++ spin_lock_bh(&fsp->scsi_pkt_lock);
++ fsp->wait_for_comp = 0;
++
++ if (!rc) {
++ FC_DBG("target abort cmd failed\n");
++ rc = FAILED;
++ } else if (fsp->state & FC_SRB_ABORTED) {
++ FC_DBG("target abort cmd passed\n");
++ rc = SUCCESS;
++ fc_fcp_complete(fsp);
++ }
++
++ return rc;
++}
++
++/*
++ * Retry LUN reset after resource allocation failed.
++ */
++static void fc_lun_reset_send(unsigned long data)
++{
++ struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)data;
++ const size_t len = sizeof(fsp->cdb_cmd);
++ struct fc_lport *lp = fsp->lp;
++ struct fc_frame *fp;
++ struct fc_seq *sp;
++ struct fc_rport *rport;
++ struct fc_rport_libfc_priv *rp;
++
++ spin_lock_bh(&fsp->scsi_pkt_lock);
++ if (fsp->state & FC_SRB_COMPL)
++ goto unlock;
++
++ fp = fc_frame_alloc(lp, len);
++ if (!fp)
++ goto retry;
++ memcpy(fc_frame_payload_get(fp, len), &fsp->cdb_cmd, len);
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CMD, FC_TYPE_FCP);
++ fc_frame_set_offset(fp, 0);
++ rport = fsp->rport;
++ rp = rport->dd_data;
++ sp = lp->tt.exch_seq_send(lp, fp,
++ fc_tm_done,
++ fsp, 0,
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++
++ if (sp) {
++ fsp->seq_ptr = sp;
++ goto unlock;
++ }
++ /*
++ * Exchange or frame allocation failed. Set timer and retry.
++ */
++ fc_frame_free(fp);
++retry:
++ setup_timer(&fsp->timer, fc_lun_reset_send, (unsigned long)fsp);
++ fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
++unlock:
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++}
++
++/*
++ * Scsi device reset handler- send a LUN RESET to the device
++ * and wait for reset reply
++ */
++static int fc_lun_reset(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
++ unsigned int id, unsigned int lun)
++{
++ int rc;
++
++ fsp->cdb_cmd.fc_dl = htonl(fsp->data_len);
++ fsp->cdb_cmd.fc_tm_flags = FCP_TMF_LUN_RESET;
++ int_to_scsilun(lun, (struct scsi_lun *)fsp->cdb_cmd.fc_lun);
++
++ fsp->wait_for_comp = 1;
++ init_completion(&fsp->tm_done);
++
++ fc_lun_reset_send((unsigned long)fsp);
++
++ /*
++ * wait for completion of reset
++ * after that make sure all commands are terminated
++ */
++ rc = wait_for_completion_timeout(&fsp->tm_done, FC_SCSI_TM_TOV);
++
++ spin_lock_bh(&fsp->scsi_pkt_lock);
++ fsp->state |= FC_SRB_COMPL;
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++
++ del_timer_sync(&fsp->timer);
++
++ spin_lock_bh(&fsp->scsi_pkt_lock);
++ if (fsp->seq_ptr) {
++ /* TODO:
++ * if the exch resp function is running and trying to grab
++ * the scsi_pkt_lock, this could free the exch from under
++ * it and it could allow the fsp to be freed from under
++ * fc_tm_done.
++ */
++ lp->tt.exch_done(fsp->seq_ptr);
++ fsp->seq_ptr = NULL;
++ }
++ fsp->wait_for_comp = 0;
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++
++ if (!rc) {
++ FC_DBG("lun reset failed\n");
++ return FAILED;
++ }
++
++ /* cdb_status holds the tmf's rsp code */
++ if (fsp->cdb_status != FCP_TMF_CMPL)
++ return FAILED;
++
++ FC_DBG("lun reset to lun %u completed\n", lun);
++ fc_fcp_cleanup_each_cmd(lp, id, lun, FC_CMD_ABORTED);
++ return SUCCESS;
++}
++
++/*
++ * Task Managment response handler
++ */
++static void fc_tm_done(struct fc_seq *sp, struct fc_frame *fp, void *arg)
++{
++ struct fc_fcp_pkt *fsp = arg;
++ struct fc_frame_header *fh;
++
++ spin_lock_bh(&fsp->scsi_pkt_lock);
++ if (IS_ERR(fp)) {
++ /*
++ * If there is an error just let it timeout or wait
++ * for TMF to be aborted if it timedout.
++ *
++ * scsi-eh will escalate for when either happens.
++ */
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++ return;
++ }
++
++ /*
++ * raced with eh timeout handler.
++ *
++ * TODO: If this happens we could be freeing the fsp right now and
++ * would oops. Next patches will fix this race.
++ */
++ if ((fsp->state & FC_SRB_COMPL) || !fsp->seq_ptr ||
++ !fsp->wait_for_comp) {
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++ return;
++ }
++
++ fh = fc_frame_header_get(fp);
++ if (fh->fh_type != FC_TYPE_BLS)
++ fc_fcp_resp(fsp, fp);
++ fsp->seq_ptr = NULL;
++ fsp->lp->tt.exch_done(sp);
++ fc_frame_free(fp);
++ spin_unlock_bh(&fsp->scsi_pkt_lock);
++}
++
++static void fc_fcp_cleanup(struct fc_lport *lp)
++{
++ fc_fcp_cleanup_each_cmd(lp, -1, -1, FC_ERROR);
++}
++
++/*
++ * fc_fcp_timeout: called by OS timer function.
++ *
++ * The timer has been inactivated and must be reactivated if desired
++ * using fc_fcp_timer_set().
++ *
++ * Algorithm:
++ *
++ * If REC is supported, just issue it, and return. The REC exchange will
++ * complete or time out, and recovery can continue at that point.
++ *
++ * Otherwise, if the response has been received without all the data,
++ * it has been ER_TIMEOUT since the response was received.
++ *
++ * If the response has not been received,
++ * we see if data was received recently. If it has been, we continue waiting,
++ * otherwise, we abort the command.
++ */
++static void fc_fcp_timeout(unsigned long data)
++{
++ struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)data;
++ struct fc_rport *rport = fsp->rport;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++
++ if (fc_fcp_lock_pkt(fsp))
++ return;
++
++ if (fsp->state & FC_SRB_COMPL)
++ goto unlock;
++ fsp->state |= FC_SRB_FCP_PROCESSING_TMO;
++
++ if (rp->flags & FC_RP_FLAGS_REC_SUPPORTED)
++ fc_fcp_rec(fsp);
++ /* TODO: change this to time_before/after */
++ else if (jiffies - fsp->last_pkt_time < FC_SCSI_ER_TIMEOUT / 2)
++ fc_fcp_timer_set(fsp, FC_SCSI_ER_TIMEOUT);
++ else if (fsp->state & FC_SRB_RCV_STATUS)
++ fc_fcp_complete(fsp);
++ else
++ fc_timeout_error(fsp);
++
++ fsp->state &= ~FC_SRB_FCP_PROCESSING_TMO;
++unlock:
++ fc_fcp_unlock_pkt(fsp);
++}
++
++/*
++ * Send a REC ELS request
++ */
++static void fc_fcp_rec(struct fc_fcp_pkt *fsp)
++{
++ struct fc_lport *lp;
++ struct fc_seq *sp;
++ struct fc_frame *fp;
++ struct fc_els_rec *rec;
++ struct fc_rport *rport;
++ struct fc_rport_libfc_priv *rp;
++ u16 ox_id;
++ u16 rx_id;
++
++ lp = fsp->lp;
++ rport = fsp->rport;
++ rp = rport->dd_data;
++ sp = fsp->seq_ptr;
++ if (!sp || rp->rp_state != RPORT_ST_READY) {
++ fsp->status_code = FC_HRD_ERROR;
++ fsp->io_status = SUGGEST_RETRY << 24;
++ fc_fcp_complete(fsp);
++ return;
++ }
++ lp->tt.seq_get_xids(sp, &ox_id, &rx_id);
++ fp = fc_frame_alloc(lp, sizeof(*rec));
++ if (!fp)
++ goto retry;
++
++ rec = fc_frame_payload_get(fp, sizeof(*rec));
++ memset(rec, 0, sizeof(*rec));
++ rec->rec_cmd = ELS_REC;
++ hton24(rec->rec_s_id, lp->fid);
++ rec->rec_ox_id = htons(ox_id);
++ rec->rec_rx_id = htons(rx_id);
++
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ fc_frame_set_offset(fp, 0);
++ sp = lp->tt.exch_seq_send(lp, fp,
++ fc_fcp_rec_resp,
++ fsp, jiffies_to_msecs(FC_SCSI_REC_TOV),
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++
++ if (sp) {
++ fc_fcp_pkt_hold(fsp); /* hold while REC outstanding */
++ return;
++ } else
++ fc_frame_free(fp);
++retry:
++ if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
++ fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
++ else
++ fc_timeout_error(fsp);
++}
++
++/*
++ * Receive handler for REC ELS frame
++ * if it is a reject then let the scsi layer to handle
++ * the timeout. if it is a LS_ACC then if the io was not completed
++ * then set the timeout and return otherwise complete the exchange
++ * and tell the scsi layer to restart the I/O.
++ */
++static void fc_fcp_rec_resp(struct fc_seq *sp, struct fc_frame *fp, void *arg)
++{
++ struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)arg;
++ struct fc_els_rec_acc *recp;
++ struct fc_els_ls_rjt *rjt;
++ u32 e_stat;
++ u8 opcode;
++ u32 offset;
++ enum dma_data_direction data_dir;
++ enum fc_rctl r_ctl;
++ struct fc_rport_libfc_priv *rp;
++
++ if (IS_ERR(fp)) {
++ fc_fcp_rec_error(fsp, fp);
++ return;
++ }
++
++ if (fc_fcp_lock_pkt(fsp))
++ goto out;
++
++ fsp->recov_retry = 0;
++ opcode = fc_frame_payload_op(fp);
++ if (opcode == ELS_LS_RJT) {
++ rjt = fc_frame_payload_get(fp, sizeof(*rjt));
++ switch (rjt->er_reason) {
++ default:
++ if (fc_fcp_debug)
++ FC_DBG("device %x unexpected REC reject "
++ "reason %d expl %d\n",
++ fsp->rport->port_id, rjt->er_reason,
++ rjt->er_explan);
++ /* fall through */
++
++ case ELS_RJT_UNSUP:
++ if (fc_fcp_debug)
++ FC_DBG("device does not support REC\n");
++ rp = fsp->rport->dd_data;
++ rp->flags &= ~FC_RP_FLAGS_REC_SUPPORTED;
++ /* fall through */
++
++ case ELS_RJT_LOGIC:
++ case ELS_RJT_UNAB:
++ /*
++ * If no data transfer, the command frame got dropped
++ * so we just retry. If data was transferred, we
++ * lost the response but the target has no record,
++ * so we abort and retry.
++ */
++ if (rjt->er_explan == ELS_EXPL_OXID_RXID &&
++ fsp->xfer_len == 0) {
++ fc_fcp_retry_cmd(fsp);
++ break;
++ }
++ fc_timeout_error(fsp);
++ break;
++ }
++ } else if (opcode == ELS_LS_ACC) {
++ if (fsp->state & FC_SRB_ABORTED)
++ goto unlock_out;
++
++ data_dir = fsp->cmd->sc_data_direction;
++ recp = fc_frame_payload_get(fp, sizeof(*recp));
++ offset = ntohl(recp->reca_fc4value);
++ e_stat = ntohl(recp->reca_e_stat);
++
++ if (e_stat & ESB_ST_COMPLETE) {
++
++ /*
++ * The exchange is complete.
++ *
++ * For output, we must've lost the response.
++ * For input, all data must've been sent.
++ * We lost may have lost the response
++ * (and a confirmation was requested) and maybe
++ * some data.
++ *
++ * If all data received, send SRR
++ * asking for response. If partial data received,
++ * or gaps, SRR requests data at start of gap.
++ * Recovery via SRR relies on in-order-delivery.
++ */
++ if (data_dir == DMA_TO_DEVICE) {
++ r_ctl = FC_RCTL_DD_CMD_STATUS;
++ } else if (fsp->xfer_contig_end == offset) {
++ r_ctl = FC_RCTL_DD_CMD_STATUS;
++ } else {
++ offset = fsp->xfer_contig_end;
++ r_ctl = FC_RCTL_DD_SOL_DATA;
++ }
++ fc_fcp_srr(fsp, r_ctl, offset);
++ } else if (e_stat & ESB_ST_SEQ_INIT) {
++
++ /*
++ * The remote port has the initiative, so just
++ * keep waiting for it to complete.
++ */
++ fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
++ } else {
++
++ /*
++ * The exchange is incomplete, we have seq. initiative.
++ * Lost response with requested confirmation,
++ * lost confirmation, lost transfer ready or
++ * lost write data.
++ *
++ * For output, if not all data was received, ask
++ * for transfer ready to be repeated.
++ *
++ * If we received or sent all the data, send SRR to
++ * request response.
++ *
++ * If we lost a response, we may have lost some read
++ * data as well.
++ */
++ r_ctl = FC_RCTL_DD_SOL_DATA;
++ if (data_dir == DMA_TO_DEVICE) {
++ r_ctl = FC_RCTL_DD_CMD_STATUS;
++ if (offset < fsp->data_len)
++ r_ctl = FC_RCTL_DD_DATA_DESC;
++ } else if (offset == fsp->xfer_contig_end) {
++ r_ctl = FC_RCTL_DD_CMD_STATUS;
++ } else if (fsp->xfer_contig_end < offset) {
++ offset = fsp->xfer_contig_end;
++ }
++ fc_fcp_srr(fsp, r_ctl, offset);
++ }
++ }
++unlock_out:
++ fc_fcp_unlock_pkt(fsp);
++out:
++ fc_fcp_pkt_release(fsp); /* drop hold for outstanding REC */
++ fc_frame_free(fp);
++}
++
++/*
++ * Handle error response or timeout for REC exchange.
++ */
++static void fc_fcp_rec_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
++{
++ int error = PTR_ERR(fp);
++
++ if (fc_fcp_lock_pkt(fsp))
++ goto out;
++
++ switch (error) {
++ case -FC_EX_CLOSED:
++ fc_fcp_retry_cmd(fsp);
++ break;
++
++ default:
++ FC_DBG("REC %p fid %x error unexpected error %d\n",
++ fsp, fsp->rport->port_id, error);
++ fsp->status_code = FC_CMD_PLOGO;
++ /* fall through */
++
++ case -FC_EX_TIMEOUT:
++ /*
++ * Assume REC or LS_ACC was lost.
++ * The exchange manager will have aborted REC, so retry.
++ */
++ FC_DBG("REC fid %x error error %d retry %d/%d\n",
++ fsp->rport->port_id, error, fsp->recov_retry,
++ FC_MAX_RECOV_RETRY);
++ if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
++ fc_fcp_rec(fsp);
++ else
++ fc_timeout_error(fsp);
++ break;
++ }
++ fc_fcp_unlock_pkt(fsp);
++out:
++ fc_fcp_pkt_release(fsp); /* drop hold for outstanding REC */
++}
++
++/*
++ * Time out error routine:
++ * abort's the I/O close the exchange and
++ * send completion notification to scsi layer
++ */
++static void fc_timeout_error(struct fc_fcp_pkt *fsp)
++{
++ fsp->status_code = FC_CMD_TIME_OUT;
++ fsp->cdb_status = 0;
++ fsp->io_status = 0;
++ /*
++ * if this fails then we let the scsi command timer fire and
++ * scsi-ml escalate.
++ */
++ fc_fcp_send_abort(fsp);
++}
++
++/*
++ * Sequence retransmission request.
++ * This is called after receiving status but insufficient data, or
++ * when expecting status but the request has timed out.
++ */
++static void fc_fcp_srr(struct fc_fcp_pkt *fsp, enum fc_rctl r_ctl, u32 offset)
++{
++ struct fc_lport *lp = fsp->lp;
++ struct fc_rport *rport;
++ struct fc_rport_libfc_priv *rp;
++ struct fc_seq *sp;
++ struct fcp_srr *srr;
++ struct fc_frame *fp;
++ u8 cdb_op;
++ u16 ox_id;
++ u16 rx_id;
++
++ rport = fsp->rport;
++ rp = rport->dd_data;
++ cdb_op = fsp->cdb_cmd.fc_cdb[0];
++ lp->tt.seq_get_xids(fsp->seq_ptr, &ox_id, &rx_id);
++
++ if (!(rp->flags & FC_RP_FLAGS_RETRY) || rp->rp_state != RPORT_ST_READY)
++ goto retry; /* shouldn't happen */
++ fp = fc_frame_alloc(lp, sizeof(*srr));
++ if (!fp)
++ goto retry;
++
++ srr = fc_frame_payload_get(fp, sizeof(*srr));
++ memset(srr, 0, sizeof(*srr));
++ srr->srr_op = ELS_SRR;
++ srr->srr_ox_id = htons(ox_id);
++ srr->srr_rx_id = htons(rx_id);
++ srr->srr_r_ctl = r_ctl;
++ srr->srr_rel_off = htonl(offset);
++
++ fc_frame_setup(fp, FC_RCTL_ELS4_REQ, FC_TYPE_FCP);
++ fc_frame_set_offset(fp, 0);
++ sp = lp->tt.exch_seq_send(lp, fp,
++ fc_fcp_srr_resp,
++ fsp, jiffies_to_msecs(FC_SCSI_REC_TOV),
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++ if (!sp) {
++ fc_frame_free(fp);
++ goto retry;
++ }
++ fsp->recov_seq = sp;
++ fsp->xfer_len = offset;
++ fsp->xfer_contig_end = offset;
++ fsp->state &= ~FC_SRB_RCV_STATUS;
++ fc_fcp_pkt_hold(fsp); /* hold for outstanding SRR */
++ return;
++retry:
++ fc_fcp_retry_cmd(fsp);
++}
++
++/*
++ * Handle response from SRR.
++ */
++static void fc_fcp_srr_resp(struct fc_seq *sp, struct fc_frame *fp, void *arg)
++{
++ struct fc_fcp_pkt *fsp = arg;
++ struct fc_frame_header *fh;
++ u16 ox_id;
++ u16 rx_id;
++
++ if (IS_ERR(fp)) {
++ fc_fcp_srr_error(fsp, fp);
++ return;
++ }
++
++ if (fc_fcp_lock_pkt(fsp))
++ goto out;
++
++ fh = fc_frame_header_get(fp);
++ /*
++ * BUG? fc_fcp_srr_error calls exch_done which would release
++ * the ep. But if fc_fcp_srr_error had got -FC_EX_TIMEOUT,
++ * then fc_exch_timeout would be sending an abort. The exch_done
++ * call by fc_fcp_srr_error would prevent fc_exch.c from seeing
++ * an abort response though.
++ */
++ if (fh->fh_type == FC_TYPE_BLS) {
++ fc_fcp_unlock_pkt(fsp);
++ return;
++ }
++
++ fsp->recov_seq = NULL;
++
++ fsp->lp->tt.seq_get_xids(fsp->seq_ptr, &ox_id, &rx_id);
++ switch (fc_frame_payload_op(fp)) {
++ case ELS_LS_ACC:
++ fsp->recov_retry = 0;
++ fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
++ break;
++ case ELS_LS_RJT:
++ default:
++ fc_timeout_error(fsp);
++ break;
++ }
++ fc_fcp_unlock_pkt(fsp);
++ fsp->lp->tt.exch_done(sp);
++out:
++ fc_frame_free(fp);
++ fc_fcp_pkt_release(fsp); /* drop hold for outstanding SRR */
++}
++
++static void fc_fcp_srr_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
++{
++ if (fc_fcp_lock_pkt(fsp))
++ goto out;
++ fsp->lp->tt.exch_done(fsp->recov_seq);
++ fsp->recov_seq = NULL;
++ switch (PTR_ERR(fp)) {
++ case -FC_EX_TIMEOUT:
++ if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
++ fc_fcp_rec(fsp);
++ else
++ fc_timeout_error(fsp);
++ break;
++ case -FC_EX_CLOSED: /* e.g., link failure */
++ /* fall through */
++ default:
++ fc_fcp_retry_cmd(fsp);
++ break;
++ }
++ fc_fcp_unlock_pkt(fsp);
++out:
++ fc_fcp_pkt_release(fsp); /* drop hold for outstanding SRR */
++}
++
++static inline int fc_fcp_lport_queue_ready(struct fc_lport *lp)
++{
++ /* lock ? */
++ return (lp->state == LPORT_ST_READY) && (lp->link_status & FC_LINK_UP);
++}
++
++/**
++ * fc_queuecommand - The queuecommand function of the scsi template
++ * @cmd: struct scsi_cmnd to be executed
++ * @done: Callback function to be called when cmd is completed
++ *
++ * this is the i/o strategy routine, called by the scsi layer
++ * this routine is called with holding the host_lock.
++ */
++int fc_queuecommand(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *))
++{
++ struct fc_lport *lp;
++ struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device));
++ struct fc_fcp_pkt *sp;
++ struct fc_rport_libfc_priv *rp;
++ int rval;
++ int rc = 0;
++ struct fcoe_dev_stats *stats;
++
++ lp = shost_priv(sc_cmd->device->host);
++
++ rval = fc_remote_port_chkready(rport);
++ if (rval) {
++ sc_cmd->result = rval;
++ done(sc_cmd);
++ goto out;
++ }
++
++ if (!*(struct fc_remote_port **)rport->dd_data) {
++ /*
++ * rport is transitioning from blocked/deleted to
++ * online
++ */
++ sc_cmd->result = DID_IMM_RETRY << 16;
++ done(sc_cmd);
++ goto out;
++ }
++
++ rp = rport->dd_data;
++
++ if (!fc_fcp_lport_queue_ready(lp)) {
++ rc = SCSI_MLQUEUE_HOST_BUSY;
++ goto out;
++ }
++
++ sp = fc_fcp_pkt_alloc(lp, GFP_ATOMIC);
++ if (sp == NULL) {
++ rc = SCSI_MLQUEUE_HOST_BUSY;
++ goto out;
++ }
++
++ /*
++ * build the libfc request pkt
++ */
++ sp->cmd = sc_cmd; /* save the cmd */
++ sp->lp = lp; /* save the softc ptr */
++ sp->rport = rport; /* set the remote port ptr */
++ sc_cmd->scsi_done = done;
++
++ /*
++ * set up the transfer length
++ */
++ sp->data_len = scsi_bufflen(sc_cmd);
++ sp->xfer_len = 0;
++
++ /*
++ * setup the data direction
++ */
++ stats = lp->dev_stats[smp_processor_id()];
++ if (sc_cmd->sc_data_direction == DMA_FROM_DEVICE) {
++ sp->req_flags = FC_SRB_READ;
++ stats->InputRequests++;
++ stats->InputMegabytes = sp->data_len;
++ } else if (sc_cmd->sc_data_direction == DMA_TO_DEVICE) {
++ sp->req_flags = FC_SRB_WRITE;
++ stats->OutputRequests++;
++ stats->OutputMegabytes = sp->data_len;
++ } else {
++ sp->req_flags = 0;
++ stats->ControlRequests++;
++ }
++
++ sp->tgt_flags = rp->flags;
++
++ init_timer(&sp->timer);
++ sp->timer.data = (unsigned long)sp;
++
++ /*
++ * send it to the lower layer
++ * if we get -1 return then put the request in the pending
++ * queue.
++ */
++ rval = fc_fcp_pkt_send(lp, sp);
++ if (rval != 0) {
++ sp->state = FC_SRB_FREE;
++ fc_fcp_pkt_release(sp);
++ rc = SCSI_MLQUEUE_HOST_BUSY;
++ }
++out:
++ return rc;
++}
++EXPORT_SYMBOL(fc_queuecommand);
++
++/**
++ * fc_io_compl - Handle responses for completed commands
++ * @sp: scsi packet
++ *
++ * Translates a error to a Linux SCSI error.
++ *
++ * The fcp packet lock must be held when calling.
++ */
++static void fc_io_compl(struct fc_fcp_pkt *sp)
++{
++ struct fc_fcp_internal *si;
++ struct scsi_cmnd *sc_cmd;
++ struct fc_lport *lp;
++ unsigned long flags;
++
++ sp->state |= FC_SRB_COMPL;
++ if (!(sp->state & FC_SRB_FCP_PROCESSING_TMO)) {
++ spin_unlock_bh(&sp->scsi_pkt_lock);
++ del_timer_sync(&sp->timer);
++ spin_lock_bh(&sp->scsi_pkt_lock);
++ }
++
++ lp = sp->lp;
++ si = fc_get_scsi_internal(lp);
++ spin_lock_irqsave(lp->host->host_lock, flags);
++ if (!sp->cmd) {
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++ return;
++ }
++
++ /*
++ * if a command timed out while we had to try and throttle IO
++ * and it is now getting cleaned up, then we are about to
++ * try again so clear the throttled flag incase we get more
++ * time outs.
++ */
++ if (si->throttled && sp->state & FC_SRB_NOMEM)
++ si->throttled = 0;
++
++ sc_cmd = sp->cmd;
++ sp->cmd = NULL;
++
++ if (!sc_cmd->SCp.ptr) {
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++ return;
++ }
++
++ CMD_SCSI_STATUS(sc_cmd) = sp->cdb_status;
++ switch (sp->status_code) {
++ case FC_COMPLETE:
++ if (sp->cdb_status == 0) {
++ /*
++ * good I/O status
++ */
++ sc_cmd->result = DID_OK << 16;
++ if (sp->scsi_resid)
++ CMD_RESID_LEN(sc_cmd) = sp->scsi_resid;
++ } else if (sp->cdb_status == QUEUE_FULL) {
++ struct scsi_device *tmp_sdev;
++ struct scsi_device *sdev = sc_cmd->device;
++
++ shost_for_each_device(tmp_sdev, sdev->host) {
++ if (tmp_sdev->id != sdev->id)
++ continue;
++
++ if (tmp_sdev->queue_depth > 1) {
++ scsi_track_queue_full(tmp_sdev,
++ tmp_sdev->
++ queue_depth - 1);
++ }
++ }
++ sc_cmd->result = (DID_OK << 16) | sp->cdb_status;
++ } else {
++ /*
++ * transport level I/O was ok but scsi
++ * has non zero status
++ */
++ sc_cmd->result = (DID_OK << 16) | sp->cdb_status;
++ }
++ break;
++ case FC_ERROR:
++ if (sp->io_status & (SUGGEST_RETRY << 24))
++ sc_cmd->result = DID_IMM_RETRY << 16;
++ else
++ sc_cmd->result = (DID_ERROR << 16) | sp->io_status;
++ break;
++ case FC_DATA_UNDRUN:
++ if (sp->cdb_status == 0) {
++ /*
++ * scsi status is good but transport level
++ * underrun. for read it should be an error??
++ */
++ sc_cmd->result = (DID_OK << 16) | sp->cdb_status;
++ } else {
++ /*
++ * scsi got underrun, this is an error
++ */
++ CMD_RESID_LEN(sc_cmd) = sp->scsi_resid;
++ sc_cmd->result = (DID_ERROR << 16) | sp->cdb_status;
++ }
++ break;
++ case FC_DATA_OVRRUN:
++ /*
++ * overrun is an error
++ */
++ sc_cmd->result = (DID_ERROR << 16) | sp->cdb_status;
++ break;
++ case FC_CMD_ABORTED:
++ sc_cmd->result = (DID_ABORT << 16) | sp->io_status;
++ break;
++ case FC_CMD_TIME_OUT:
++ sc_cmd->result = (DID_BUS_BUSY << 16) | sp->io_status;
++ break;
++ case FC_CMD_RESET:
++ sc_cmd->result = (DID_RESET << 16);
++ break;
++ case FC_HRD_ERROR:
++ sc_cmd->result = (DID_NO_CONNECT << 16);
++ break;
++ default:
++ sc_cmd->result = (DID_ERROR << 16);
++ break;
++ }
++
++ list_del(&sp->list);
++ sc_cmd->SCp.ptr = NULL;
++ sc_cmd->scsi_done(sc_cmd);
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++
++ /* release ref from initial allocation in queue command */
++ fc_fcp_pkt_release(sp);
++}
++
++/**
++ * fc_eh_abort - Abort a command...from scsi host template
++ * @sc_cmd: scsi command to abort
++ *
++ * send ABTS to the target device and wait for the response
++ * sc_cmd is the pointer to the command to be aborted.
++ */
++int fc_eh_abort(struct scsi_cmnd *sc_cmd)
++{
++ struct fc_fcp_pkt *sp;
++ struct fc_lport *lp;
++ int rc = FAILED;
++ unsigned long flags;
++
++ lp = shost_priv(sc_cmd->device->host);
++ if (lp->state != LPORT_ST_READY)
++ return rc;
++ else if (!(lp->link_status & FC_LINK_UP))
++ return rc;
++
++ spin_lock_irqsave(lp->host->host_lock, flags);
++ sp = CMD_SP(sc_cmd);
++ if (!sp) {
++ /* command completed while scsi eh was setting up */
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++ return SUCCESS;
++ }
++ /* grab a ref so the sp and sc_cmd cannot be relased from under us */
++ fc_fcp_pkt_hold(sp);
++ spin_unlock_irqrestore(lp->host->host_lock, flags);
++
++ if (fc_fcp_lock_pkt(sp)) {
++ /* completed while we were waiting for timer to be deleted */
++ rc = SUCCESS;
++ goto release_pkt;
++ }
++
++ rc = fc_fcp_pkt_abort(lp, sp);
++ fc_fcp_unlock_pkt(sp);
++
++release_pkt:
++ fc_fcp_pkt_release(sp);
++ return rc;
++}
++EXPORT_SYMBOL(fc_eh_abort);
++
++/**
++ * fc_eh_device_reset: Reset a single LUN
++ * @sc_cmd: scsi command
++ *
++ * Set from scsi host template to send tm cmd to the target and wait for the
++ * response.
++ */
++int fc_eh_device_reset(struct scsi_cmnd *sc_cmd)
++{
++ struct fc_lport *lp;
++ struct fc_fcp_pkt *sp;
++ struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device));
++ int rc = FAILED;
++ struct fc_rport_libfc_priv *rp;
++ int rval;
++
++ rval = fc_remote_port_chkready(rport);
++ if (rval)
++ goto out;
++
++ rp = rport->dd_data;
++ lp = shost_priv(sc_cmd->device->host);
++
++ if (lp->state != LPORT_ST_READY)
++ return rc;
++
++ sp = fc_fcp_pkt_alloc(lp, GFP_NOIO);
++ if (sp == NULL) {
++ FC_DBG("could not allocate scsi_pkt\n");
++ sc_cmd->result = DID_NO_CONNECT << 16;
++ goto out;
++ }
++
++ /*
++ * Build the libfc request pkt. Do not set the scsi cmnd, because
++ * the sc passed in is not setup for execution like when sent
++ * through the queuecommand callout.
++ */
++ sp->lp = lp; /* save the softc ptr */
++ sp->rport = rport; /* set the remote port ptr */
++
++ /*
++ * flush outstanding commands
++ */
++ rc = fc_lun_reset(lp, sp, scmd_id(sc_cmd), sc_cmd->device->lun);
++ sp->state = FC_SRB_FREE;
++ fc_fcp_pkt_release(sp);
++
++out:
++ return rc;
++}
++EXPORT_SYMBOL(fc_eh_device_reset);
++
++/**
++ * fc_eh_host_reset - The reset function will reset the ports on the host.
++ * @sc_cmd: scsi command
++ */
++int fc_eh_host_reset(struct scsi_cmnd *sc_cmd)
++{
++ struct Scsi_Host *shost = sc_cmd->device->host;
++ struct fc_lport *lp = shost_priv(shost);
++ unsigned long wait_tmo;
++
++ lp->tt.lport_reset(lp);
++ wait_tmo = jiffies + FC_HOST_RESET_TIMEOUT;
++ while (!fc_fcp_lport_queue_ready(lp) && time_before(jiffies, wait_tmo))
++ msleep(1000);
++
++ if (fc_fcp_lport_queue_ready(lp)) {
++ shost_printk(KERN_INFO, shost, "Host reset succeeded.\n");
++ return SUCCESS;
++ } else {
++ shost_printk(KERN_INFO, shost, "Host reset succeeded failed."
++ "lport not ready.\n");
++ return FAILED;
++ }
++}
++EXPORT_SYMBOL(fc_eh_host_reset);
++
++/**
++ * fc_slave_alloc - configure queue depth
++ * @sdev: scsi device
++ *
++ * Configures queue depth based on host's cmd_per_len. If not set
++ * then we use the libfc default.
++ */
++int fc_slave_alloc(struct scsi_device *sdev)
++{
++ struct fc_rport *rport = starget_to_rport(scsi_target(sdev));
++ int queue_depth;
++
++ if (!rport || fc_remote_port_chkready(rport))
++ return -ENXIO;
++
++ if (sdev->tagged_supported) {
++ if (sdev->host->hostt->cmd_per_lun)
++ queue_depth = sdev->host->hostt->cmd_per_lun;
++ else
++ queue_depth = FC_FCP_DFLT_QUEUE_DEPTH;
++ scsi_activate_tcq(sdev, queue_depth);
++ }
++ return 0;
++}
++EXPORT_SYMBOL(fc_slave_alloc);
++
++int fc_change_queue_depth(struct scsi_device *sdev, int qdepth)
++{
++ scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), qdepth);
++ return sdev->queue_depth;
++}
++EXPORT_SYMBOL(fc_change_queue_depth);
++
++int fc_change_queue_type(struct scsi_device *sdev, int tag_type)
++{
++ if (sdev->tagged_supported) {
++ scsi_set_tag_type(sdev, tag_type);
++ if (tag_type)
++ scsi_activate_tcq(sdev, sdev->queue_depth);
++ else
++ scsi_deactivate_tcq(sdev, sdev->queue_depth);
++ } else
++ tag_type = 0;
++
++ return tag_type;
++}
++EXPORT_SYMBOL(fc_change_queue_type);
++
++void fc_fcp_destroy(struct fc_lport *lp)
++{
++ struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
++
++ if (!list_empty(&si->scsi_pkt_queue))
++ printk(KERN_ERR "Leaked scsi packets.\n");
++
++ mempool_destroy(si->scsi_pkt_pool);
++ kfree(si);
++ lp->scsi_priv = NULL;
++}
++EXPORT_SYMBOL(fc_fcp_destroy);
++
++int fc_fcp_init(struct fc_lport *lp)
++{
++ int rc;
++ struct fc_fcp_internal *si;
++
++ if (!lp->tt.scsi_cleanup)
++ lp->tt.scsi_cleanup = fc_fcp_cleanup;
++
++ if (!lp->tt.scsi_abort_io)
++ lp->tt.scsi_abort_io = fc_fcp_abort_io;
++
++ si = kzalloc(sizeof(struct fc_fcp_internal), GFP_KERNEL);
++ if (!si)
++ return -ENOMEM;
++ lp->scsi_priv = si;
++ INIT_LIST_HEAD(&si->scsi_pkt_queue);
++
++ si->scsi_pkt_pool = mempool_create_slab_pool(2, scsi_pkt_cachep);
++ if (!si->scsi_pkt_pool) {
++ rc = -ENOMEM;
++ goto free_internal;
++ }
++ return 0;
++
++free_internal:
++ kfree(si);
++ return rc;
++}
++EXPORT_SYMBOL(fc_fcp_init);
++
++static int __init libfc_init(void)
++{
++ int rc;
++
++ scsi_pkt_cachep = kmem_cache_create("libfc_fcp_pkt",
++ sizeof(struct fc_fcp_pkt),
++ 0, SLAB_HWCACHE_ALIGN, NULL);
++ if (scsi_pkt_cachep == NULL) {
++ FC_DBG("Unable to allocate SRB cache...module load failed!");
++ return -ENOMEM;
++ }
++
++ rc = fc_setup_exch_mgr();
++ if (rc)
++ kmem_cache_destroy(scsi_pkt_cachep);
++ return rc;
++}
++
++static void __exit libfc_exit(void)
++{
++ kmem_cache_destroy(scsi_pkt_cachep);
++ fc_destroy_exch_mgr();
++}
++
++module_init(libfc_init);
++module_exit(libfc_exit);
+diff --git a/drivers/scsi/libfc/fc_frame.c b/drivers/scsi/libfc/fc_frame.c
+new file mode 100644
+index 0000000..7ba241e
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_frame.c
+@@ -0,0 +1,88 @@
++/*
++ * Copyright(c) 2007 Intel Corporation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++/*
++ * Frame allocation.
++ */
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/skbuff.h>
++#include <linux/crc32.h>
++
++#include <scsi/libfc/fc_frame.h>
++
++/*
++ * Check the CRC in a frame.
++ */
++u32 fc_frame_crc_check(struct fc_frame *fp)
++{
++ u32 crc;
++ u32 error;
++ const u8 *bp;
++ unsigned int len;
++
++ WARN_ON(!fc_frame_is_linear(fp));
++ fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED;
++ len = (fr_len(fp) + 3) & ~3; /* round up length to include fill */
++ bp = (const u8 *) fr_hdr(fp);
++ crc = ~crc32(~0, bp, len);
++ error = crc ^ *(u32 *) (bp + len);
++ return error;
++}
++EXPORT_SYMBOL(fc_frame_crc_check);
++
++/*
++ * Allocate a frame intended to be sent via fcoe_xmit.
++ * Get an sk_buff for the frame and set the length.
++ */
++struct fc_frame *__fc_frame_alloc(size_t len)
++{
++ struct fc_frame *fp;
++ struct sk_buff *skb;
++
++ WARN_ON((len % sizeof(u32)) != 0);
++ len += sizeof(struct fc_frame_header);
++ skb = dev_alloc_skb(len + FC_FRAME_HEADROOM + FC_FRAME_TAILROOM);
++ if (!skb)
++ return NULL;
++ fp = (struct fc_frame *) skb;
++ fc_frame_init(fp);
++ skb_reserve(skb, FC_FRAME_HEADROOM);
++ skb_put(skb, len);
++ return fp;
++}
++EXPORT_SYMBOL(__fc_frame_alloc);
++
++
++struct fc_frame *fc_frame_alloc_fill(struct fc_lport *lp, size_t payload_len)
++{
++ struct fc_frame *fp;
++ size_t fill;
++
++ fill = payload_len % 4;
++ if (fill != 0)
++ fill = 4 - fill;
++ fp = __fc_frame_alloc(payload_len + fill);
++ if (fp) {
++ memset((char *) fr_hdr(fp) + payload_len, 0, fill);
++ /* trim is OK, we just allocated it so there are no fragments */
++ skb_trim(fp_skb(fp), payload_len);
++ }
++ return fp;
++}
+diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c
+new file mode 100644
+index 0000000..b390a32
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_lport.c
+@@ -0,0 +1,926 @@
++/*
++ * Copyright(c) 2007 Intel Corporation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++/*
++ * Logical interface support.
++ */
++
++#include <linux/timer.h>
++#include <asm/unaligned.h>
++
++#include <scsi/fc/fc_gs.h>
++
++#include <scsi/libfc/libfc.h>
++
++/* Fabric IDs to use for point-to-point mode, chosen on whims. */
++#define FC_LOCAL_PTP_FID_LO 0x010101
++#define FC_LOCAL_PTP_FID_HI 0x010102
++
++#define DNS_DELAY 3 /* Discovery delay after RSCN (in seconds)*/
++
++static int fc_lport_debug;
++
++static void fc_lport_enter_flogi(struct fc_lport *);
++static void fc_lport_enter_logo(struct fc_lport *);
++
++static const char *fc_lport_state_names[] = {
++ [LPORT_ST_NONE] = "none",
++ [LPORT_ST_FLOGI] = "FLOGI",
++ [LPORT_ST_DNS] = "dNS",
++ [LPORT_ST_REG_PN] = "REG_PN",
++ [LPORT_ST_REG_FT] = "REG_FT",
++ [LPORT_ST_SCR] = "SCR",
++ [LPORT_ST_READY] = "ready",
++ [LPORT_ST_DNS_STOP] = "stop",
++ [LPORT_ST_LOGO] = "LOGO",
++ [LPORT_ST_RESET] = "reset",
++};
++
++static int fc_frame_drop(struct fc_lport *lp, struct fc_frame *fp)
++{
++ fc_frame_free(fp);
++ return 0;
++}
++
++static const char *fc_lport_state(struct fc_lport *lp)
++{
++ const char *cp;
++
++ cp = fc_lport_state_names[lp->state];
++ if (!cp)
++ cp = "unknown";
++ return cp;
++}
++
++static void fc_lport_ptp_setup(struct fc_lport *lp,
++ u32 remote_fid, u64 remote_wwpn,
++ u64 remote_wwnn)
++{
++ struct fc_rport *rport;
++ struct fc_rport_identifiers ids = {
++ .port_id = remote_fid,
++ .port_name = remote_wwpn,
++ .node_name = remote_wwnn,
++ };
++
++ /*
++ * if we have to create a rport the fc class can sleep so we must
++ * drop the lock here
++ */
++ fc_lport_unlock(lp);
++ rport = lp->tt.rport_lookup(lp, ids.port_id); /* lookup and hold */
++ if (rport == NULL)
++ rport = lp->tt.rport_create(lp, &ids); /* create and hold */
++ fc_lport_lock(lp);
++ if (rport) {
++ if (lp->ptp_rp)
++ fc_remote_port_delete(lp->ptp_rp);
++ lp->ptp_rp = rport;
++ fc_lport_state_enter(lp, LPORT_ST_READY);
++ }
++}
++
++static void fc_lport_ptp_clear(struct fc_lport *lp)
++{
++ if (lp->ptp_rp) {
++ fc_remote_port_delete(lp->ptp_rp);
++ lp->ptp_rp = NULL;
++ }
++}
++
++/*
++ * Routines to support struct fc_function_template
++ */
++void fc_get_host_port_state(struct Scsi_Host *shost)
++{
++ struct fc_lport *lp = shost_priv(shost);
++
++ if ((lp->link_status & FC_LINK_UP) == FC_LINK_UP)
++ fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
++ else
++ fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
++}
++EXPORT_SYMBOL(fc_get_host_port_state);
++
++/*
++ * Fill in FLOGI command for request.
++ */
++static void
++fc_lport_flogi_fill(struct fc_lport *lp,
++ struct fc_els_flogi *flogi, unsigned int op)
++{
++ struct fc_els_csp *sp;
++ struct fc_els_cssp *cp;
++
++ memset(flogi, 0, sizeof(*flogi));
++ flogi->fl_cmd = (u8) op;
++ put_unaligned_be64(lp->wwpn, &flogi->fl_wwpn);
++ put_unaligned_be64(lp->wwnn, &flogi->fl_wwnn);
++ sp = &flogi->fl_csp;
++ sp->sp_hi_ver = 0x20;
++ sp->sp_lo_ver = 0x20;
++ sp->sp_bb_cred = htons(10); /* this gets set by gateway */
++ sp->sp_bb_data = htons((u16) lp->mfs);
++ cp = &flogi->fl_cssp[3 - 1]; /* class 3 parameters */
++ cp->cp_class = htons(FC_CPC_VALID | FC_CPC_SEQ);
++ if (op != ELS_FLOGI) {
++ sp->sp_features = htons(FC_SP_FT_CIRO);
++ sp->sp_tot_seq = htons(255); /* seq. we accept */
++ sp->sp_rel_off = htons(0x1f);
++ sp->sp_e_d_tov = htonl(lp->e_d_tov);
++
++ cp->cp_rdfs = htons((u16) lp->mfs);
++ cp->cp_con_seq = htons(255);
++ cp->cp_open_seq = 1;
++ }
++}
++
++/*
++ * Set the fid. This indicates that we have a new connection to the
++ * fabric so we should reset our list of fc_rports. Passing a fid of
++ * 0 will also reset the rport list regardless of the previous fid.
++ */
++static void fc_lport_set_fid(struct fc_lport *lp, u32 fid)
++{
++ if (fid != 0 && lp->fid == fid)
++ return;
++
++ if (fc_lport_debug)
++ FC_DBG("changing local port fid from %x to %x\n",
++ lp->fid, fid);
++ lp->fid = fid;
++ lp->tt.rport_reset_list(lp);
++}
++
++/*
++ * Add a supported FC-4 type.
++ */
++static void fc_lport_add_fc4_type(struct fc_lport *lp, enum fc_fh_type type)
++{
++ __be32 *mp;
++
++ mp = &lp->fcts.ff_type_map[type / FC_NS_BPW];
++ *mp = htonl(ntohl(*mp) | 1UL << (type % FC_NS_BPW));
++}
++
++/*
++ * Handle received RLIR - registered link incident report.
++ */
++static void fc_lport_rlir_req(struct fc_seq *sp, struct fc_frame *fp,
++ struct fc_lport *lp)
++{
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
++ fc_frame_free(fp);
++}
++
++/*
++ * Handle received ECHO.
++ */
++static void fc_lport_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
++ struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ unsigned int len;
++ void *pp;
++ void *dp;
++ u32 f_ctl;
++
++ len = fr_len(in_fp) - sizeof(struct fc_frame_header);
++ pp = fc_frame_payload_get(in_fp, len);
++
++ if (len < sizeof(__be32))
++ len = sizeof(__be32);
++ fp = fc_frame_alloc(lp, len);
++ if (fp) {
++ dp = fc_frame_payload_get(fp, len);
++ memcpy(dp, pp, len);
++ *((u32 *)dp) = htonl(ELS_LS_ACC << 24);
++ sp = lp->tt.seq_start_next(sp);
++ f_ctl = FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ fc_frame_setup(fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ lp->tt.seq_send(lp, sp, fp, f_ctl);
++ }
++ fc_frame_free(in_fp);
++}
++
++/*
++ * Handle received RNID.
++ */
++static void fc_lport_rnid_req(struct fc_seq *sp, struct fc_frame *in_fp,
++ struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct fc_els_rnid *req;
++ struct {
++ struct fc_els_rnid_resp rnid;
++ struct fc_els_rnid_cid cid;
++ struct fc_els_rnid_gen gen;
++ } *rp;
++ struct fc_seq_els_data rjt_data;
++ u8 fmt;
++ size_t len;
++ u32 f_ctl;
++
++ req = fc_frame_payload_get(in_fp, sizeof(*req));
++ if (!req) {
++ rjt_data.fp = NULL;
++ rjt_data.reason = ELS_RJT_LOGIC;
++ rjt_data.explan = ELS_EXPL_NONE;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
++ } else {
++ fmt = req->rnid_fmt;
++ len = sizeof(*rp);
++ if (fmt != ELS_RNIDF_GEN ||
++ ntohl(lp->rnid_gen.rnid_atype) == 0) {
++ fmt = ELS_RNIDF_NONE; /* nothing to provide */
++ len -= sizeof(rp->gen);
++ }
++ fp = fc_frame_alloc(lp, len);
++ if (fp) {
++ rp = fc_frame_payload_get(fp, len);
++ memset(rp, 0, len);
++ rp->rnid.rnid_cmd = ELS_LS_ACC;
++ rp->rnid.rnid_fmt = fmt;
++ rp->rnid.rnid_cid_len = sizeof(rp->cid);
++ rp->cid.rnid_wwpn = htonll(lp->wwpn);
++ rp->cid.rnid_wwnn = htonll(lp->wwnn);
++ if (fmt == ELS_RNIDF_GEN) {
++ rp->rnid.rnid_sid_len = sizeof(rp->gen);
++ memcpy(&rp->gen, &lp->rnid_gen,
++ sizeof(rp->gen));
++ }
++ sp = lp->tt.seq_start_next(sp);
++ f_ctl = FC_FC_SEQ_INIT | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ fc_frame_setup(fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ lp->tt.seq_send(lp, sp, fp, f_ctl);
++ }
++ }
++ fc_frame_free(in_fp);
++}
++
++/*
++ * Handle received fabric logout request.
++ */
++static void fc_lport_recv_logo_req(struct fc_seq *sp, struct fc_frame *fp,
++ struct fc_lport *lp)
++{
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
++ fc_lport_enter_reset(lp);
++ fc_frame_free(fp);
++}
++
++/*
++ * Receive request frame
++ */
++
++int fc_fabric_login(struct fc_lport *lp)
++{
++ int rc = -1;
++
++ if (lp->state == LPORT_ST_NONE) {
++ fc_lport_lock(lp);
++ fc_lport_enter_reset(lp);
++ fc_lport_unlock(lp);
++ rc = 0;
++ }
++ return rc;
++}
++EXPORT_SYMBOL(fc_fabric_login);
++
++/**
++ * fc_linkup - link up notification
++ * @dev: Pointer to fc_lport .
++ **/
++void fc_linkup(struct fc_lport *lp)
++{
++ if ((lp->link_status & FC_LINK_UP) != FC_LINK_UP) {
++ lp->link_status |= FC_LINK_UP;
++ fc_lport_lock(lp);
++ if (lp->state == LPORT_ST_RESET)
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++}
++EXPORT_SYMBOL(fc_linkup);
++
++/**
++ * fc_linkdown - link down notification
++ * @dev: Pointer to fc_lport .
++ **/
++void fc_linkdown(struct fc_lport *lp)
++{
++ if ((lp->link_status & FC_LINK_UP) == FC_LINK_UP) {
++ lp->link_status &= ~(FC_LINK_UP);
++ fc_lport_enter_reset(lp);
++ lp->tt.scsi_cleanup(lp);
++ }
++}
++EXPORT_SYMBOL(fc_linkdown);
++
++void fc_pause(struct fc_lport *lp)
++{
++ lp->link_status |= FC_PAUSE;
++}
++EXPORT_SYMBOL(fc_pause);
++
++void fc_unpause(struct fc_lport *lp)
++{
++ lp->link_status &= ~(FC_PAUSE);
++}
++EXPORT_SYMBOL(fc_unpause);
++
++int fc_fabric_logoff(struct fc_lport *lp)
++{
++ fc_lport_lock(lp);
++ switch (lp->state) {
++ case LPORT_ST_NONE:
++ break;
++ case LPORT_ST_FLOGI:
++ case LPORT_ST_LOGO:
++ case LPORT_ST_RESET:
++ fc_lport_enter_reset(lp);
++ break;
++ case LPORT_ST_DNS:
++ case LPORT_ST_DNS_STOP:
++ fc_lport_enter_logo(lp);
++ break;
++ case LPORT_ST_REG_PN:
++ case LPORT_ST_REG_FT:
++ case LPORT_ST_SCR:
++ case LPORT_ST_READY:
++ lp->tt.disc_stop(lp);
++ break;
++ }
++ fc_lport_unlock(lp);
++ lp->tt.scsi_cleanup(lp);
++
++ return 0;
++}
++EXPORT_SYMBOL(fc_fabric_logoff);
++
++/**
++ * fc_lport_destroy - unregister a fc_lport
++ * @lp: fc_lport pointer to unregister
++ *
++ * Return value:
++ * None
++ * Note:
++ * exit routine for fc_lport instance
++ * clean-up all the allocated memory
++ * and free up other system resources.
++ *
++ **/
++int fc_lport_destroy(struct fc_lport *lp)
++{
++ fc_lport_lock(lp);
++ fc_lport_state_enter(lp, LPORT_ST_LOGO);
++ fc_lport_unlock(lp);
++
++ cancel_delayed_work_sync(&lp->ns_disc_work);
++
++ lp->tt.scsi_abort_io(lp);
++
++ lp->tt.frame_send = fc_frame_drop;
++
++ lp->tt.exch_mgr_reset(lp->emp, 0, 0);
++
++ return 0;
++}
++EXPORT_SYMBOL(fc_lport_destroy);
++
++int fc_set_mfs(struct fc_lport *lp, u32 mfs)
++{
++ unsigned int old_mfs;
++ int rc = -1;
++
++ old_mfs = lp->mfs;
++
++ if (mfs >= FC_MIN_MAX_FRAME) {
++ mfs &= ~3;
++ WARN_ON((size_t) mfs < FC_MIN_MAX_FRAME);
++ if (mfs > FC_MAX_FRAME)
++ mfs = FC_MAX_FRAME;
++ mfs -= sizeof(struct fc_frame_header);
++ lp->mfs = mfs;
++ rc = 0;
++ }
++
++ if (!rc && mfs < old_mfs) {
++ lp->ns_disc_done = 0;
++ fc_lport_enter_reset(lp);
++ }
++ return rc;
++}
++EXPORT_SYMBOL(fc_set_mfs);
++
++/*
++ * re-enter state for retrying a request after a timeout or alloc failure.
++ */
++static void fc_lport_enter_retry(struct fc_lport *lp)
++{
++ switch (lp->state) {
++ case LPORT_ST_NONE:
++ case LPORT_ST_READY:
++ case LPORT_ST_RESET:
++ case LPORT_ST_DNS:
++ case LPORT_ST_DNS_STOP:
++ case LPORT_ST_REG_PN:
++ case LPORT_ST_REG_FT:
++ case LPORT_ST_SCR:
++ WARN_ON(1);
++ break;
++ case LPORT_ST_FLOGI:
++ fc_lport_enter_flogi(lp);
++ break;
++ case LPORT_ST_LOGO:
++ fc_lport_enter_logo(lp);
++ break;
++ }
++}
++
++/*
++ * enter next state for handling an exchange reject or retry exhaustion
++ * in the current state.
++ */
++static void fc_lport_enter_reject(struct fc_lport *lp)
++{
++ switch (lp->state) {
++ case LPORT_ST_NONE:
++ case LPORT_ST_READY:
++ case LPORT_ST_RESET:
++ case LPORT_ST_REG_PN:
++ case LPORT_ST_REG_FT:
++ case LPORT_ST_SCR:
++ case LPORT_ST_DNS_STOP:
++ case LPORT_ST_DNS:
++ WARN_ON(1);
++ break;
++ case LPORT_ST_FLOGI:
++ fc_lport_enter_flogi(lp);
++ break;
++ case LPORT_ST_LOGO:
++ fc_lport_enter_reset(lp);
++ break;
++ }
++}
++
++/*
++ * Handle resource allocation problem by retrying in a bit.
++ */
++static void fc_lport_retry(struct fc_lport *lp)
++{
++ if (lp->retry_count == 0)
++ FC_DBG("local port %6x alloc failure in state %s "
++ "- will retry\n", lp->fid, fc_lport_state(lp));
++ if (lp->retry_count < lp->max_retry_count) {
++ lp->retry_count++;
++ mod_timer(&lp->state_timer,
++ jiffies + msecs_to_jiffies(lp->e_d_tov));
++ } else {
++ FC_DBG("local port %6x alloc failure in state %s "
++ "- retries exhausted\n", lp->fid,
++ fc_lport_state(lp));
++ fc_lport_enter_reject(lp);
++ }
++}
++
++/*
++ * A received FLOGI request indicates a point-to-point connection.
++ * Accept it with the common service parameters indicating our N port.
++ * Set up to do a PLOGI if we have the higher-number WWPN.
++ */
++static void fc_lport_recv_flogi_req(struct fc_seq *sp_in,
++ struct fc_frame *rx_fp,
++ struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct fc_frame_header *fh;
++ struct fc_seq *sp;
++ struct fc_els_flogi *flp;
++ struct fc_els_flogi *new_flp;
++ u64 remote_wwpn;
++ u32 remote_fid;
++ u32 local_fid;
++ u32 f_ctl;
++
++ fh = fc_frame_header_get(rx_fp);
++ remote_fid = ntoh24(fh->fh_s_id);
++ flp = fc_frame_payload_get(rx_fp, sizeof(*flp));
++ if (!flp)
++ goto out;
++ remote_wwpn = get_unaligned_be64(&flp->fl_wwpn);
++ if (remote_wwpn == lp->wwpn) {
++ FC_DBG("FLOGI from port with same WWPN %llx "
++ "possible configuration error\n", remote_wwpn);
++ goto out;
++ }
++ FC_DBG("FLOGI from port WWPN %llx\n", remote_wwpn);
++ fc_lport_lock(lp);
++
++ /*
++ * XXX what is the right thing to do for FIDs?
++ * The originator might expect our S_ID to be 0xfffffe.
++ * But if so, both of us could end up with the same FID.
++ */
++ local_fid = FC_LOCAL_PTP_FID_LO;
++ if (remote_wwpn < lp->wwpn) {
++ local_fid = FC_LOCAL_PTP_FID_HI;
++ if (!remote_fid || remote_fid == local_fid)
++ remote_fid = FC_LOCAL_PTP_FID_LO;
++ } else if (!remote_fid) {
++ remote_fid = FC_LOCAL_PTP_FID_HI;
++ }
++ fc_lport_set_fid(lp, local_fid);
++
++ fp = fc_frame_alloc(lp, sizeof(*flp));
++ if (fp) {
++ sp = lp->tt.seq_start_next(fr_seq(rx_fp));
++ new_flp = fc_frame_payload_get(fp, sizeof(*flp));
++ fc_lport_flogi_fill(lp, new_flp, ELS_FLOGI);
++ new_flp->fl_cmd = (u8) ELS_LS_ACC;
++
++ /*
++ * Send the response. If this fails, the originator should
++ * repeat the sequence.
++ */
++ f_ctl = FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ fc_frame_setup(fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ lp->tt.seq_send(lp, sp, fp, f_ctl);
++
++ } else {
++ fc_lport_retry(lp);
++ }
++ fc_lport_ptp_setup(lp, remote_fid, remote_wwpn,
++ get_unaligned_be64(&flp->fl_wwnn));
++ fc_lport_unlock(lp);
++ if (lp->tt.disc_start(lp))
++ FC_DBG("target discovery start error\n");
++out:
++ sp = fr_seq(rx_fp);
++ fc_frame_free(rx_fp);
++}
++
++static void fc_lport_recv(struct fc_lport *lp, struct fc_seq *sp,
++ struct fc_frame *fp)
++{
++ struct fc_frame_header *fh = fc_frame_header_get(fp);
++ void (*recv) (struct fc_seq *, struct fc_frame *, struct fc_lport *);
++ struct fc_rport *rport;
++ u32 s_id;
++ u32 d_id;
++ struct fc_seq_els_data rjt_data;
++
++ /*
++ * Handle special ELS cases like FLOGI, LOGO, and
++ * RSCN here. These don't require a session.
++ * Even if we had a session, it might not be ready.
++ */
++ if (fh->fh_type == FC_TYPE_ELS && fh->fh_r_ctl == FC_RCTL_ELS_REQ) {
++ /*
++ * Check opcode.
++ */
++ recv = NULL;
++ switch (fc_frame_payload_op(fp)) {
++ case ELS_FLOGI:
++ recv = fc_lport_recv_flogi_req;
++ break;
++ case ELS_LOGO:
++ fh = fc_frame_header_get(fp);
++ if (ntoh24(fh->fh_s_id) == FC_FID_FLOGI)
++ recv = fc_lport_recv_logo_req;
++ break;
++ case ELS_RSCN:
++ recv = lp->tt.disc_recv_req;
++ break;
++ case ELS_ECHO:
++ recv = fc_lport_echo_req;
++ break;
++ case ELS_RLIR:
++ recv = fc_lport_rlir_req;
++ break;
++ case ELS_RNID:
++ recv = fc_lport_rnid_req;
++ break;
++ }
++
++ if (recv)
++ recv(sp, fp, lp);
++ else {
++ /*
++ * Find session.
++ * If this is a new incoming PLOGI, we won't find it.
++ */
++ s_id = ntoh24(fh->fh_s_id);
++ d_id = ntoh24(fh->fh_d_id);
++
++ rport = lp->tt.rport_lookup(lp, s_id);
++ if (rport) {
++ lp->tt.rport_recv_req(sp, fp, rport);
++ put_device(&rport->dev); /* hold from lookup */
++ } else {
++ rjt_data.fp = NULL;
++ rjt_data.reason = ELS_RJT_UNAB;
++ rjt_data.explan = ELS_EXPL_NONE;
++ lp->tt.seq_els_rsp_send(sp,
++ ELS_LS_RJT, &rjt_data);
++ fc_frame_free(fp);
++ }
++ }
++ } else {
++ FC_DBG("dropping invalid frame (eof %x)\n", fr_eof(fp));
++ fc_frame_free(fp);
++ }
++
++ /*
++ * The common exch_done for all request may not be good
++ * if any request requires longer hold on exhange. XXX
++ */
++ lp->tt.exch_done(sp);
++}
++
++/*
++ * Put the local port back into the initial state. Reset all sessions.
++ * This is called after a SCSI reset or the driver is unloading
++ * or the program is exiting.
++ */
++int fc_lport_enter_reset(struct fc_lport *lp)
++{
++ if (fc_lport_debug)
++ FC_DBG("Processing RESET state\n");
++
++ if (lp->dns_rp) {
++ fc_remote_port_delete(lp->dns_rp);
++ lp->dns_rp = NULL;
++ }
++ fc_lport_ptp_clear(lp);
++
++ /*
++ * Setting state RESET keeps fc_lport_error() callbacks
++ * by exch_mgr_reset() from recursing on the lock.
++ * It also causes fc_lport_sess_event() to ignore events.
++ * The lock is held for the duration of the time in RESET state.
++ */
++ fc_lport_state_enter(lp, LPORT_ST_RESET);
++ lp->tt.exch_mgr_reset(lp->emp, 0, 0);
++ fc_lport_set_fid(lp, 0);
++ if ((lp->link_status & FC_LINK_UP) == FC_LINK_UP)
++ fc_lport_enter_flogi(lp);
++ return 0;
++}
++EXPORT_SYMBOL(fc_lport_enter_reset);
++
++/*
++ * Handle errors on local port requests.
++ * Don't get locks if in RESET state.
++ * The only possible errors so far are exchange TIMEOUT and CLOSED (reset).
++ */
++static void fc_lport_error(struct fc_lport *lp, struct fc_frame *fp)
++{
++ if (lp->state == LPORT_ST_RESET)
++ return;
++
++ fc_lport_lock(lp);
++ if (PTR_ERR(fp) == -FC_EX_TIMEOUT) {
++ if (lp->retry_count < lp->max_retry_count) {
++ lp->retry_count++;
++ fc_lport_enter_retry(lp);
++ } else {
++ fc_lport_enter_reject(lp);
++
++ }
++ }
++ if (fc_lport_debug)
++ FC_DBG("error %ld retries %d limit %d\n",
++ PTR_ERR(fp), lp->retry_count, lp->max_retry_count);
++ fc_lport_unlock(lp);
++}
++
++static void fc_lport_timeout(unsigned long lp_arg)
++{
++ struct fc_lport *lp = (struct fc_lport *)lp_arg;
++
++ fc_lport_lock(lp);
++ fc_lport_enter_retry(lp);
++ fc_lport_unlock(lp);
++}
++
++static void fc_lport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *lp_arg)
++{
++ struct fc_lport *lp = lp_arg;
++
++ if (IS_ERR(fp))
++ fc_lport_error(lp, fp);
++ else {
++ fc_frame_free(fp);
++ fc_lport_lock(lp);
++ fc_lport_enter_reset(lp);
++ fc_lport_unlock(lp);
++ }
++}
++
++/* Logout of the FC fabric */
++static void fc_lport_enter_logo(struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct fc_els_logo *logo;
++
++ if (fc_lport_debug)
++ FC_DBG("Processing LOGO state\n");
++
++ fc_lport_state_enter(lp, LPORT_ST_LOGO);
++
++ /* DNS session should be closed so we can release it here */
++ if (lp->dns_rp) {
++ fc_remote_port_delete(lp->dns_rp);
++ lp->dns_rp = NULL;
++ }
++
++ fp = fc_frame_alloc(lp, sizeof(*logo));
++ if (!fp) {
++ FC_DBG("failed to allocate frame\n");
++ return;
++ }
++
++ logo = fc_frame_payload_get(fp, sizeof(*logo));
++ memset(logo, 0, sizeof(*logo));
++ logo->fl_cmd = ELS_LOGO;
++ hton24(logo->fl_n_port_id, lp->fid);
++ logo->fl_n_port_wwn = htonll(lp->wwpn);
++
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ fc_frame_set_offset(fp, 0);
++
++ lp->tt.exch_seq_send(lp, fp,
++ fc_lport_logo_resp,
++ lp, lp->e_d_tov,
++ lp->fid, FC_FID_FLOGI,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++}
++
++static int fc_lport_logout(struct fc_lport *lp)
++{
++ fc_lport_lock(lp);
++ if (lp->state != LPORT_ST_LOGO)
++ fc_lport_enter_logo(lp);
++ fc_lport_unlock(lp);
++ return 0;
++}
++
++/*
++ * Handle incoming ELS FLOGI response.
++ * Save parameters of remote switch. Finish exchange.
++ */
++static void
++fc_lport_flogi_resp(struct fc_seq *sp, struct fc_frame *fp, void *lp_arg)
++{
++ struct fc_lport *lp = lp_arg;
++ struct fc_frame_header *fh;
++ struct fc_els_flogi *flp;
++ u32 did;
++ u16 csp_flags;
++ unsigned int r_a_tov;
++ unsigned int e_d_tov;
++ u16 mfs;
++
++ if (IS_ERR(fp)) {
++ fc_lport_error(lp, fp);
++ return;
++ }
++
++ fh = fc_frame_header_get(fp);
++ did = ntoh24(fh->fh_d_id);
++ if (fc_frame_payload_op(fp) == ELS_LS_ACC && did != 0) {
++ if (fc_lport_debug)
++ FC_DBG("assigned fid %x\n", did);
++ fc_lport_lock(lp);
++ fc_lport_set_fid(lp, did);
++ flp = fc_frame_payload_get(fp, sizeof(*flp));
++ if (flp) {
++ mfs = ntohs(flp->fl_csp.sp_bb_data) &
++ FC_SP_BB_DATA_MASK;
++ if (mfs >= FC_SP_MIN_MAX_PAYLOAD &&
++ mfs < lp->mfs)
++ lp->mfs = mfs;
++ csp_flags = ntohs(flp->fl_csp.sp_features);
++ r_a_tov = ntohl(flp->fl_csp.sp_r_a_tov);
++ e_d_tov = ntohl(flp->fl_csp.sp_e_d_tov);
++ if (csp_flags & FC_SP_FT_EDTR)
++ e_d_tov /= 1000000;
++ if ((csp_flags & FC_SP_FT_FPORT) == 0) {
++ if (e_d_tov > lp->e_d_tov)
++ lp->e_d_tov = e_d_tov;
++ lp->r_a_tov = 2 * e_d_tov;
++ FC_DBG("point-to-point mode\n");
++ fc_lport_ptp_setup(lp, ntoh24(fh->fh_s_id),
++ get_unaligned_be64(
++ &flp->fl_wwpn),
++ get_unaligned_be64(
++ &flp->fl_wwnn));
++ } else {
++ lp->e_d_tov = e_d_tov;
++ lp->r_a_tov = r_a_tov;
++ lp->tt.dns_register(lp);
++ }
++ }
++ fc_lport_unlock(lp);
++ if (flp) {
++ csp_flags = ntohs(flp->fl_csp.sp_features);
++ if ((csp_flags & FC_SP_FT_FPORT) == 0) {
++ if (lp->tt.disc_start(lp))
++ FC_DBG("target disc start error\n");
++ }
++ }
++ } else {
++ FC_DBG("bad FLOGI response\n");
++ }
++ fc_frame_free(fp);
++}
++
++/*
++ * Send ELS (extended link service) FLOGI request to peer.
++ */
++static void fc_lport_flogi_send(struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct fc_els_flogi *flp;
++
++ fp = fc_frame_alloc(lp, sizeof(*flp));
++ if (!fp)
++ return fc_lport_retry(lp);
++
++ flp = fc_frame_payload_get(fp, sizeof(*flp));
++ fc_lport_flogi_fill(lp, flp, ELS_FLOGI);
++
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ fc_frame_set_offset(fp, 0);
++
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_lport_flogi_resp,
++ lp, lp->e_d_tov,
++ 0, FC_FID_FLOGI,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ fc_lport_retry(lp);
++
++}
++
++void fc_lport_enter_flogi(struct fc_lport *lp)
++{
++ if (fc_lport_debug)
++ FC_DBG("Processing FLOGI state\n");
++ fc_lport_state_enter(lp, LPORT_ST_FLOGI);
++ fc_lport_flogi_send(lp);
++}
++
++/* Configure a fc_lport */
++int fc_lport_config(struct fc_lport *lp)
++{
++ setup_timer(&lp->state_timer, fc_lport_timeout, (unsigned long)lp);
++ spin_lock_init(&lp->state_lock);
++
++ fc_lport_lock(lp);
++ fc_lport_state_enter(lp, LPORT_ST_NONE);
++ fc_lport_unlock(lp);
++
++ lp->ns_disc_delay = DNS_DELAY;
++
++ fc_lport_add_fc4_type(lp, FC_TYPE_FCP);
++ fc_lport_add_fc4_type(lp, FC_TYPE_CT);
++
++ return 0;
++}
++EXPORT_SYMBOL(fc_lport_config);
++
++int fc_lport_init(struct fc_lport *lp)
++{
++ if (!lp->tt.lport_recv)
++ lp->tt.lport_recv = fc_lport_recv;
++
++ if (!lp->tt.lport_login)
++ lp->tt.lport_login = fc_lport_enter_reset;
++
++ if (!lp->tt.lport_reset)
++ lp->tt.lport_reset = fc_lport_enter_reset;
++
++ if (!lp->tt.lport_logout)
++ lp->tt.lport_logout = fc_lport_logout;
++
++ return 0;
++}
++EXPORT_SYMBOL(fc_lport_init);
+diff --git a/drivers/scsi/libfc/fc_ns.c b/drivers/scsi/libfc/fc_ns.c
+new file mode 100644
+index 0000000..5c9272c
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_ns.c
+@@ -0,0 +1,1283 @@
++/*
++ * Copyright(c) 2007 - 2008 Intel Corporation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++/*
++ * Target Discovery
++ * Actually, this discovers all FC-4 remote ports, including FCP initiators.
++ */
++
++#include <linux/timer.h>
++#include <linux/err.h>
++#include <asm/unaligned.h>
++
++#include <scsi/fc/fc_gs.h>
++
++#include <scsi/libfc/libfc.h>
++
++#define FC_NS_RETRY_LIMIT 3 /* max retries */
++#define FC_NS_RETRY_DELAY 500UL /* (msecs) delay */
++
++int fc_ns_debug;
++
++static void fc_ns_gpn_ft_req(struct fc_lport *);
++static void fc_ns_gpn_ft_resp(struct fc_seq *, struct fc_frame *, void *);
++static int fc_ns_new_target(struct fc_lport *, struct fc_rport *,
++ struct fc_rport_identifiers *);
++static void fc_ns_del_target(struct fc_lport *, struct fc_rport *);
++static void fc_ns_disc_done(struct fc_lport *);
++static void fcdt_ns_error(struct fc_lport *, struct fc_frame *);
++static void fc_ns_timeout(struct work_struct *);
++
++/**
++ * struct fc_ns_port - temporary discovery port to hold rport identifiers
++ * @lp: Fibre Channel host port instance
++ * @peers: node for list management during discovery and RSCN processing
++ * @ids: identifiers structure to pass to fc_remote_port_add()
++ */
++struct fc_ns_port {
++ struct fc_lport *lp;
++ struct list_head peers;
++ struct fc_rport_identifiers ids;
++};
++
++static int fc_ns_gpn_id_req(struct fc_lport *, struct fc_ns_port *);
++static void fc_ns_gpn_id_resp(struct fc_seq *, struct fc_frame *, void *);
++static void fc_ns_gpn_id_error(struct fc_ns_port *rp, struct fc_frame *fp);
++
++static int fc_ns_gnn_id_req(struct fc_lport *, struct fc_ns_port *);
++static void fc_ns_gnn_id_resp(struct fc_seq *, struct fc_frame *, void *);
++static void fc_ns_gnn_id_error(struct fc_ns_port *, struct fc_frame *);
++static void fc_ns_enter_reg_pn(struct fc_lport *lp);
++static void fc_ns_error(struct fc_lport *lp, struct fc_frame *fp);
++static void fc_lport_fill_dns_hdr(struct fc_lport *lp, struct fc_ct_hdr *ct,
++ unsigned int op, unsigned int req_size);
++static void fc_ns_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *lp_arg);
++static void fc_ns_retry(struct fc_lport *lp);
++static void fc_ns_single(struct fc_lport *, struct fc_ns_port *);
++static int fc_ns_restart(struct fc_lport *);
++
++
++/**
++ * fc_ns_rscn_req - Handle Registered State Change Notification (RSCN)
++ * @sp: Current sequence of the RSCN exchange
++ * @fp: RSCN Frame
++ * @lp: Fibre Channel host port instance
++ */
++static void fc_ns_rscn_req(struct fc_seq *sp, struct fc_frame *fp,
++ struct fc_lport *lp)
++{
++ struct fc_els_rscn *rp;
++ struct fc_els_rscn_page *pp;
++ struct fc_seq_els_data rjt_data;
++ unsigned int len;
++ int redisc = 0;
++ enum fc_els_rscn_ev_qual ev_qual;
++ enum fc_els_rscn_addr_fmt fmt;
++ LIST_HEAD(disc_list);
++ struct fc_ns_port *dp, *next;
++
++ rp = fc_frame_payload_get(fp, sizeof(*rp));
++
++ if (!rp || rp->rscn_page_len != sizeof(*pp))
++ goto reject;
++
++ len = ntohs(rp->rscn_plen);
++ if (len < sizeof(*rp))
++ goto reject;
++ len -= sizeof(*rp);
++
++ for (pp = (void *)(rp + 1); len; len -= sizeof(*pp), pp++) {
++ ev_qual = pp->rscn_page_flags >> ELS_RSCN_EV_QUAL_BIT;
++ ev_qual &= ELS_RSCN_EV_QUAL_MASK;
++ fmt = pp->rscn_page_flags >> ELS_RSCN_ADDR_FMT_BIT;
++ fmt &= ELS_RSCN_ADDR_FMT_MASK;
++ /*
++ * if we get an address format other than port
++ * (area, domain, fabric), then do a full discovery
++ */
++ switch (fmt) {
++ case ELS_ADDR_FMT_PORT:
++ dp = kzalloc(sizeof(*dp), GFP_KERNEL);
++ if (!dp) {
++ redisc = 1;
++ break;
++ }
++ dp->lp = lp;
++ dp->ids.port_id = ntoh24(pp->rscn_fid);
++ dp->ids.port_name = -1;
++ dp->ids.node_name = -1;
++ dp->ids.roles = FC_RPORT_ROLE_UNKNOWN;
++ list_add_tail(&dp->peers, &disc_list);
++ break;
++ case ELS_ADDR_FMT_AREA:
++ case ELS_ADDR_FMT_DOM:
++ case ELS_ADDR_FMT_FAB:
++ default:
++ redisc = 1;
++ break;
++ }
++ }
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
++ if (redisc) {
++ if (fc_ns_debug)
++ FC_DBG("RSCN received: rediscovering\n");
++ list_for_each_entry_safe(dp, next, &disc_list, peers) {
++ list_del(&dp->peers);
++ kfree(dp);
++ }
++ fc_ns_restart(lp);
++ } else {
++ if (fc_ns_debug)
++ FC_DBG("RSCN received: not rediscovering. "
++ "redisc %d state %d in_prog %d\n",
++ redisc, lp->state, lp->ns_disc_pending);
++ list_for_each_entry_safe(dp, next, &disc_list, peers) {
++ list_del(&dp->peers);
++ fc_ns_single(lp, dp);
++ }
++ }
++ fc_frame_free(fp);
++ return;
++reject:
++ rjt_data.fp = NULL;
++ rjt_data.reason = ELS_RJT_LOGIC;
++ rjt_data.explan = ELS_EXPL_NONE;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
++ fc_frame_free(fp);
++}
++
++static void fc_ns_recv_req(struct fc_seq *sp, struct fc_frame *fp,
++ struct fc_lport *lp)
++{
++ switch (fc_frame_payload_op(fp)) {
++ case ELS_RSCN:
++ fc_ns_rscn_req(sp, fp, lp);
++ break;
++ default:
++ FC_DBG("fc_ns recieved an unexpected request\n");
++ break;
++ }
++}
++
++/**
++ * fc_ns_scr_resp - Handle response to State Change Register (SCR) request
++ * @sp: current sequence in SCR exchange
++ * @fp: response frame
++ * @lp_arg: Fibre Channel host port instance
++ */
++static void fc_ns_scr_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *lp_arg)
++{
++ struct fc_lport *lp = lp_arg;
++ int err;
++
++ if (IS_ERR(fp))
++ fc_ns_error(lp, fp);
++ else {
++ fc_lport_lock(lp);
++ fc_lport_state_enter(lp, LPORT_ST_READY);
++ fc_lport_unlock(lp);
++ err = lp->tt.disc_start(lp);
++ if (err)
++ FC_DBG("target discovery start error\n");
++ fc_frame_free(fp);
++ }
++}
++
++/**
++ * fc_ns_enter scr - Send a State Change Register (SCR) request
++ * @lp: Fibre Channel host port instance
++ */
++static void fc_ns_enter_scr(struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct fc_els_scr *scr;
++
++ if (fc_ns_debug)
++ FC_DBG("Processing SCR state\n");
++
++ fc_lport_state_enter(lp, LPORT_ST_SCR);
++
++ fp = fc_frame_alloc(lp, sizeof(*scr));
++ if (fp) {
++ scr = fc_frame_payload_get(fp, sizeof(*scr));
++ memset(scr, 0, sizeof(*scr));
++ scr->scr_cmd = ELS_SCR;
++ scr->scr_reg_func = ELS_SCRF_FULL;
++ }
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ fc_frame_set_offset(fp, 0);
++
++ lp->tt.exch_seq_send(lp, fp,
++ fc_ns_scr_resp,
++ lp, lp->e_d_tov,
++ lp->fid, FC_FID_FCTRL,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++}
++
++/**
++ * fc_ns_enter_reg_ft - Register FC4-types with the name server
++ * @lp: Fibre Channel host port instance
++ */
++static void fc_ns_enter_reg_ft(struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct req {
++ struct fc_ct_hdr ct;
++ struct fc_ns_fid fid; /* port ID object */
++ struct fc_ns_fts fts; /* FC4-types object */
++ } *req;
++ struct fc_ns_fts *lps;
++ int i;
++
++ if (fc_ns_debug)
++ FC_DBG("Processing REG_FT state\n");
++
++ fc_lport_state_enter(lp, LPORT_ST_REG_FT);
++
++ lps = &lp->fcts;
++ i = sizeof(lps->ff_type_map) / sizeof(lps->ff_type_map[0]);
++ while (--i >= 0)
++ if (ntohl(lps->ff_type_map[i]) != 0)
++ break;
++ if (i >= 0) {
++ fp = fc_frame_alloc(lp, sizeof(*req));
++ if (fp) {
++ req = fc_frame_payload_get(fp, sizeof(*req));
++ fc_lport_fill_dns_hdr(lp, &req->ct,
++ FC_NS_RFT_ID,
++ sizeof(*req) -
++ sizeof(struct fc_ct_hdr));
++ hton24(req->fid.fp_fid, lp->fid);
++ req->fts = *lps;
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CTL, FC_TYPE_CT);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_ns_resp, lp,
++ lp->e_d_tov,
++ lp->fid,
++ lp->dns_rp->port_id,
++ FC_FC_SEQ_INIT |
++ FC_FC_END_SEQ))
++ fc_ns_retry(lp);
++ } else {
++ fc_ns_retry(lp);
++ }
++ } else {
++ fc_ns_enter_scr(lp);
++ }
++}
++
++/*
++ * enter next state for handling an exchange reject or retry exhaustion
++ * in the current state.
++ */
++static void fc_ns_enter_reject(struct fc_lport *lp)
++{
++ switch (lp->state) {
++ case LPORT_ST_NONE:
++ case LPORT_ST_READY:
++ case LPORT_ST_RESET:
++ case LPORT_ST_FLOGI:
++ case LPORT_ST_LOGO:
++ WARN_ON(1);
++ break;
++ case LPORT_ST_REG_PN:
++ fc_ns_enter_reg_ft(lp);
++ break;
++ case LPORT_ST_REG_FT:
++ fc_ns_enter_scr(lp);
++ break;
++ case LPORT_ST_SCR:
++ case LPORT_ST_DNS_STOP:
++ lp->tt.disc_stop(lp);
++ break;
++ case LPORT_ST_DNS:
++ lp->tt.lport_reset(lp);
++ break;
++ }
++}
++
++static void fc_ns_enter_retry(struct fc_lport *lp)
++{
++ switch (lp->state) {
++ case LPORT_ST_NONE:
++ case LPORT_ST_RESET:
++ case LPORT_ST_READY:
++ case LPORT_ST_FLOGI:
++ case LPORT_ST_LOGO:
++ WARN_ON(1);
++ break;
++ case LPORT_ST_DNS:
++ lp->tt.dns_register(lp);
++ break;
++ case LPORT_ST_DNS_STOP:
++ lp->tt.disc_stop(lp);
++ break;
++ case LPORT_ST_REG_PN:
++ fc_ns_enter_reg_pn(lp);
++ break;
++ case LPORT_ST_REG_FT:
++ fc_ns_enter_reg_ft(lp);
++ break;
++ case LPORT_ST_SCR:
++ fc_ns_enter_scr(lp);
++ break;
++ }
++}
++
++/*
++ * Refresh target discovery, perhaps due to an RSCN.
++ * A configurable delay is introduced to collect any subsequent RSCNs.
++ */
++static int fc_ns_restart(struct fc_lport *lp)
++{
++ fc_lport_lock(lp);
++ if (!lp->ns_disc_requested && !lp->ns_disc_pending) {
++ schedule_delayed_work(&lp->ns_disc_work,
++ msecs_to_jiffies(lp->ns_disc_delay * 1000));
++ }
++ lp->ns_disc_requested = 1;
++ fc_lport_unlock(lp);
++ return 0;
++}
++
++/* unlocked varient of scsi_target_block from scsi_lib.c */
++#include "../scsi_priv.h"
++
++static void __device_block(struct scsi_device *sdev, void *data)
++{
++ scsi_internal_device_block(sdev);
++}
++
++static int __target_block(struct device *dev, void *data)
++{
++ if (scsi_is_target_device(dev))
++ __starget_for_each_device(to_scsi_target(dev),
++ NULL, __device_block);
++ return 0;
++}
++
++static void __scsi_target_block(struct device *dev)
++{
++ if (scsi_is_target_device(dev))
++ __starget_for_each_device(to_scsi_target(dev),
++ NULL, __device_block);
++ else
++ device_for_each_child(dev, NULL, __target_block);
++}
++
++static void fc_block_rports(struct fc_lport *lp)
++{
++ struct Scsi_Host *shost = lp->host;
++ struct fc_rport *rport;
++ unsigned long flags;
++
++ spin_lock_irqsave(shost->host_lock, flags);
++ list_for_each_entry(rport, &fc_host_rports(shost), peers) {
++ /* protect the name service remote port */
++ if (rport == lp->dns_rp)
++ continue;
++ if (rport->port_state != FC_PORTSTATE_ONLINE)
++ continue;
++ rport->port_state = FC_PORTSTATE_BLOCKED;
++ rport->flags |= FC_RPORT_DEVLOSS_PENDING;
++ __scsi_target_block(&rport->dev);
++ }
++ spin_unlock_irqrestore(shost->host_lock, flags);
++}
++
++/*
++ * Fibre Channel Target discovery.
++ *
++ * Returns non-zero if discovery cannot be started.
++ *
++ * Callback is called for each target remote port found in discovery.
++ * When discovery is complete, the callback is called with a NULL remote port.
++ * Discovery may be restarted after an RSCN is received, causing the
++ * callback to be called after discovery complete is indicated.
++ */
++int fc_ns_disc_start(struct fc_lport *lp)
++{
++ struct fc_rport *rport;
++ int error;
++ struct fc_rport_identifiers ids;
++
++ fc_lport_lock(lp);
++
++ /*
++ * If not ready, or already running discovery, just set request flag.
++ */
++ if (!fc_lport_test_ready(lp) || lp->ns_disc_pending) {
++ lp->ns_disc_requested = 1;
++ fc_lport_unlock(lp);
++ return 0;
++ }
++ lp->ns_disc_pending = 1;
++ lp->ns_disc_requested = 0;
++ lp->ns_disc_retry_count = 0;
++
++ /*
++ * Handle point-to-point mode as a simple discovery
++ * of the remote port.
++ */
++ rport = lp->ptp_rp;
++ if (rport) {
++ ids.port_id = rport->port_id;
++ ids.port_name = rport->port_name;
++ ids.node_name = rport->node_name;
++ ids.roles = FC_RPORT_ROLE_UNKNOWN;
++ get_device(&rport->dev);
++ fc_lport_unlock(lp);
++ error = fc_ns_new_target(lp, rport, &ids);
++ put_device(&rport->dev);
++ if (!error)
++ fc_ns_disc_done(lp);
++ } else {
++ fc_lport_unlock(lp);
++ fc_block_rports(lp);
++ fc_ns_gpn_ft_req(lp); /* get ports by FC-4 type */
++ error = 0;
++ }
++ return error;
++}
++
++/*
++ * Handle resource allocation problem by retrying in a bit.
++ */
++static void fc_ns_retry(struct fc_lport *lp)
++{
++ if (lp->retry_count == 0)
++ FC_DBG("local port %6x alloc failure "
++ "- will retry\n", lp->fid);
++ if (lp->retry_count < lp->max_retry_count) {
++ lp->retry_count++;
++ mod_timer(&lp->state_timer,
++ jiffies + msecs_to_jiffies(lp->e_d_tov));
++ } else {
++ FC_DBG("local port %6x alloc failure "
++ "- retries exhausted\n", lp->fid);
++ fc_ns_enter_reject(lp);
++ }
++}
++
++/*
++ * Handle errors on local port requests.
++ * Don't get locks if in RESET state.
++ * The only possible errors so far are exchange TIMEOUT and CLOSED (reset).
++ */
++static void fc_ns_error(struct fc_lport *lp, struct fc_frame *fp)
++{
++ if (lp->state == LPORT_ST_RESET)
++ return;
++
++ fc_lport_lock(lp);
++ if (PTR_ERR(fp) == -FC_EX_TIMEOUT) {
++ if (lp->retry_count < lp->max_retry_count) {
++ lp->retry_count++;
++ fc_ns_enter_retry(lp);
++ } else {
++ fc_ns_enter_reject(lp);
++ }
++ }
++ if (fc_ns_debug)
++ FC_DBG("error %ld retries %d limit %d\n",
++ PTR_ERR(fp), lp->retry_count, lp->max_retry_count);
++ fc_lport_unlock(lp);
++}
++
++/*
++ * Restart discovery after a delay due to resource shortages.
++ * If the error persists, the discovery will be abandoned.
++ */
++static void fcdt_ns_retry(struct fc_lport *lp)
++{
++ unsigned long delay = FC_NS_RETRY_DELAY;
++
++ if (!lp->ns_disc_retry_count)
++ delay /= 4; /* timeout faster first time */
++ if (lp->ns_disc_retry_count++ < FC_NS_RETRY_LIMIT)
++ schedule_delayed_work(&lp->ns_disc_work,
++ msecs_to_jiffies(delay));
++ else
++ fc_ns_disc_done(lp);
++}
++
++/*
++ * Test for dNS accept in response payload.
++ */
++static int fc_lport_dns_acc(struct fc_frame *fp)
++{
++ struct fc_frame_header *fh;
++ struct fc_ct_hdr *ct;
++ int rc = 0;
++
++ fh = fc_frame_header_get(fp);
++ ct = fc_frame_payload_get(fp, sizeof(*ct));
++ if (fh && ct && fh->fh_type == FC_TYPE_CT &&
++ ct->ct_fs_type == FC_FST_DIR &&
++ ct->ct_fs_subtype == FC_NS_SUBTYPE &&
++ ntohs(ct->ct_cmd) == FC_FS_ACC) {
++ rc = 1;
++ }
++ return rc;
++}
++
++/*
++ * Handle response from name server.
++ */
++static void
++fc_ns_resp(struct fc_seq *sp, struct fc_frame *fp, void *lp_arg)
++{
++ struct fc_lport *lp = lp_arg;
++
++ if (!IS_ERR(fp)) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ if (fc_lport_dns_acc(fp)) {
++ if (lp->state == LPORT_ST_REG_PN)
++ fc_ns_enter_reg_ft(lp);
++ else
++ fc_ns_enter_scr(lp);
++
++ } else {
++ fc_ns_retry(lp);
++ }
++ fc_lport_unlock(lp);
++ fc_frame_free(fp);
++ } else
++ fc_ns_error(lp, fp);
++}
++
++/*
++ * Handle new target found by discovery.
++ * Create remote port and session if needed.
++ * Ignore returns of our own FID & WWPN.
++ *
++ * If a non-NULL rp is passed in, it is held for the caller, but not for us.
++ *
++ * Events delivered are:
++ * FC_EV_READY, when remote port is rediscovered.
++ */
++static int fc_ns_new_target(struct fc_lport *lp,
++ struct fc_rport *rport,
++ struct fc_rport_identifiers *ids)
++{
++ struct fc_rport_libfc_priv *rp;
++ int error = 0;
++
++ if (rport && ids->port_name) {
++ if (rport->port_name == -1) {
++ /*
++ * Set WWN and fall through to notify of create.
++ */
++ fc_rport_set_name(rport, ids->port_name,
++ rport->node_name);
++ } else if (rport->port_name != ids->port_name) {
++ /*
++ * This is a new port with the same FCID as
++ * a previously-discovered port. Presumably the old
++ * port logged out and a new port logged in and was
++ * assigned the same FCID. This should be rare.
++ * Delete the old one and fall thru to re-create.
++ */
++ fc_ns_del_target(lp, rport);
++ rport = NULL;
++ }
++ }
++ if (((ids->port_name != -1) || (ids->port_id != -1)) &&
++ ids->port_id != lp->fid && ids->port_name != lp->wwpn) {
++ if (!rport) {
++ rport = lp->tt.rport_lookup(lp, ids->port_id);
++ if (rport == NULL)
++ rport = lp->tt.rport_create(lp, ids);
++ if (!rport)
++ error = ENOMEM;
++ }
++ if (rport) {
++ rp = rport->dd_data;
++ rp->rp_state = RPORT_ST_INIT;
++ lp->tt.rport_login(rport);
++ }
++ }
++ return error;
++}
++
++/*
++ * Delete the remote port.
++ */
++static void fc_ns_del_target(struct fc_lport *lp, struct fc_rport *rport)
++{
++ lp->tt.rport_reset(rport);
++ fc_remote_port_delete(rport); /* release hold from create */
++}
++
++/*
++ * Done with discovery
++ */
++static void fc_ns_disc_done(struct fc_lport *lp)
++{
++ lp->ns_disc_done = 1;
++ lp->ns_disc_pending = 0;
++ if (lp->ns_disc_requested)
++ lp->tt.disc_start(lp);
++}
++
++/**
++ * fc_ns_fill_dns_hdr - Fill in a name service request header
++ * @lp: Fibre Channel host port instance
++ * @ct: Common Transport (CT) header structure
++ * @op: Name Service request code
++ * @req_size: Full size of Name Service request
++ */
++static void fc_ns_fill_dns_hdr(struct fc_lport *lp, struct fc_ct_hdr *ct,
++ unsigned int op, unsigned int req_size)
++{
++ memset(ct, 0, sizeof(*ct) + req_size);
++ ct->ct_rev = FC_CT_REV;
++ ct->ct_fs_type = FC_FST_DIR;
++ ct->ct_fs_subtype = FC_NS_SUBTYPE;
++ ct->ct_cmd = htons((u16) op);
++}
++
++/**
++ * fc_ns_gpn_ft_req - Send Get Port Names by FC-4 type (GPN_FT) request
++ * @lp: Fibre Channel host port instance
++ */
++static void fc_ns_gpn_ft_req(struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct fc_seq *sp = NULL;
++ struct req {
++ struct fc_ct_hdr ct;
++ struct fc_ns_gid_ft gid;
++ } *rp;
++ int error = 0;
++
++ lp->ns_disc_buf_len = 0;
++ lp->ns_disc_seq_count = 0;
++ fp = fc_frame_alloc(lp, sizeof(*rp));
++ if (fp == NULL) {
++ error = ENOMEM;
++ } else {
++ rp = fc_frame_payload_get(fp, sizeof(*rp));
++ fc_ns_fill_dns_hdr(lp, &rp->ct, FC_NS_GPN_FT, sizeof(rp->gid));
++ rp->gid.fn_fc4_type = FC_TYPE_FCP;
++
++ WARN_ON(!fc_lport_test_ready(lp));
++
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CTL, FC_TYPE_CT);
++ sp = lp->tt.exch_seq_send(lp, fp,
++ fc_ns_gpn_ft_resp,
++ lp, lp->e_d_tov,
++ lp->fid,
++ lp->dns_rp->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ);
++ }
++ if (error || sp == NULL)
++ fcdt_ns_retry(lp);
++}
++
++/*
++ * Handle error on dNS request.
++ */
++static void fcdt_ns_error(struct fc_lport *lp, struct fc_frame *fp)
++{
++ int err = PTR_ERR(fp);
++
++ switch (err) {
++ case -FC_EX_TIMEOUT:
++ if (lp->ns_disc_retry_count++ < FC_NS_RETRY_LIMIT) {
++ fc_ns_gpn_ft_req(lp);
++ } else {
++ FC_DBG("err %d - ending\n", err);
++ fc_ns_disc_done(lp);
++ }
++ break;
++ default:
++ FC_DBG("err %d - ending\n", err);
++ fc_ns_disc_done(lp);
++ break;
++ }
++}
++
++/**
++ * fc_ns_gpn_ft_parse - Parse the list of IDs and names resulting from a request
++ * @lp: Fibre Channel host port instance
++ * @buf: GPN_FT response buffer
++ * @len: size of response buffer
++ */
++static int fc_ns_gpn_ft_parse(struct fc_lport *lp, void *buf, size_t len)
++{
++ struct fc_gpn_ft_resp *np;
++ char *bp;
++ size_t plen;
++ size_t tlen;
++ int error = 0;
++ struct fc_ns_port *dp;
++
++ /*
++ * Handle partial name record left over from previous call.
++ */
++ bp = buf;
++ plen = len;
++ np = (struct fc_gpn_ft_resp *)bp;
++ tlen = lp->ns_disc_buf_len;
++ if (tlen) {
++ WARN_ON(tlen >= sizeof(*np));
++ plen = sizeof(*np) - tlen;
++ WARN_ON(plen <= 0);
++ WARN_ON(plen >= sizeof(*np));
++ if (plen > len)
++ plen = len;
++ np = &lp->ns_disc_buf;
++ memcpy((char *)np + tlen, bp, plen);
++
++ /*
++ * Set bp so that the loop below will advance it to the
++ * first valid full name element.
++ */
++ bp -= tlen;
++ len += tlen;
++ plen += tlen;
++ lp->ns_disc_buf_len = (unsigned char) plen;
++ if (plen == sizeof(*np))
++ lp->ns_disc_buf_len = 0;
++ }
++
++ /*
++ * Handle full name records, including the one filled from above.
++ * Normally, np == bp and plen == len, but from the partial case above,
++ * bp, len describe the overall buffer, and np, plen describe the
++ * partial buffer, which if would usually be full now.
++ * After the first time through the loop, things return to "normal".
++ */
++ while (plen >= sizeof(*np)) {
++ dp = kzalloc(sizeof(*dp), GFP_KERNEL);
++ if (!dp)
++ break;
++ dp->lp = lp;
++ dp->ids.port_id = ntoh24(np->fp_fid);
++ dp->ids.port_name = ntohll(np->fp_wwpn);
++ dp->ids.node_name = -1;
++ dp->ids.roles = FC_RPORT_ROLE_UNKNOWN;
++ error = fc_ns_gnn_id_req(lp, dp);
++ if (error)
++ break;
++ if (np->fp_flags & FC_NS_FID_LAST) {
++ fc_ns_disc_done(lp);
++ len = 0;
++ break;
++ }
++ len -= sizeof(*np);
++ bp += sizeof(*np);
++ np = (struct fc_gpn_ft_resp *)bp;
++ plen = len;
++ }
++
++ /*
++ * Save any partial record at the end of the buffer for next time.
++ */
++ if (error == 0 && len > 0 && len < sizeof(*np)) {
++ if (np != &lp->ns_disc_buf)
++ memcpy(&lp->ns_disc_buf, np, len);
++ lp->ns_disc_buf_len = (unsigned char) len;
++ } else {
++ lp->ns_disc_buf_len = 0;
++ }
++ return error;
++}
++
++/*
++ * Handle retry of memory allocation for remote ports.
++ */
++static void fc_ns_timeout(struct work_struct *work)
++{
++ struct fc_lport *lp;
++
++ lp = container_of(work, struct fc_lport, ns_disc_work.work);
++
++ if (lp->ns_disc_pending)
++ fc_ns_gpn_ft_req(lp);
++ else
++ lp->tt.disc_start(lp);
++}
++
++/**
++ * fc_ns_gpn_ft_resp - Handle a response frame from Get Port Names (GPN_FT)
++ * @sp: Current sequence of GPN_FT exchange
++ * @fp: response frame
++ * @lp_arg: Fibre Channel host port instance
++ *
++ * The response may be in multiple frames
++ */
++static void fc_ns_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *lp_arg)
++{
++ struct fc_lport *lp = lp_arg;
++ struct fc_ct_hdr *cp;
++ struct fc_frame_header *fh;
++ unsigned int seq_cnt;
++ void *buf = NULL;
++ unsigned int len;
++ int error;
++
++ if (IS_ERR(fp)) {
++ fcdt_ns_error(lp, fp);
++ return;
++ }
++
++ WARN_ON(!fc_frame_is_linear(fp)); /* buffer must be contiguous */
++ fh = fc_frame_header_get(fp);
++ len = fr_len(fp) - sizeof(*fh);
++ seq_cnt = ntohs(fh->fh_seq_cnt);
++ if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 &&
++ lp->ns_disc_seq_count == 0) {
++ cp = fc_frame_payload_get(fp, sizeof(*cp));
++ if (cp == NULL) {
++ FC_DBG("GPN_FT response too short, len %d\n",
++ fr_len(fp));
++ } else if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
++
++ /*
++ * Accepted. Parse response.
++ */
++ buf = cp + 1;
++ len -= sizeof(*cp);
++ } else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
++ FC_DBG("GPN_FT rejected reason %x exp %x "
++ "(check zoning)\n", cp->ct_reason,
++ cp->ct_explan);
++ fc_ns_disc_done(lp);
++ } else {
++ FC_DBG("GPN_FT unexpected response code %x\n",
++ ntohs(cp->ct_cmd));
++ }
++ } else if (fr_sof(fp) == FC_SOF_N3 &&
++ seq_cnt == lp->ns_disc_seq_count) {
++ buf = fh + 1;
++ } else {
++ FC_DBG("GPN_FT unexpected frame - out of sequence? "
++ "seq_cnt %x expected %x sof %x eof %x\n",
++ seq_cnt, lp->ns_disc_seq_count, fr_sof(fp), fr_eof(fp));
++ }
++ if (buf) {
++ error = fc_ns_gpn_ft_parse(lp, buf, len);
++ if (error)
++ fcdt_ns_retry(lp);
++ else
++ lp->ns_disc_seq_count++;
++ }
++ fc_frame_free(fp);
++}
++
++/*
++ * Discover the directory information for a single target.
++ * This could be from an RSCN that reported a change for the target.
++ */
++static void fc_ns_single(struct fc_lport *lp, struct fc_ns_port *dp)
++{
++ struct fc_rport *rport;
++
++ if (dp->ids.port_id == lp->fid)
++ goto out;
++
++ rport = lp->tt.rport_lookup(lp, dp->ids.port_id);
++ if (rport) {
++ fc_ns_del_target(lp, rport);
++ put_device(&rport->dev); /* hold from lookup */
++ }
++
++ if (fc_ns_gpn_id_req(lp, dp) != 0)
++ goto error;
++ return;
++error:
++ fc_ns_restart(lp);
++out:
++ kfree(dp);
++}
++
++/**
++ * fc_ns_gpn_id_req - Send Get Port Name by ID (GPN_ID) request
++ * @lp: Fibre Channel host port instance
++ * @dp: Temporary discovery port for holding IDs and world wide names
++ *
++ * The remote port is held by the caller for us.
++ */
++static int fc_ns_gpn_id_req(struct fc_lport *lp, struct fc_ns_port *dp)
++{
++ struct fc_frame *fp;
++ struct req {
++ struct fc_ct_hdr ct;
++ struct fc_ns_fid fid;
++ } *cp;
++ int error = 0;
++
++ fp = fc_frame_alloc(lp, sizeof(*cp));
++ if (fp == NULL)
++ return -ENOMEM;
++
++ cp = fc_frame_payload_get(fp, sizeof(*cp));
++ fc_ns_fill_dns_hdr(lp, &cp->ct, FC_NS_GPN_ID, sizeof(cp->fid));
++ hton24(cp->fid.fp_fid, dp->ids.port_id);
++
++ WARN_ON(!fc_lport_test_ready(lp));
++
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CTL, FC_TYPE_CT);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_ns_gpn_id_resp,
++ dp, lp->e_d_tov,
++ lp->fid,
++ lp->dns_rp->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ error = -ENOMEM;
++
++ return error;
++}
++
++/**
++ * fc_ns_gpn_id_resp - Handle response to GPN_ID
++ * @sp: Current sequence of GPN_ID exchange
++ * @fp: response frame
++ * @dp_arg: Temporary discovery port for holding IDs and world wide names
++ */
++static void fc_ns_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *dp_arg)
++{
++ struct fc_ns_port *dp = dp_arg;
++ struct fc_lport *lp;
++ struct resp {
++ struct fc_ct_hdr ct;
++ __be64 wwn;
++ } *cp;
++ unsigned int cmd;
++
++ if (IS_ERR(fp)) {
++ fc_ns_gpn_id_error(dp, fp);
++ return;
++ }
++
++ lp = dp->lp;
++ WARN_ON(!fc_frame_is_linear(fp)); /* buffer must be contiguous */
++
++ cp = fc_frame_payload_get(fp, sizeof(cp->ct));
++ if (cp == NULL) {
++ FC_DBG("GPN_ID response too short, len %d\n", fr_len(fp));
++ return;
++ }
++ cmd = ntohs(cp->ct.ct_cmd);
++ switch (cmd) {
++ case FC_FS_ACC:
++ cp = fc_frame_payload_get(fp, sizeof(*cp));
++ if (cp == NULL) {
++ FC_DBG("GPN_ID response payload too short, len %d\n",
++ fr_len(fp));
++ break;
++ }
++ dp->ids.port_name = ntohll(cp->wwn);
++ fc_ns_gnn_id_req(lp, dp);
++ break;
++ case FC_FS_RJT:
++ fc_ns_restart(lp);
++ break;
++ default:
++ FC_DBG("GPN_ID unexpected CT response cmd %x\n", cmd);
++ break;
++ }
++ fc_frame_free(fp);
++}
++
++/**
++ * fc_ns_gpn_id_error - Handle error from GPN_ID
++ * @dp: Temporary discovery port for holding IDs and world wide names
++ * @fp: response frame
++ */
++static void fc_ns_gpn_id_error(struct fc_ns_port *dp, struct fc_frame *fp)
++{
++ struct fc_lport *lp = dp->lp;
++
++ switch (PTR_ERR(fp)) {
++ case -FC_EX_TIMEOUT:
++ fc_ns_restart(lp);
++ break;
++ case -FC_EX_CLOSED:
++ default:
++ break;
++ }
++ kfree(dp);
++}
++
++/*
++ * Setup session to dNS if not already set up.
++ */
++static void fc_ns_enter_dns(struct fc_lport *lp)
++{
++ struct fc_rport *rport;
++ struct fc_rport_libfc_priv *rp;
++ struct fc_rport_identifiers ids = {
++ .port_id = FC_FID_DIR_SERV,
++ .port_name = -1,
++ .node_name = -1,
++ .roles = FC_RPORT_ROLE_UNKNOWN,
++ };
++
++ if (fc_ns_debug)
++ FC_DBG("Processing DNS state\n");
++
++ fc_lport_state_enter(lp, LPORT_ST_DNS);
++
++ if (!lp->dns_rp) {
++ /*
++ * Set up remote port to directory server.
++ */
++
++ /*
++ * we are called with the state_lock, but if rport_lookup_create
++ * needs to create a rport then it will sleep.
++ */
++ fc_lport_unlock(lp);
++ rport = lp->tt.rport_lookup(lp, ids.port_id);
++ if (rport == NULL)
++ rport = lp->tt.rport_create(lp, &ids);
++ fc_lport_lock(lp);
++ if (!rport)
++ goto err;
++ lp->dns_rp = rport;
++ }
++
++ rport = lp->dns_rp;
++ rp = rport->dd_data;
++
++ /*
++ * If dNS session isn't ready, start its logon.
++ */
++ if (rp->rp_state != RPORT_ST_READY) {
++ lp->tt.rport_login(rport);
++ } else {
++ del_timer(&lp->state_timer);
++ fc_ns_enter_reg_pn(lp);
++ }
++ return;
++
++ /*
++ * Resource allocation problem (malloc). Try again in 500 mS.
++ */
++err:
++ fc_ns_retry(lp);
++}
++
++/*
++ * Logoff DNS session.
++ * We should get an event call when the session has been logged out.
++ */
++static void fc_ns_enter_dns_stop(struct fc_lport *lp)
++{
++ struct fc_rport *rport = lp->dns_rp;
++
++ if (fc_ns_debug)
++ FC_DBG("Processing DNS_STOP state\n");
++
++ fc_lport_state_enter(lp, LPORT_ST_DNS_STOP);
++
++ if (rport)
++ lp->tt.rport_logout(rport);
++ else
++ lp->tt.lport_logout(lp);
++}
++
++/*
++ * Fill in dNS request header.
++ */
++static void
++fc_lport_fill_dns_hdr(struct fc_lport *lp, struct fc_ct_hdr *ct,
++ unsigned int op, unsigned int req_size)
++{
++ memset(ct, 0, sizeof(*ct) + req_size);
++ ct->ct_rev = FC_CT_REV;
++ ct->ct_fs_type = FC_FST_DIR;
++ ct->ct_fs_subtype = FC_NS_SUBTYPE;
++ ct->ct_cmd = htons(op);
++}
++
++/*
++ * Register port name with name server.
++ */
++static void fc_ns_enter_reg_pn(struct fc_lport *lp)
++{
++ struct fc_frame *fp;
++ struct req {
++ struct fc_ct_hdr ct;
++ struct fc_ns_rn_id rn;
++ } *req;
++
++ if (fc_ns_debug)
++ FC_DBG("Processing REG_PN state\n");
++
++ fc_lport_state_enter(lp, LPORT_ST_REG_PN);
++ fp = fc_frame_alloc(lp, sizeof(*req));
++ if (!fp) {
++ fc_ns_retry(lp);
++ return;
++ }
++ req = fc_frame_payload_get(fp, sizeof(*req));
++ memset(req, 0, sizeof(*req));
++ fc_lport_fill_dns_hdr(lp, &req->ct, FC_NS_RPN_ID, sizeof(req->rn));
++ hton24(req->rn.fr_fid.fp_fid, lp->fid);
++ put_unaligned_be64(lp->wwpn, &req->rn.fr_wwn);
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CTL, FC_TYPE_CT);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_ns_resp, lp,
++ lp->e_d_tov,
++ lp->fid,
++ lp->dns_rp->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ fc_ns_retry(lp);
++}
++
++int fc_ns_init(struct fc_lport *lp)
++{
++ INIT_DELAYED_WORK(&lp->ns_disc_work, fc_ns_timeout);
++
++ if (!lp->tt.disc_start)
++ lp->tt.disc_start = fc_ns_disc_start;
++
++ if (!lp->tt.disc_recv_req)
++ lp->tt.disc_recv_req = fc_ns_recv_req;
++
++ if (!lp->tt.dns_register)
++ lp->tt.dns_register = fc_ns_enter_dns;
++
++ if (!lp->tt.disc_stop)
++ lp->tt.disc_stop = fc_ns_enter_dns_stop;
++
++ return 0;
++}
++EXPORT_SYMBOL(fc_ns_init);
++
++/**
++ * fc_ns_gnn_id_req - Send Get Node Name by ID (GNN_ID) request
++ * @lp: Fibre Channel host port instance
++ * @dp: Temporary discovery port for holding IDs and world wide names
++ *
++ * The remote port is held by the caller for us.
++ */
++static int fc_ns_gnn_id_req(struct fc_lport *lp, struct fc_ns_port *dp)
++{
++ struct fc_frame *fp;
++ struct req {
++ struct fc_ct_hdr ct;
++ struct fc_ns_fid fid;
++ } *cp;
++ int error = 0;
++
++ fp = fc_frame_alloc(lp, sizeof(*cp));
++ if (fp == NULL)
++ return -ENOMEM;
++
++ cp = fc_frame_payload_get(fp, sizeof(*cp));
++ fc_ns_fill_dns_hdr(lp, &cp->ct, FC_NS_GNN_ID, sizeof(cp->fid));
++ hton24(cp->fid.fp_fid, dp->ids.port_id);
++
++ WARN_ON(!fc_lport_test_ready(lp));
++
++ fc_frame_setup(fp, FC_RCTL_DD_UNSOL_CTL, FC_TYPE_CT);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_ns_gnn_id_resp,
++ dp, lp->e_d_tov,
++ lp->fid,
++ lp->dns_rp->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ error = -ENOMEM;
++
++ return error;
++}
++
++/**
++ * fc_ns_gnn_id_resp - Handle response to GNN_ID
++ * @sp: Current sequence of GNN_ID exchange
++ * @fp: response frame
++ * @dp_arg: Temporary discovery port for holding IDs and world wide names
++ */
++static void fc_ns_gnn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *dp_arg)
++{
++ struct fc_ns_port *dp = dp_arg;
++ struct fc_lport *lp;
++ struct resp {
++ struct fc_ct_hdr ct;
++ __be64 wwn;
++ } *cp;
++ unsigned int cmd;
++
++ if (IS_ERR(fp)) {
++ fc_ns_gnn_id_error(dp, fp);
++ return;
++ }
++
++ lp = dp->lp;
++ WARN_ON(!fc_frame_is_linear(fp)); /* buffer must be contiguous */
++
++ cp = fc_frame_payload_get(fp, sizeof(cp->ct));
++ if (cp == NULL) {
++ FC_DBG("GNN_ID response too short, len %d\n", fr_len(fp));
++ return;
++ }
++ cmd = ntohs(cp->ct.ct_cmd);
++ switch (cmd) {
++ case FC_FS_ACC:
++ cp = fc_frame_payload_get(fp, sizeof(*cp));
++ if (cp == NULL) {
++ FC_DBG("GNN_ID response payload too short, len %d\n",
++ fr_len(fp));
++ break;
++ }
++ dp->ids.node_name = ntohll(cp->wwn);
++ fc_ns_new_target(lp, NULL, &dp->ids);
++ break;
++ case FC_FS_RJT:
++ fc_ns_restart(lp);
++ break;
++ default:
++ FC_DBG("GNN_ID unexpected CT response cmd %x\n", cmd);
++ break;
++ }
++ kfree(dp);
++ fc_frame_free(fp);
++}
++
++/**
++ * fc_ns_gnn_id_error - Handle error from GNN_ID
++ * @dp: Temporary discovery port for holding IDs and world wide names
++ * @fp: response frame
++ */
++static void fc_ns_gnn_id_error(struct fc_ns_port *dp, struct fc_frame *fp)
++{
++ struct fc_lport *lp = dp->lp;
++
++ switch (PTR_ERR(fp)) {
++ case -FC_EX_TIMEOUT:
++ fc_ns_restart(lp);
++ break;
++ case -FC_EX_CLOSED:
++ default:
++ break;
++ }
++ kfree(dp);
++}
++
+diff --git a/drivers/scsi/libfc/fc_rport.c b/drivers/scsi/libfc/fc_rport.c
+new file mode 100644
+index 0000000..6d0c970
+--- /dev/null
++++ b/drivers/scsi/libfc/fc_rport.c
+@@ -0,0 +1,1301 @@
++/*
++ * Copyright(c) 2007 - 2008 Intel Corporation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
++ * more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
++ *
++ * Maintained at www.Open-FCoE.org
++ */
++
++/*
++ * Remote Port support.
++ *
++ * A remote port structure contains information about an N port to which we
++ * will create sessions.
++ */
++
++#include <linux/kernel.h>
++#include <linux/spinlock.h>
++#include <linux/interrupt.h>
++#include <linux/rcupdate.h>
++#include <linux/timer.h>
++#include <linux/workqueue.h>
++#include <asm/unaligned.h>
++
++#include <scsi/libfc/libfc.h>
++
++static int fc_rp_debug;
++
++/*
++ * static functions.
++ */
++static void fc_rport_enter_start(struct fc_rport *);
++static void fc_rport_enter_plogi(struct fc_rport *);
++static void fc_rport_enter_prli(struct fc_rport *);
++static void fc_rport_enter_rtv(struct fc_rport *);
++static void fc_rport_enter_logo(struct fc_rport *);
++static void fc_rport_recv_plogi_req(struct fc_rport *,
++ struct fc_seq *, struct fc_frame *);
++static void fc_rport_recv_prli_req(struct fc_rport *,
++ struct fc_seq *, struct fc_frame *);
++static void fc_rport_recv_prlo_req(struct fc_rport *,
++ struct fc_seq *, struct fc_frame *);
++static void fc_rport_recv_logo_req(struct fc_rport *,
++ struct fc_seq *, struct fc_frame *);
++static void fc_rport_timeout(struct work_struct *);
++
++static struct fc_rport *fc_remote_port_create(struct fc_lport *,
++ struct fc_rport_identifiers *);
++
++/**
++ * fc_rport_lookup - lookup a remote port by port_id
++ * @lp: Fibre Channel host port instance
++ * @fid: remote port port_id to match
++ */
++struct fc_rport *fc_rport_lookup(const struct fc_lport *lp, u32 fid)
++{
++ struct Scsi_Host *shost = lp->host;
++ struct fc_rport *rport, *found;
++ unsigned long flags;
++
++ found = NULL;
++ spin_lock_irqsave(shost->host_lock, flags);
++ list_for_each_entry(rport, &fc_host_rports(shost), peers)
++ if (rport->port_id == fid &&
++ rport->port_state == FC_PORTSTATE_ONLINE) {
++ found = rport;
++ get_device(&found->dev);
++ break;
++ }
++ spin_unlock_irqrestore(shost->host_lock, flags);
++ return found;
++}
++
++/**
++ * fc_remote_port_create - create a remote port
++ * @lp: Fibre Channel host port instance
++ * @ids: remote port identifiers (port_id, port_name, and node_name must be set)
++ */
++static struct fc_rport *fc_remote_port_create(struct fc_lport *lp,
++ struct fc_rport_identifiers *ids)
++{
++ struct fc_rport_libfc_priv *rp;
++ struct fc_rport *rport;
++
++ rport = fc_remote_port_add(lp->host, 0, ids);
++ if (!rport)
++ return NULL;
++
++ rp = rport->dd_data;
++ rp->local_port = lp;
++
++ /* default value until service parameters are exchanged in PLOGI */
++ rport->maxframe_size = FC_MIN_MAX_PAYLOAD;
++
++ spin_lock_init(&rp->rp_lock);
++ rp->rp_state = RPORT_ST_INIT;
++ rp->local_port = lp;
++ rp->e_d_tov = lp->e_d_tov;
++ rp->r_a_tov = lp->r_a_tov;
++ rp->flags = FC_RP_FLAGS_REC_SUPPORTED;
++ INIT_DELAYED_WORK(&rp->retry_work, fc_rport_timeout);
++
++ return rport;
++}
++
++static inline void fc_rport_lock(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ spin_lock_bh(&rp->rp_lock);
++}
++
++static inline void fc_rport_unlock(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ spin_unlock_bh(&rp->rp_lock);
++}
++
++/**
++ * fc_plogi_get_maxframe - Get max payload from the common service parameters
++ * @flp: FLOGI payload structure
++ * @maxval: upper limit, may be less than what is in the service parameters
++ */
++static unsigned int
++fc_plogi_get_maxframe(struct fc_els_flogi *flp, unsigned int maxval)
++{
++ unsigned int mfs;
++
++ /*
++ * Get max payload from the common service parameters and the
++ * class 3 receive data field size.
++ */
++ mfs = ntohs(flp->fl_csp.sp_bb_data) & FC_SP_BB_DATA_MASK;
++ if (mfs >= FC_SP_MIN_MAX_PAYLOAD && mfs < maxval)
++ maxval = mfs;
++ mfs = ntohs(flp->fl_cssp[3 - 1].cp_rdfs);
++ if (mfs >= FC_SP_MIN_MAX_PAYLOAD && mfs < maxval)
++ maxval = mfs;
++ return maxval;
++}
++
++/**
++ * fc_lport_plogi_fill - Fill in PLOGI command for request
++ * @lp: Fibre Channel host port instance
++ * @plogi: PLOGI command structure to fill (same structure as FLOGI)
++ * @op: either ELS_PLOGI for a localy generated request, or ELS_LS_ACC
++ */
++static void
++fc_lport_plogi_fill(struct fc_lport *lp,
++ struct fc_els_flogi *plogi, unsigned int op)
++{
++ struct fc_els_csp *sp;
++ struct fc_els_cssp *cp;
++
++ memset(plogi, 0, sizeof(*plogi));
++ plogi->fl_cmd = (u8) op;
++ put_unaligned_be64(lp->wwpn, &plogi->fl_wwpn);
++ put_unaligned_be64(lp->wwnn, &plogi->fl_wwnn);
++
++ sp = &plogi->fl_csp;
++ sp->sp_hi_ver = 0x20;
++ sp->sp_lo_ver = 0x20;
++ sp->sp_bb_cred = htons(10); /* this gets set by gateway */
++ sp->sp_bb_data = htons((u16) lp->mfs);
++ cp = &plogi->fl_cssp[3 - 1]; /* class 3 parameters */
++ cp->cp_class = htons(FC_CPC_VALID | FC_CPC_SEQ);
++ if (op != ELS_FLOGI) {
++ sp->sp_features = htons(FC_SP_FT_CIRO);
++ sp->sp_tot_seq = htons(255); /* seq. we accept */
++ sp->sp_rel_off = htons(0x1f);
++ sp->sp_e_d_tov = htonl(lp->e_d_tov);
++
++ cp->cp_rdfs = htons((u16) lp->mfs);
++ cp->cp_con_seq = htons(255);
++ cp->cp_open_seq = 1;
++ }
++}
++
++static void fc_rport_state_enter(struct fc_rport *rport,
++ enum fc_rport_state new)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ if (rp->rp_state != new)
++ rp->retries = 0;
++ rp->rp_state = new;
++}
++
++/**
++ * fc_rport_login - Start the remote port login state machine
++ * @rport: Fibre Channel remote port
++ */
++int fc_rport_login(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++
++ fc_rport_lock(rport);
++ if (rp->rp_state == RPORT_ST_INIT) {
++ fc_rport_unlock(rport);
++ fc_rport_enter_start(rport);
++ } else if (rp->rp_state == RPORT_ST_ERROR) {
++ fc_rport_state_enter(rport, RPORT_ST_INIT);
++ fc_rport_unlock(rport);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x closed\n", rport->port_id);
++
++ if (rport == lp->dns_rp &&
++ lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++ } else
++ fc_rport_unlock(rport);
++
++ return 0;
++}
++
++/*
++ * Stop the session - log it off.
++ */
++int fc_rport_logout(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++
++ fc_rport_lock(rport);
++ switch (rp->rp_state) {
++ case RPORT_ST_PRLI:
++ case RPORT_ST_RTV:
++ case RPORT_ST_READY:
++ fc_rport_enter_logo(rport);
++ fc_rport_unlock(rport);
++ break;
++ default:
++ fc_rport_state_enter(rport, RPORT_ST_INIT);
++ fc_rport_unlock(rport);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x closed\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++
++ fc_remote_port_delete(rport);
++ }
++ break;
++ }
++
++ return 0;
++}
++
++/*
++ * Reset the session - assume it is logged off. Used after fabric logoff.
++ * The local port code takes care of resetting the exchange manager.
++ */
++void fc_rport_reset(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp;
++
++ if (fc_rp_debug)
++ FC_DBG("sess to %6x reset\n", rport->port_id);
++ fc_rport_lock(rport);
++
++ lp = rp->local_port;
++ fc_rport_state_enter(rport, RPORT_ST_INIT);
++ fc_rport_unlock(rport);
++
++ if (fc_rp_debug)
++ FC_DBG("remote %6x closed\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++}
++
++/*
++ * Reset all sessions for a local port session list.
++ */
++void fc_rport_reset_list(struct fc_lport *lp)
++{
++ struct Scsi_Host *shost = lp->host;
++ struct fc_rport *rport;
++ struct fc_rport *next;
++ unsigned long flags;
++
++ spin_lock_irqsave(shost->host_lock, flags);
++ list_for_each_entry_safe(rport, next, &fc_host_rports(shost), peers) {
++ lp->tt.rport_reset(rport);
++ }
++ spin_unlock_irqrestore(shost->host_lock, flags);
++}
++
++static void fc_rport_enter_start(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++
++ /*
++ * If the local port is already logged on, advance to next state.
++ * Otherwise the local port will be logged on by fc_rport_unlock().
++ */
++ fc_rport_state_enter(rport, RPORT_ST_STARTED);
++
++ if (rport == lp->dns_rp || fc_lport_test_ready(lp))
++ fc_rport_enter_plogi(rport);
++}
++
++/*
++ * Handle exchange reject or retry exhaustion in various states.
++ */
++static void fc_rport_reject(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++ switch (rp->rp_state) {
++ case RPORT_ST_PLOGI:
++ case RPORT_ST_PRLI:
++ fc_rport_state_enter(rport, RPORT_ST_ERROR);
++ if (rport == lp->dns_rp &&
++ lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++ break;
++ case RPORT_ST_RTV:
++ fc_rport_state_enter(rport, RPORT_ST_READY);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x ready\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state == LPORT_ST_DNS) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->tt.dns_register(lp);
++ fc_lport_unlock(lp);
++ }
++ break;
++ case RPORT_ST_LOGO:
++ fc_rport_state_enter(rport, RPORT_ST_INIT);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x closed\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++ break;
++ case RPORT_ST_NONE:
++ case RPORT_ST_READY:
++ case RPORT_ST_ERROR:
++ case RPORT_ST_PLOGI_RECV:
++ case RPORT_ST_STARTED:
++ case RPORT_ST_INIT:
++ BUG();
++ break;
++ }
++ return;
++}
++
++/*
++ * Timeout handler for retrying after allocation failures or exchange timeout.
++ */
++static void fc_rport_timeout(struct work_struct *work)
++{
++ struct fc_rport_libfc_priv *rp =
++ container_of(work, struct fc_rport_libfc_priv, retry_work.work);
++ struct fc_rport *rport = (((void *)rp) - sizeof(struct fc_rport));
++
++ switch (rp->rp_state) {
++ case RPORT_ST_PLOGI:
++ fc_rport_enter_plogi(rport);
++ break;
++ case RPORT_ST_PRLI:
++ fc_rport_enter_prli(rport);
++ break;
++ case RPORT_ST_RTV:
++ fc_rport_enter_rtv(rport);
++ break;
++ case RPORT_ST_LOGO:
++ fc_rport_enter_logo(rport);
++ break;
++ case RPORT_ST_READY:
++ case RPORT_ST_ERROR:
++ case RPORT_ST_INIT:
++ break;
++ case RPORT_ST_NONE:
++ case RPORT_ST_PLOGI_RECV:
++ case RPORT_ST_STARTED:
++ BUG();
++ break;
++ }
++ put_device(&rport->dev);
++}
++
++/*
++ * Handle retry for allocation failure via timeout.
++ */
++static void fc_rport_retry(struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++
++ if (rp->retries < lp->max_retry_count) {
++ rp->retries++;
++ get_device(&rport->dev);
++ schedule_delayed_work(&rp->retry_work,
++ msecs_to_jiffies(rp->e_d_tov));
++ } else {
++ FC_DBG("sess %6x alloc failure in state %d, "
++ "retries exhausted\n",
++ rport->port_id, rp->rp_state);
++ fc_rport_reject(rport);
++ }
++}
++
++/*
++ * Handle error from a sequence issued by the rport state machine.
++ */
++static void fc_rport_error(struct fc_rport *rport, struct fc_frame *fp)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ fc_rport_lock(rport);
++ if (fc_rp_debug)
++ FC_DBG("state %d error %ld retries %d\n",
++ rp->rp_state, PTR_ERR(fp), rp->retries);
++
++ if (PTR_ERR(fp) == -FC_EX_TIMEOUT &&
++ rp->retries++ >= rp->local_port->max_retry_count) {
++ get_device(&rport->dev);
++ schedule_delayed_work(&rp->retry_work, 0);
++ } else
++ fc_rport_reject(rport);
++
++ fc_rport_unlock(rport);
++}
++
++/**
++ * fc_rport_plpogi_recv_resp - Handle incoming ELS PLOGI response
++ * @sp: current sequence in the PLOGI exchange
++ * @fp: response frame
++ * @rp_arg: Fibre Channel remote port
++ */
++static void fc_rport_plogi_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *rp_arg)
++{
++ struct fc_els_ls_rjt *rjp;
++ struct fc_els_flogi *plp;
++ u64 wwpn, wwnn;
++ unsigned int tov;
++ u16 csp_seq;
++ u16 cssp_seq;
++ u8 op;
++ struct fc_rport *rport = rp_arg;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++
++ if (!IS_ERR(fp)) {
++ op = fc_frame_payload_op(fp);
++ fc_rport_lock(rport);
++ if (op == ELS_LS_ACC &&
++ (plp = fc_frame_payload_get(fp, sizeof(*plp))) != NULL) {
++ wwpn = get_unaligned_be64(&plp->fl_wwpn);
++ wwnn = get_unaligned_be64(&plp->fl_wwnn);
++
++ fc_rport_set_name(rport, wwpn, wwnn);
++ tov = ntohl(plp->fl_csp.sp_e_d_tov);
++ if (ntohs(plp->fl_csp.sp_features) & FC_SP_FT_EDTR)
++ tov /= 1000;
++ if (tov > rp->e_d_tov)
++ rp->e_d_tov = tov;
++ csp_seq = ntohs(plp->fl_csp.sp_tot_seq);
++ cssp_seq = ntohs(plp->fl_cssp[3 - 1].cp_con_seq);
++ if (cssp_seq < csp_seq)
++ csp_seq = cssp_seq;
++ rp->max_seq = csp_seq;
++ rport->maxframe_size =
++ fc_plogi_get_maxframe(plp, rp->local_port->mfs);
++ if (rp->rp_state == RPORT_ST_PLOGI)
++ fc_rport_enter_prli(rport);
++ } else {
++ if (fc_rp_debug)
++ FC_DBG("bad PLOGI response\n");
++
++ rjp = fc_frame_payload_get(fp, sizeof(*rjp));
++ if (op == ELS_LS_RJT && rjp != NULL &&
++ rjp->er_reason == ELS_RJT_INPROG)
++ fc_rport_retry(rport); /* try again */
++ else
++ fc_rport_reject(rport); /* error */
++ }
++ fc_rport_unlock(rport);
++ fc_frame_free(fp);
++ } else {
++ fc_rport_error(rport, fp);
++ }
++}
++
++/**
++ * fc_rport_enter_plogi - Send Port Login (PLOGI) request to peer
++ * @rport: Fibre Channel remote port to send PLOGI to
++ */
++static void fc_rport_enter_plogi(struct fc_rport *rport)
++{
++ struct fc_frame *fp;
++ struct fc_els_flogi *plogi;
++ struct fc_lport *lp;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++
++ lp = rp->local_port;
++ fc_rport_state_enter(rport, RPORT_ST_PLOGI);
++ rport->maxframe_size = FC_MIN_MAX_PAYLOAD;
++ fp = fc_frame_alloc(lp, sizeof(*plogi));
++ if (!fp)
++ return fc_rport_retry(rport);
++ plogi = fc_frame_payload_get(fp, sizeof(*plogi));
++ WARN_ON(!plogi);
++ fc_lport_plogi_fill(rp->local_port, plogi, ELS_PLOGI);
++ rp->e_d_tov = lp->e_d_tov;
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_rport_plogi_resp,
++ rport, lp->e_d_tov,
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ fc_rport_retry(rport);
++}
++
++/**
++ * fc_rport_prli_resp - Process Login (PRLI) response handler
++ * @sp: current sequence in the PRLI exchange
++ * @fp: response frame
++ * @rp_arg: Fibre Channel remote port
++ */
++static void fc_rport_prli_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *rp_arg)
++{
++ struct fc_rport *rport = rp_arg;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++ struct {
++ struct fc_els_prli prli;
++ struct fc_els_spp spp;
++ } *pp;
++ u32 roles = FC_RPORT_ROLE_UNKNOWN;
++ u32 fcp_parm = 0;
++ u8 op;
++
++ if (IS_ERR(fp)) {
++ fc_rport_error(rport, fp);
++ return;
++ }
++
++ fc_rport_lock(rport);
++ op = fc_frame_payload_op(fp);
++ if (op == ELS_LS_ACC) {
++ pp = fc_frame_payload_get(fp, sizeof(*pp));
++ if (pp && pp->prli.prli_spp_len >= sizeof(pp->spp)) {
++ fcp_parm = ntohl(pp->spp.spp_params);
++ if (fcp_parm & FCP_SPPF_RETRY)
++ rp->flags |= FC_RP_FLAGS_RETRY;
++ }
++
++ rport->supported_classes = FC_COS_CLASS3;
++ if (fcp_parm & FCP_SPPF_INIT_FCN)
++ roles |= FC_RPORT_ROLE_FCP_INITIATOR;
++ if (fcp_parm & FCP_SPPF_TARG_FCN)
++ roles |= FC_RPORT_ROLE_FCP_TARGET;
++
++ fc_rport_enter_rtv(rport);
++ fc_rport_unlock(rport);
++ fc_remote_port_rolechg(rport, roles);
++ } else {
++ FC_DBG("bad ELS response\n");
++ fc_rport_state_enter(rport, RPORT_ST_ERROR);
++ fc_rport_unlock(rport);
++ if (rport == lp->dns_rp && lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++ }
++
++ fc_frame_free(fp);
++}
++
++/**
++ * fc_rport_logo_resp - Logout (LOGO) response handler
++ * @sp: current sequence in the LOGO exchange
++ * @fp: response frame
++ * @rp_arg: Fibre Channel remote port
++ */
++static void fc_rport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *rp_arg)
++{
++ struct fc_rport *rport = rp_arg;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++ u8 op;
++
++ if (IS_ERR(fp)) {
++ fc_rport_error(rport, fp);
++ return;
++ }
++
++ fc_rport_lock(rport);
++ op = fc_frame_payload_op(fp);
++ if (op == ELS_LS_ACC) {
++ fc_rport_enter_rtv(rport);
++ fc_rport_unlock(rport);
++ } else {
++ FC_DBG("bad ELS response\n");
++ fc_rport_state_enter(rport, RPORT_ST_ERROR);
++ fc_rport_unlock(rport);
++ if (rport == lp->dns_rp && lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++ }
++
++ fc_frame_free(fp);
++}
++
++/**
++ * fc_rport_enter_prli - Send Process Login (PRLI) request to peer
++ * @rport: Fibre Channel remote port to send PRLI to
++ */
++static void fc_rport_enter_prli(struct fc_rport *rport)
++{
++ struct {
++ struct fc_els_prli prli;
++ struct fc_els_spp spp;
++ } *pp;
++ struct fc_frame *fp;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++
++ fc_rport_state_enter(rport, RPORT_ST_PRLI);
++
++ /*
++ * Special case if session is for name server or any other
++ * well-known address: Skip the PRLI step.
++ * This should be made more general, possibly moved to the FCP layer.
++ */
++ if (rport->port_id >= FC_FID_DOM_MGR) {
++ fc_rport_state_enter(rport, RPORT_ST_READY);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x ready\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state == LPORT_ST_DNS) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->tt.dns_register(lp);
++ fc_lport_unlock(lp);
++ }
++ return;
++ }
++ fp = fc_frame_alloc(lp, sizeof(*pp));
++ if (!fp)
++ return fc_rport_retry(rport);
++ pp = fc_frame_payload_get(fp, sizeof(*pp));
++ WARN_ON(!pp);
++ memset(pp, 0, sizeof(*pp));
++ pp->prli.prli_cmd = ELS_PRLI;
++ pp->prli.prli_spp_len = sizeof(struct fc_els_spp);
++ pp->prli.prli_len = htons(sizeof(*pp));
++ pp->spp.spp_type = FC_TYPE_FCP;
++ pp->spp.spp_flags = FC_SPP_EST_IMG_PAIR;
++ pp->spp.spp_params = htonl(rp->local_port->service_params);
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_rport_prli_resp,
++ rport, lp->e_d_tov,
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ fc_rport_retry(rport);
++}
++
++/**
++ * fc_rport_els_rtv_resp - Request Timeout Value response handler
++ * @sp: current sequence in the RTV exchange
++ * @fp: response frame
++ * @rp_arg: Fibre Channel remote port
++ *
++ * Many targets don't seem to support this.
++ */
++static void fc_rport_rtv_resp(struct fc_seq *sp, struct fc_frame *fp,
++ void *rp_arg)
++{
++ struct fc_rport *rport = rp_arg;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++ u8 op;
++
++ if (IS_ERR(fp)) {
++ fc_rport_error(rport, fp);
++ return;
++ }
++
++ fc_rport_lock(rport);
++ op = fc_frame_payload_op(fp);
++ if (op == ELS_LS_ACC) {
++ struct fc_els_rtv_acc *rtv;
++ u32 toq;
++ u32 tov;
++
++ rtv = fc_frame_payload_get(fp, sizeof(*rtv));
++ if (rtv) {
++ toq = ntohl(rtv->rtv_toq);
++ tov = ntohl(rtv->rtv_r_a_tov);
++ if (tov == 0)
++ tov = 1;
++ rp->r_a_tov = tov;
++ tov = ntohl(rtv->rtv_e_d_tov);
++ if (toq & FC_ELS_RTV_EDRES)
++ tov /= 1000000;
++ if (tov == 0)
++ tov = 1;
++ rp->e_d_tov = tov;
++ }
++ }
++ fc_rport_state_enter(rport, RPORT_ST_READY);
++ fc_rport_unlock(rport);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x ready\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state == LPORT_ST_DNS) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->tt.dns_register(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_frame_free(fp);
++}
++
++/**
++ * fc_rport_enter_rtv - Send Request Timeout Value (RTV) request to peer
++ * @rport: Fibre Channel remote port to send RTV to
++ */
++static void fc_rport_enter_rtv(struct fc_rport *rport)
++{
++ struct fc_els_rtv *rtv;
++ struct fc_frame *fp;
++ struct fc_lport *lp;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++
++ lp = rp->local_port;
++ fc_rport_state_enter(rport, RPORT_ST_RTV);
++
++ fp = fc_frame_alloc(lp, sizeof(*rtv));
++ if (!fp)
++ return fc_rport_retry(rport);
++ rtv = fc_frame_payload_get(fp, sizeof(*rtv));
++ WARN_ON(!rtv);
++ memset(rtv, 0, sizeof(*rtv));
++ rtv->rtv_cmd = ELS_RTV;
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_rport_rtv_resp,
++ rport, lp->e_d_tov,
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ fc_rport_retry(rport);
++}
++
++/**
++ * fc_rport_enter_logo - Send Logout (LOGO) request to peer
++ * @rport: Fibre Channel remote port to send LOGO to
++ */
++static void fc_rport_enter_logo(struct fc_rport *rport)
++{
++ struct fc_frame *fp;
++ struct fc_els_logo *logo;
++ struct fc_lport *lp;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++
++ fc_rport_state_enter(rport, RPORT_ST_LOGO);
++
++ lp = rp->local_port;
++ fp = fc_frame_alloc(lp, sizeof(*logo));
++ if (!fp)
++ return fc_rport_retry(rport);
++ logo = fc_frame_payload_get(fp, sizeof(*logo));
++ memset(logo, 0, sizeof(*logo));
++ logo->fl_cmd = ELS_LOGO;
++ hton24(logo->fl_n_port_id, lp->fid);
++ logo->fl_n_port_wwn = htonll(lp->wwpn);
++
++ fc_frame_setup(fp, FC_RCTL_ELS_REQ, FC_TYPE_ELS);
++ if (!lp->tt.exch_seq_send(lp, fp,
++ fc_rport_logo_resp,
++ rport, lp->e_d_tov,
++ rp->local_port->fid,
++ rport->port_id,
++ FC_FC_SEQ_INIT | FC_FC_END_SEQ))
++ fc_rport_retry(rport);
++}
++
++/*
++ * Handle a request received by the exchange manager for the session.
++ * This may be an entirely new session, or a PLOGI or LOGO for an existing one.
++ * This will free the frame.
++ */
++void fc_rport_recv_req(struct fc_seq *sp, struct fc_frame *fp,
++ struct fc_rport *rport)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_frame_header *fh;
++ struct fc_lport *lp = rp->local_port;
++ struct fc_seq_els_data els_data;
++ u8 op;
++
++ els_data.fp = NULL;
++ els_data.explan = ELS_EXPL_NONE;
++ els_data.reason = ELS_RJT_NONE;
++
++ fh = fc_frame_header_get(fp);
++
++ if (fh->fh_r_ctl == FC_RCTL_ELS_REQ && fh->fh_type == FC_TYPE_ELS) {
++ op = fc_frame_payload_op(fp);
++ switch (op) {
++ case ELS_PLOGI:
++ fc_rport_recv_plogi_req(rport, sp, fp);
++ break;
++ case ELS_PRLI:
++ fc_rport_recv_prli_req(rport, sp, fp);
++ break;
++ case ELS_PRLO:
++ fc_rport_recv_prlo_req(rport, sp, fp);
++ break;
++ case ELS_LOGO:
++ fc_rport_recv_logo_req(rport, sp, fp);
++ break;
++ case ELS_RRQ:
++ els_data.fp = fp;
++ lp->tt.seq_els_rsp_send(sp, ELS_RRQ, &els_data);
++ break;
++ case ELS_REC:
++ els_data.fp = fp;
++ lp->tt.seq_els_rsp_send(sp, ELS_REC, &els_data);
++ break;
++ default:
++ els_data.reason = ELS_RJT_UNSUP;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &els_data);
++ fc_frame_free(fp);
++ break;
++ }
++ } else {
++ fc_frame_free(fp);
++ }
++}
++
++/**
++ * fc_rport_recv_plogi_req - Handle incoming Port Login (PLOGI) request
++ * @rport: Fibre Channel remote port that initiated PLOGI
++ * @sp: current sequence in the PLOGI exchange
++ * @fp: PLOGI request frame
++ */
++static void fc_rport_recv_plogi_req(struct fc_rport *rport,
++ struct fc_seq *sp, struct fc_frame *rx_fp)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_frame *fp = rx_fp;
++ struct fc_frame_header *fh;
++ struct fc_lport *lp;
++ struct fc_els_flogi *pl;
++ struct fc_seq_els_data rjt_data;
++ u32 sid;
++ u64 wwpn;
++ u64 wwnn;
++ enum fc_els_rjt_reason reject = 0;
++ u32 f_ctl;
++
++ rjt_data.fp = NULL;
++ fh = fc_frame_header_get(fp);
++ sid = ntoh24(fh->fh_s_id);
++ pl = fc_frame_payload_get(fp, sizeof(*pl));
++ if (!pl) {
++ FC_DBG("incoming PLOGI from %x too short\n", sid);
++ WARN_ON(1);
++ /* XXX TBD: send reject? */
++ fc_frame_free(fp);
++ return;
++ }
++ wwpn = get_unaligned_be64(&pl->fl_wwpn);
++ wwnn = get_unaligned_be64(&pl->fl_wwnn);
++ fc_rport_lock(rport);
++ lp = rp->local_port;
++
++ /*
++ * If the session was just created, possibly due to the incoming PLOGI,
++ * set the state appropriately and accept the PLOGI.
++ *
++ * If we had also sent a PLOGI, and if the received PLOGI is from a
++ * higher WWPN, we accept it, otherwise an LS_RJT is sent with reason
++ * "command already in progress".
++ *
++ * XXX TBD: If the session was ready before, the PLOGI should result in
++ * all outstanding exchanges being reset.
++ */
++ switch (rp->rp_state) {
++ case RPORT_ST_INIT:
++ if (fc_rp_debug)
++ FC_DBG("incoming PLOGI from %6x wwpn %llx state INIT "
++ "- reject\n", sid, wwpn);
++ reject = ELS_RJT_UNSUP;
++ break;
++ case RPORT_ST_STARTED:
++ /*
++ * we'll only accept a login if the port name
++ * matches or was unknown.
++ */
++ if (rport->port_name != -1 &&
++ rport->port_name != wwpn) {
++ FC_DBG("incoming PLOGI from name %llx expected %llx\n",
++ wwpn, rport->port_name);
++ reject = ELS_RJT_UNAB;
++ }
++ break;
++ case RPORT_ST_PLOGI:
++ if (fc_rp_debug)
++ FC_DBG("incoming PLOGI from %x in PLOGI state %d\n",
++ sid, rp->rp_state);
++ if (wwpn < lp->wwpn)
++ reject = ELS_RJT_INPROG;
++ break;
++ case RPORT_ST_PRLI:
++ case RPORT_ST_ERROR:
++ case RPORT_ST_READY:
++ if (fc_rp_debug)
++ FC_DBG("incoming PLOGI from %x in logged-in state %d "
++ "- ignored for now\n", sid, rp->rp_state);
++ /* XXX TBD - should reset */
++ break;
++ case RPORT_ST_NONE:
++ default:
++ if (fc_rp_debug)
++ FC_DBG("incoming PLOGI from %x in unexpected "
++ "state %d\n", sid, rp->rp_state);
++ break;
++ }
++
++ if (reject) {
++ rjt_data.reason = reject;
++ rjt_data.explan = ELS_EXPL_NONE;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
++ fc_frame_free(fp);
++ } else {
++ fp = fc_frame_alloc(lp, sizeof(*pl));
++ if (fp == NULL) {
++ fp = rx_fp;
++ rjt_data.reason = ELS_RJT_UNAB;
++ rjt_data.explan = ELS_EXPL_NONE;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
++ fc_frame_free(fp);
++ } else {
++ sp = lp->tt.seq_start_next(sp);
++ WARN_ON(!sp);
++ fc_rport_set_name(rport, wwpn, wwnn);
++
++ /*
++ * Get session payload size from incoming PLOGI.
++ */
++ rport->maxframe_size =
++ fc_plogi_get_maxframe(pl, lp->mfs);
++ fc_frame_free(rx_fp);
++ pl = fc_frame_payload_get(fp, sizeof(*pl));
++ WARN_ON(!pl);
++ fc_lport_plogi_fill(lp, pl, ELS_LS_ACC);
++
++ /*
++ * Send LS_ACC. If this fails,
++ * the originator should retry.
++ */
++ f_ctl = FC_FC_SEQ_INIT | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ fc_frame_setup(fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ lp->tt.seq_send(lp, sp, fp, f_ctl);
++ if (rp->rp_state == RPORT_ST_PLOGI)
++ fc_rport_enter_prli(rport);
++ else
++ fc_rport_state_enter(rport,
++ RPORT_ST_PLOGI_RECV);
++ }
++ }
++ fc_rport_unlock(rport);
++}
++
++/**
++ * fc_rport_recv_prli_req - Handle incoming Process Login (PRLI) request
++ * @rport: Fibre Channel remote port that initiated PRLI
++ * @sp: current sequence in the PRLI exchange
++ * @fp: PRLI request frame
++ */
++static void fc_rport_recv_prli_req(struct fc_rport *rport,
++ struct fc_seq *sp, struct fc_frame *rx_fp)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_frame *fp;
++ struct fc_frame_header *fh;
++ struct fc_lport *lp;
++ struct {
++ struct fc_els_prli prli;
++ struct fc_els_spp spp;
++ } *pp;
++ struct fc_els_spp *rspp; /* request service param page */
++ struct fc_els_spp *spp; /* response spp */
++ unsigned int len;
++ unsigned int plen;
++ enum fc_els_rjt_reason reason = ELS_RJT_UNAB;
++ enum fc_els_rjt_explan explan = ELS_EXPL_NONE;
++ enum fc_els_spp_resp resp;
++ struct fc_seq_els_data rjt_data;
++ u32 f_ctl;
++ u32 fcp_parm;
++ u32 roles = FC_RPORT_ROLE_UNKNOWN;
++
++ rjt_data.fp = NULL;
++ fh = fc_frame_header_get(rx_fp);
++ lp = rp->local_port;
++ switch (rp->rp_state) {
++ case RPORT_ST_PLOGI_RECV:
++ case RPORT_ST_PRLI:
++ case RPORT_ST_READY:
++ reason = ELS_RJT_NONE;
++ break;
++ default:
++ break;
++ }
++ len = fr_len(rx_fp) - sizeof(*fh);
++ pp = fc_frame_payload_get(rx_fp, sizeof(*pp));
++ if (pp == NULL) {
++ reason = ELS_RJT_PROT;
++ explan = ELS_EXPL_INV_LEN;
++ } else {
++ plen = ntohs(pp->prli.prli_len);
++ if ((plen % 4) != 0 || plen > len) {
++ reason = ELS_RJT_PROT;
++ explan = ELS_EXPL_INV_LEN;
++ } else if (plen < len) {
++ len = plen;
++ }
++ plen = pp->prli.prli_spp_len;
++ if ((plen % 4) != 0 || plen < sizeof(*spp) ||
++ plen > len || len < sizeof(*pp)) {
++ reason = ELS_RJT_PROT;
++ explan = ELS_EXPL_INV_LEN;
++ }
++ rspp = &pp->spp;
++ }
++ if (reason != ELS_RJT_NONE ||
++ (fp = fc_frame_alloc(lp, len)) == NULL) {
++ rjt_data.reason = reason;
++ rjt_data.explan = explan;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
++ } else {
++ sp = lp->tt.seq_start_next(sp);
++ WARN_ON(!sp);
++ pp = fc_frame_payload_get(fp, len);
++ WARN_ON(!pp);
++ memset(pp, 0, len);
++ pp->prli.prli_cmd = ELS_LS_ACC;
++ pp->prli.prli_spp_len = plen;
++ pp->prli.prli_len = htons(len);
++ len -= sizeof(struct fc_els_prli);
++
++ /*
++ * Go through all the service parameter pages and build
++ * response. If plen indicates longer SPP than standard,
++ * use that. The entire response has been pre-cleared above.
++ */
++ spp = &pp->spp;
++ while (len >= plen) {
++ spp->spp_type = rspp->spp_type;
++ spp->spp_type_ext = rspp->spp_type_ext;
++ spp->spp_flags = rspp->spp_flags & FC_SPP_EST_IMG_PAIR;
++ resp = FC_SPP_RESP_ACK;
++ if (rspp->spp_flags & FC_SPP_RPA_VAL)
++ resp = FC_SPP_RESP_NO_PA;
++ switch (rspp->spp_type) {
++ case 0: /* common to all FC-4 types */
++ break;
++ case FC_TYPE_FCP:
++ fcp_parm = ntohl(rspp->spp_params);
++ if (fcp_parm * FCP_SPPF_RETRY)
++ rp->flags |= FC_RP_FLAGS_RETRY;
++ rport->supported_classes = FC_COS_CLASS3;
++ if (fcp_parm & FCP_SPPF_INIT_FCN)
++ roles |= FC_RPORT_ROLE_FCP_INITIATOR;
++ if (fcp_parm & FCP_SPPF_TARG_FCN)
++ roles |= FC_RPORT_ROLE_FCP_TARGET;
++ fc_remote_port_rolechg(rport, roles);
++ spp->spp_params =
++ htonl(rp->local_port->service_params);
++ break;
++ default:
++ resp = FC_SPP_RESP_INVL;
++ break;
++ }
++ spp->spp_flags |= resp;
++ len -= plen;
++ rspp = (struct fc_els_spp *)((char *)rspp + plen);
++ spp = (struct fc_els_spp *)((char *)spp + plen);
++ }
++
++ /*
++ * Send LS_ACC. If this fails, the originator should retry.
++ */
++ f_ctl = FC_FC_SEQ_INIT | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
++ fc_frame_setup(fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
++ lp->tt.seq_send(lp, sp, fp, f_ctl);
++
++ /*
++ * Get lock and re-check state.
++ */
++ fc_rport_lock(rport);
++ switch (rp->rp_state) {
++ case RPORT_ST_PLOGI_RECV:
++ case RPORT_ST_PRLI:
++ fc_rport_state_enter(rport, RPORT_ST_READY);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x ready\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state == LPORT_ST_DNS) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->tt.dns_register(lp);
++ fc_lport_unlock(lp);
++ }
++ break;
++ case RPORT_ST_READY:
++ break;
++ default:
++ break;
++ }
++ fc_rport_unlock(rport);
++ }
++ fc_frame_free(rx_fp);
++}
++
++/**
++ * fc_rport_recv_prlo_req - Handle incoming Process Logout (PRLO) request
++ * @rport: Fibre Channel remote port that initiated PRLO
++ * @sp: current sequence in the PRLO exchange
++ * @fp: PRLO request frame
++ */
++static void fc_rport_recv_prlo_req(struct fc_rport *rport, struct fc_seq *sp,
++ struct fc_frame *fp)
++{
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_frame_header *fh;
++ struct fc_lport *lp = rp->local_port;
++ struct fc_seq_els_data rjt_data;
++
++ fh = fc_frame_header_get(fp);
++ FC_DBG("incoming PRLO from %x state %d\n",
++ ntoh24(fh->fh_s_id), rp->rp_state);
++ rjt_data.fp = NULL;
++ rjt_data.reason = ELS_RJT_UNAB;
++ rjt_data.explan = ELS_EXPL_NONE;
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
++ fc_frame_free(fp);
++}
++
++/**
++ * fc_rport_recv_logo_req - Handle incoming Logout (LOGO) request
++ * @rport: Fibre Channel remote port that initiated LOGO
++ * @sp: current sequence in the LOGO exchange
++ * @fp: LOGO request frame
++ */
++static void fc_rport_recv_logo_req(struct fc_rport *rport, struct fc_seq *sp,
++ struct fc_frame *fp)
++{
++ struct fc_frame_header *fh;
++ struct fc_rport_libfc_priv *rp = rport->dd_data;
++ struct fc_lport *lp = rp->local_port;
++
++ fh = fc_frame_header_get(fp);
++ fc_rport_lock(rport);
++ fc_rport_state_enter(rport, RPORT_ST_INIT);
++ fc_rport_unlock(rport);
++ if (fc_rp_debug)
++ FC_DBG("remote %6x closed\n", rport->port_id);
++ if (rport == lp->dns_rp &&
++ lp->state != LPORT_ST_RESET) {
++ fc_lport_lock(lp);
++ del_timer(&lp->state_timer);
++ lp->dns_rp = NULL;
++ if (lp->state == LPORT_ST_DNS_STOP) {
++ fc_lport_unlock(lp);
++ lp->tt.lport_logout(lp);
++ } else {
++ lp->tt.lport_login(lp);
++ fc_lport_unlock(lp);
++ }
++ fc_remote_port_delete(rport);
++ }
++ lp->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
++ fc_frame_free(fp);
++}
++
++int fc_rport_init(struct fc_lport *lp)
++{
++ if (!lp->tt.rport_login)
++ lp->tt.rport_login = fc_rport_login;
++
++ if (!lp->tt.rport_logout)
++ lp->tt.rport_logout = fc_rport_logout;
++
++ if (!lp->tt.rport_recv_req)
++ lp->tt.rport_recv_req = fc_rport_recv_req;
++
++ if (!lp->tt.rport_create)
++ lp->tt.rport_create = fc_remote_port_create;
++
++ if (!lp->tt.rport_lookup)
++ lp->tt.rport_lookup = fc_rport_lookup;
++
++ if (!lp->tt.rport_reset)
++ lp->tt.rport_reset = fc_rport_reset;
++
++ if (!lp->tt.rport_reset_list)
++ lp->tt.rport_reset_list = fc_rport_reset_list;
++
++ return 0;
++}
++EXPORT_SYMBOL(fc_rport_init);
++
+--
+1.5.2.4
+