xref: /freebsd/sys/dev/vmware/vmci/vmci_hashtable.c (revision af23369a6deaaeb612ab266eb88b8bb8d560c322)
1 /*-
2  * Copyright (c) 2018 VMware, Inc.
3  *
4  * SPDX-License-Identifier: (BSD-2-Clause OR GPL-2.0)
5  */
6 
7 /* Implementation of the VMCI Hashtable. */
8 
9 #include <sys/cdefs.h>
10 __FBSDID("$FreeBSD$");
11 
12 #include "vmci.h"
13 #include "vmci_driver.h"
14 #include "vmci_hashtable.h"
15 #include "vmci_kernel_defs.h"
16 #include "vmci_utils.h"
17 
18 #define LGPFX	"vmci_hashtable: "
19 
20 #define VMCI_HASHTABLE_HASH(_h, _sz)					\
21 	vmci_hash_id(VMCI_HANDLE_TO_RESOURCE_ID(_h), (_sz))
22 
23 static int	hashtable_unlink_entry(struct vmci_hashtable *table,
24 		    struct vmci_hash_entry *entry);
25 static bool	vmci_hashtable_entry_exists_locked(struct vmci_hashtable *table,
26 		    struct vmci_handle handle);
27 
28 /*
29  *------------------------------------------------------------------------------
30  *
31  * vmci_hashtable_create --
32  *
33  *     Creates a hashtable.
34  *
35  * Result:
36  *     None.
37  *
38  * Side effects:
39  *     None.
40  *
41  *------------------------------------------------------------------------------
42  */
43 
44 struct vmci_hashtable *
45 vmci_hashtable_create(int size)
46 {
47 	struct vmci_hashtable *table;
48 
49 	table = vmci_alloc_kernel_mem(sizeof(*table),
50 	    VMCI_MEMORY_NORMAL);
51 	if (table == NULL)
52 		return (NULL);
53 	memset(table, 0, sizeof(*table));
54 
55 	table->entries = vmci_alloc_kernel_mem(sizeof(*table->entries) * size,
56 	    VMCI_MEMORY_NORMAL);
57 	if (table->entries == NULL) {
58 		vmci_free_kernel_mem(table, sizeof(*table));
59 		return (NULL);
60 	}
61 	memset(table->entries, 0, sizeof(*table->entries) * size);
62 	table->size = size;
63 	if (vmci_init_lock(&table->lock, "VMCI Hashtable lock") <
64 	    VMCI_SUCCESS) {
65 		vmci_free_kernel_mem(table->entries, sizeof(*table->entries) * size);
66 		vmci_free_kernel_mem(table, sizeof(*table));
67 		return (NULL);
68 	}
69 
70 	return (table);
71 }
72 
73 /*
74  *------------------------------------------------------------------------------
75  *
76  * vmci_hashtable_destroy --
77  *
78  *     This function should be called at module exit time. We rely on the
79  *     module ref count to insure that no one is accessing any hash table
80  *     entries at this point in time. Hence we should be able to just remove
81  *     all entries from the hash table.
82  *
83  * Result:
84  *     None.
85  *
86  * Side effects:
87  *     None.
88  *
89  *------------------------------------------------------------------------------
90  */
91 
92 void
93 vmci_hashtable_destroy(struct vmci_hashtable *table)
94 {
95 
96 	ASSERT(table);
97 
98 	vmci_grab_lock_bh(&table->lock);
99 	vmci_free_kernel_mem(table->entries, sizeof(*table->entries) *
100 	    table->size);
101 	table->entries = NULL;
102 	vmci_release_lock_bh(&table->lock);
103 	vmci_cleanup_lock(&table->lock);
104 	vmci_free_kernel_mem(table, sizeof(*table));
105 }
106 
107 /*
108  *------------------------------------------------------------------------------
109  *
110  * vmci_hashtable_init_entry --
111  *
112  *     Initializes a hash entry.
113  *
114  * Result:
115  *     None.
116  *
117  * Side effects:
118  *     None.
119  *
120  *------------------------------------------------------------------------------
121  */
122 void
123 vmci_hashtable_init_entry(struct vmci_hash_entry *entry,
124     struct vmci_handle handle)
125 {
126 
127 	ASSERT(entry);
128 	entry->handle = handle;
129 	entry->ref_count = 0;
130 }
131 
132 /*
133  *------------------------------------------------------------------------------
134  *
135  * vmci_hashtable_add_entry --
136  *
137  *     Adds an entry to the hashtable.
138  *
139  * Result:
140  *     None.
141  *
142  * Side effects:
143  *     None.
144  *
145  *------------------------------------------------------------------------------
146  */
147 
148 int
149 vmci_hashtable_add_entry(struct vmci_hashtable *table,
150     struct vmci_hash_entry *entry)
151 {
152 	int idx;
153 
154 	ASSERT(entry);
155 	ASSERT(table);
156 
157 	vmci_grab_lock_bh(&table->lock);
158 
159 	if (vmci_hashtable_entry_exists_locked(table, entry->handle)) {
160 		VMCI_LOG_DEBUG(LGPFX"Entry (handle=0x%x:0x%x) already "
161 		    "exists.\n", entry->handle.context,
162 		    entry->handle.resource);
163 		vmci_release_lock_bh(&table->lock);
164 		return (VMCI_ERROR_DUPLICATE_ENTRY);
165 	}
166 
167 	idx = VMCI_HASHTABLE_HASH(entry->handle, table->size);
168 	ASSERT(idx < table->size);
169 
170 	/* New entry is added to top/front of hash bucket. */
171 	entry->ref_count++;
172 	entry->next = table->entries[idx];
173 	table->entries[idx] = entry;
174 	vmci_release_lock_bh(&table->lock);
175 
176 	return (VMCI_SUCCESS);
177 }
178 
179 /*
180  *------------------------------------------------------------------------------
181  *
182  * vmci_hashtable_remove_entry --
183  *
184  *     Removes an entry from the hashtable.
185  *
186  * Result:
187  *     None.
188  *
189  * Side effects:
190  *     None.
191  *
192  *------------------------------------------------------------------------------
193  */
194 
195 int
196 vmci_hashtable_remove_entry(struct vmci_hashtable *table,
197     struct vmci_hash_entry *entry)
198 {
199 	int result;
200 
201 	ASSERT(table);
202 	ASSERT(entry);
203 
204 	vmci_grab_lock_bh(&table->lock);
205 
206 	/* First unlink the entry. */
207 	result = hashtable_unlink_entry(table, entry);
208 	if (result != VMCI_SUCCESS) {
209 		/* We failed to find the entry. */
210 		goto done;
211 	}
212 
213 	/* Decrement refcount and check if this is last reference. */
214 	entry->ref_count--;
215 	if (entry->ref_count == 0) {
216 		result = VMCI_SUCCESS_ENTRY_DEAD;
217 		goto done;
218 	}
219 
220 done:
221 	vmci_release_lock_bh(&table->lock);
222 
223 	return (result);
224 }
225 
226 /*
227  *------------------------------------------------------------------------------
228  *
229  * vmci_hashtable_get_entry_locked --
230  *
231  *     Looks up an entry in the hash table, that is already locked.
232  *
233  * Result:
234  *     If the element is found, a pointer to the element is returned.
235  *     Otherwise NULL is returned.
236  *
237  * Side effects:
238  *     The reference count of the returned element is increased.
239  *
240  *------------------------------------------------------------------------------
241  */
242 
243 static struct vmci_hash_entry *
244 vmci_hashtable_get_entry_locked(struct vmci_hashtable *table,
245     struct vmci_handle handle)
246 {
247 	struct vmci_hash_entry *cur = NULL;
248 	int idx;
249 
250 	ASSERT(!VMCI_HANDLE_EQUAL(handle, VMCI_INVALID_HANDLE));
251 	ASSERT(table);
252 
253 	idx = VMCI_HASHTABLE_HASH(handle, table->size);
254 
255 	cur = table->entries[idx];
256 	while (true) {
257 		if (cur == NULL)
258 			break;
259 
260 		if (VMCI_HANDLE_TO_RESOURCE_ID(cur->handle) ==
261 		    VMCI_HANDLE_TO_RESOURCE_ID(handle)) {
262 			if ((VMCI_HANDLE_TO_CONTEXT_ID(cur->handle) ==
263 			    VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
264 			    (VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(cur->handle))) {
265 				cur->ref_count++;
266 				break;
267 			}
268 		}
269 		cur = cur->next;
270 	}
271 
272 	return (cur);
273 }
274 
275 /*
276  *------------------------------------------------------------------------------
277  *
278  * vmci_hashtable_get_entry --
279  *
280  *     Gets an entry from the hashtable.
281  *
282  * Result:
283  *     None.
284  *
285  * Side effects:
286  *     None.
287  *
288  *------------------------------------------------------------------------------
289  */
290 
291 struct vmci_hash_entry *
292 vmci_hashtable_get_entry(struct vmci_hashtable *table,
293     struct vmci_handle handle)
294 {
295 	struct vmci_hash_entry *entry;
296 
297 	if (VMCI_HANDLE_EQUAL(handle, VMCI_INVALID_HANDLE))
298 		return (NULL);
299 
300 	ASSERT(table);
301 
302 	vmci_grab_lock_bh(&table->lock);
303 	entry = vmci_hashtable_get_entry_locked(table, handle);
304 	vmci_release_lock_bh(&table->lock);
305 
306 	return (entry);
307 }
308 
309 /*
310  *------------------------------------------------------------------------------
311  *
312  * vmci_hashtable_hold_entry --
313  *
314  *     Hold the given entry. This will increment the entry's reference count.
315  *     This is like a GetEntry() but without having to lookup the entry by
316  *     handle.
317  *
318  * Result:
319  *     None.
320  *
321  * Side effects:
322  *     None.
323  *
324  *------------------------------------------------------------------------------
325  */
326 
327 void
328 vmci_hashtable_hold_entry(struct vmci_hashtable *table,
329     struct vmci_hash_entry *entry)
330 {
331 
332 	ASSERT(table);
333 	ASSERT(entry);
334 
335 	vmci_grab_lock_bh(&table->lock);
336 	entry->ref_count++;
337 	vmci_release_lock_bh(&table->lock);
338 }
339 
340 /*
341  *------------------------------------------------------------------------------
342  *
343  * vmci_hashtable_release_entry_locked --
344  *
345  *     Releases an element previously obtained with
346  *     vmci_hashtable_get_entry_locked.
347  *
348  * Result:
349  *     If the entry is removed from the hash table, VMCI_SUCCESS_ENTRY_DEAD
350  *     is returned. Otherwise, VMCI_SUCCESS is returned.
351  *
352  * Side effects:
353  *     The reference count of the entry is decreased and the entry is removed
354  *     from the hash table on 0.
355  *
356  *------------------------------------------------------------------------------
357  */
358 
359 static int
360 vmci_hashtable_release_entry_locked(struct vmci_hashtable *table,
361     struct vmci_hash_entry *entry)
362 {
363 	int result = VMCI_SUCCESS;
364 
365 	ASSERT(table);
366 	ASSERT(entry);
367 
368 	entry->ref_count--;
369 	/* Check if this is last reference and report if so. */
370 	if (entry->ref_count == 0) {
371 		/*
372 		 * Remove entry from hash table if not already removed. This
373 		 * could have happened already because VMCIHashTable_RemoveEntry
374 		 * was called to unlink it. We ignore if it is not found.
375 		 * Datagram handles will often have RemoveEntry called, whereas
376 		 * SharedMemory regions rely on ReleaseEntry to unlink the entry
377 		 * , since the creator does not call RemoveEntry when it
378 		 * detaches.
379 		 */
380 
381 		hashtable_unlink_entry(table, entry);
382 		result = VMCI_SUCCESS_ENTRY_DEAD;
383 	}
384 
385 	return (result);
386 }
387 
388 /*
389  *------------------------------------------------------------------------------
390  *
391  * vmci_hashtable_release_entry --
392  *
393  *     Releases an entry from the hashtable.
394  *
395  * Result:
396  *     None.
397  *
398  * Side effects:
399  *     None.
400  *
401  *------------------------------------------------------------------------------
402  */
403 
404 int
405 vmci_hashtable_release_entry(struct vmci_hashtable *table,
406     struct vmci_hash_entry *entry)
407 {
408 	int result;
409 
410 	ASSERT(table);
411 	vmci_grab_lock_bh(&table->lock);
412 	result = vmci_hashtable_release_entry_locked(table, entry);
413 	vmci_release_lock_bh(&table->lock);
414 
415 	return (result);
416 }
417 
418 /*
419  *------------------------------------------------------------------------------
420  *
421  * vmci_hashtable_entry_exists --
422  *
423  *     Returns whether an entry exists in the hashtable
424  *
425  * Result:
426  *     true if handle already in hashtable. false otherwise.
427  *
428  * Side effects:
429  *     None.
430  *
431  *------------------------------------------------------------------------------
432  */
433 
434 bool
435 vmci_hashtable_entry_exists(struct vmci_hashtable *table,
436     struct vmci_handle handle)
437 {
438 	bool exists;
439 
440 	ASSERT(table);
441 
442 	vmci_grab_lock_bh(&table->lock);
443 	exists = vmci_hashtable_entry_exists_locked(table, handle);
444 	vmci_release_lock_bh(&table->lock);
445 
446 	return (exists);
447 }
448 
449 /*
450  *------------------------------------------------------------------------------
451  *
452  * vmci_hashtable_entry_exists_locked --
453  *
454  *     Unlocked version of vmci_hashtable_entry_exists.
455  *
456  * Result:
457  *     true if handle already in hashtable. false otherwise.
458  *
459  * Side effects:
460  *     None.
461  *
462  *------------------------------------------------------------------------------
463  */
464 
465 static bool
466 vmci_hashtable_entry_exists_locked(struct vmci_hashtable *table,
467     struct vmci_handle handle)
468 
469 {
470 	struct vmci_hash_entry *entry;
471 	int idx;
472 
473 	ASSERT(table);
474 
475 	idx = VMCI_HASHTABLE_HASH(handle, table->size);
476 
477 	entry = table->entries[idx];
478 	while (entry) {
479 		if (VMCI_HANDLE_TO_RESOURCE_ID(entry->handle) ==
480 		    VMCI_HANDLE_TO_RESOURCE_ID(handle))
481 			if ((VMCI_HANDLE_TO_CONTEXT_ID(entry->handle) ==
482 			    VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
483 			    (VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
484 			    (VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(entry->handle)))
485 				return (true);
486 		entry = entry->next;
487 	}
488 
489 	return (false);
490 }
491 
492 /*
493  *------------------------------------------------------------------------------
494  *
495  * hashtable_unlink_entry --
496  *
497  *     Assumes caller holds table lock.
498  *
499  * Result:
500  *     None.
501  *
502  * Side effects:
503  *     None.
504  *
505  *------------------------------------------------------------------------------
506  */
507 
508 static int
509 hashtable_unlink_entry(struct vmci_hashtable *table,
510     struct vmci_hash_entry *entry)
511 {
512 	int result;
513 	struct vmci_hash_entry *prev, *cur;
514 	int idx;
515 
516 	idx = VMCI_HASHTABLE_HASH(entry->handle, table->size);
517 
518 	prev = NULL;
519 	cur = table->entries[idx];
520 	while (true) {
521 		if (cur == NULL) {
522 			result = VMCI_ERROR_NOT_FOUND;
523 			break;
524 		}
525 		if (VMCI_HANDLE_EQUAL(cur->handle, entry->handle)) {
526 			ASSERT(cur == entry);
527 
528 			/* Remove entry and break. */
529 			if (prev)
530 				prev->next = cur->next;
531 			else
532 				table->entries[idx] = cur->next;
533 			cur->next = NULL;
534 			result = VMCI_SUCCESS;
535 			break;
536 		}
537 		prev = cur;
538 		cur = cur->next;
539 	}
540 	return (result);
541 }
542 
543 /*
544  *------------------------------------------------------------------------------
545  *
546  * vmci_hashtable_sync --
547  *
548  *     Use this as a synchronization point when setting globals, for example,
549  *     during device shutdown.
550  *
551  * Results:
552  *     None.
553  *
554  * Side effects:
555  *     None.
556  *
557  *------------------------------------------------------------------------------
558  */
559 
560 void
561 vmci_hashtable_sync(struct vmci_hashtable *table)
562 {
563 
564 	ASSERT(table);
565 	vmci_grab_lock_bh(&table->lock);
566 	vmci_release_lock_bh(&table->lock);
567 }
568