diff options
Diffstat (limited to 'drivers/scsi/libfc/fc_exch.c')
-rw-r--r-- | drivers/scsi/libfc/fc_exch.c | 1970 |
1 files changed, 1970 insertions, 0 deletions
diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c new file mode 100644 index 00000000000..66db08a5f27 --- /dev/null +++ b/drivers/scsi/libfc/fc_exch.c @@ -0,0 +1,1970 @@ +/* + * 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 + */ + +/* + * Fibre Channel exchange and sequence handling. + */ + +#include <linux/timer.h> +#include <linux/gfp.h> +#include <linux/err.h> + +#include <scsi/fc/fc_fc2.h> + +#include <scsi/libfc.h> +#include <scsi/fc_encode.h> + +#define FC_DEF_R_A_TOV (10 * 1000) /* resource allocation timeout */ + +/* + * fc_exch_debug can be set in debugger or at compile time to get more logs. + */ +static int fc_exch_debug; + +#define FC_DEBUG_EXCH(fmt...) \ + do { \ + if (fc_exch_debug) \ + FC_DBG(fmt); \ + } while (0) + +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. + */ + +/* + * 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, + must be taken before ex_lock */ + u16 last_xid; /* last allocated exchange ID */ + u16 min_xid; /* min exchange ID */ + u16 max_xid; /* max exchange ID */ + u16 max_read; /* max exchange ID for read */ + u16 last_read; /* last xid allocated for read */ + 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) + +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. + * - If the em_lock and ex_lock must be taken at the same time, then the + * em_lock must be taken before the ex_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); +} + +/* + * setup fc hdr by initializing few more FC header fields and sof/eof. + * Initialized fields by this func: + * - fh_ox_id, fh_rx_id, fh_seq_id, fh_seq_cnt + * - sof and eof + */ +static void fc_exch_setup_hdr(struct fc_exch *ep, struct fc_frame *fp, + u32 f_ctl) +{ + struct fc_frame_header *fh = fc_frame_header_get(fp); + u16 fill; + + fr_sof(fp) = ep->class; + if (ep->seq.cnt) + fr_sof(fp) = fc_sof_normal(ep->class); + + if (f_ctl & FC_FC_END_SEQ) { + fr_eof(fp) = FC_EOF_T; + if (fc_sof_needs_ack(ep->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. + */ + 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); + hton24(fh->fh_f_ctl, f_ctl | fill); + } + } else { + WARN_ON(fr_len(fp) % 4 != 0); /* no pad to non last frame */ + fr_eof(fp) = FC_EOF_N; + } + + /* + * Initialize remainig fh fields + * from fc_fill_fc_hdr + */ + fh->fh_ox_id = htons(ep->oxid); + fh->fh_rx_id = htons(ep->rxid); + fh->fh_seq_id = ep->seq.id; + fh->fh_seq_cnt = htons(ep->seq.cnt); +} + + +/* + * 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->destructor) + ep->destructor(&ep->seq, ep->arg); + if (ep->lp->tt.exch_put) + ep->lp->tt.exch_put(ep->lp, mp, ep->xid); + WARN_ON(!ep->esb_stat & ESB_ST_COMPLETE); + 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 (cancel_delayed_work(&ep->timeout_work)) + atomic_dec(&ep->ex_refcnt); /* drop hold for timer */ + 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); + fc_exch_release(ep); /* drop hold for exch in mp */ +} + +/* + * 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; + + FC_DEBUG_EXCH("Exchange (%4x) timed out, notifying the upper layer\n", + ep->xid); + if (schedule_delayed_work(&ep->timeout_work, + 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; + } + + 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_fill_fc_hdr(fp, FC_RCTL_BA_ABTS, ep->did, ep->sid, + FC_TYPE_BLS, FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0); + error = fc_seq_send(ep->lp, sp, fp); + } 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(struct work_struct *work) +{ + struct fc_exch *ep = container_of(work, struct fc_exch, + timeout_work.work); + 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; + if (e_stat & ESB_ST_REC_QUAL) + fc_exch_rrq(ep); + spin_unlock_bh(&ep->ex_lock); + goto done; + } else { + resp = ep->resp; + arg = ep->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->cnt = 0; + sp->id = seq_id; + return sp; +} + +/* + * fc_em_alloc_xid - returns an xid based on request type + * @lp : ptr to associated lport + * @fp : ptr to the assocated frame + * + * check the associated fc_fsp_pkt to get scsi command type and + * command direction to decide from which range this exch id + * will be allocated from. + * + * Returns : 0 or an valid xid + */ +static u16 fc_em_alloc_xid(struct fc_exch_mgr *mp, const struct fc_frame *fp) +{ + u16 xid, min, max; + u16 *plast; + struct fc_exch *ep = NULL; + + if (mp->max_read) { + if (fc_frame_is_read(fp)) { + min = mp->min_xid; + max = mp->max_read; + plast = &mp->last_read; + } else { + min = mp->max_read + 1; + max = mp->max_xid; + plast = &mp->last_xid; + } + } else { + min = mp->min_xid; + max = mp->max_xid; + plast = &mp->last_xid; + } + xid = *plast; + do { + xid = (xid == max) ? min : xid + 1; + ep = mp->exches[xid - mp->min_xid]; + } while ((ep != NULL) && (xid != *plast)); + + if (unlikely(ep)) + xid = 0; + else + *plast = xid; + + return xid; +} + +/* + * fc_exch_alloc - allocate an exchange. + * @mp : ptr to the exchange manager + * @xid: input xid + * + * if xid is supplied zero then assign next free exchange ID + * from exchange manager, otherwise use supplied xid. + * Returns with exch lock held. + */ +struct fc_exch *fc_exch_alloc(struct fc_exch_mgr *mp, + struct fc_frame *fp, u16 xid) +{ + struct fc_exch *ep; + + /* allocate memory for 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); + /* alloc xid if input xid 0 */ + if (!xid) { + /* alloc a new xid */ + xid = fc_em_alloc_xid(mp, fp); + if (!xid) { + printk(KERN_ERR "fc_em_alloc_xid() failed\n"); + goto err; + } + } + + fc_exch_hold(ep); /* hold for exch in mp */ + spin_lock_init(&ep->ex_lock); + /* + * Hold exch lock for caller to prevent fc_exch_reset() + * from releasing exch while fc_exch_alloc() caller is + * still working on exch. + */ + spin_lock_bh(&ep->ex_lock); + + mp->exches[xid - mp->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; + INIT_DELAYED_WORK(&ep->timeout_work, fc_exch_timeout); +out: + return ep; +err: + spin_unlock_bh(&mp->em_lock); + atomic_inc(&mp->stats.no_free_exch_xid); + mempool_free(ep, mp->ep_pool); + return NULL; +} +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); + } + fc_exch_hold(ep); /* hold for caller */ + spin_unlock_bh(&ep->ex_lock); /* lock from exch_get */ + } + 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; + 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; + } + ep = fc_exch_resp(mp, fp); + if (!ep) { + reject = FC_RJT_EXCH_EST; /* XXX */ + goto out; + } + 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_done(&ep->seq); + fc_exch_release(ep); /* hold from fc_exch_find/fc_exch_resp */ + 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++); + FC_DEBUG_EXCH("exch %4x f_ctl %6x seq %2x\n", + ep->xid, ep->f_ctl, sp->id); + 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) +{ + struct fc_exch *ep; + struct fc_frame_header *fh = fc_frame_header_get(fp); + int error; + u32 f_ctl; + + ep = fc_seq_exch(sp); + WARN_ON((ep->esb_stat & ESB_ST_SEQ_INIT) != ESB_ST_SEQ_INIT); + + f_ctl = ntoh24(fh->fh_f_ctl); + fc_exch_setup_hdr(ep, fp, f_ctl); + + /* + * update sequence count if this frame is carrying + * multiple FC frames when sequence offload is enabled + * by LLD. + */ + if (fr_max_payload(fp)) + sp->cnt += DIV_ROUND_UP((fr_len(fp) - sizeof(*fh)), + fr_max_payload(fp)); + else + 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); + ep->f_ctl = f_ctl & ~FC_FC_FIRST_SEQ; /* not first seq */ + if (f_ctl & (FC_FC_END_SEQ | 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; + struct fc_exch *ep = fc_seq_exch(sp); + + f_ctl = FC_FC_LAST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT; + f_ctl |= ep->f_ctl; + fc_fill_fc_hdr(fp, rctl, ep->did, ep->sid, fh_type, f_ctl, 0); + fc_seq_send(ep->lp, sp, fp); +} + +/* + * 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_exch *ep = fc_seq_exch(sp); + struct fc_lport *lp = ep->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); + if (!fp) + return; + + 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); + + fc_exch_setup_hdr(ep, fp, 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->arg); + else + lp->tt.lport_recv(lp, sp, fp); + fc_exch_release(ep); /* release from lookup */ + } else { + FC_DEBUG_EXCH("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); |