root/fs/btrfs/lru_cache.c
// SPDX-License-Identifier: GPL-2.0

#include <linux/mm.h>
#include "lru_cache.h"
#include "messages.h"

/*
 * Initialize a cache object.
 *
 * @cache:      The cache.
 * @max_size:   Maximum size (number of entries) for the cache.
 *              Use 0 for unlimited size, it's the user's responsibility to
 *              trim the cache in that case.
 */
void btrfs_lru_cache_init(struct btrfs_lru_cache *cache, unsigned int max_size)
{
        INIT_LIST_HEAD(&cache->lru_list);
        mt_init(&cache->entries);
        cache->size = 0;
        cache->max_size = max_size;
}

static struct btrfs_lru_cache_entry *match_entry(struct list_head *head, u64 key,
                                                 u64 gen)
{
        struct btrfs_lru_cache_entry *entry;

        list_for_each_entry(entry, head, list) {
                if (entry->key == key && entry->gen == gen)
                        return entry;
        }

        return NULL;
}

/*
 * Lookup for an entry in the cache.
 *
 * @cache:      The cache.
 * @key:        The key of the entry we are looking for.
 * @gen:        Generation associated to the key.
 *
 * Returns the entry associated with the key or NULL if none found.
 */
struct btrfs_lru_cache_entry *btrfs_lru_cache_lookup(struct btrfs_lru_cache *cache,
                                                     u64 key, u64 gen)
{
        struct list_head *head;
        struct btrfs_lru_cache_entry *entry;

        head = mtree_load(&cache->entries, key);
        if (!head)
                return NULL;

        entry = match_entry(head, key, gen);
        if (entry)
                list_move_tail(&entry->lru_list, &cache->lru_list);

        return entry;
}

/*
 * Remove an entry from the cache.
 *
 * @cache:     The cache to remove from.
 * @entry:     The entry to remove from the cache.
 *
 * Note: this also frees the memory used by the entry.
 */
void btrfs_lru_cache_remove(struct btrfs_lru_cache *cache,
                            struct btrfs_lru_cache_entry *entry)
{
        struct list_head *prev = entry->list.prev;

        ASSERT(cache->size > 0);
        ASSERT(!mtree_empty(&cache->entries));

        list_del(&entry->list);
        list_del(&entry->lru_list);

        if (list_empty(prev)) {
                struct list_head *head;

                /*
                 * If previous element in the list entry->list is now empty, it
                 * means it's a head entry not pointing to any cached entries,
                 * so remove it from the maple tree and free it.
                 */
                head = mtree_erase(&cache->entries, entry->key);
                ASSERT(head == prev);
                kfree(head);
        }

        kfree(entry);
        cache->size--;
}

/*
 * Store an entry in the cache.
 *
 * @cache:      The cache.
 * @entry:      The entry to store.
 *
 * Returns 0 on success and < 0 on error.
 */
int btrfs_lru_cache_store(struct btrfs_lru_cache *cache,
                          struct btrfs_lru_cache_entry *new_entry,
                          gfp_t gfp)
{
        const u64 key = new_entry->key;
        struct list_head *head;
        int ret;

        head = kmalloc_obj(*head, gfp);
        if (!head)
                return -ENOMEM;

        ret = mtree_insert(&cache->entries, key, head, gfp);
        if (ret == 0) {
                INIT_LIST_HEAD(head);
                list_add_tail(&new_entry->list, head);
        } else if (ret == -EEXIST) {
                kfree(head);
                head = mtree_load(&cache->entries, key);
                ASSERT(head != NULL);
                if (match_entry(head, key, new_entry->gen) != NULL)
                        return -EEXIST;
                list_add_tail(&new_entry->list, head);
        } else if (ret < 0) {
                kfree(head);
                return ret;
        }

        if (cache->max_size > 0 && cache->size == cache->max_size) {
                struct btrfs_lru_cache_entry *lru_entry;

                lru_entry = list_first_entry(&cache->lru_list,
                                             struct btrfs_lru_cache_entry,
                                             lru_list);
                btrfs_lru_cache_remove(cache, lru_entry);
        }

        list_add_tail(&new_entry->lru_list, &cache->lru_list);
        cache->size++;

        return 0;
}

/*
 * Empty a cache.
 *
 * @cache:     The cache to empty.
 *
 * Removes all entries from the cache.
 */
void btrfs_lru_cache_clear(struct btrfs_lru_cache *cache)
{
        struct btrfs_lru_cache_entry *entry;
        struct btrfs_lru_cache_entry *tmp;

        list_for_each_entry_safe(entry, tmp, &cache->lru_list, lru_list)
                btrfs_lru_cache_remove(cache, entry);

        ASSERT(cache->size == 0);
        ASSERT(mtree_empty(&cache->entries));
}