From: Hannes Reinecke 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 Signed-off-by: Chris Leech Signed-off-by: Vasu Dev Signed-off-by: Yi Zou Signed-off-by: Steve Ma Signed-off-by: Hannes Reinecke --- 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 +#include + +#include + +#include + +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 +#include +#include + +#include + +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +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/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 +#include +#include +#include + +#include + +/* + * 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 +#include + +#include + +#include + +/* 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 +#include +#include + +#include + +#include + +#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 +#include +#include +#include +#include +#include +#include + +#include + +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