162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* Volume-level cache cookie handling.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define FSCACHE_DEBUG_LEVEL COOKIE
962306a36Sopenharmony_ci#include <linux/export.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include "internal.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define fscache_volume_hash_shift 10
1462306a36Sopenharmony_cistatic struct hlist_bl_head fscache_volume_hash[1 << fscache_volume_hash_shift];
1562306a36Sopenharmony_cistatic atomic_t fscache_volume_debug_id;
1662306a36Sopenharmony_cistatic LIST_HEAD(fscache_volumes);
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic void fscache_create_volume_work(struct work_struct *work);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct fscache_volume *fscache_get_volume(struct fscache_volume *volume,
2162306a36Sopenharmony_ci					  enum fscache_volume_trace where)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	int ref;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	__refcount_inc(&volume->ref, &ref);
2662306a36Sopenharmony_ci	trace_fscache_volume(volume->debug_id, ref + 1, where);
2762306a36Sopenharmony_ci	return volume;
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic void fscache_see_volume(struct fscache_volume *volume,
3162306a36Sopenharmony_ci			       enum fscache_volume_trace where)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	int ref = refcount_read(&volume->ref);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	trace_fscache_volume(volume->debug_id, ref, where);
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/*
3962306a36Sopenharmony_ci * Pin the cache behind a volume so that we can access it.
4062306a36Sopenharmony_ci */
4162306a36Sopenharmony_cistatic void __fscache_begin_volume_access(struct fscache_volume *volume,
4262306a36Sopenharmony_ci					  struct fscache_cookie *cookie,
4362306a36Sopenharmony_ci					  enum fscache_access_trace why)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	int n_accesses;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	n_accesses = atomic_inc_return(&volume->n_accesses);
4862306a36Sopenharmony_ci	smp_mb__after_atomic();
4962306a36Sopenharmony_ci	trace_fscache_access_volume(volume->debug_id, cookie ? cookie->debug_id : 0,
5062306a36Sopenharmony_ci				    refcount_read(&volume->ref),
5162306a36Sopenharmony_ci				    n_accesses, why);
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/**
5562306a36Sopenharmony_ci * fscache_begin_volume_access - Pin a cache so a volume can be accessed
5662306a36Sopenharmony_ci * @volume: The volume cookie
5762306a36Sopenharmony_ci * @cookie: A datafile cookie for a tracing reference (or NULL)
5862306a36Sopenharmony_ci * @why: An indication of the circumstances of the access for tracing
5962306a36Sopenharmony_ci *
6062306a36Sopenharmony_ci * Attempt to pin the cache to prevent it from going away whilst we're
6162306a36Sopenharmony_ci * accessing a volume and returns true if successful.  This works as follows:
6262306a36Sopenharmony_ci *
6362306a36Sopenharmony_ci *  (1) If the cache tests as not live (state is not FSCACHE_CACHE_IS_ACTIVE),
6462306a36Sopenharmony_ci *      then we return false to indicate access was not permitted.
6562306a36Sopenharmony_ci *
6662306a36Sopenharmony_ci *  (2) If the cache tests as live, then we increment the volume's n_accesses
6762306a36Sopenharmony_ci *      count and then recheck the cache liveness, ending the access if it
6862306a36Sopenharmony_ci *      ceased to be live.
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci *  (3) When we end the access, we decrement the volume's n_accesses and wake
7162306a36Sopenharmony_ci *      up the any waiters if it reaches 0.
7262306a36Sopenharmony_ci *
7362306a36Sopenharmony_ci *  (4) Whilst the cache is caching, the volume's n_accesses is kept
7462306a36Sopenharmony_ci *      artificially incremented to prevent wakeups from happening.
7562306a36Sopenharmony_ci *
7662306a36Sopenharmony_ci *  (5) When the cache is taken offline, the state is changed to prevent new
7762306a36Sopenharmony_ci *      accesses, the volume's n_accesses is decremented and we wait for it to
7862306a36Sopenharmony_ci *      become 0.
7962306a36Sopenharmony_ci *
8062306a36Sopenharmony_ci * The datafile @cookie and the @why indicator are merely provided for tracing
8162306a36Sopenharmony_ci * purposes.
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_cibool fscache_begin_volume_access(struct fscache_volume *volume,
8462306a36Sopenharmony_ci				 struct fscache_cookie *cookie,
8562306a36Sopenharmony_ci				 enum fscache_access_trace why)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	if (!fscache_cache_is_live(volume->cache))
8862306a36Sopenharmony_ci		return false;
8962306a36Sopenharmony_ci	__fscache_begin_volume_access(volume, cookie, why);
9062306a36Sopenharmony_ci	if (!fscache_cache_is_live(volume->cache)) {
9162306a36Sopenharmony_ci		fscache_end_volume_access(volume, cookie, fscache_access_unlive);
9262306a36Sopenharmony_ci		return false;
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci	return true;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci/**
9862306a36Sopenharmony_ci * fscache_end_volume_access - Unpin a cache at the end of an access.
9962306a36Sopenharmony_ci * @volume: The volume cookie
10062306a36Sopenharmony_ci * @cookie: A datafile cookie for a tracing reference (or NULL)
10162306a36Sopenharmony_ci * @why: An indication of the circumstances of the access for tracing
10262306a36Sopenharmony_ci *
10362306a36Sopenharmony_ci * Unpin a cache volume after we've accessed it.  The datafile @cookie and the
10462306a36Sopenharmony_ci * @why indicator are merely provided for tracing purposes.
10562306a36Sopenharmony_ci */
10662306a36Sopenharmony_civoid fscache_end_volume_access(struct fscache_volume *volume,
10762306a36Sopenharmony_ci			       struct fscache_cookie *cookie,
10862306a36Sopenharmony_ci			       enum fscache_access_trace why)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	int n_accesses;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	smp_mb__before_atomic();
11362306a36Sopenharmony_ci	n_accesses = atomic_dec_return(&volume->n_accesses);
11462306a36Sopenharmony_ci	trace_fscache_access_volume(volume->debug_id, cookie ? cookie->debug_id : 0,
11562306a36Sopenharmony_ci				    refcount_read(&volume->ref),
11662306a36Sopenharmony_ci				    n_accesses, why);
11762306a36Sopenharmony_ci	if (n_accesses == 0)
11862306a36Sopenharmony_ci		wake_up_var(&volume->n_accesses);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ciEXPORT_SYMBOL(fscache_end_volume_access);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic bool fscache_volume_same(const struct fscache_volume *a,
12362306a36Sopenharmony_ci				const struct fscache_volume *b)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	size_t klen;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	if (a->key_hash	!= b->key_hash ||
12862306a36Sopenharmony_ci	    a->cache	!= b->cache ||
12962306a36Sopenharmony_ci	    a->key[0]	!= b->key[0])
13062306a36Sopenharmony_ci		return false;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	klen = round_up(a->key[0] + 1, sizeof(__le32));
13362306a36Sopenharmony_ci	return memcmp(a->key, b->key, klen) == 0;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic bool fscache_is_acquire_pending(struct fscache_volume *volume)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	return test_bit(FSCACHE_VOLUME_ACQUIRE_PENDING, &volume->flags);
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic void fscache_wait_on_volume_collision(struct fscache_volume *candidate,
14262306a36Sopenharmony_ci					     unsigned int collidee_debug_id)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	wait_on_bit_timeout(&candidate->flags, FSCACHE_VOLUME_ACQUIRE_PENDING,
14562306a36Sopenharmony_ci			    TASK_UNINTERRUPTIBLE, 20 * HZ);
14662306a36Sopenharmony_ci	if (fscache_is_acquire_pending(candidate)) {
14762306a36Sopenharmony_ci		pr_notice("Potential volume collision new=%08x old=%08x",
14862306a36Sopenharmony_ci			  candidate->debug_id, collidee_debug_id);
14962306a36Sopenharmony_ci		fscache_stat(&fscache_n_volumes_collision);
15062306a36Sopenharmony_ci		wait_on_bit(&candidate->flags, FSCACHE_VOLUME_ACQUIRE_PENDING,
15162306a36Sopenharmony_ci			    TASK_UNINTERRUPTIBLE);
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/*
15662306a36Sopenharmony_ci * Attempt to insert the new volume into the hash.  If there's a collision, we
15762306a36Sopenharmony_ci * wait for the old volume to complete if it's being relinquished and an error
15862306a36Sopenharmony_ci * otherwise.
15962306a36Sopenharmony_ci */
16062306a36Sopenharmony_cistatic bool fscache_hash_volume(struct fscache_volume *candidate)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct fscache_volume *cursor;
16362306a36Sopenharmony_ci	struct hlist_bl_head *h;
16462306a36Sopenharmony_ci	struct hlist_bl_node *p;
16562306a36Sopenharmony_ci	unsigned int bucket, collidee_debug_id = 0;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	bucket = candidate->key_hash & (ARRAY_SIZE(fscache_volume_hash) - 1);
16862306a36Sopenharmony_ci	h = &fscache_volume_hash[bucket];
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	hlist_bl_lock(h);
17162306a36Sopenharmony_ci	hlist_bl_for_each_entry(cursor, p, h, hash_link) {
17262306a36Sopenharmony_ci		if (fscache_volume_same(candidate, cursor)) {
17362306a36Sopenharmony_ci			if (!test_bit(FSCACHE_VOLUME_RELINQUISHED, &cursor->flags))
17462306a36Sopenharmony_ci				goto collision;
17562306a36Sopenharmony_ci			fscache_see_volume(cursor, fscache_volume_get_hash_collision);
17662306a36Sopenharmony_ci			set_bit(FSCACHE_VOLUME_COLLIDED_WITH, &cursor->flags);
17762306a36Sopenharmony_ci			set_bit(FSCACHE_VOLUME_ACQUIRE_PENDING, &candidate->flags);
17862306a36Sopenharmony_ci			collidee_debug_id = cursor->debug_id;
17962306a36Sopenharmony_ci			break;
18062306a36Sopenharmony_ci		}
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	hlist_bl_add_head(&candidate->hash_link, h);
18462306a36Sopenharmony_ci	hlist_bl_unlock(h);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (fscache_is_acquire_pending(candidate))
18762306a36Sopenharmony_ci		fscache_wait_on_volume_collision(candidate, collidee_debug_id);
18862306a36Sopenharmony_ci	return true;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cicollision:
19162306a36Sopenharmony_ci	fscache_see_volume(cursor, fscache_volume_collision);
19262306a36Sopenharmony_ci	hlist_bl_unlock(h);
19362306a36Sopenharmony_ci	return false;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci/*
19762306a36Sopenharmony_ci * Allocate and initialise a volume representation cookie.
19862306a36Sopenharmony_ci */
19962306a36Sopenharmony_cistatic struct fscache_volume *fscache_alloc_volume(const char *volume_key,
20062306a36Sopenharmony_ci						   const char *cache_name,
20162306a36Sopenharmony_ci						   const void *coherency_data,
20262306a36Sopenharmony_ci						   size_t coherency_len)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	struct fscache_volume *volume;
20562306a36Sopenharmony_ci	struct fscache_cache *cache;
20662306a36Sopenharmony_ci	size_t klen, hlen;
20762306a36Sopenharmony_ci	u8 *key;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	klen = strlen(volume_key);
21062306a36Sopenharmony_ci	if (klen > NAME_MAX)
21162306a36Sopenharmony_ci		return NULL;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (!coherency_data)
21462306a36Sopenharmony_ci		coherency_len = 0;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	cache = fscache_lookup_cache(cache_name, false);
21762306a36Sopenharmony_ci	if (IS_ERR(cache))
21862306a36Sopenharmony_ci		return NULL;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	volume = kzalloc(struct_size(volume, coherency, coherency_len),
22162306a36Sopenharmony_ci			 GFP_KERNEL);
22262306a36Sopenharmony_ci	if (!volume)
22362306a36Sopenharmony_ci		goto err_cache;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	volume->cache = cache;
22662306a36Sopenharmony_ci	volume->coherency_len = coherency_len;
22762306a36Sopenharmony_ci	if (coherency_data)
22862306a36Sopenharmony_ci		memcpy(volume->coherency, coherency_data, coherency_len);
22962306a36Sopenharmony_ci	INIT_LIST_HEAD(&volume->proc_link);
23062306a36Sopenharmony_ci	INIT_WORK(&volume->work, fscache_create_volume_work);
23162306a36Sopenharmony_ci	refcount_set(&volume->ref, 1);
23262306a36Sopenharmony_ci	spin_lock_init(&volume->lock);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	/* Stick the length on the front of the key and pad it out to make
23562306a36Sopenharmony_ci	 * hashing easier.
23662306a36Sopenharmony_ci	 */
23762306a36Sopenharmony_ci	hlen = round_up(1 + klen + 1, sizeof(__le32));
23862306a36Sopenharmony_ci	key = kzalloc(hlen, GFP_KERNEL);
23962306a36Sopenharmony_ci	if (!key)
24062306a36Sopenharmony_ci		goto err_vol;
24162306a36Sopenharmony_ci	key[0] = klen;
24262306a36Sopenharmony_ci	memcpy(key + 1, volume_key, klen);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	volume->key = key;
24562306a36Sopenharmony_ci	volume->key_hash = fscache_hash(0, key, hlen);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	volume->debug_id = atomic_inc_return(&fscache_volume_debug_id);
24862306a36Sopenharmony_ci	down_write(&fscache_addremove_sem);
24962306a36Sopenharmony_ci	atomic_inc(&cache->n_volumes);
25062306a36Sopenharmony_ci	list_add_tail(&volume->proc_link, &fscache_volumes);
25162306a36Sopenharmony_ci	fscache_see_volume(volume, fscache_volume_new_acquire);
25262306a36Sopenharmony_ci	fscache_stat(&fscache_n_volumes);
25362306a36Sopenharmony_ci	up_write(&fscache_addremove_sem);
25462306a36Sopenharmony_ci	_leave(" = v=%x", volume->debug_id);
25562306a36Sopenharmony_ci	return volume;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cierr_vol:
25862306a36Sopenharmony_ci	kfree(volume);
25962306a36Sopenharmony_cierr_cache:
26062306a36Sopenharmony_ci	fscache_put_cache(cache, fscache_cache_put_alloc_volume);
26162306a36Sopenharmony_ci	fscache_stat(&fscache_n_volumes_nomem);
26262306a36Sopenharmony_ci	return NULL;
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci/*
26662306a36Sopenharmony_ci * Create a volume's representation on disk.  Have a volume ref and a cache
26762306a36Sopenharmony_ci * access we have to release.
26862306a36Sopenharmony_ci */
26962306a36Sopenharmony_cistatic void fscache_create_volume_work(struct work_struct *work)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	const struct fscache_cache_ops *ops;
27262306a36Sopenharmony_ci	struct fscache_volume *volume =
27362306a36Sopenharmony_ci		container_of(work, struct fscache_volume, work);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	fscache_see_volume(volume, fscache_volume_see_create_work);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	ops = volume->cache->ops;
27862306a36Sopenharmony_ci	if (ops->acquire_volume)
27962306a36Sopenharmony_ci		ops->acquire_volume(volume);
28062306a36Sopenharmony_ci	fscache_end_cache_access(volume->cache,
28162306a36Sopenharmony_ci				 fscache_access_acquire_volume_end);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	clear_and_wake_up_bit(FSCACHE_VOLUME_CREATING, &volume->flags);
28462306a36Sopenharmony_ci	fscache_put_volume(volume, fscache_volume_put_create_work);
28562306a36Sopenharmony_ci}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci/*
28862306a36Sopenharmony_ci * Dispatch a worker thread to create a volume's representation on disk.
28962306a36Sopenharmony_ci */
29062306a36Sopenharmony_civoid fscache_create_volume(struct fscache_volume *volume, bool wait)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	if (test_and_set_bit(FSCACHE_VOLUME_CREATING, &volume->flags))
29362306a36Sopenharmony_ci		goto maybe_wait;
29462306a36Sopenharmony_ci	if (volume->cache_priv)
29562306a36Sopenharmony_ci		goto no_wait; /* We raced */
29662306a36Sopenharmony_ci	if (!fscache_begin_cache_access(volume->cache,
29762306a36Sopenharmony_ci					fscache_access_acquire_volume))
29862306a36Sopenharmony_ci		goto no_wait;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	fscache_get_volume(volume, fscache_volume_get_create_work);
30162306a36Sopenharmony_ci	if (!schedule_work(&volume->work))
30262306a36Sopenharmony_ci		fscache_put_volume(volume, fscache_volume_put_create_work);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cimaybe_wait:
30562306a36Sopenharmony_ci	if (wait) {
30662306a36Sopenharmony_ci		fscache_see_volume(volume, fscache_volume_wait_create_work);
30762306a36Sopenharmony_ci		wait_on_bit(&volume->flags, FSCACHE_VOLUME_CREATING,
30862306a36Sopenharmony_ci			    TASK_UNINTERRUPTIBLE);
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci	return;
31162306a36Sopenharmony_cino_wait:
31262306a36Sopenharmony_ci	clear_bit_unlock(FSCACHE_VOLUME_CREATING, &volume->flags);
31362306a36Sopenharmony_ci	wake_up_bit(&volume->flags, FSCACHE_VOLUME_CREATING);
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci/*
31762306a36Sopenharmony_ci * Acquire a volume representation cookie and link it to a (proposed) cache.
31862306a36Sopenharmony_ci */
31962306a36Sopenharmony_cistruct fscache_volume *__fscache_acquire_volume(const char *volume_key,
32062306a36Sopenharmony_ci						const char *cache_name,
32162306a36Sopenharmony_ci						const void *coherency_data,
32262306a36Sopenharmony_ci						size_t coherency_len)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	struct fscache_volume *volume;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	volume = fscache_alloc_volume(volume_key, cache_name,
32762306a36Sopenharmony_ci				      coherency_data, coherency_len);
32862306a36Sopenharmony_ci	if (!volume)
32962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	if (!fscache_hash_volume(volume)) {
33262306a36Sopenharmony_ci		fscache_put_volume(volume, fscache_volume_put_hash_collision);
33362306a36Sopenharmony_ci		return ERR_PTR(-EBUSY);
33462306a36Sopenharmony_ci	}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	fscache_create_volume(volume, false);
33762306a36Sopenharmony_ci	return volume;
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_acquire_volume);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic void fscache_wake_pending_volume(struct fscache_volume *volume,
34262306a36Sopenharmony_ci					struct hlist_bl_head *h)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct fscache_volume *cursor;
34562306a36Sopenharmony_ci	struct hlist_bl_node *p;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	hlist_bl_for_each_entry(cursor, p, h, hash_link) {
34862306a36Sopenharmony_ci		if (fscache_volume_same(cursor, volume)) {
34962306a36Sopenharmony_ci			fscache_see_volume(cursor, fscache_volume_see_hash_wake);
35062306a36Sopenharmony_ci			clear_and_wake_up_bit(FSCACHE_VOLUME_ACQUIRE_PENDING,
35162306a36Sopenharmony_ci					      &cursor->flags);
35262306a36Sopenharmony_ci			return;
35362306a36Sopenharmony_ci		}
35462306a36Sopenharmony_ci	}
35562306a36Sopenharmony_ci}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci/*
35862306a36Sopenharmony_ci * Remove a volume cookie from the hash table.
35962306a36Sopenharmony_ci */
36062306a36Sopenharmony_cistatic void fscache_unhash_volume(struct fscache_volume *volume)
36162306a36Sopenharmony_ci{
36262306a36Sopenharmony_ci	struct hlist_bl_head *h;
36362306a36Sopenharmony_ci	unsigned int bucket;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	bucket = volume->key_hash & (ARRAY_SIZE(fscache_volume_hash) - 1);
36662306a36Sopenharmony_ci	h = &fscache_volume_hash[bucket];
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	hlist_bl_lock(h);
36962306a36Sopenharmony_ci	hlist_bl_del(&volume->hash_link);
37062306a36Sopenharmony_ci	if (test_bit(FSCACHE_VOLUME_COLLIDED_WITH, &volume->flags))
37162306a36Sopenharmony_ci		fscache_wake_pending_volume(volume, h);
37262306a36Sopenharmony_ci	hlist_bl_unlock(h);
37362306a36Sopenharmony_ci}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci/*
37662306a36Sopenharmony_ci * Drop a cache's volume attachments.
37762306a36Sopenharmony_ci */
37862306a36Sopenharmony_cistatic void fscache_free_volume(struct fscache_volume *volume)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	struct fscache_cache *cache = volume->cache;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	if (volume->cache_priv) {
38362306a36Sopenharmony_ci		__fscache_begin_volume_access(volume, NULL,
38462306a36Sopenharmony_ci					      fscache_access_relinquish_volume);
38562306a36Sopenharmony_ci		if (volume->cache_priv)
38662306a36Sopenharmony_ci			cache->ops->free_volume(volume);
38762306a36Sopenharmony_ci		fscache_end_volume_access(volume, NULL,
38862306a36Sopenharmony_ci					  fscache_access_relinquish_volume_end);
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	down_write(&fscache_addremove_sem);
39262306a36Sopenharmony_ci	list_del_init(&volume->proc_link);
39362306a36Sopenharmony_ci	atomic_dec(&volume->cache->n_volumes);
39462306a36Sopenharmony_ci	up_write(&fscache_addremove_sem);
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	if (!hlist_bl_unhashed(&volume->hash_link))
39762306a36Sopenharmony_ci		fscache_unhash_volume(volume);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	trace_fscache_volume(volume->debug_id, 0, fscache_volume_free);
40062306a36Sopenharmony_ci	kfree(volume->key);
40162306a36Sopenharmony_ci	kfree(volume);
40262306a36Sopenharmony_ci	fscache_stat_d(&fscache_n_volumes);
40362306a36Sopenharmony_ci	fscache_put_cache(cache, fscache_cache_put_volume);
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci/*
40762306a36Sopenharmony_ci * Drop a reference to a volume cookie.
40862306a36Sopenharmony_ci */
40962306a36Sopenharmony_civoid fscache_put_volume(struct fscache_volume *volume,
41062306a36Sopenharmony_ci			enum fscache_volume_trace where)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	if (volume) {
41362306a36Sopenharmony_ci		unsigned int debug_id = volume->debug_id;
41462306a36Sopenharmony_ci		bool zero;
41562306a36Sopenharmony_ci		int ref;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci		zero = __refcount_dec_and_test(&volume->ref, &ref);
41862306a36Sopenharmony_ci		trace_fscache_volume(debug_id, ref - 1, where);
41962306a36Sopenharmony_ci		if (zero)
42062306a36Sopenharmony_ci			fscache_free_volume(volume);
42162306a36Sopenharmony_ci	}
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci/*
42562306a36Sopenharmony_ci * Relinquish a volume representation cookie.
42662306a36Sopenharmony_ci */
42762306a36Sopenharmony_civoid __fscache_relinquish_volume(struct fscache_volume *volume,
42862306a36Sopenharmony_ci				 const void *coherency_data,
42962306a36Sopenharmony_ci				 bool invalidate)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	if (WARN_ON(test_and_set_bit(FSCACHE_VOLUME_RELINQUISHED, &volume->flags)))
43262306a36Sopenharmony_ci		return;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	if (invalidate) {
43562306a36Sopenharmony_ci		set_bit(FSCACHE_VOLUME_INVALIDATE, &volume->flags);
43662306a36Sopenharmony_ci	} else if (coherency_data) {
43762306a36Sopenharmony_ci		memcpy(volume->coherency, coherency_data, volume->coherency_len);
43862306a36Sopenharmony_ci	}
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	fscache_put_volume(volume, fscache_volume_put_relinquish);
44162306a36Sopenharmony_ci}
44262306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_relinquish_volume);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci/**
44562306a36Sopenharmony_ci * fscache_withdraw_volume - Withdraw a volume from being cached
44662306a36Sopenharmony_ci * @volume: Volume cookie
44762306a36Sopenharmony_ci *
44862306a36Sopenharmony_ci * Withdraw a cache volume from service, waiting for all accesses to complete
44962306a36Sopenharmony_ci * before returning.
45062306a36Sopenharmony_ci */
45162306a36Sopenharmony_civoid fscache_withdraw_volume(struct fscache_volume *volume)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	int n_accesses;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	_debug("withdraw V=%x", volume->debug_id);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	/* Allow wakeups on dec-to-0 */
45862306a36Sopenharmony_ci	n_accesses = atomic_dec_return(&volume->n_accesses);
45962306a36Sopenharmony_ci	trace_fscache_access_volume(volume->debug_id, 0,
46062306a36Sopenharmony_ci				    refcount_read(&volume->ref),
46162306a36Sopenharmony_ci				    n_accesses, fscache_access_cache_unpin);
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	wait_var_event(&volume->n_accesses,
46462306a36Sopenharmony_ci		       atomic_read(&volume->n_accesses) == 0);
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ciEXPORT_SYMBOL(fscache_withdraw_volume);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci#ifdef CONFIG_PROC_FS
46962306a36Sopenharmony_ci/*
47062306a36Sopenharmony_ci * Generate a list of volumes in /proc/fs/fscache/volumes
47162306a36Sopenharmony_ci */
47262306a36Sopenharmony_cistatic int fscache_volumes_seq_show(struct seq_file *m, void *v)
47362306a36Sopenharmony_ci{
47462306a36Sopenharmony_ci	struct fscache_volume *volume;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	if (v == &fscache_volumes) {
47762306a36Sopenharmony_ci		seq_puts(m,
47862306a36Sopenharmony_ci			 "VOLUME   REF   nCOOK ACC FL CACHE           KEY\n"
47962306a36Sopenharmony_ci			 "======== ===== ===== === == =============== ================\n");
48062306a36Sopenharmony_ci		return 0;
48162306a36Sopenharmony_ci	}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	volume = list_entry(v, struct fscache_volume, proc_link);
48462306a36Sopenharmony_ci	seq_printf(m,
48562306a36Sopenharmony_ci		   "%08x %5d %5d %3d %02lx %-15.15s %s\n",
48662306a36Sopenharmony_ci		   volume->debug_id,
48762306a36Sopenharmony_ci		   refcount_read(&volume->ref),
48862306a36Sopenharmony_ci		   atomic_read(&volume->n_cookies),
48962306a36Sopenharmony_ci		   atomic_read(&volume->n_accesses),
49062306a36Sopenharmony_ci		   volume->flags,
49162306a36Sopenharmony_ci		   volume->cache->name ?: "-",
49262306a36Sopenharmony_ci		   volume->key + 1);
49362306a36Sopenharmony_ci	return 0;
49462306a36Sopenharmony_ci}
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_cistatic void *fscache_volumes_seq_start(struct seq_file *m, loff_t *_pos)
49762306a36Sopenharmony_ci	__acquires(&fscache_addremove_sem)
49862306a36Sopenharmony_ci{
49962306a36Sopenharmony_ci	down_read(&fscache_addremove_sem);
50062306a36Sopenharmony_ci	return seq_list_start_head(&fscache_volumes, *_pos);
50162306a36Sopenharmony_ci}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_cistatic void *fscache_volumes_seq_next(struct seq_file *m, void *v, loff_t *_pos)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci	return seq_list_next(v, &fscache_volumes, _pos);
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic void fscache_volumes_seq_stop(struct seq_file *m, void *v)
50962306a36Sopenharmony_ci	__releases(&fscache_addremove_sem)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	up_read(&fscache_addremove_sem);
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ciconst struct seq_operations fscache_volumes_seq_ops = {
51562306a36Sopenharmony_ci	.start  = fscache_volumes_seq_start,
51662306a36Sopenharmony_ci	.next   = fscache_volumes_seq_next,
51762306a36Sopenharmony_ci	.stop   = fscache_volumes_seq_stop,
51862306a36Sopenharmony_ci	.show   = fscache_volumes_seq_show,
51962306a36Sopenharmony_ci};
52062306a36Sopenharmony_ci#endif /* CONFIG_PROC_FS */
521