2012-11-26 21:49:34 +04:00
/*
* fs / nfs / nfs4session . c
*
* Copyright ( c ) 2012 Trond Myklebust < Trond . Myklebust @ netapp . com >
*
*/
# include <linux/kernel.h>
# include <linux/errno.h>
# include <linux/string.h>
# include <linux/printk.h>
# include <linux/slab.h>
# include <linux/sunrpc/sched.h>
# include <linux/sunrpc/bc_xprt.h>
# include <linux/nfs.h>
# include <linux/nfs4.h>
# include <linux/nfs_fs.h>
# include <linux/module.h>
# include "nfs4_fs.h"
# include "internal.h"
# include "nfs4session.h"
# include "callback.h"
# define NFSDBG_FACILITY NFSDBG_STATE
2013-08-09 20:48:53 +04:00
static void nfs4_init_slot_table ( struct nfs4_slot_table * tbl , const char * queue )
{
tbl - > highest_used_slotid = NFS4_NO_SLOT ;
spin_lock_init ( & tbl - > slot_tbl_lock ) ;
rpc_init_priority_wait_queue ( & tbl - > slot_tbl_waitq , queue ) ;
init_completion ( & tbl - > complete ) ;
}
2012-11-26 21:49:34 +04:00
/*
* nfs4_shrink_slot_table - free retired slots from the slot table
*/
static void nfs4_shrink_slot_table ( struct nfs4_slot_table * tbl , u32 newsize )
{
struct nfs4_slot * * p ;
if ( newsize > = tbl - > max_slots )
return ;
p = & tbl - > slots ;
while ( newsize - - )
p = & ( * p ) - > next ;
while ( * p ) {
struct nfs4_slot * slot = * p ;
* p = slot - > next ;
kfree ( slot ) ;
tbl - > max_slots - - ;
}
}
2013-08-09 20:48:44 +04:00
/**
* nfs4_slot_tbl_drain_complete - wake waiters when drain is complete
* @ tbl - controlling slot table
*
*/
void nfs4_slot_tbl_drain_complete ( struct nfs4_slot_table * tbl )
{
if ( nfs4_slot_tbl_draining ( tbl ) )
complete ( & tbl - > complete ) ;
}
2012-11-26 21:49:34 +04:00
/*
* nfs4_free_slot - free a slot and efficiently update slot table .
*
* freeing a slot is trivially done by clearing its respective bit
* in the bitmap .
* If the freed slotid equals highest_used_slotid we want to update it
* so that the server would be able to size down the slot table if needed ,
* otherwise we know that the highest_used_slotid is still in use .
* When updating highest_used_slotid there may be " holes " in the bitmap
* so we need to scan down from highest_used_slotid to 0 looking for the now
* highest slotid in use .
* If none found , highest_used_slotid is set to NFS4_NO_SLOT .
*
* Must be called while holding tbl - > slot_tbl_lock
*/
void nfs4_free_slot ( struct nfs4_slot_table * tbl , struct nfs4_slot * slot )
{
u32 slotid = slot - > slot_nr ;
/* clear used bit in bitmap */
__clear_bit ( slotid , tbl - > used_slots ) ;
/* update highest_used_slotid when it is freed */
if ( slotid = = tbl - > highest_used_slotid ) {
u32 new_max = find_last_bit ( tbl - > used_slots , slotid ) ;
if ( new_max < slotid )
tbl - > highest_used_slotid = new_max ;
else {
tbl - > highest_used_slotid = NFS4_NO_SLOT ;
2013-05-20 22:13:50 +04:00
nfs4_slot_tbl_drain_complete ( tbl ) ;
2012-11-26 21:49:34 +04:00
}
}
2013-08-09 20:47:51 +04:00
dprintk ( " %s: slotid %u highest_used_slotid %u \n " , __func__ ,
2012-11-26 21:49:34 +04:00
slotid , tbl - > highest_used_slotid ) ;
}
static struct nfs4_slot * nfs4_new_slot ( struct nfs4_slot_table * tbl ,
u32 slotid , u32 seq_init , gfp_t gfp_mask )
{
struct nfs4_slot * slot ;
slot = kzalloc ( sizeof ( * slot ) , gfp_mask ) ;
if ( slot ) {
slot - > table = tbl ;
slot - > slot_nr = slotid ;
slot - > seq_nr = seq_init ;
}
return slot ;
}
static struct nfs4_slot * nfs4_find_or_create_slot ( struct nfs4_slot_table * tbl ,
u32 slotid , u32 seq_init , gfp_t gfp_mask )
{
struct nfs4_slot * * p , * slot ;
p = & tbl - > slots ;
for ( ; ; ) {
if ( * p = = NULL ) {
* p = nfs4_new_slot ( tbl , tbl - > max_slots ,
seq_init , gfp_mask ) ;
if ( * p = = NULL )
break ;
tbl - > max_slots + + ;
}
slot = * p ;
if ( slot - > slot_nr = = slotid )
return slot ;
p = & slot - > next ;
}
return ERR_PTR ( - ENOMEM ) ;
}
/*
* nfs4_alloc_slot - efficiently look for a free slot
*
* nfs4_alloc_slot looks for an unset bit in the used_slots bitmap .
* If found , we mark the slot as used , update the highest_used_slotid ,
* and respectively set up the sequence operation args .
*
* Note : must be called with under the slot_tbl_lock .
*/
struct nfs4_slot * nfs4_alloc_slot ( struct nfs4_slot_table * tbl )
{
struct nfs4_slot * ret = ERR_PTR ( - EBUSY ) ;
u32 slotid ;
dprintk ( " --> %s used_slots=%04lx highest_used=%u max_slots=%u \n " ,
__func__ , tbl - > used_slots [ 0 ] , tbl - > highest_used_slotid ,
tbl - > max_slotid + 1 ) ;
slotid = find_first_zero_bit ( tbl - > used_slots , tbl - > max_slotid + 1 ) ;
if ( slotid > tbl - > max_slotid )
goto out ;
ret = nfs4_find_or_create_slot ( tbl , slotid , 1 , GFP_NOWAIT ) ;
if ( IS_ERR ( ret ) )
goto out ;
__set_bit ( slotid , tbl - > used_slots ) ;
if ( slotid > tbl - > highest_used_slotid | |
tbl - > highest_used_slotid = = NFS4_NO_SLOT )
tbl - > highest_used_slotid = slotid ;
ret - > generation = tbl - > generation ;
out :
2013-08-09 20:47:51 +04:00
dprintk ( " <-- %s used_slots=%04lx highest_used=%u slotid=%u \n " ,
2012-11-26 21:49:34 +04:00
__func__ , tbl - > used_slots [ 0 ] , tbl - > highest_used_slotid ,
2013-08-09 20:47:51 +04:00
! IS_ERR ( ret ) ? ret - > slot_nr : NFS4_NO_SLOT ) ;
2012-11-26 21:49:34 +04:00
return ret ;
}
static int nfs4_grow_slot_table ( struct nfs4_slot_table * tbl ,
u32 max_reqs , u32 ivalue )
{
if ( max_reqs < = tbl - > max_slots )
return 0 ;
if ( ! IS_ERR ( nfs4_find_or_create_slot ( tbl , max_reqs - 1 , ivalue , GFP_NOFS ) ) )
return 0 ;
return - ENOMEM ;
}
static void nfs4_reset_slot_table ( struct nfs4_slot_table * tbl ,
u32 server_highest_slotid ,
u32 ivalue )
{
struct nfs4_slot * * p ;
nfs4_shrink_slot_table ( tbl , server_highest_slotid + 1 ) ;
p = & tbl - > slots ;
while ( * p ) {
( * p ) - > seq_nr = ivalue ;
2012-12-16 00:36:07 +04:00
( * p ) - > interrupted = 0 ;
2012-11-26 21:49:34 +04:00
p = & ( * p ) - > next ;
}
tbl - > highest_used_slotid = NFS4_NO_SLOT ;
tbl - > target_highest_slotid = server_highest_slotid ;
tbl - > server_highest_slotid = server_highest_slotid ;
2012-12-02 22:54:59 +04:00
tbl - > d_target_highest_slotid = 0 ;
tbl - > d2_target_highest_slotid = 0 ;
2012-11-26 21:49:34 +04:00
tbl - > max_slotid = server_highest_slotid ;
}
/*
* ( re ) Initialise a slot table
*/
static int nfs4_realloc_slot_table ( struct nfs4_slot_table * tbl ,
u32 max_reqs , u32 ivalue )
{
int ret ;
2013-08-09 20:47:51 +04:00
dprintk ( " --> %s: max_reqs=%u, tbl->max_slots %u \n " , __func__ ,
2012-11-26 21:49:34 +04:00
max_reqs , tbl - > max_slots ) ;
if ( max_reqs > NFS4_MAX_SLOT_TABLE )
max_reqs = NFS4_MAX_SLOT_TABLE ;
ret = nfs4_grow_slot_table ( tbl , max_reqs , ivalue ) ;
if ( ret )
goto out ;
spin_lock ( & tbl - > slot_tbl_lock ) ;
nfs4_reset_slot_table ( tbl , max_reqs - 1 , ivalue ) ;
spin_unlock ( & tbl - > slot_tbl_lock ) ;
2013-08-09 20:47:51 +04:00
dprintk ( " %s: tbl=%p slots=%p max_slots=%u \n " , __func__ ,
2012-11-26 21:49:34 +04:00
tbl , tbl - > slots , tbl - > max_slots ) ;
out :
dprintk ( " <-- %s: return %d \n " , __func__ , ret ) ;
return ret ;
}
2013-08-09 20:49:02 +04:00
/**
* nfs4_release_slot_table - release resources attached to a slot table
* @ tbl : slot table to shut down
*
*/
void nfs4_release_slot_table ( struct nfs4_slot_table * tbl )
{
nfs4_shrink_slot_table ( tbl , 0 ) ;
}
2013-08-09 20:48:53 +04:00
/**
* nfs4_setup_slot_table - prepare a stand - alone slot table for use
* @ tbl : slot table to set up
* @ max_reqs : maximum number of requests allowed
* @ queue : name to give RPC wait queue
*
* Returns zero on success , or a negative errno .
*/
int nfs4_setup_slot_table ( struct nfs4_slot_table * tbl , unsigned int max_reqs ,
const char * queue )
{
nfs4_init_slot_table ( tbl , queue ) ;
return nfs4_realloc_slot_table ( tbl , max_reqs , 0 ) ;
}
2012-11-30 02:27:47 +04:00
static bool nfs41_assign_slot ( struct rpc_task * task , void * pslot )
{
struct nfs4_sequence_args * args = task - > tk_msg . rpc_argp ;
struct nfs4_sequence_res * res = task - > tk_msg . rpc_resp ;
struct nfs4_slot * slot = pslot ;
struct nfs4_slot_table * tbl = slot - > table ;
2013-05-20 22:13:50 +04:00
if ( nfs4_slot_tbl_draining ( tbl ) & & ! args - > sa_privileged )
2012-11-30 02:27:47 +04:00
return false ;
slot - > generation = tbl - > generation ;
args - > sa_slot = slot ;
2012-12-16 00:21:52 +04:00
res - > sr_timestamp = jiffies ;
2012-11-30 02:27:47 +04:00
res - > sr_slot = slot ;
res - > sr_status_flags = 0 ;
res - > sr_status = 1 ;
return true ;
}
static bool __nfs41_wake_and_assign_slot ( struct nfs4_slot_table * tbl ,
struct nfs4_slot * slot )
{
if ( rpc_wake_up_first ( & tbl - > slot_tbl_waitq , nfs41_assign_slot , slot ) )
return true ;
return false ;
}
bool nfs41_wake_and_assign_slot ( struct nfs4_slot_table * tbl ,
struct nfs4_slot * slot )
{
if ( slot - > slot_nr > tbl - > max_slotid )
return false ;
return __nfs41_wake_and_assign_slot ( tbl , slot ) ;
}
static bool nfs41_try_wake_next_slot_table_entry ( struct nfs4_slot_table * tbl )
{
struct nfs4_slot * slot = nfs4_alloc_slot ( tbl ) ;
if ( ! IS_ERR ( slot ) ) {
bool ret = __nfs41_wake_and_assign_slot ( tbl , slot ) ;
if ( ret )
return ret ;
nfs4_free_slot ( tbl , slot ) ;
}
return false ;
}
void nfs41_wake_slot_table ( struct nfs4_slot_table * tbl )
{
for ( ; ; ) {
if ( ! nfs41_try_wake_next_slot_table_entry ( tbl ) )
break ;
}
}
2012-12-11 21:10:14 +04:00
static void nfs41_set_max_slotid_locked ( struct nfs4_slot_table * tbl ,
u32 target_highest_slotid )
{
u32 max_slotid ;
max_slotid = min ( NFS4_MAX_SLOT_TABLE - 1 , target_highest_slotid ) ;
if ( max_slotid > tbl - > server_highest_slotid )
max_slotid = tbl - > server_highest_slotid ;
if ( max_slotid > tbl - > target_highest_slotid )
max_slotid = tbl - > target_highest_slotid ;
tbl - > max_slotid = max_slotid ;
nfs41_wake_slot_table ( tbl ) ;
}
2012-11-26 21:49:34 +04:00
/* Update the client's idea of target_highest_slotid */
static void nfs41_set_target_slotid_locked ( struct nfs4_slot_table * tbl ,
u32 target_highest_slotid )
{
if ( tbl - > target_highest_slotid = = target_highest_slotid )
return ;
tbl - > target_highest_slotid = target_highest_slotid ;
tbl - > generation + + ;
}
void nfs41_set_target_slotid ( struct nfs4_slot_table * tbl ,
u32 target_highest_slotid )
{
spin_lock ( & tbl - > slot_tbl_lock ) ;
nfs41_set_target_slotid_locked ( tbl , target_highest_slotid ) ;
2012-12-02 22:54:59 +04:00
tbl - > d_target_highest_slotid = 0 ;
tbl - > d2_target_highest_slotid = 0 ;
2012-12-11 21:10:14 +04:00
nfs41_set_max_slotid_locked ( tbl , target_highest_slotid ) ;
2012-11-26 21:49:34 +04:00
spin_unlock ( & tbl - > slot_tbl_lock ) ;
}
static void nfs41_set_server_slotid_locked ( struct nfs4_slot_table * tbl ,
u32 highest_slotid )
{
if ( tbl - > server_highest_slotid = = highest_slotid )
return ;
if ( tbl - > highest_used_slotid > highest_slotid )
return ;
/* Deallocate slots */
nfs4_shrink_slot_table ( tbl , highest_slotid + 1 ) ;
tbl - > server_highest_slotid = highest_slotid ;
}
2012-12-02 22:54:59 +04:00
static s32 nfs41_derivative_target_slotid ( s32 s1 , s32 s2 )
{
s1 - = s2 ;
if ( s1 = = 0 )
return 0 ;
if ( s1 < 0 )
return ( s1 - 1 ) > > 1 ;
return ( s1 + 1 ) > > 1 ;
}
static int nfs41_sign_s32 ( s32 s1 )
{
if ( s1 > 0 )
return 1 ;
if ( s1 < 0 )
return - 1 ;
return 0 ;
}
static bool nfs41_same_sign_or_zero_s32 ( s32 s1 , s32 s2 )
{
if ( ! s1 | | ! s2 )
return true ;
return nfs41_sign_s32 ( s1 ) = = nfs41_sign_s32 ( s2 ) ;
}
/* Try to eliminate outliers by checking for sharp changes in the
* derivatives and second derivatives
*/
static bool nfs41_is_outlier_target_slotid ( struct nfs4_slot_table * tbl ,
u32 new_target )
{
s32 d_target , d2_target ;
bool ret = true ;
d_target = nfs41_derivative_target_slotid ( new_target ,
tbl - > target_highest_slotid ) ;
d2_target = nfs41_derivative_target_slotid ( d_target ,
tbl - > d_target_highest_slotid ) ;
/* Is first derivative same sign? */
if ( nfs41_same_sign_or_zero_s32 ( d_target , tbl - > d_target_highest_slotid ) )
ret = false ;
/* Is second derivative same sign? */
if ( nfs41_same_sign_or_zero_s32 ( d2_target , tbl - > d2_target_highest_slotid ) )
ret = false ;
tbl - > d_target_highest_slotid = d_target ;
tbl - > d2_target_highest_slotid = d2_target ;
return ret ;
}
2012-11-26 21:49:34 +04:00
void nfs41_update_target_slotid ( struct nfs4_slot_table * tbl ,
struct nfs4_slot * slot ,
struct nfs4_sequence_res * res )
{
spin_lock ( & tbl - > slot_tbl_lock ) ;
2012-12-02 22:54:59 +04:00
if ( ! nfs41_is_outlier_target_slotid ( tbl , res - > sr_target_highest_slotid ) )
nfs41_set_target_slotid_locked ( tbl , res - > sr_target_highest_slotid ) ;
if ( tbl - > generation = = slot - > generation )
nfs41_set_server_slotid_locked ( tbl , res - > sr_highest_slotid ) ;
2012-12-11 21:10:14 +04:00
nfs41_set_max_slotid_locked ( tbl , res - > sr_target_highest_slotid ) ;
2012-11-26 21:49:34 +04:00
spin_unlock ( & tbl - > slot_tbl_lock ) ;
}
2013-08-09 20:48:44 +04:00
# if defined(CONFIG_NFS_V4_1)
2013-08-09 20:49:02 +04:00
static void nfs4_destroy_session_slot_tables ( struct nfs4_session * session )
2013-08-09 20:48:44 +04:00
{
2013-08-09 20:49:02 +04:00
nfs4_release_slot_table ( & session - > fc_slot_table ) ;
nfs4_release_slot_table ( & session - > bc_slot_table ) ;
2013-08-09 20:48:44 +04:00
}
2012-11-26 21:49:34 +04:00
/*
* Initialize or reset the forechannel and backchannel tables
*/
int nfs4_setup_session_slot_tables ( struct nfs4_session * ses )
{
struct nfs4_slot_table * tbl ;
int status ;
dprintk ( " --> %s \n " , __func__ ) ;
/* Fore channel */
tbl = & ses - > fc_slot_table ;
tbl - > session = ses ;
status = nfs4_realloc_slot_table ( tbl , ses - > fc_attrs . max_reqs , 1 ) ;
if ( status ) /* -ENOMEM */
return status ;
/* Back channel */
tbl = & ses - > bc_slot_table ;
tbl - > session = ses ;
status = nfs4_realloc_slot_table ( tbl , ses - > bc_attrs . max_reqs , 0 ) ;
if ( status & & tbl - > slots = = NULL )
/* Fore and back channel share a connection so get
* both slot tables or neither */
2013-08-09 20:49:02 +04:00
nfs4_destroy_session_slot_tables ( ses ) ;
2012-11-26 21:49:34 +04:00
return status ;
}
struct nfs4_session * nfs4_alloc_session ( struct nfs_client * clp )
{
struct nfs4_session * session ;
session = kzalloc ( sizeof ( struct nfs4_session ) , GFP_NOFS ) ;
if ( ! session )
return NULL ;
2013-08-09 20:48:53 +04:00
nfs4_init_slot_table ( & session - > fc_slot_table , " ForeChannel Slot table " ) ;
nfs4_init_slot_table ( & session - > bc_slot_table , " BackChannel Slot table " ) ;
2012-11-26 21:49:34 +04:00
session - > session_state = 1 < < NFS4_SESSION_INITING ;
session - > clp = clp ;
return session ;
}
void nfs4_destroy_session ( struct nfs4_session * session )
{
struct rpc_xprt * xprt ;
struct rpc_cred * cred ;
2013-07-24 20:28:37 +04:00
cred = nfs4_get_clid_cred ( session - > clp ) ;
2012-11-26 21:49:34 +04:00
nfs4_proc_destroy_session ( session , cred ) ;
if ( cred )
put_rpccred ( cred ) ;
rcu_read_lock ( ) ;
xprt = rcu_dereference ( session - > clp - > cl_rpcclient - > cl_xprt ) ;
rcu_read_unlock ( ) ;
dprintk ( " %s Destroy backchannel for xprt %p \n " ,
__func__ , xprt ) ;
xprt_destroy_backchannel ( xprt , NFS41_BC_MIN_CALLBACKS ) ;
2013-08-09 20:49:02 +04:00
nfs4_destroy_session_slot_tables ( session ) ;
2012-11-26 21:49:34 +04:00
kfree ( session ) ;
}
/*
* With sessions , the client is not marked ready until after a
* successful EXCHANGE_ID and CREATE_SESSION .
*
* Map errors cl_cons_state errors to EPROTONOSUPPORT to indicate
* other versions of NFS can be tried .
*/
static int nfs41_check_session_ready ( struct nfs_client * clp )
{
int ret ;
if ( clp - > cl_cons_state = = NFS_CS_SESSION_INITING ) {
ret = nfs4_client_recover_expired_lease ( clp ) ;
if ( ret )
return ret ;
}
if ( clp - > cl_cons_state < NFS_CS_READY )
return - EPROTONOSUPPORT ;
smp_rmb ( ) ;
return 0 ;
}
2013-06-26 20:21:49 +04:00
int nfs4_init_session ( struct nfs_client * clp )
2012-11-26 21:49:34 +04:00
{
if ( ! nfs4_has_session ( clp ) )
return 0 ;
2013-06-26 20:21:49 +04:00
clear_bit ( NFS4_SESSION_INITING , & clp - > cl_session - > session_state ) ;
2012-11-26 21:49:34 +04:00
return nfs41_check_session_ready ( clp ) ;
}
int nfs4_init_ds_session ( struct nfs_client * clp , unsigned long lease_time )
{
struct nfs4_session * session = clp - > cl_session ;
int ret ;
spin_lock ( & clp - > cl_lock ) ;
if ( test_and_clear_bit ( NFS4_SESSION_INITING , & session - > session_state ) ) {
/*
* Do not set NFS_CS_CHECK_LEASE_TIME instead set the
* DS lease to be equal to the MDS lease .
*/
clp - > cl_lease_time = lease_time ;
clp - > cl_last_renewal = jiffies ;
}
spin_unlock ( & clp - > cl_lock ) ;
ret = nfs41_check_session_ready ( clp ) ;
if ( ret )
return ret ;
/* Test for the DS role */
if ( ! is_ds_client ( clp ) )
return - ENODEV ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( nfs4_init_ds_session ) ;
2013-08-09 20:48:44 +04:00
# endif /* defined(CONFIG_NFS_V4_1) */