| // 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); |
| } |