// SPDX-License-Identifier: GPL-2.0 #include "bcachefs.h" #include "buckets_waiting_for_journal.h" #include #include static inline struct bucket_hashed * bucket_hash(struct buckets_waiting_for_journal_table *t, unsigned hash_seed_idx, u64 dev_bucket) { return t->d + hash_64(dev_bucket ^ t->hash_seeds[hash_seed_idx], t->bits); } static void bucket_table_init(struct buckets_waiting_for_journal_table *t, size_t bits) { unsigned i; t->bits = bits; for (i = 0; i < ARRAY_SIZE(t->hash_seeds); i++) get_random_bytes(&t->hash_seeds[i], sizeof(t->hash_seeds[i])); memset(t->d, 0, sizeof(t->d[0]) << t->bits); } bool bch2_bucket_needs_journal_commit(struct buckets_waiting_for_journal *b, u64 flushed_seq, unsigned dev, u64 bucket) { struct buckets_waiting_for_journal_table *t; u64 dev_bucket = (u64) dev << 56 | bucket; bool ret = false; unsigned i; mutex_lock(&b->lock); t = b->t; for (i = 0; i < ARRAY_SIZE(t->hash_seeds); i++) { struct bucket_hashed *h = bucket_hash(t, i, dev_bucket); if (h->dev_bucket == dev_bucket) { ret = h->journal_seq > flushed_seq; break; } } mutex_unlock(&b->lock); return ret; } static bool bucket_table_insert(struct buckets_waiting_for_journal_table *t, struct bucket_hashed *new, u64 flushed_seq) { struct bucket_hashed *last_evicted = NULL; unsigned tries, i; for (tries = 0; tries < 10; tries++) { struct bucket_hashed *old, *victim = NULL; for (i = 0; i < ARRAY_SIZE(t->hash_seeds); i++) { old = bucket_hash(t, i, new->dev_bucket); if (old->dev_bucket == new->dev_bucket || old->journal_seq <= flushed_seq) { *old = *new; return true; } if (last_evicted != old) victim = old; } /* hashed to same slot 3 times: */ if (!victim) break; /* Failed to find an empty slot: */ swap(*new, *victim); last_evicted = victim; } return false; } int bch2_set_bucket_needs_journal_commit(struct buckets_waiting_for_journal *b, u64 flushed_seq, unsigned dev, u64 bucket, u64 journal_seq) { struct buckets_waiting_for_journal_table *t, *n; struct bucket_hashed tmp, new = { .dev_bucket = (u64) dev << 56 | bucket, .journal_seq = journal_seq, }; size_t i, size, new_bits, nr_elements = 1, nr_rehashes = 0; int ret = 0; mutex_lock(&b->lock); if (likely(bucket_table_insert(b->t, &new, flushed_seq))) goto out; t = b->t; size = 1UL << t->bits; for (i = 0; i < size; i++) nr_elements += t->d[i].journal_seq > flushed_seq; new_bits = t->bits + (nr_elements * 3 > size); n = kvmalloc(sizeof(*n) + (sizeof(n->d[0]) << new_bits), GFP_KERNEL); if (!n) { ret = -BCH_ERR_ENOMEM_buckets_waiting_for_journal_set; goto out; } retry_rehash: nr_rehashes++; bucket_table_init(n, new_bits); tmp = new; BUG_ON(!bucket_table_insert(n, &tmp, flushed_seq)); for (i = 0; i < 1UL << t->bits; i++) { if (t->d[i].journal_seq <= flushed_seq) continue; tmp = t->d[i]; if (!bucket_table_insert(n, &tmp, flushed_seq)) goto retry_rehash; } b->t = n; kvfree(t); pr_debug("took %zu rehashes, table at %zu/%lu elements", nr_rehashes, nr_elements, 1UL << b->t->bits); out: mutex_unlock(&b->lock); return ret; } void bch2_fs_buckets_waiting_for_journal_exit(struct bch_fs *c) { struct buckets_waiting_for_journal *b = &c->buckets_waiting_for_journal; kvfree(b->t); } #define INITIAL_TABLE_BITS 3 int bch2_fs_buckets_waiting_for_journal_init(struct bch_fs *c) { struct buckets_waiting_for_journal *b = &c->buckets_waiting_for_journal; mutex_init(&b->lock); b->t = kvmalloc(sizeof(*b->t) + (sizeof(b->t->d[0]) << INITIAL_TABLE_BITS), GFP_KERNEL); if (!b->t) return -BCH_ERR_ENOMEM_buckets_waiting_for_journal_init; bucket_table_init(b->t, INITIAL_TABLE_BITS); return 0; }