xref: /freebsd/sys/dev/vmware/vmci/vmci_hashtable.c (revision 4f52dfbb8d6c4d446500c5b097e3806ec219fbd4)
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 		/*
373 		 * Remove entry from hash table if not already removed. This
374 		 * could have happened already because VMCIHashTable_RemoveEntry
375 		 * was called to unlink it. We ignore if it is not found.
376 		 * Datagram handles will often have RemoveEntry called, whereas
377 		 * SharedMemory regions rely on ReleaseEntry to unlink the entry
378 		 * , since the creator does not call RemoveEntry when it
379 		 * detaches.
380 		 */
381 
382 		hashtable_unlink_entry(table, entry);
383 		result = VMCI_SUCCESS_ENTRY_DEAD;
384 	}
385 
386 	return (result);
387 }
388 
389 /*
390  *------------------------------------------------------------------------------
391  *
392  * vmci_hashtable_release_entry --
393  *
394  *     Releases an entry from the hashtable.
395  *
396  * Result:
397  *     None.
398  *
399  * Side effects:
400  *     None.
401  *
402  *------------------------------------------------------------------------------
403  */
404 
405 int
406 vmci_hashtable_release_entry(struct vmci_hashtable *table,
407     struct vmci_hash_entry *entry)
408 {
409 	int result;
410 
411 	ASSERT(table);
412 	vmci_grab_lock_bh(&table->lock);
413 	result = vmci_hashtable_release_entry_locked(table, entry);
414 	vmci_release_lock_bh(&table->lock);
415 
416 	return (result);
417 }
418 
419 /*
420  *------------------------------------------------------------------------------
421  *
422  * vmci_hashtable_entry_exists --
423  *
424  *     Returns whether an entry exists in the hashtable
425  *
426  * Result:
427  *     true if handle already in hashtable. false otherwise.
428  *
429  * Side effects:
430  *     None.
431  *
432  *------------------------------------------------------------------------------
433  */
434 
435 bool
436 vmci_hashtable_entry_exists(struct vmci_hashtable *table,
437     struct vmci_handle handle)
438 {
439 	bool exists;
440 
441 	ASSERT(table);
442 
443 	vmci_grab_lock_bh(&table->lock);
444 	exists = vmci_hashtable_entry_exists_locked(table, handle);
445 	vmci_release_lock_bh(&table->lock);
446 
447 	return (exists);
448 }
449 
450 /*
451  *------------------------------------------------------------------------------
452  *
453  * vmci_hashtable_entry_exists_locked --
454  *
455  *     Unlocked version of vmci_hashtable_entry_exists.
456  *
457  * Result:
458  *     true if handle already in hashtable. false otherwise.
459  *
460  * Side effects:
461  *     None.
462  *
463  *------------------------------------------------------------------------------
464  */
465 
466 static bool
467 vmci_hashtable_entry_exists_locked(struct vmci_hashtable *table,
468     struct vmci_handle handle)
469 
470 {
471 	struct vmci_hash_entry *entry;
472 	int idx;
473 
474 	ASSERT(table);
475 
476 	idx = VMCI_HASHTABLE_HASH(handle, table->size);
477 
478 	entry = table->entries[idx];
479 	while (entry) {
480 		if (VMCI_HANDLE_TO_RESOURCE_ID(entry->handle) ==
481 		    VMCI_HANDLE_TO_RESOURCE_ID(handle))
482 			if ((VMCI_HANDLE_TO_CONTEXT_ID(entry->handle) ==
483 			    VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
484 			    (VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(handle)) ||
485 			    (VMCI_INVALID_ID == VMCI_HANDLE_TO_CONTEXT_ID(entry->handle)))
486 				return (true);
487 		entry = entry->next;
488 	}
489 
490 	return (false);
491 }
492 
493 /*
494  *------------------------------------------------------------------------------
495  *
496  * hashtable_unlink_entry --
497  *
498  *     Assumes caller holds table lock.
499  *
500  * Result:
501  *     None.
502  *
503  * Side effects:
504  *     None.
505  *
506  *------------------------------------------------------------------------------
507  */
508 
509 static int
510 hashtable_unlink_entry(struct vmci_hashtable *table,
511     struct vmci_hash_entry *entry)
512 {
513 	int result;
514 	struct vmci_hash_entry *prev, *cur;
515 	int idx;
516 
517 	idx = VMCI_HASHTABLE_HASH(entry->handle, table->size);
518 
519 	prev = NULL;
520 	cur = table->entries[idx];
521 	while (true) {
522 		if (cur == NULL) {
523 			result = VMCI_ERROR_NOT_FOUND;
524 			break;
525 		}
526 		if (VMCI_HANDLE_EQUAL(cur->handle, entry->handle)) {
527 			ASSERT(cur == entry);
528 
529 			/* Remove entry and break. */
530 			if (prev)
531 				prev->next = cur->next;
532 			else
533 				table->entries[idx] = cur->next;
534 			cur->next = NULL;
535 			result = VMCI_SUCCESS;
536 			break;
537 		}
538 		prev = cur;
539 		cur = cur->next;
540 	}
541 	return (result);
542 }
543 
544 /*
545  *------------------------------------------------------------------------------
546  *
547  * vmci_hashtable_sync --
548  *
549  *     Use this as a synchronization point when setting globals, for example,
550  *     during device shutdown.
551  *
552  * Results:
553  *     None.
554  *
555  * Side effects:
556  *     None.
557  *
558  *------------------------------------------------------------------------------
559  */
560 
561 void
562 vmci_hashtable_sync(struct vmci_hashtable *table)
563 {
564 
565 	ASSERT(table);
566 	vmci_grab_lock_bh(&table->lock);
567 	vmci_release_lock_bh(&table->lock);
568 }
569