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