2008-12-10 02:10:17 +03:00
/*
* 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 ) ;
2009-02-05 00:17:29 +03:00
WARN_ON ( ! ( ep - > esb_stat & ESB_ST_COMPLETE ) ) ;
2008-12-10 02:10:17 +03:00
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 ) {
2009-02-28 01:07:21 +03:00
if ( fc_fcp_is_read ( fr_fsp ( fp ) ) ) {
2008-12-10 02:10:17 +03:00
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 .
*/
2009-02-27 21:55:50 +03:00
static enum fc_pf_rjt_reason fc_seq_lookup_recip ( struct fc_exch_mgr * mp ,
struct fc_frame * fp )
2008-12-10 02:10:17 +03:00
{
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 .
*/
2009-02-27 21:55:50 +03:00
static void fc_exch_send_ba_rjt ( struct fc_frame * rx_fp ,
enum fc_ba_rjt_reason reason ,
enum fc_ba_rjt_explan explan )
2008-12-10 02:10:17 +03:00
{
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 ) ;
}
2009-02-27 21:54:51 +03:00
sp = fc_seq_start_next_locked ( sp ) ;
2008-12-10 02:10:17 +03:00
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 .
*/
2009-02-27 21:54:35 +03:00
void fc_exch_mgr_reset ( struct fc_lport * lp , u32 sid , u32 did )
2008-12-10 02:10:17 +03:00
{
struct fc_exch * ep ;
struct fc_exch * next ;
2009-02-27 21:54:35 +03:00
struct fc_exch_mgr * mp = lp - > emp ;
2008-12-10 02:10:17 +03:00
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 ) ;
2009-02-27 21:54:46 +03:00
if ( err = = - FC_EX_CLOSED | | err = = - FC_EX_TIMEOUT )
2008-12-10 02:10:17 +03:00
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 + + ;
2009-02-28 01:07:21 +03:00
fc_fcp_ddp_setup ( fr_fsp ( fp ) , ep - > xid ) ;
2008-12-10 02:10:17 +03:00
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 ) ;
}