| /* |
| * 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> |
| |
| /* |
| * 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; |
| |
| 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; |
| |
| 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_locked(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); |
| 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->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); |
| FC_DEBUG_EXCH("seq lookup failed\n"); |
| } else { |
| atomic_inc(&mp->stats.non_bls_resp); |
| FC_DEBUG_EXCH("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); |
| FC_DEBUG_EXCH("exch: BLS rctl %x - %s\n", |
| fh->fh_r_ctl, fc_exch_rctl_name(fh->fh_r_ctl)); |
| |
| if (cancel_delayed_work_sync(&ep->timeout_work)) |
| 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->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 (ep->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: |
| FC_DEBUG_EXCH("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 (cancel_delayed_work(&ep->timeout_work)) |
| 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->arg; |
| sp = &ep->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, 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_lport *lp, u32 sid, u32 did) |
| { |
| struct fc_exch *ep; |
| struct fc_exch *next; |
| struct fc_exch_mgr *mp = lp->emp; |
| |
| 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); |
| |
| /* |
| * 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 *aborted_ep = arg; |
| unsigned int op; |
| |
| if (IS_ERR(fp)) { |
| int err = PTR_ERR(fp); |
| |
| if (err == -FC_EX_CLOSED || err == -FC_EX_TIMEOUT) |
| 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: |
| 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; |
| u32 did; |
| |
| lp = ep->lp; |
| |
| fp = fc_frame_alloc(lp, sizeof(*rrq)); |
| if (!fp) |
| return; |
| 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; |
| |
| fc_fill_fc_hdr(fp, FC_RCTL_ELS_REQ, did, |
| fc_host_port_id(lp->host), FC_TYPE_ELS, |
| FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0); |
| |
| rrq_sp = fc_exch_seq_send(lp, fp, fc_exch_rrq_resp, NULL, ep, |
| lp->e_d_tov); |
| if (!rrq_sp) { |
| ep->esb_stat |= ESB_ST_REC_QUAL; |
| fc_exch_timer_set_locked(ep, ep->r_a_tov); |
| return; |
| } |
| } |
| |
| |
| /* |
| * 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) { |
| if (cancel_delayed_work(&ep->timeout_work)) |
| atomic_dec(&ep->ex_refcnt); /* drop timer hold */ |
| } |
| |
| 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 |
| */ |
| #define xid_ok(i, m1, m2) (((i) >= (m1)) && ((i) <= (m2))) |
| len = (max_xid - min_xid + 1) * (sizeof(struct fc_exch *)); |
| len += sizeof(struct fc_exch_mgr); |
| |
| mp = kzalloc(len, GFP_ATOMIC); |
| if (!mp) |
| return NULL; |
| |
| mp->class = class; |
| mp->total_exches = 0; |
| mp->exches = (struct fc_exch **)(mp + 1); |
| mp->lp = lp; |
| /* adjust em exch xid range for offload */ |
| mp->min_xid = min_xid; |
| mp->max_xid = max_xid; |
| mp->last_xid = min_xid - 1; |
| mp->max_read = 0; |
| mp->last_read = 0; |
| if (lp->lro_enabled && xid_ok(lp->lro_xid, min_xid, max_xid)) { |
| mp->max_read = lp->lro_xid; |
| mp->last_read = min_xid - 1; |
| mp->last_xid = mp->max_read; |
| } else { |
| /* disable lro if no xid control over read */ |
| lp->lro_enabled = 0; |
| } |
| |
| 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, fp, 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 (*destructor)(struct fc_seq *, void *), |
| void *arg, u32 timer_msec) |
| { |
| struct fc_exch *ep; |
| struct fc_seq *sp = NULL; |
| struct fc_frame_header *fh; |
| int rc = 1; |
| |
| ep = lp->tt.exch_get(lp, fp); |
| if (!ep) { |
| fc_frame_free(fp); |
| return NULL; |
| } |
| ep->esb_stat |= ESB_ST_SEQ_INIT; |
| fh = fc_frame_header_get(fp); |
| fc_exch_set_addr(ep, ntoh24(fh->fh_s_id), ntoh24(fh->fh_d_id)); |
| ep->resp = resp; |
| ep->destructor = destructor; |
| ep->arg = arg; |
| ep->r_a_tov = FC_DEF_R_A_TOV; |
| ep->lp = lp; |
| sp = &ep->seq; |
| |
| ep->fh_type = fh->fh_type; /* save for possbile timeout handling */ |
| ep->f_ctl = ntoh24(fh->fh_f_ctl); |
| fc_exch_setup_hdr(ep, fp, ep->f_ctl); |
| sp->cnt++; |
| |
| if (unlikely(lp->tt.frame_send(lp, fp))) |
| goto err; |
| |
| if (timer_msec) |
| fc_exch_timer_set_locked(ep, timer_msec); |
| ep->f_ctl &= ~FC_FC_FIRST_SEQ; /* not first seq */ |
| |
| if (ep->f_ctl & FC_FC_SEQ_INIT) |
| ep->esb_stat &= ~ESB_ST_SEQ_INIT; |
| spin_unlock_bh(&ep->ex_lock); |
| return sp; |
| err: |
| rc = fc_exch_done_locked(ep); |
| spin_unlock_bh(&ep->ex_lock); |
| if (!rc) |
| fc_exch_mgr_delete_ep(ep); |
| 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; |
| |
| /* lport lock ? */ |
| 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; |
| |
| 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); |
| } |