| |
| |
| |
| |
| |
| |
| |
| #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 |
| |
| 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_waitqueue_head(&tbl->slot_waitq); |
| <------>init_completion(&tbl->complete); |
| } |
| |
| |
| |
| |
| 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--; |
| <------>} |
| } |
| |
| |
| |
| |
| |
| |
| void nfs4_slot_tbl_drain_complete(struct nfs4_slot_table *tbl) |
| { |
| <------>if (nfs4_slot_tbl_draining(tbl)) |
| <------><------>complete(&tbl->complete); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void nfs4_free_slot(struct nfs4_slot_table *tbl, struct nfs4_slot *slot) |
| { |
| <------>u32 slotid = slot->slot_nr; |
| |
| <------> |
| <------>__clear_bit(slotid, tbl->used_slots); |
| |
| <------> |
| <------>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; |
| <------><------><------>nfs4_slot_tbl_drain_complete(tbl); |
| <------><------>} |
| <------>} |
| <------>dprintk("%s: slotid %u highest_used_slotid %u\n", __func__, |
| <------><------>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; |
| <------><------>slot->seq_nr_highest_sent = seq_init; |
| <------><------>slot->seq_nr_last_acked = seq_init - 1; |
| <------>} |
| <------>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); |
| } |
| |
| static void nfs4_lock_slot(struct nfs4_slot_table *tbl, |
| <------><------>struct nfs4_slot *slot) |
| { |
| <------>u32 slotid = slot->slot_nr; |
| |
| <------>__set_bit(slotid, tbl->used_slots); |
| <------>if (slotid > tbl->highest_used_slotid || |
| <------> tbl->highest_used_slotid == NFS4_NO_SLOT) |
| <------><------>tbl->highest_used_slotid = slotid; |
| <------>slot->generation = tbl->generation; |
| } |
| |
| |
| |
| |
| |
| |
| bool nfs4_try_to_lock_slot(struct nfs4_slot_table *tbl, struct nfs4_slot *slot) |
| { |
| <------>if (nfs4_test_locked_slot(tbl, slot->slot_nr)) |
| <------><------>return false; |
| <------>nfs4_lock_slot(tbl, slot); |
| <------>return true; |
| } |
| |
| |
| |
| |
| |
| |
| struct nfs4_slot *nfs4_lookup_slot(struct nfs4_slot_table *tbl, u32 slotid) |
| { |
| <------>if (slotid <= tbl->max_slotid) |
| <------><------>return nfs4_find_or_create_slot(tbl, slotid, 0, GFP_NOWAIT); |
| <------>return ERR_PTR(-E2BIG); |
| } |
| |
| static int nfs4_slot_get_seqid(struct nfs4_slot_table *tbl, u32 slotid, |
| <------><------>u32 *seq_nr) |
| <------>__must_hold(&tbl->slot_tbl_lock) |
| { |
| <------>struct nfs4_slot *slot; |
| <------>int ret; |
| |
| <------>slot = nfs4_lookup_slot(tbl, slotid); |
| <------>ret = PTR_ERR_OR_ZERO(slot); |
| <------>if (!ret) |
| <------><------>*seq_nr = slot->seq_nr; |
| |
| <------>return ret; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| static bool nfs4_slot_seqid_in_use(struct nfs4_slot_table *tbl, |
| <------><------>u32 slotid, u32 seq_nr) |
| { |
| <------>u32 cur_seq = 0; |
| <------>bool ret = false; |
| |
| <------>spin_lock(&tbl->slot_tbl_lock); |
| <------>if (nfs4_slot_get_seqid(tbl, slotid, &cur_seq) == 0 && |
| <------> cur_seq == seq_nr && test_bit(slotid, tbl->used_slots)) |
| <------><------>ret = true; |
| <------>spin_unlock(&tbl->slot_tbl_lock); |
| <------>return ret; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| int nfs4_slot_wait_on_seqid(struct nfs4_slot_table *tbl, |
| <------><------>u32 slotid, u32 seq_nr, |
| <------><------>unsigned long timeout) |
| { |
| <------>if (wait_event_timeout(tbl->slot_waitq, |
| <------><------><------>!nfs4_slot_seqid_in_use(tbl, slotid, seq_nr), |
| <------><------><------>timeout) == 0) |
| <------><------>return -ETIMEDOUT; |
| <------>return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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) { |
| <------><------>ret = nfs4_find_or_create_slot(tbl, slotid, 1, GFP_NOWAIT); |
| <------><------>if (!IS_ERR(ret)) |
| <------><------><------>nfs4_lock_slot(tbl, ret); |
| <------>} |
| <------>dprintk("<-- %s used_slots=%04lx highest_used=%u slotid=%u\n", |
| <------><------>__func__, tbl->used_slots[0], tbl->highest_used_slotid, |
| <------><------>!IS_ERR(ret) ? ret->slot_nr : NFS4_NO_SLOT); |
| <------>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; |
| <------><------>(*p)->seq_nr_highest_sent = ivalue; |
| <------><------>(*p)->seq_nr_last_acked = ivalue - 1; |
| <------><------>p = &(*p)->next; |
| <------>} |
| <------>tbl->highest_used_slotid = NFS4_NO_SLOT; |
| <------>tbl->target_highest_slotid = server_highest_slotid; |
| <------>tbl->server_highest_slotid = server_highest_slotid; |
| <------>tbl->d_target_highest_slotid = 0; |
| <------>tbl->d2_target_highest_slotid = 0; |
| <------>tbl->max_slotid = server_highest_slotid; |
| } |
| |
| |
| |
| |
| static int nfs4_realloc_slot_table(struct nfs4_slot_table *tbl, |
| <------><------>u32 max_reqs, u32 ivalue) |
| { |
| <------>int ret; |
| |
| <------>dprintk("--> %s: max_reqs=%u, tbl->max_slots %u\n", __func__, |
| <------><------>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); |
| |
| <------>dprintk("%s: tbl=%p slots=%p max_slots=%u\n", __func__, |
| <------><------>tbl, tbl->slots, tbl->max_slots); |
| out: |
| <------>dprintk("<-- %s: return %d\n", __func__, ret); |
| <------>return ret; |
| } |
| |
| |
| |
| |
| static void nfs4_release_slot_table(struct nfs4_slot_table *tbl) |
| { |
| <------>nfs4_shrink_slot_table(tbl, 0); |
| } |
| |
| |
| |
| |
| |
| |
| void nfs4_shutdown_slot_table(struct nfs4_slot_table *tbl) |
| { |
| <------>nfs4_release_slot_table(tbl); |
| <------>rpc_destroy_wait_queue(&tbl->slot_tbl_waitq); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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); |
| } |
| |
| 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; |
| |
| <------>if (nfs4_slot_tbl_draining(tbl) && !args->sa_privileged) |
| <------><------>return false; |
| <------>slot->generation = tbl->generation; |
| <------>args->sa_slot = slot; |
| <------>res->sr_timestamp = jiffies; |
| <------>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; |
| <------>} |
| } |
| |
| #if defined(CONFIG_NFS_V4_1) |
| |
| 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); |
| } |
| |
| |
| 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); |
| <------>tbl->d_target_highest_slotid = 0; |
| <------>tbl->d2_target_highest_slotid = 0; |
| <------>nfs41_set_max_slotid_locked(tbl, target_highest_slotid); |
| <------>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; |
| <------> |
| <------>nfs4_shrink_slot_table(tbl, highest_slotid + 1); |
| <------>tbl->server_highest_slotid = highest_slotid; |
| } |
| |
| 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); |
| } |
| |
| |
| |
| |
| 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); |
| <------> |
| <------>if (nfs41_same_sign_or_zero_s32(d_target, tbl->d_target_highest_slotid)) |
| <------><------>ret = false; |
| <------> |
| <------>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; |
| } |
| |
| 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); |
| <------>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); |
| <------>nfs41_set_max_slotid_locked(tbl, res->sr_target_highest_slotid); |
| <------>spin_unlock(&tbl->slot_tbl_lock); |
| } |
| |
| static void nfs4_release_session_slot_tables(struct nfs4_session *session) |
| { |
| <------>nfs4_release_slot_table(&session->fc_slot_table); |
| <------>nfs4_release_slot_table(&session->bc_slot_table); |
| } |
| |
| |
| |
| |
| int nfs4_setup_session_slot_tables(struct nfs4_session *ses) |
| { |
| <------>struct nfs4_slot_table *tbl; |
| <------>int status; |
| |
| <------>dprintk("--> %s\n", __func__); |
| <------> |
| <------>tbl = &ses->fc_slot_table; |
| <------>tbl->session = ses; |
| <------>status = nfs4_realloc_slot_table(tbl, ses->fc_attrs.max_reqs, 1); |
| <------>if (status || !(ses->flags & SESSION4_BACK_CHAN)) |
| <------><------>return status; |
| <------> |
| <------>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) |
| <------><------> |
| <------><------> * both slot tables or neither */ |
| <------><------>nfs4_release_session_slot_tables(ses); |
| <------>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; |
| |
| <------>nfs4_init_slot_table(&session->fc_slot_table, "ForeChannel Slot table"); |
| <------>nfs4_init_slot_table(&session->bc_slot_table, "BackChannel Slot table"); |
| <------>session->session_state = 1<<NFS4_SESSION_INITING; |
| |
| <------>session->clp = clp; |
| <------>return session; |
| } |
| |
| static void nfs4_destroy_session_slot_tables(struct nfs4_session *session) |
| { |
| <------>nfs4_shutdown_slot_table(&session->fc_slot_table); |
| <------>nfs4_shutdown_slot_table(&session->bc_slot_table); |
| } |
| |
| void nfs4_destroy_session(struct nfs4_session *session) |
| { |
| <------>struct rpc_xprt *xprt; |
| <------>const struct cred *cred; |
| |
| <------>cred = nfs4_get_clid_cred(session->clp); |
| <------>nfs4_proc_destroy_session(session, cred); |
| <------>put_cred(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); |
| <------>nfs4_destroy_session_slot_tables(session); |
| <------>kfree(session); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| |
| int nfs4_init_session(struct nfs_client *clp) |
| { |
| <------>if (!nfs4_has_session(clp)) |
| <------><------>return 0; |
| |
| <------>clear_bit(NFS4_SESSION_INITING, &clp->cl_session->session_state); |
| <------>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; |
| <------> |
| <------>if (!is_ds_client(clp)) |
| <------><------>return -ENODEV; |
| <------>return 0; |
| } |
| EXPORT_SYMBOL_GPL(nfs4_init_ds_session); |
| |
| #endif |
| |