Merge tag 'hyperv-next-signed-20220807' of git://git.kernel.org/pub/scm/linux/kernel...
[linux-2.6-microblaze.git] / fs / mbcache.c
index 0b833da..47ccfcb 100644 (file)
@@ -11,7 +11,7 @@
 /*
  * Mbcache is a simple key-value store. Keys need not be unique, however
  * key-value pairs are expected to be unique (we use this fact in
- * mb_cache_entry_delete()).
+ * mb_cache_entry_delete_or_get()).
  *
  * Ext2 and ext4 use this cache for deduplication of extended attribute blocks.
  * Ext4 also uses it for deduplication of xattr values stored in inodes.
@@ -90,7 +90,7 @@ int mb_cache_entry_create(struct mb_cache *cache, gfp_t mask, u32 key,
                return -ENOMEM;
 
        INIT_LIST_HEAD(&entry->e_list);
-       /* One ref for hash, one ref returned */
+       /* Initial hash reference */
        atomic_set(&entry->e_refcnt, 1);
        entry->e_key = key;
        entry->e_value = value;
@@ -106,25 +106,45 @@ int mb_cache_entry_create(struct mb_cache *cache, gfp_t mask, u32 key,
                }
        }
        hlist_bl_add_head(&entry->e_hash_list, head);
-       hlist_bl_unlock(head);
-
+       /*
+        * Add entry to LRU list before it can be found by
+        * mb_cache_entry_delete() to avoid races
+        */
        spin_lock(&cache->c_list_lock);
        list_add_tail(&entry->e_list, &cache->c_list);
-       /* Grab ref for LRU list */
-       atomic_inc(&entry->e_refcnt);
        cache->c_entry_count++;
        spin_unlock(&cache->c_list_lock);
+       hlist_bl_unlock(head);
 
        return 0;
 }
 EXPORT_SYMBOL(mb_cache_entry_create);
 
-void __mb_cache_entry_free(struct mb_cache_entry *entry)
+void __mb_cache_entry_free(struct mb_cache *cache, struct mb_cache_entry *entry)
 {
+       struct hlist_bl_head *head;
+
+       head = mb_cache_entry_head(cache, entry->e_key);
+       hlist_bl_lock(head);
+       hlist_bl_del(&entry->e_hash_list);
+       hlist_bl_unlock(head);
        kmem_cache_free(mb_entry_cache, entry);
 }
 EXPORT_SYMBOL(__mb_cache_entry_free);
 
+/*
+ * mb_cache_entry_wait_unused - wait to be the last user of the entry
+ *
+ * @entry - entry to work on
+ *
+ * Wait to be the last user of the entry.
+ */
+void mb_cache_entry_wait_unused(struct mb_cache_entry *entry)
+{
+       wait_var_event(&entry->e_refcnt, atomic_read(&entry->e_refcnt) <= 2);
+}
+EXPORT_SYMBOL(mb_cache_entry_wait_unused);
+
 static struct mb_cache_entry *__entry_find(struct mb_cache *cache,
                                           struct mb_cache_entry *entry,
                                           u32 key)
@@ -142,10 +162,9 @@ static struct mb_cache_entry *__entry_find(struct mb_cache *cache,
        while (node) {
                entry = hlist_bl_entry(node, struct mb_cache_entry,
                                       e_hash_list);
-               if (entry->e_key == key && entry->e_reusable) {
-                       atomic_inc(&entry->e_refcnt);
+               if (entry->e_key == key && entry->e_reusable &&
+                   atomic_inc_not_zero(&entry->e_refcnt))
                        goto out;
-               }
                node = node->next;
        }
        entry = NULL;
@@ -205,10 +224,9 @@ struct mb_cache_entry *mb_cache_entry_get(struct mb_cache *cache, u32 key,
        head = mb_cache_entry_head(cache, key);
        hlist_bl_lock(head);
        hlist_bl_for_each_entry(entry, node, head, e_hash_list) {
-               if (entry->e_key == key && entry->e_value == value) {
-                       atomic_inc(&entry->e_refcnt);
+               if (entry->e_key == key && entry->e_value == value &&
+                   atomic_inc_not_zero(&entry->e_refcnt))
                        goto out;
-               }
        }
        entry = NULL;
 out:
@@ -217,42 +235,42 @@ out:
 }
 EXPORT_SYMBOL(mb_cache_entry_get);
 
-/* mb_cache_entry_delete - remove a cache entry
+/* mb_cache_entry_delete_or_get - remove a cache entry if it has no users
  * @cache - cache we work with
  * @key - key
  * @value - value
  *
- * Remove entry from cache @cache with key @key and value @value.
+ * Remove entry from cache @cache with key @key and value @value. The removal
+ * happens only if the entry is unused. The function returns NULL in case the
+ * entry was successfully removed or there's no entry in cache. Otherwise the
+ * function grabs reference of the entry that we failed to delete because it
+ * still has users and return it.
  */
-void mb_cache_entry_delete(struct mb_cache *cache, u32 key, u64 value)
+struct mb_cache_entry *mb_cache_entry_delete_or_get(struct mb_cache *cache,
+                                                   u32 key, u64 value)
 {
-       struct hlist_bl_node *node;
-       struct hlist_bl_head *head;
        struct mb_cache_entry *entry;
 
-       head = mb_cache_entry_head(cache, key);
-       hlist_bl_lock(head);
-       hlist_bl_for_each_entry(entry, node, head, e_hash_list) {
-               if (entry->e_key == key && entry->e_value == value) {
-                       /* We keep hash list reference to keep entry alive */
-                       hlist_bl_del_init(&entry->e_hash_list);
-                       hlist_bl_unlock(head);
-                       spin_lock(&cache->c_list_lock);
-                       if (!list_empty(&entry->e_list)) {
-                               list_del_init(&entry->e_list);
-                               if (!WARN_ONCE(cache->c_entry_count == 0,
-               "mbcache: attempt to decrement c_entry_count past zero"))
-                                       cache->c_entry_count--;
-                               atomic_dec(&entry->e_refcnt);
-                       }
-                       spin_unlock(&cache->c_list_lock);
-                       mb_cache_entry_put(cache, entry);
-                       return;
-               }
-       }
-       hlist_bl_unlock(head);
+       entry = mb_cache_entry_get(cache, key, value);
+       if (!entry)
+               return NULL;
+
+       /*
+        * Drop the ref we got from mb_cache_entry_get() and the initial hash
+        * ref if we are the last user
+        */
+       if (atomic_cmpxchg(&entry->e_refcnt, 2, 0) != 2)
+               return entry;
+
+       spin_lock(&cache->c_list_lock);
+       if (!list_empty(&entry->e_list))
+               list_del_init(&entry->e_list);
+       cache->c_entry_count--;
+       spin_unlock(&cache->c_list_lock);
+       __mb_cache_entry_free(cache, entry);
+       return NULL;
 }
-EXPORT_SYMBOL(mb_cache_entry_delete);
+EXPORT_SYMBOL(mb_cache_entry_delete_or_get);
 
 /* mb_cache_entry_touch - cache entry got used
  * @cache - cache the entry belongs to
@@ -281,34 +299,24 @@ static unsigned long mb_cache_shrink(struct mb_cache *cache,
                                     unsigned long nr_to_scan)
 {
        struct mb_cache_entry *entry;
-       struct hlist_bl_head *head;
        unsigned long shrunk = 0;
 
        spin_lock(&cache->c_list_lock);
        while (nr_to_scan-- && !list_empty(&cache->c_list)) {
                entry = list_first_entry(&cache->c_list,
                                         struct mb_cache_entry, e_list);
-               if (entry->e_referenced) {
+               /* Drop initial hash reference if there is no user */
+               if (entry->e_referenced ||
+                   atomic_cmpxchg(&entry->e_refcnt, 1, 0) != 1) {
                        entry->e_referenced = 0;
                        list_move_tail(&entry->e_list, &cache->c_list);
                        continue;
                }
                list_del_init(&entry->e_list);
                cache->c_entry_count--;
-               /*
-                * We keep LRU list reference so that entry doesn't go away
-                * from under us.
-                */
                spin_unlock(&cache->c_list_lock);
-               head = mb_cache_entry_head(cache, entry->e_key);
-               hlist_bl_lock(head);
-               if (!hlist_bl_unhashed(&entry->e_hash_list)) {
-                       hlist_bl_del_init(&entry->e_hash_list);
-                       atomic_dec(&entry->e_refcnt);
-               }
-               hlist_bl_unlock(head);
-               if (mb_cache_entry_put(cache, entry))
-                       shrunk++;
+               __mb_cache_entry_free(cache, entry);
+               shrunk++;
                cond_resched();
                spin_lock(&cache->c_list_lock);
        }
@@ -400,11 +408,6 @@ void mb_cache_destroy(struct mb_cache *cache)
         * point.
         */
        list_for_each_entry_safe(entry, next, &cache->c_list, e_list) {
-               if (!hlist_bl_unhashed(&entry->e_hash_list)) {
-                       hlist_bl_del_init(&entry->e_hash_list);
-                       atomic_dec(&entry->e_refcnt);
-               } else
-                       WARN_ON(1);
                list_del(&entry->e_list);
                WARN_ON(atomic_read(&entry->e_refcnt) != 1);
                mb_cache_entry_put(cache, entry);