summaryrefslogtreecommitdiff
path: root/drivers/misc/vmw_vmci/vmci_resource.c
blob: 692daa9eff34115f8766a1128365cef7a3b4f4f3 (plain) (blame)
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// SPDX-License-Identifier: GPL-2.0-only
/*
 * VMware VMCI Driver
 *
 * Copyright (C) 2012 VMware, Inc. All rights reserved.
 */

#include <linux/vmw_vmci_defs.h>
#include <linux/hash.h>
#include <linux/types.h>
#include <linux/rculist.h>
#include <linux/completion.h>

#include "vmci_resource.h"
#include "vmci_driver.h"


#define VMCI_RESOURCE_HASH_BITS         7
#define VMCI_RESOURCE_HASH_BUCKETS      (1 << VMCI_RESOURCE_HASH_BITS)

struct vmci_hash_table {
	spinlock_t lock;
	struct hlist_head entries[VMCI_RESOURCE_HASH_BUCKETS];
};

static struct vmci_hash_table vmci_resource_table = {
	.lock = __SPIN_LOCK_UNLOCKED(vmci_resource_table.lock),
};

static unsigned int vmci_resource_hash(struct vmci_handle handle)
{
	return hash_32(handle.resource, VMCI_RESOURCE_HASH_BITS);
}

/*
 * Gets a resource (if one exists) matching given handle from the hash table.
 */
static struct vmci_resource *vmci_resource_lookup(struct vmci_handle handle,
						  enum vmci_resource_type type)
{
	struct vmci_resource *r, *resource = NULL;
	unsigned int idx = vmci_resource_hash(handle);

	rcu_read_lock();
	hlist_for_each_entry_rcu(r,
				 &vmci_resource_table.entries[idx], node) {
		u32 cid = r->handle.context;
		u32 rid = r->handle.resource;

		if (r->type == type &&
		    rid == handle.resource &&
		    (cid == handle.context || cid == VMCI_INVALID_ID ||
		     handle.context == VMCI_INVALID_ID)) {
			resource = r;
			break;
		}
	}
	rcu_read_unlock();

	return resource;
}

/*
 * Find an unused resource ID and return it. The first
 * VMCI_RESERVED_RESOURCE_ID_MAX are reserved so we start from
 * its value + 1.
 * Returns VMCI resource id on success, VMCI_INVALID_ID on failure.
 */
static u32 vmci_resource_find_id(u32 context_id,
				 enum vmci_resource_type resource_type)
{
	static u32 resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
	u32 old_rid = resource_id;
	u32 current_rid;

	/*
	 * Generate a unique resource ID.  Keep on trying until we wrap around
	 * in the RID space.
	 */
	do {
		struct vmci_handle handle;

		current_rid = resource_id;
		resource_id++;
		if (unlikely(resource_id == VMCI_INVALID_ID)) {
			/* Skip the reserved rids. */
			resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1;
		}

		handle = vmci_make_handle(context_id, current_rid);
		if (!vmci_resource_lookup(handle, resource_type))
			return current_rid;
	} while (resource_id != old_rid);

	return VMCI_INVALID_ID;
}


int vmci_resource_add(struct vmci_resource *resource,
		      enum vmci_resource_type resource_type,
		      struct vmci_handle handle)

{
	unsigned int idx;
	int result;

	spin_lock(&vmci_resource_table.lock);

	if (handle.resource == VMCI_INVALID_ID) {
		handle.resource = vmci_resource_find_id(handle.context,
			resource_type);
		if (handle.resource == VMCI_INVALID_ID) {
			result = VMCI_ERROR_NO_HANDLE;
			goto out;
		}
	} else if (vmci_resource_lookup(handle, resource_type)) {
		result = VMCI_ERROR_ALREADY_EXISTS;
		goto out;
	}

	resource->handle = handle;
	resource->type = resource_type;
	INIT_HLIST_NODE(&resource->node);
	kref_init(&resource->kref);
	init_completion(&resource->done);

	idx = vmci_resource_hash(resource->handle);
	hlist_add_head_rcu(&resource->node, &vmci_resource_table.entries[idx]);

	result = VMCI_SUCCESS;

out:
	spin_unlock(&vmci_resource_table.lock);
	return result;
}

void vmci_resource_remove(struct vmci_resource *resource)
{
	struct vmci_handle handle = resource->handle;
	unsigned int idx = vmci_resource_hash(handle);
	struct vmci_resource *r;

	/* Remove resource from hash table. */
	spin_lock(&vmci_resource_table.lock);

	hlist_for_each_entry(r, &vmci_resource_table.entries[idx], node) {
		if (vmci_handle_is_equal(r->handle, resource->handle)) {
			hlist_del_init_rcu(&r->node);
			break;
		}
	}

	spin_unlock(&vmci_resource_table.lock);
	synchronize_rcu();

	vmci_resource_put(resource);
	wait_for_completion(&resource->done);
}

struct vmci_resource *
vmci_resource_by_handle(struct vmci_handle resource_handle,
			enum vmci_resource_type resource_type)
{
	struct vmci_resource *r, *resource = NULL;

	rcu_read_lock();

	r = vmci_resource_lookup(resource_handle, resource_type);
	if (r &&
	    (resource_type == r->type ||
	     resource_type == VMCI_RESOURCE_TYPE_ANY)) {
		resource = vmci_resource_get(r);
	}

	rcu_read_unlock();

	return resource;
}

/*
 * Get a reference to given resource.
 */
struct vmci_resource *vmci_resource_get(struct vmci_resource *resource)
{
	kref_get(&resource->kref);

	return resource;
}

static void vmci_release_resource(struct kref *kref)
{
	struct vmci_resource *resource =
		container_of(kref, struct vmci_resource, kref);

	/* Verify the resource has been unlinked from hash table */
	WARN_ON(!hlist_unhashed(&resource->node));

	/* Signal that container of this resource can now be destroyed */
	complete(&resource->done);
}

/*
 * Resource's release function will get called if last reference.
 * If it is the last reference, then we are sure that nobody else
 * can increment the count again (it's gone from the resource hash
 * table), so there's no need for locking here.
 */
int vmci_resource_put(struct vmci_resource *resource)
{
	/*
	 * We propagate the information back to caller in case it wants to know
	 * whether entry was freed.
	 */
	return kref_put(&resource->kref, vmci_release_resource) ?
		VMCI_SUCCESS_ENTRY_DEAD : VMCI_SUCCESS;
}

struct vmci_handle vmci_resource_handle(struct vmci_resource *resource)
{
	return resource->handle;
}