Viewing: connection.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2011, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*/
#define DEBUG_SUBSYSTEM S_RPC
#include <linux/delay.h>
#include <linux/rhashtable.h>
#include <obd_support.h>
#include <obd_class.h>
#include <lustre_net.h>
#include "ptlrpc_internal.h"
static struct rhashtable conn_hash;
/* per-cpu PM QoS management */
struct cpu_latency_qos *cpus_latency_qos;
/*
* struct lnet_process_id may contain unassigned bytes which might not
* be zero, so we cannot just hash and compare bytes.
*/
static u32 lnet_process_id_hash(const void *data, u32 len, u32 seed)
{
const struct lnet_processid *lpi = data;
seed = hash_32(seed ^ lpi->pid, 32);
seed = hash_32(nidhash(&lpi->nid) ^ seed, 32);
return seed;
}
static int lnet_process_id_cmp(struct rhashtable_compare_arg *arg,
const void *obj)
{
const struct lnet_processid *lpi = arg->key;
const struct ptlrpc_connection *con = obj;
if (nid_same(&lpi->nid, &con->c_peer.nid) &&
lpi->pid == con->c_peer.pid)
return 0;
return -ESRCH;
}
static const struct rhashtable_params conn_hash_params = {
.key_len = 1, /* actually variable-length */
.key_offset = offsetof(struct ptlrpc_connection, c_peer),
.head_offset = offsetof(struct ptlrpc_connection, c_hash),
.hashfn = lnet_process_id_hash,
.obj_cmpfn = lnet_process_id_cmp,
};
static void cpu_latency_work(struct work_struct *work)
{
struct cpu_latency_qos *latency_qos;
struct dev_pm_qos_request *pm_qos_req_done = NULL;
int cpu;
latency_qos = container_of(work, struct cpu_latency_qos,
delayed_work.work);
cpu = (latency_qos - cpus_latency_qos) / sizeof(struct cpu_latency_qos);
mutex_lock(&latency_qos->lock);
if (time_after_eq64(jiffies_64, latency_qos->deadline)) {
CDEBUG(D_INFO, "work item of %p (cpu %d) has reached its deadline %llu, at %llu\n",
latency_qos, cpu, latency_qos->deadline, jiffies_64);
pm_qos_req_done = latency_qos->pm_qos_req;
latency_qos->pm_qos_req = NULL;
} else {
/* XXX Is this expected to happen?
* anyway, reschedule for the remaining time
*/
cancel_delayed_work(&latency_qos->delayed_work);
schedule_delayed_work(&latency_qos->delayed_work,
(unsigned long)(latency_qos->deadline -
jiffies_64));
CDEBUG(D_INFO, "work item of %p (cpu %d) has not reached its deadline %llu, at %llu\n",
latency_qos, cpu, latency_qos->deadline, jiffies_64);
}
mutex_unlock(&latency_qos->lock);
/* must be done outside atomic section */
if (pm_qos_req_done != NULL) {
dev_pm_qos_remove_request(pm_qos_req_done);
OBD_FREE_PTR(pm_qos_req_done);
}
}
struct ptlrpc_connection *
ptlrpc_connection_get(struct lnet_processid *peer_orig, struct lnet_nid *self,
struct obd_uuid *uuid)
{
struct ptlrpc_connection *conn, *conn2;
struct lnet_processid peer = *peer_orig;
ENTRY;
LNetPrimaryNID(&peer.nid);
conn = rhashtable_lookup_fast(&conn_hash, &peer, conn_hash_params);
if (conn) {
ptlrpc_connection_addref(conn);
GOTO(out, conn);
}
OBD_ALLOC_PTR(conn);
if (!conn)
RETURN(NULL);
conn->c_peer = peer;
atomic_set(&conn->c_refcount, 1);
/*
* Add the newly created conn to the hash, on key collision we
* lost a racing addition and must destroy our newly allocated
* connection. The object which exists in the hash will be
* returned,otherwise NULL is returned on success.
*/
try_again:
conn2 = rhashtable_lookup_get_insert_fast(&conn_hash, &conn->c_hash,
conn_hash_params);
if (conn2) {
/* insertion failed */
if (IS_ERR(conn2)) {
/* hash table could be resizing. */
if (PTR_ERR(conn2) == -ENOMEM ||
PTR_ERR(conn2) == -EBUSY) {
msleep(5);
goto try_again;
}
conn2 = NULL;
}
OBD_FREE_PTR(conn);
conn = conn2;
if (conn)
ptlrpc_connection_addref(conn);
}
EXIT;
out:
CDEBUG(D_INFO, "conn=%p refcount %d to %s\n",
conn, atomic_read(&conn->c_refcount),
libcfs_nidstr(&conn->c_peer.nid));
return conn;
}
struct ptlrpc_connection *
ptlrpc_connection_addref(struct ptlrpc_connection *conn)
{
ENTRY;
atomic_inc(&conn->c_refcount);
CDEBUG(D_INFO, "conn=%p refcount %d to %s\n",
conn, atomic_read(&conn->c_refcount),
libcfs_nidstr(&conn->c_peer.nid));
RETURN(conn);
}
static void
conn_exit(void *vconn, void *data)
{
struct ptlrpc_connection *conn = vconn;
/*
* Nothing should be left. Connection user put it and
* connection also was deleted from table by this time
* so we should have 0 refs.
*/
LASSERTF(atomic_read(&conn->c_refcount) == 0,
"Busy connection with %d refs\n",
atomic_read(&conn->c_refcount));
OBD_FREE_PTR(conn);
}
int ptlrpc_connection_init(void)
{
int cpu;
OBD_ALLOC_PTR_ARRAY(cpus_latency_qos, nr_cpu_ids);
if (!cpus_latency_qos) {
CWARN("Failed to allocate PM-QoS management structs\n");
} else {
for (cpu = 0; cpu < nr_cpu_ids; cpu++) {
struct cpu_latency_qos *cpu_latency_qos =
&cpus_latency_qos[cpu];
INIT_DELAYED_WORK(&cpu_latency_qos->delayed_work,
cpu_latency_work);
mutex_init(&cpu_latency_qos->lock);
cpu_latency_qos->max_time =
DEFAULT_CPU_LATENCY_TIMEOUT_US;
}
}
return rhashtable_init(&conn_hash, &conn_hash_params);
}
static void ptlrpc_latency_req_fini(struct cpu_latency_qos *lq, int cpu)
{
mutex_lock(&lq->lock);
if (lq->pm_qos_req != NULL) {
if (dev_pm_qos_request_active(lq->pm_qos_req))
dev_pm_qos_remove_request(lq->pm_qos_req);
cancel_delayed_work(&lq->delayed_work);
CDEBUG(D_INFO, "remove PM QoS request %p and associated work" \
" item, still active for this cpu %d\n", lq, cpu);
OBD_FREE_PTR(lq->pm_qos_req);
}
mutex_unlock(&lq->lock);
}
void ptlrpc_connection_fini(void)
{
int cpu;
if (cpus_latency_qos != NULL) {
for (cpu = 0; cpu < nr_cpu_ids; cpu++)
ptlrpc_latency_req_fini(&cpus_latency_qos[cpu], cpu);
OBD_FREE_PTR_ARRAY(cpus_latency_qos, nr_cpu_ids);
}
rhashtable_free_and_destroy(&conn_hash, conn_exit, NULL);
}