aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/infiniband/hw/efa/efa_ah_cache.c
blob: 6219529fa889cbb9bb0c065c5df2343efd1d02f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
 * Copyright 2026 Amazon.com, Inc. or its affiliates. All rights reserved.
 */

#include <linux/slab.h>

#include "efa_ah_cache.h"

static const struct rhashtable_params ah_cache_params = {
	.key_len = sizeof(struct efa_ah_cache_key),
	.key_offset = offsetof(struct efa_ah_cache_entry, key),
	.head_offset = offsetof(struct efa_ah_cache_entry, linkage),
};

int efa_ah_cache_init(struct efa_ah_cache *ah_cache)
{
	int err;

	mutex_init(&ah_cache->lock);
	err = rhashtable_init(&ah_cache->hashtable, &ah_cache_params);
	if (err)
		mutex_destroy(&ah_cache->lock);

	return err;
}

static void efa_ah_cache_entry_free(void *ptr, void *arg)
{
	struct efa_ah_cache_entry *entry = ptr;

	WARN_ON(entry->usecnt);
	mutex_destroy(&entry->lock);
	kfree(entry);
}

void efa_ah_cache_destroy(struct efa_ah_cache *ah_cache)
{
	rhashtable_free_and_destroy(&ah_cache->hashtable, efa_ah_cache_entry_free, NULL);
	mutex_destroy(&ah_cache->lock);
}

static struct efa_ah_cache_entry *efa_ah_cache_lookup_locked(struct efa_ah_cache *ah_cache, u16 pd,
							     u8 *gid)
	__must_hold(&ah_cache->lock)
{
	struct efa_ah_cache_key key = {};

	memcpy(key.gid, gid, sizeof(key.gid));
	key.pd = pd;

	return rhashtable_lookup_fast(&ah_cache->hashtable, &key, ah_cache_params);
}

struct efa_ah_cache_entry *efa_ah_cache_lookup(struct efa_ah_cache *ah_cache, u16 pd, u8 *gid)
{
	struct efa_ah_cache_entry *entry;

	mutex_lock(&ah_cache->lock);
	entry = efa_ah_cache_lookup_locked(ah_cache, pd, gid);
	mutex_unlock(&ah_cache->lock);

	return entry;
}

/**
 * efa_ah_cache_get - Get or create an AH cache entry
 * @ah_cache: AH cache
 * @pd: Protection domain number
 * @gid: GID address
 *
 * Look up an AH cache entry by PD and GID. If found, take a reference and
 * return it. If not found, allocate a new entry and insert it. The caller must lock
 * the entry mutex and check usecnt to determine whether a device create
 * command is needed.
 *
 * Return: Pointer to the entry on success, ERR_PTR on failure.
 */
struct efa_ah_cache_entry *efa_ah_cache_get(struct efa_ah_cache *ah_cache, u16 pd, u8 *gid)
{
	struct efa_ah_cache_entry *entry;
	int err;

	mutex_lock(&ah_cache->lock);

	entry = efa_ah_cache_lookup_locked(ah_cache, pd, gid);
	if (entry) {
		refcount_inc(&entry->refcount);
		mutex_unlock(&ah_cache->lock);
		return entry;
	}

	entry = kzalloc_obj(*entry);
	if (!entry) {
		mutex_unlock(&ah_cache->lock);
		return ERR_PTR(-ENOMEM);
	}

	memcpy(entry->key.gid, gid, sizeof(entry->key.gid));
	entry->key.pd = pd;
	refcount_set(&entry->refcount, 1);
	mutex_init(&entry->lock);

	err = rhashtable_insert_fast(&ah_cache->hashtable, &entry->linkage, ah_cache_params);
	if (err) {
		mutex_destroy(&entry->lock);
		kfree(entry);
		mutex_unlock(&ah_cache->lock);
		return ERR_PTR(err);
	}

	mutex_unlock(&ah_cache->lock);
	return entry;
}

/**
 * efa_ah_cache_put - Put a refcount of an AH cache entry
 * @ah_cache: AH cache
 * @entry: AH cache entry
 *
 * Drop the refcount. If it reaches zero, remove the entry from the hashtable
 * and free it.
 */
void efa_ah_cache_put(struct efa_ah_cache *ah_cache, struct efa_ah_cache_entry *entry)
{
	if (!refcount_dec_and_mutex_lock(&entry->refcount, &ah_cache->lock))
		return;

	/* AH cache lock is held here */
	rhashtable_remove_fast(&ah_cache->hashtable, &entry->linkage, ah_cache_params);
	mutex_unlock(&ah_cache->lock);

	mutex_destroy(&entry->lock);
	kfree(entry);
}