| /* |
| * GPL HEADER START |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 only, |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that 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 version 2 for more details (a copy is included |
| * in the LICENSE file that accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License |
| * version 2 along with this program; If not, see |
| * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| * |
| * GPL HEADER END |
| */ |
| /* |
| * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Use is subject to license terms. |
| * |
| * Copyright (c) 2011, 2012, Intel Corporation. |
| */ |
| /* |
| * This file is part of Lustre, http://www.lustre.org/ |
| * Lustre is a trademark of Sun Microsystems, Inc. |
| * |
| * lnet/selftest/conctl.c |
| * |
| * Console framework rpcs |
| * |
| * Author: Liang Zhen <liang@whamcloud.com> |
| */ |
| |
| #include "../../include/linux/libcfs/libcfs.h" |
| #include "../../include/linux/lnet/lib-lnet.h" |
| #include "timer.h" |
| #include "conrpc.h" |
| #include "console.h" |
| |
| void lstcon_rpc_stat_reply(lstcon_rpc_trans_t *, srpc_msg_t *, |
| lstcon_node_t *, lstcon_trans_stat_t *); |
| |
| static void |
| lstcon_rpc_done(srpc_client_rpc_t *rpc) |
| { |
| lstcon_rpc_t *crpc = (lstcon_rpc_t *)rpc->crpc_priv; |
| |
| LASSERT(crpc && rpc == crpc->crp_rpc); |
| LASSERT(crpc->crp_posted && !crpc->crp_finished); |
| |
| spin_lock(&rpc->crpc_lock); |
| |
| if (!crpc->crp_trans) { |
| /* |
| * Orphan RPC is not in any transaction, |
| * I'm just a poor body and nobody loves me |
| */ |
| spin_unlock(&rpc->crpc_lock); |
| |
| /* release it */ |
| lstcon_rpc_put(crpc); |
| return; |
| } |
| |
| /* not an orphan RPC */ |
| crpc->crp_finished = 1; |
| |
| if (!crpc->crp_stamp) { |
| /* not aborted */ |
| LASSERT(!crpc->crp_status); |
| |
| crpc->crp_stamp = cfs_time_current(); |
| crpc->crp_status = rpc->crpc_status; |
| } |
| |
| /* wakeup (transaction)thread if I'm the last RPC in the transaction */ |
| if (atomic_dec_and_test(&crpc->crp_trans->tas_remaining)) |
| wake_up(&crpc->crp_trans->tas_waitq); |
| |
| spin_unlock(&rpc->crpc_lock); |
| } |
| |
| static int |
| lstcon_rpc_init(lstcon_node_t *nd, int service, unsigned feats, |
| int bulk_npg, int bulk_len, int embedded, lstcon_rpc_t *crpc) |
| { |
| crpc->crp_rpc = sfw_create_rpc(nd->nd_id, service, |
| feats, bulk_npg, bulk_len, |
| lstcon_rpc_done, (void *)crpc); |
| if (!crpc->crp_rpc) |
| return -ENOMEM; |
| |
| crpc->crp_trans = NULL; |
| crpc->crp_node = nd; |
| crpc->crp_posted = 0; |
| crpc->crp_finished = 0; |
| crpc->crp_unpacked = 0; |
| crpc->crp_status = 0; |
| crpc->crp_stamp = 0; |
| crpc->crp_embedded = embedded; |
| INIT_LIST_HEAD(&crpc->crp_link); |
| |
| atomic_inc(&console_session.ses_rpc_counter); |
| |
| return 0; |
| } |
| |
| static int |
| lstcon_rpc_prep(lstcon_node_t *nd, int service, unsigned feats, |
| int bulk_npg, int bulk_len, lstcon_rpc_t **crpcpp) |
| { |
| lstcon_rpc_t *crpc = NULL; |
| int rc; |
| |
| spin_lock(&console_session.ses_rpc_lock); |
| |
| if (!list_empty(&console_session.ses_rpc_freelist)) { |
| crpc = list_entry(console_session.ses_rpc_freelist.next, |
| lstcon_rpc_t, crp_link); |
| list_del_init(&crpc->crp_link); |
| } |
| |
| spin_unlock(&console_session.ses_rpc_lock); |
| |
| if (!crpc) { |
| LIBCFS_ALLOC(crpc, sizeof(*crpc)); |
| if (!crpc) |
| return -ENOMEM; |
| } |
| |
| rc = lstcon_rpc_init(nd, service, feats, bulk_npg, bulk_len, 0, crpc); |
| if (!rc) { |
| *crpcpp = crpc; |
| return 0; |
| } |
| |
| LIBCFS_FREE(crpc, sizeof(*crpc)); |
| |
| return rc; |
| } |
| |
| void |
| lstcon_rpc_put(lstcon_rpc_t *crpc) |
| { |
| srpc_bulk_t *bulk = &crpc->crp_rpc->crpc_bulk; |
| int i; |
| |
| LASSERT(list_empty(&crpc->crp_link)); |
| |
| for (i = 0; i < bulk->bk_niov; i++) { |
| if (!bulk->bk_iovs[i].kiov_page) |
| continue; |
| |
| __free_page(bulk->bk_iovs[i].kiov_page); |
| } |
| |
| srpc_client_rpc_decref(crpc->crp_rpc); |
| |
| if (crpc->crp_embedded) { |
| /* embedded RPC, don't recycle it */ |
| memset(crpc, 0, sizeof(*crpc)); |
| crpc->crp_embedded = 1; |
| |
| } else { |
| spin_lock(&console_session.ses_rpc_lock); |
| |
| list_add(&crpc->crp_link, |
| &console_session.ses_rpc_freelist); |
| |
| spin_unlock(&console_session.ses_rpc_lock); |
| } |
| |
| /* RPC is not alive now */ |
| atomic_dec(&console_session.ses_rpc_counter); |
| } |
| |
| static void |
| lstcon_rpc_post(lstcon_rpc_t *crpc) |
| { |
| lstcon_rpc_trans_t *trans = crpc->crp_trans; |
| |
| LASSERT(trans); |
| |
| atomic_inc(&trans->tas_remaining); |
| crpc->crp_posted = 1; |
| |
| sfw_post_rpc(crpc->crp_rpc); |
| } |
| |
| static char * |
| lstcon_rpc_trans_name(int transop) |
| { |
| if (transop == LST_TRANS_SESNEW) |
| return "SESNEW"; |
| |
| if (transop == LST_TRANS_SESEND) |
| return "SESEND"; |
| |
| if (transop == LST_TRANS_SESQRY) |
| return "SESQRY"; |
| |
| if (transop == LST_TRANS_SESPING) |
| return "SESPING"; |
| |
| if (transop == LST_TRANS_TSBCLIADD) |
| return "TSBCLIADD"; |
| |
| if (transop == LST_TRANS_TSBSRVADD) |
| return "TSBSRVADD"; |
| |
| if (transop == LST_TRANS_TSBRUN) |
| return "TSBRUN"; |
| |
| if (transop == LST_TRANS_TSBSTOP) |
| return "TSBSTOP"; |
| |
| if (transop == LST_TRANS_TSBCLIQRY) |
| return "TSBCLIQRY"; |
| |
| if (transop == LST_TRANS_TSBSRVQRY) |
| return "TSBSRVQRY"; |
| |
| if (transop == LST_TRANS_STATQRY) |
| return "STATQRY"; |
| |
| return "Unknown"; |
| } |
| |
| int |
| lstcon_rpc_trans_prep(struct list_head *translist, |
| int transop, lstcon_rpc_trans_t **transpp) |
| { |
| lstcon_rpc_trans_t *trans; |
| |
| if (translist) { |
| list_for_each_entry(trans, translist, tas_link) { |
| /* |
| * Can't enqueue two private transaction on |
| * the same object |
| */ |
| if ((trans->tas_opc & transop) == LST_TRANS_PRIVATE) |
| return -EPERM; |
| } |
| } |
| |
| /* create a trans group */ |
| LIBCFS_ALLOC(trans, sizeof(*trans)); |
| if (!trans) |
| return -ENOMEM; |
| |
| trans->tas_opc = transop; |
| |
| if (!translist) |
| INIT_LIST_HEAD(&trans->tas_olink); |
| else |
| list_add_tail(&trans->tas_olink, translist); |
| |
| list_add_tail(&trans->tas_link, &console_session.ses_trans_list); |
| |
| INIT_LIST_HEAD(&trans->tas_rpcs_list); |
| atomic_set(&trans->tas_remaining, 0); |
| init_waitqueue_head(&trans->tas_waitq); |
| |
| spin_lock(&console_session.ses_rpc_lock); |
| trans->tas_features = console_session.ses_features; |
| spin_unlock(&console_session.ses_rpc_lock); |
| |
| *transpp = trans; |
| return 0; |
| } |
| |
| void |
| lstcon_rpc_trans_addreq(lstcon_rpc_trans_t *trans, lstcon_rpc_t *crpc) |
| { |
| list_add_tail(&crpc->crp_link, &trans->tas_rpcs_list); |
| crpc->crp_trans = trans; |
| } |
| |
| void |
| lstcon_rpc_trans_abort(lstcon_rpc_trans_t *trans, int error) |
| { |
| srpc_client_rpc_t *rpc; |
| lstcon_rpc_t *crpc; |
| lstcon_node_t *nd; |
| |
| list_for_each_entry(crpc, &trans->tas_rpcs_list, crp_link) { |
| rpc = crpc->crp_rpc; |
| |
| spin_lock(&rpc->crpc_lock); |
| |
| if (!crpc->crp_posted || /* not posted */ |
| crpc->crp_stamp) { /* rpc done or aborted already */ |
| if (!crpc->crp_stamp) { |
| crpc->crp_stamp = cfs_time_current(); |
| crpc->crp_status = -EINTR; |
| } |
| spin_unlock(&rpc->crpc_lock); |
| continue; |
| } |
| |
| crpc->crp_stamp = cfs_time_current(); |
| crpc->crp_status = error; |
| |
| spin_unlock(&rpc->crpc_lock); |
| |
| sfw_abort_rpc(rpc); |
| |
| if (error != ETIMEDOUT) |
| continue; |
| |
| nd = crpc->crp_node; |
| if (cfs_time_after(nd->nd_stamp, crpc->crp_stamp)) |
| continue; |
| |
| nd->nd_stamp = crpc->crp_stamp; |
| nd->nd_state = LST_NODE_DOWN; |
| } |
| } |
| |
| static int |
| lstcon_rpc_trans_check(lstcon_rpc_trans_t *trans) |
| { |
| if (console_session.ses_shutdown && |
| !list_empty(&trans->tas_olink)) /* Not an end session RPC */ |
| return 1; |
| |
| return !atomic_read(&trans->tas_remaining) ? 1 : 0; |
| } |
| |
| int |
| lstcon_rpc_trans_postwait(lstcon_rpc_trans_t *trans, int timeout) |
| { |
| lstcon_rpc_t *crpc; |
| int rc; |
| |
| if (list_empty(&trans->tas_rpcs_list)) |
| return 0; |
| |
| if (timeout < LST_TRANS_MIN_TIMEOUT) |
| timeout = LST_TRANS_MIN_TIMEOUT; |
| |
| CDEBUG(D_NET, "Transaction %s started\n", |
| lstcon_rpc_trans_name(trans->tas_opc)); |
| |
| /* post all requests */ |
| list_for_each_entry(crpc, &trans->tas_rpcs_list, crp_link) { |
| LASSERT(!crpc->crp_posted); |
| |
| lstcon_rpc_post(crpc); |
| } |
| |
| mutex_unlock(&console_session.ses_mutex); |
| |
| rc = wait_event_interruptible_timeout(trans->tas_waitq, |
| lstcon_rpc_trans_check(trans), |
| cfs_time_seconds(timeout)); |
| rc = (rc > 0) ? 0 : ((rc < 0) ? -EINTR : -ETIMEDOUT); |
| |
| mutex_lock(&console_session.ses_mutex); |
| |
| if (console_session.ses_shutdown) |
| rc = -ESHUTDOWN; |
| |
| if (rc || atomic_read(&trans->tas_remaining)) { |
| /* treat short timeout as canceled */ |
| if (rc == -ETIMEDOUT && timeout < LST_TRANS_MIN_TIMEOUT * 2) |
| rc = -EINTR; |
| |
| lstcon_rpc_trans_abort(trans, rc); |
| } |
| |
| CDEBUG(D_NET, "Transaction %s stopped: %d\n", |
| lstcon_rpc_trans_name(trans->tas_opc), rc); |
| |
| lstcon_rpc_trans_stat(trans, lstcon_trans_stat()); |
| |
| return rc; |
| } |
| |
| static int |
| lstcon_rpc_get_reply(lstcon_rpc_t *crpc, srpc_msg_t **msgpp) |
| { |
| lstcon_node_t *nd = crpc->crp_node; |
| srpc_client_rpc_t *rpc = crpc->crp_rpc; |
| srpc_generic_reply_t *rep; |
| |
| LASSERT(nd && rpc); |
| LASSERT(crpc->crp_stamp); |
| |
| if (crpc->crp_status) { |
| *msgpp = NULL; |
| return crpc->crp_status; |
| } |
| |
| *msgpp = &rpc->crpc_replymsg; |
| if (!crpc->crp_unpacked) { |
| sfw_unpack_message(*msgpp); |
| crpc->crp_unpacked = 1; |
| } |
| |
| if (cfs_time_after(nd->nd_stamp, crpc->crp_stamp)) |
| return 0; |
| |
| nd->nd_stamp = crpc->crp_stamp; |
| rep = &(*msgpp)->msg_body.reply; |
| |
| if (rep->sid.ses_nid == LNET_NID_ANY) |
| nd->nd_state = LST_NODE_UNKNOWN; |
| else if (lstcon_session_match(rep->sid)) |
| nd->nd_state = LST_NODE_ACTIVE; |
| else |
| nd->nd_state = LST_NODE_BUSY; |
| |
| return 0; |
| } |
| |
| void |
| lstcon_rpc_trans_stat(lstcon_rpc_trans_t *trans, lstcon_trans_stat_t *stat) |
| { |
| lstcon_rpc_t *crpc; |
| srpc_msg_t *rep; |
| int error; |
| |
| LASSERT(stat); |
| |
| memset(stat, 0, sizeof(*stat)); |
| |
| list_for_each_entry(crpc, &trans->tas_rpcs_list, crp_link) { |
| lstcon_rpc_stat_total(stat, 1); |
| |
| LASSERT(crpc->crp_stamp); |
| |
| error = lstcon_rpc_get_reply(crpc, &rep); |
| if (error) { |
| lstcon_rpc_stat_failure(stat, 1); |
| if (!stat->trs_rpc_errno) |
| stat->trs_rpc_errno = -error; |
| |
| continue; |
| } |
| |
| lstcon_rpc_stat_success(stat, 1); |
| |
| lstcon_rpc_stat_reply(trans, rep, crpc->crp_node, stat); |
| } |
| |
| if (trans->tas_opc == LST_TRANS_SESNEW && !stat->trs_fwk_errno) { |
| stat->trs_fwk_errno = |
| lstcon_session_feats_check(trans->tas_features); |
| } |
| |
| CDEBUG(D_NET, "transaction %s : success %d, failure %d, total %d, RPC error(%d), Framework error(%d)\n", |
| lstcon_rpc_trans_name(trans->tas_opc), |
| lstcon_rpc_stat_success(stat, 0), |
| lstcon_rpc_stat_failure(stat, 0), |
| lstcon_rpc_stat_total(stat, 0), |
| stat->trs_rpc_errno, stat->trs_fwk_errno); |
| |
| return; |
| } |
| |
| int |
| lstcon_rpc_trans_interpreter(lstcon_rpc_trans_t *trans, |
| struct list_head __user *head_up, |
| lstcon_rpc_readent_func_t readent) |
| { |
| struct list_head tmp; |
| struct list_head __user *next; |
| lstcon_rpc_ent_t *ent; |
| srpc_generic_reply_t *rep; |
| lstcon_rpc_t *crpc; |
| srpc_msg_t *msg; |
| lstcon_node_t *nd; |
| long dur; |
| struct timeval tv; |
| int error; |
| |
| LASSERT(head_up); |
| |
| next = head_up; |
| |
| list_for_each_entry(crpc, &trans->tas_rpcs_list, crp_link) { |
| if (copy_from_user(&tmp, next, |
| sizeof(struct list_head))) |
| return -EFAULT; |
| |
| if (tmp.next == head_up) |
| return 0; |
| |
| next = tmp.next; |
| |
| ent = list_entry(next, lstcon_rpc_ent_t, rpe_link); |
| |
| LASSERT(crpc->crp_stamp); |
| |
| error = lstcon_rpc_get_reply(crpc, &msg); |
| |
| nd = crpc->crp_node; |
| |
| dur = (long)cfs_time_sub(crpc->crp_stamp, |
| (unsigned long)console_session.ses_id.ses_stamp); |
| jiffies_to_timeval(dur, &tv); |
| |
| if (copy_to_user(&ent->rpe_peer, &nd->nd_id, |
| sizeof(lnet_process_id_t)) || |
| copy_to_user(&ent->rpe_stamp, &tv, sizeof(tv)) || |
| copy_to_user(&ent->rpe_state, &nd->nd_state, |
| sizeof(nd->nd_state)) || |
| copy_to_user(&ent->rpe_rpc_errno, &error, |
| sizeof(error))) |
| return -EFAULT; |
| |
| if (error) |
| continue; |
| |
| /* RPC is done */ |
| rep = (srpc_generic_reply_t *)&msg->msg_body.reply; |
| |
| if (copy_to_user(&ent->rpe_sid, &rep->sid, sizeof(lst_sid_t)) || |
| copy_to_user(&ent->rpe_fwk_errno, &rep->status, |
| sizeof(rep->status))) |
| return -EFAULT; |
| |
| if (!readent) |
| continue; |
| |
| error = readent(trans->tas_opc, msg, ent); |
| |
| if (error) |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| void |
| lstcon_rpc_trans_destroy(lstcon_rpc_trans_t *trans) |
| { |
| srpc_client_rpc_t *rpc; |
| lstcon_rpc_t *crpc; |
| lstcon_rpc_t *tmp; |
| int count = 0; |
| |
| list_for_each_entry_safe(crpc, tmp, &trans->tas_rpcs_list, |
| crp_link) { |
| rpc = crpc->crp_rpc; |
| |
| spin_lock(&rpc->crpc_lock); |
| |
| /* free it if not posted or finished already */ |
| if (!crpc->crp_posted || crpc->crp_finished) { |
| spin_unlock(&rpc->crpc_lock); |
| |
| list_del_init(&crpc->crp_link); |
| lstcon_rpc_put(crpc); |
| |
| continue; |
| } |
| |
| /* |
| * rpcs can be still not callbacked (even LNetMDUnlink is called) |
| * because huge timeout for inaccessible network, don't make |
| * user wait for them, just abandon them, they will be recycled |
| * in callback |
| */ |
| LASSERT(crpc->crp_status); |
| |
| crpc->crp_node = NULL; |
| crpc->crp_trans = NULL; |
| list_del_init(&crpc->crp_link); |
| count++; |
| |
| spin_unlock(&rpc->crpc_lock); |
| |
| atomic_dec(&trans->tas_remaining); |
| } |
| |
| LASSERT(!atomic_read(&trans->tas_remaining)); |
| |
| list_del(&trans->tas_link); |
| if (!list_empty(&trans->tas_olink)) |
| list_del(&trans->tas_olink); |
| |
| CDEBUG(D_NET, "Transaction %s destroyed with %d pending RPCs\n", |
| lstcon_rpc_trans_name(trans->tas_opc), count); |
| |
| LIBCFS_FREE(trans, sizeof(*trans)); |
| |
| return; |
| } |
| |
| int |
| lstcon_sesrpc_prep(lstcon_node_t *nd, int transop, |
| unsigned feats, lstcon_rpc_t **crpc) |
| { |
| srpc_mksn_reqst_t *msrq; |
| srpc_rmsn_reqst_t *rsrq; |
| int rc; |
| |
| switch (transop) { |
| case LST_TRANS_SESNEW: |
| rc = lstcon_rpc_prep(nd, SRPC_SERVICE_MAKE_SESSION, |
| feats, 0, 0, crpc); |
| if (rc) |
| return rc; |
| |
| msrq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.mksn_reqst; |
| msrq->mksn_sid = console_session.ses_id; |
| msrq->mksn_force = console_session.ses_force; |
| strlcpy(msrq->mksn_name, console_session.ses_name, |
| sizeof(msrq->mksn_name)); |
| break; |
| |
| case LST_TRANS_SESEND: |
| rc = lstcon_rpc_prep(nd, SRPC_SERVICE_REMOVE_SESSION, |
| feats, 0, 0, crpc); |
| if (rc) |
| return rc; |
| |
| rsrq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.rmsn_reqst; |
| rsrq->rmsn_sid = console_session.ses_id; |
| break; |
| |
| default: |
| LBUG(); |
| } |
| |
| return 0; |
| } |
| |
| int |
| lstcon_dbgrpc_prep(lstcon_node_t *nd, unsigned feats, lstcon_rpc_t **crpc) |
| { |
| srpc_debug_reqst_t *drq; |
| int rc; |
| |
| rc = lstcon_rpc_prep(nd, SRPC_SERVICE_DEBUG, feats, 0, 0, crpc); |
| if (rc) |
| return rc; |
| |
| drq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.dbg_reqst; |
| |
| drq->dbg_sid = console_session.ses_id; |
| drq->dbg_flags = 0; |
| |
| return rc; |
| } |
| |
| int |
| lstcon_batrpc_prep(lstcon_node_t *nd, int transop, unsigned feats, |
| lstcon_tsb_hdr_t *tsb, lstcon_rpc_t **crpc) |
| { |
| lstcon_batch_t *batch; |
| srpc_batch_reqst_t *brq; |
| int rc; |
| |
| rc = lstcon_rpc_prep(nd, SRPC_SERVICE_BATCH, feats, 0, 0, crpc); |
| if (rc) |
| return rc; |
| |
| brq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.bat_reqst; |
| |
| brq->bar_sid = console_session.ses_id; |
| brq->bar_bid = tsb->tsb_id; |
| brq->bar_testidx = tsb->tsb_index; |
| brq->bar_opc = transop == LST_TRANS_TSBRUN ? SRPC_BATCH_OPC_RUN : |
| (transop == LST_TRANS_TSBSTOP ? SRPC_BATCH_OPC_STOP : |
| SRPC_BATCH_OPC_QUERY); |
| |
| if (transop != LST_TRANS_TSBRUN && |
| transop != LST_TRANS_TSBSTOP) |
| return 0; |
| |
| LASSERT(!tsb->tsb_index); |
| |
| batch = (lstcon_batch_t *)tsb; |
| brq->bar_arg = batch->bat_arg; |
| |
| return 0; |
| } |
| |
| int |
| lstcon_statrpc_prep(lstcon_node_t *nd, unsigned feats, lstcon_rpc_t **crpc) |
| { |
| srpc_stat_reqst_t *srq; |
| int rc; |
| |
| rc = lstcon_rpc_prep(nd, SRPC_SERVICE_QUERY_STAT, feats, 0, 0, crpc); |
| if (rc) |
| return rc; |
| |
| srq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.stat_reqst; |
| |
| srq->str_sid = console_session.ses_id; |
| srq->str_type = 0; /* XXX remove it */ |
| |
| return 0; |
| } |
| |
| static lnet_process_id_packed_t * |
| lstcon_next_id(int idx, int nkiov, lnet_kiov_t *kiov) |
| { |
| lnet_process_id_packed_t *pid; |
| int i; |
| |
| i = idx / SFW_ID_PER_PAGE; |
| |
| LASSERT(i < nkiov); |
| |
| pid = (lnet_process_id_packed_t *)page_address(kiov[i].kiov_page); |
| |
| return &pid[idx % SFW_ID_PER_PAGE]; |
| } |
| |
| static int |
| lstcon_dstnodes_prep(lstcon_group_t *grp, int idx, |
| int dist, int span, int nkiov, lnet_kiov_t *kiov) |
| { |
| lnet_process_id_packed_t *pid; |
| lstcon_ndlink_t *ndl; |
| lstcon_node_t *nd; |
| int start; |
| int end; |
| int i = 0; |
| |
| LASSERT(dist >= 1); |
| LASSERT(span >= 1); |
| LASSERT(grp->grp_nnode >= 1); |
| |
| if (span > grp->grp_nnode) |
| return -EINVAL; |
| |
| start = ((idx / dist) * span) % grp->grp_nnode; |
| end = ((idx / dist) * span + span - 1) % grp->grp_nnode; |
| |
| list_for_each_entry(ndl, &grp->grp_ndl_list, ndl_link) { |
| nd = ndl->ndl_node; |
| if (i < start) { |
| i++; |
| continue; |
| } |
| |
| if (i > (end >= start ? end : grp->grp_nnode)) |
| break; |
| |
| pid = lstcon_next_id((i - start), nkiov, kiov); |
| pid->nid = nd->nd_id.nid; |
| pid->pid = nd->nd_id.pid; |
| i++; |
| } |
| |
| if (start <= end) /* done */ |
| return 0; |
| |
| list_for_each_entry(ndl, &grp->grp_ndl_list, ndl_link) { |
| if (i > grp->grp_nnode + end) |
| break; |
| |
| nd = ndl->ndl_node; |
| pid = lstcon_next_id((i - start), nkiov, kiov); |
| pid->nid = nd->nd_id.nid; |
| pid->pid = nd->nd_id.pid; |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| lstcon_pingrpc_prep(lst_test_ping_param_t *param, srpc_test_reqst_t *req) |
| { |
| test_ping_req_t *prq = &req->tsr_u.ping; |
| |
| prq->png_size = param->png_size; |
| prq->png_flags = param->png_flags; |
| /* TODO dest */ |
| return 0; |
| } |
| |
| static int |
| lstcon_bulkrpc_v0_prep(lst_test_bulk_param_t *param, srpc_test_reqst_t *req) |
| { |
| test_bulk_req_t *brq = &req->tsr_u.bulk_v0; |
| |
| brq->blk_opc = param->blk_opc; |
| brq->blk_npg = (param->blk_size + PAGE_CACHE_SIZE - 1) / |
| PAGE_CACHE_SIZE; |
| brq->blk_flags = param->blk_flags; |
| |
| return 0; |
| } |
| |
| static int |
| lstcon_bulkrpc_v1_prep(lst_test_bulk_param_t *param, srpc_test_reqst_t *req) |
| { |
| test_bulk_req_v1_t *brq = &req->tsr_u.bulk_v1; |
| |
| brq->blk_opc = param->blk_opc; |
| brq->blk_flags = param->blk_flags; |
| brq->blk_len = param->blk_size; |
| brq->blk_offset = 0; /* reserved */ |
| |
| return 0; |
| } |
| |
| int |
| lstcon_testrpc_prep(lstcon_node_t *nd, int transop, unsigned feats, |
| lstcon_test_t *test, lstcon_rpc_t **crpc) |
| { |
| lstcon_group_t *sgrp = test->tes_src_grp; |
| lstcon_group_t *dgrp = test->tes_dst_grp; |
| srpc_test_reqst_t *trq; |
| srpc_bulk_t *bulk; |
| int i; |
| int npg = 0; |
| int nob = 0; |
| int rc = 0; |
| |
| if (transop == LST_TRANS_TSBCLIADD) { |
| npg = sfw_id_pages(test->tes_span); |
| nob = !(feats & LST_FEAT_BULK_LEN) ? |
| npg * PAGE_CACHE_SIZE : |
| sizeof(lnet_process_id_packed_t) * test->tes_span; |
| } |
| |
| rc = lstcon_rpc_prep(nd, SRPC_SERVICE_TEST, feats, npg, nob, crpc); |
| if (rc) |
| return rc; |
| |
| trq = &(*crpc)->crp_rpc->crpc_reqstmsg.msg_body.tes_reqst; |
| |
| if (transop == LST_TRANS_TSBSRVADD) { |
| int ndist = (sgrp->grp_nnode + test->tes_dist - 1) / |
| test->tes_dist; |
| int nspan = (dgrp->grp_nnode + test->tes_span - 1) / |
| test->tes_span; |
| int nmax = (ndist + nspan - 1) / nspan; |
| |
| trq->tsr_ndest = 0; |
| trq->tsr_loop = nmax * test->tes_dist * test->tes_concur; |
| |
| } else { |
| bulk = &(*crpc)->crp_rpc->crpc_bulk; |
| |
| for (i = 0; i < npg; i++) { |
| int len; |
| |
| LASSERT(nob > 0); |
| |
| len = !(feats & LST_FEAT_BULK_LEN) ? |
| PAGE_CACHE_SIZE : |
| min_t(int, nob, PAGE_CACHE_SIZE); |
| nob -= len; |
| |
| bulk->bk_iovs[i].kiov_offset = 0; |
| bulk->bk_iovs[i].kiov_len = len; |
| bulk->bk_iovs[i].kiov_page = |
| alloc_page(GFP_KERNEL); |
| |
| if (!bulk->bk_iovs[i].kiov_page) { |
| lstcon_rpc_put(*crpc); |
| return -ENOMEM; |
| } |
| } |
| |
| bulk->bk_sink = 0; |
| |
| LASSERT(transop == LST_TRANS_TSBCLIADD); |
| |
| rc = lstcon_dstnodes_prep(test->tes_dst_grp, |
| test->tes_cliidx++, |
| test->tes_dist, |
| test->tes_span, |
| npg, &bulk->bk_iovs[0]); |
| if (rc) { |
| lstcon_rpc_put(*crpc); |
| return rc; |
| } |
| |
| trq->tsr_ndest = test->tes_span; |
| trq->tsr_loop = test->tes_loop; |
| } |
| |
| trq->tsr_sid = console_session.ses_id; |
| trq->tsr_bid = test->tes_hdr.tsb_id; |
| trq->tsr_concur = test->tes_concur; |
| trq->tsr_is_client = (transop == LST_TRANS_TSBCLIADD) ? 1 : 0; |
| trq->tsr_stop_onerr = !!test->tes_stop_onerr; |
| |
| switch (test->tes_type) { |
| case LST_TEST_PING: |
| trq->tsr_service = SRPC_SERVICE_PING; |
| rc = lstcon_pingrpc_prep((lst_test_ping_param_t *) |
| &test->tes_param[0], trq); |
| break; |
| |
| case LST_TEST_BULK: |
| trq->tsr_service = SRPC_SERVICE_BRW; |
| if (!(feats & LST_FEAT_BULK_LEN)) { |
| rc = lstcon_bulkrpc_v0_prep((lst_test_bulk_param_t *) |
| &test->tes_param[0], trq); |
| } else { |
| rc = lstcon_bulkrpc_v1_prep((lst_test_bulk_param_t *) |
| &test->tes_param[0], trq); |
| } |
| |
| break; |
| default: |
| LBUG(); |
| break; |
| } |
| |
| return rc; |
| } |
| |
| static int |
| lstcon_sesnew_stat_reply(lstcon_rpc_trans_t *trans, |
| lstcon_node_t *nd, srpc_msg_t *reply) |
| { |
| srpc_mksn_reply_t *mksn_rep = &reply->msg_body.mksn_reply; |
| int status = mksn_rep->mksn_status; |
| |
| if (!status && |
| (reply->msg_ses_feats & ~LST_FEATS_MASK)) { |
| mksn_rep->mksn_status = EPROTO; |
| status = EPROTO; |
| } |
| |
| if (status == EPROTO) { |
| CNETERR("session protocol error from %s: %u\n", |
| libcfs_nid2str(nd->nd_id.nid), |
| reply->msg_ses_feats); |
| } |
| |
| if (status) |
| return status; |
| |
| if (!trans->tas_feats_updated) { |
| spin_lock(&console_session.ses_rpc_lock); |
| if (!trans->tas_feats_updated) { /* recheck with lock */ |
| trans->tas_feats_updated = 1; |
| trans->tas_features = reply->msg_ses_feats; |
| } |
| spin_unlock(&console_session.ses_rpc_lock); |
| } |
| |
| if (reply->msg_ses_feats != trans->tas_features) { |
| CNETERR("Framework features %x from %s is different with features on this transaction: %x\n", |
| reply->msg_ses_feats, libcfs_nid2str(nd->nd_id.nid), |
| trans->tas_features); |
| mksn_rep->mksn_status = EPROTO; |
| status = EPROTO; |
| } |
| |
| if (!status) { |
| /* session timeout on remote node */ |
| nd->nd_timeout = mksn_rep->mksn_timeout; |
| } |
| |
| return status; |
| } |
| |
| void |
| lstcon_rpc_stat_reply(lstcon_rpc_trans_t *trans, srpc_msg_t *msg, |
| lstcon_node_t *nd, lstcon_trans_stat_t *stat) |
| { |
| srpc_rmsn_reply_t *rmsn_rep; |
| srpc_debug_reply_t *dbg_rep; |
| srpc_batch_reply_t *bat_rep; |
| srpc_test_reply_t *test_rep; |
| srpc_stat_reply_t *stat_rep; |
| int rc = 0; |
| |
| switch (trans->tas_opc) { |
| case LST_TRANS_SESNEW: |
| rc = lstcon_sesnew_stat_reply(trans, nd, msg); |
| if (!rc) { |
| lstcon_sesop_stat_success(stat, 1); |
| return; |
| } |
| |
| lstcon_sesop_stat_failure(stat, 1); |
| break; |
| |
| case LST_TRANS_SESEND: |
| rmsn_rep = &msg->msg_body.rmsn_reply; |
| /* ESRCH is not an error for end session */ |
| if (!rmsn_rep->rmsn_status || |
| rmsn_rep->rmsn_status == ESRCH) { |
| lstcon_sesop_stat_success(stat, 1); |
| return; |
| } |
| |
| lstcon_sesop_stat_failure(stat, 1); |
| rc = rmsn_rep->rmsn_status; |
| break; |
| |
| case LST_TRANS_SESQRY: |
| case LST_TRANS_SESPING: |
| dbg_rep = &msg->msg_body.dbg_reply; |
| |
| if (dbg_rep->dbg_status == ESRCH) { |
| lstcon_sesqry_stat_unknown(stat, 1); |
| return; |
| } |
| |
| if (lstcon_session_match(dbg_rep->dbg_sid)) |
| lstcon_sesqry_stat_active(stat, 1); |
| else |
| lstcon_sesqry_stat_busy(stat, 1); |
| return; |
| |
| case LST_TRANS_TSBRUN: |
| case LST_TRANS_TSBSTOP: |
| bat_rep = &msg->msg_body.bat_reply; |
| |
| if (!bat_rep->bar_status) { |
| lstcon_tsbop_stat_success(stat, 1); |
| return; |
| } |
| |
| if (bat_rep->bar_status == EPERM && |
| trans->tas_opc == LST_TRANS_TSBSTOP) { |
| lstcon_tsbop_stat_success(stat, 1); |
| return; |
| } |
| |
| lstcon_tsbop_stat_failure(stat, 1); |
| rc = bat_rep->bar_status; |
| break; |
| |
| case LST_TRANS_TSBCLIQRY: |
| case LST_TRANS_TSBSRVQRY: |
| bat_rep = &msg->msg_body.bat_reply; |
| |
| if (bat_rep->bar_active) |
| lstcon_tsbqry_stat_run(stat, 1); |
| else |
| lstcon_tsbqry_stat_idle(stat, 1); |
| |
| if (!bat_rep->bar_status) |
| return; |
| |
| lstcon_tsbqry_stat_failure(stat, 1); |
| rc = bat_rep->bar_status; |
| break; |
| |
| case LST_TRANS_TSBCLIADD: |
| case LST_TRANS_TSBSRVADD: |
| test_rep = &msg->msg_body.tes_reply; |
| |
| if (!test_rep->tsr_status) { |
| lstcon_tsbop_stat_success(stat, 1); |
| return; |
| } |
| |
| lstcon_tsbop_stat_failure(stat, 1); |
| rc = test_rep->tsr_status; |
| break; |
| |
| case LST_TRANS_STATQRY: |
| stat_rep = &msg->msg_body.stat_reply; |
| |
| if (!stat_rep->str_status) { |
| lstcon_statqry_stat_success(stat, 1); |
| return; |
| } |
| |
| lstcon_statqry_stat_failure(stat, 1); |
| rc = stat_rep->str_status; |
| break; |
| |
| default: |
| LBUG(); |
| } |
| |
| if (!stat->trs_fwk_errno) |
| stat->trs_fwk_errno = rc; |
| |
| return; |
| } |
| |
| int |
| lstcon_rpc_trans_ndlist(struct list_head *ndlist, |
| struct list_head *translist, int transop, |
| void *arg, lstcon_rpc_cond_func_t condition, |
| lstcon_rpc_trans_t **transpp) |
| { |
| lstcon_rpc_trans_t *trans; |
| lstcon_ndlink_t *ndl; |
| lstcon_node_t *nd; |
| lstcon_rpc_t *rpc; |
| unsigned feats; |
| int rc; |
| |
| /* Creating session RPG for list of nodes */ |
| |
| rc = lstcon_rpc_trans_prep(translist, transop, &trans); |
| if (rc) { |
| CERROR("Can't create transaction %d: %d\n", transop, rc); |
| return rc; |
| } |
| |
| feats = trans->tas_features; |
| list_for_each_entry(ndl, ndlist, ndl_link) { |
| rc = !condition ? 1 : |
| condition(transop, ndl->ndl_node, arg); |
| |
| if (!rc) |
| continue; |
| |
| if (rc < 0) { |
| CDEBUG(D_NET, "Condition error while creating RPC for transaction %d: %d\n", |
| transop, rc); |
| break; |
| } |
| |
| nd = ndl->ndl_node; |
| |
| switch (transop) { |
| case LST_TRANS_SESNEW: |
| case LST_TRANS_SESEND: |
| rc = lstcon_sesrpc_prep(nd, transop, feats, &rpc); |
| break; |
| case LST_TRANS_SESQRY: |
| case LST_TRANS_SESPING: |
| rc = lstcon_dbgrpc_prep(nd, feats, &rpc); |
| break; |
| case LST_TRANS_TSBCLIADD: |
| case LST_TRANS_TSBSRVADD: |
| rc = lstcon_testrpc_prep(nd, transop, feats, |
| (lstcon_test_t *)arg, &rpc); |
| break; |
| case LST_TRANS_TSBRUN: |
| case LST_TRANS_TSBSTOP: |
| case LST_TRANS_TSBCLIQRY: |
| case LST_TRANS_TSBSRVQRY: |
| rc = lstcon_batrpc_prep(nd, transop, feats, |
| (lstcon_tsb_hdr_t *)arg, &rpc); |
| break; |
| case LST_TRANS_STATQRY: |
| rc = lstcon_statrpc_prep(nd, feats, &rpc); |
| break; |
| default: |
| rc = -EINVAL; |
| break; |
| } |
| |
| if (rc) { |
| CERROR("Failed to create RPC for transaction %s: %d\n", |
| lstcon_rpc_trans_name(transop), rc); |
| break; |
| } |
| |
| lstcon_rpc_trans_addreq(trans, rpc); |
| } |
| |
| if (!rc) { |
| *transpp = trans; |
| return 0; |
| } |
| |
| lstcon_rpc_trans_destroy(trans); |
| |
| return rc; |
| } |
| |
| static void |
| lstcon_rpc_pinger(void *arg) |
| { |
| struct stt_timer *ptimer = (struct stt_timer *)arg; |
| lstcon_rpc_trans_t *trans; |
| lstcon_rpc_t *crpc; |
| srpc_msg_t *rep; |
| srpc_debug_reqst_t *drq; |
| lstcon_ndlink_t *ndl; |
| lstcon_node_t *nd; |
| int intv; |
| int count = 0; |
| int rc; |
| |
| /* RPC pinger is a special case of transaction, |
| * it's called by timer at 8 seconds interval. |
| */ |
| mutex_lock(&console_session.ses_mutex); |
| |
| if (console_session.ses_shutdown || console_session.ses_expired) { |
| mutex_unlock(&console_session.ses_mutex); |
| return; |
| } |
| |
| if (!console_session.ses_expired && |
| ktime_get_real_seconds() - console_session.ses_laststamp > |
| (time64_t)console_session.ses_timeout) |
| console_session.ses_expired = 1; |
| |
| trans = console_session.ses_ping; |
| |
| LASSERT(trans); |
| |
| list_for_each_entry(ndl, &console_session.ses_ndl_list, ndl_link) { |
| nd = ndl->ndl_node; |
| |
| if (console_session.ses_expired) { |
| /* idle console, end session on all nodes */ |
| if (nd->nd_state != LST_NODE_ACTIVE) |
| continue; |
| |
| rc = lstcon_sesrpc_prep(nd, LST_TRANS_SESEND, |
| trans->tas_features, &crpc); |
| if (rc) { |
| CERROR("Out of memory\n"); |
| break; |
| } |
| |
| lstcon_rpc_trans_addreq(trans, crpc); |
| lstcon_rpc_post(crpc); |
| |
| continue; |
| } |
| |
| crpc = &nd->nd_ping; |
| |
| if (crpc->crp_rpc) { |
| LASSERT(crpc->crp_trans == trans); |
| LASSERT(!list_empty(&crpc->crp_link)); |
| |
| spin_lock(&crpc->crp_rpc->crpc_lock); |
| |
| LASSERT(crpc->crp_posted); |
| |
| if (!crpc->crp_finished) { |
| /* in flight */ |
| spin_unlock(&crpc->crp_rpc->crpc_lock); |
| continue; |
| } |
| |
| spin_unlock(&crpc->crp_rpc->crpc_lock); |
| |
| lstcon_rpc_get_reply(crpc, &rep); |
| |
| list_del_init(&crpc->crp_link); |
| |
| lstcon_rpc_put(crpc); |
| } |
| |
| if (nd->nd_state != LST_NODE_ACTIVE) |
| continue; |
| |
| intv = (jiffies - nd->nd_stamp) / msecs_to_jiffies(MSEC_PER_SEC); |
| if (intv < nd->nd_timeout / 2) |
| continue; |
| |
| rc = lstcon_rpc_init(nd, SRPC_SERVICE_DEBUG, |
| trans->tas_features, 0, 0, 1, crpc); |
| if (rc) { |
| CERROR("Out of memory\n"); |
| break; |
| } |
| |
| drq = &crpc->crp_rpc->crpc_reqstmsg.msg_body.dbg_reqst; |
| |
| drq->dbg_sid = console_session.ses_id; |
| drq->dbg_flags = 0; |
| |
| lstcon_rpc_trans_addreq(trans, crpc); |
| lstcon_rpc_post(crpc); |
| |
| count++; |
| } |
| |
| if (console_session.ses_expired) { |
| mutex_unlock(&console_session.ses_mutex); |
| return; |
| } |
| |
| CDEBUG(D_NET, "Ping %d nodes in session\n", count); |
| |
| ptimer->stt_expires = ktime_get_real_seconds() + LST_PING_INTERVAL; |
| stt_add_timer(ptimer); |
| |
| mutex_unlock(&console_session.ses_mutex); |
| } |
| |
| int |
| lstcon_rpc_pinger_start(void) |
| { |
| struct stt_timer *ptimer; |
| int rc; |
| |
| LASSERT(list_empty(&console_session.ses_rpc_freelist)); |
| LASSERT(!atomic_read(&console_session.ses_rpc_counter)); |
| |
| rc = lstcon_rpc_trans_prep(NULL, LST_TRANS_SESPING, |
| &console_session.ses_ping); |
| if (rc) { |
| CERROR("Failed to create console pinger\n"); |
| return rc; |
| } |
| |
| ptimer = &console_session.ses_ping_timer; |
| ptimer->stt_expires = ktime_get_real_seconds() + LST_PING_INTERVAL; |
| |
| stt_add_timer(ptimer); |
| |
| return 0; |
| } |
| |
| void |
| lstcon_rpc_pinger_stop(void) |
| { |
| LASSERT(console_session.ses_shutdown); |
| |
| stt_del_timer(&console_session.ses_ping_timer); |
| |
| lstcon_rpc_trans_abort(console_session.ses_ping, -ESHUTDOWN); |
| lstcon_rpc_trans_stat(console_session.ses_ping, lstcon_trans_stat()); |
| lstcon_rpc_trans_destroy(console_session.ses_ping); |
| |
| memset(lstcon_trans_stat(), 0, sizeof(lstcon_trans_stat_t)); |
| |
| console_session.ses_ping = NULL; |
| } |
| |
| void |
| lstcon_rpc_cleanup_wait(void) |
| { |
| lstcon_rpc_trans_t *trans; |
| lstcon_rpc_t *crpc; |
| struct list_head *pacer; |
| struct list_head zlist; |
| |
| /* Called with hold of global mutex */ |
| |
| LASSERT(console_session.ses_shutdown); |
| |
| while (!list_empty(&console_session.ses_trans_list)) { |
| list_for_each(pacer, &console_session.ses_trans_list) { |
| trans = list_entry(pacer, lstcon_rpc_trans_t, |
| tas_link); |
| |
| CDEBUG(D_NET, "Session closed, wakeup transaction %s\n", |
| lstcon_rpc_trans_name(trans->tas_opc)); |
| |
| wake_up(&trans->tas_waitq); |
| } |
| |
| mutex_unlock(&console_session.ses_mutex); |
| |
| CWARN("Session is shutting down, waiting for termination of transactions\n"); |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout(cfs_time_seconds(1)); |
| |
| mutex_lock(&console_session.ses_mutex); |
| } |
| |
| spin_lock(&console_session.ses_rpc_lock); |
| |
| lst_wait_until(!atomic_read(&console_session.ses_rpc_counter), |
| console_session.ses_rpc_lock, |
| "Network is not accessible or target is down, waiting for %d console RPCs to being recycled\n", |
| atomic_read(&console_session.ses_rpc_counter)); |
| |
| list_add(&zlist, &console_session.ses_rpc_freelist); |
| list_del_init(&console_session.ses_rpc_freelist); |
| |
| spin_unlock(&console_session.ses_rpc_lock); |
| |
| while (!list_empty(&zlist)) { |
| crpc = list_entry(zlist.next, lstcon_rpc_t, crp_link); |
| |
| list_del(&crpc->crp_link); |
| LIBCFS_FREE(crpc, sizeof(lstcon_rpc_t)); |
| } |
| } |
| |
| int |
| lstcon_rpc_module_init(void) |
| { |
| INIT_LIST_HEAD(&console_session.ses_ping_timer.stt_list); |
| console_session.ses_ping_timer.stt_func = lstcon_rpc_pinger; |
| console_session.ses_ping_timer.stt_data = &console_session.ses_ping_timer; |
| |
| console_session.ses_ping = NULL; |
| |
| spin_lock_init(&console_session.ses_rpc_lock); |
| atomic_set(&console_session.ses_rpc_counter, 0); |
| INIT_LIST_HEAD(&console_session.ses_rpc_freelist); |
| |
| return 0; |
| } |
| |
| void |
| lstcon_rpc_module_fini(void) |
| { |
| LASSERT(list_empty(&console_session.ses_rpc_freelist)); |
| LASSERT(!atomic_read(&console_session.ses_rpc_counter)); |
| } |