|  | // SPDX-License-Identifier: GPL-2.0 | 
|  |  | 
|  | #include "bcachefs.h" | 
|  | #include "enumerated_ref.h" | 
|  | #include "util.h" | 
|  |  | 
|  | #include <linux/completion.h> | 
|  |  | 
|  | #ifdef ENUMERATED_REF_DEBUG | 
|  | void enumerated_ref_get(struct enumerated_ref *ref, unsigned idx) | 
|  | { | 
|  | BUG_ON(idx >= ref->nr); | 
|  | atomic_long_inc(&ref->refs[idx]); | 
|  | } | 
|  |  | 
|  | bool __enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx) | 
|  | { | 
|  | BUG_ON(idx >= ref->nr); | 
|  | return atomic_long_inc_not_zero(&ref->refs[idx]); | 
|  | } | 
|  |  | 
|  | bool enumerated_ref_tryget(struct enumerated_ref *ref, unsigned idx) | 
|  | { | 
|  | BUG_ON(idx >= ref->nr); | 
|  | return !ref->dying && | 
|  | atomic_long_inc_not_zero(&ref->refs[idx]); | 
|  | } | 
|  |  | 
|  | void enumerated_ref_put(struct enumerated_ref *ref, unsigned idx) | 
|  | { | 
|  | BUG_ON(idx >= ref->nr); | 
|  | long v = atomic_long_dec_return(&ref->refs[idx]); | 
|  |  | 
|  | BUG_ON(v < 0); | 
|  | if (v) | 
|  | return; | 
|  |  | 
|  | for (unsigned i = 0; i < ref->nr; i++) | 
|  | if (atomic_long_read(&ref->refs[i])) | 
|  | return; | 
|  |  | 
|  | if (ref->stop_fn) | 
|  | ref->stop_fn(ref); | 
|  | complete(&ref->stop_complete); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | #ifndef ENUMERATED_REF_DEBUG | 
|  | static void enumerated_ref_kill_cb(struct percpu_ref *percpu_ref) | 
|  | { | 
|  | struct enumerated_ref *ref = | 
|  | container_of(percpu_ref, struct enumerated_ref, ref); | 
|  |  | 
|  | if (ref->stop_fn) | 
|  | ref->stop_fn(ref); | 
|  | complete(&ref->stop_complete); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void enumerated_ref_stop_async(struct enumerated_ref *ref) | 
|  | { | 
|  | reinit_completion(&ref->stop_complete); | 
|  |  | 
|  | #ifndef ENUMERATED_REF_DEBUG | 
|  | percpu_ref_kill(&ref->ref); | 
|  | #else | 
|  | ref->dying = true; | 
|  | for (unsigned i = 0; i < ref->nr; i++) | 
|  | enumerated_ref_put(ref, i); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void enumerated_ref_stop(struct enumerated_ref *ref, | 
|  | const char * const names[]) | 
|  | { | 
|  | enumerated_ref_stop_async(ref); | 
|  | while (!wait_for_completion_timeout(&ref->stop_complete, HZ * 10)) { | 
|  | struct printbuf buf = PRINTBUF; | 
|  |  | 
|  | prt_str(&buf, "Waited for 10 seconds to shutdown enumerated ref\n"); | 
|  | prt_str(&buf, "Outstanding refs:\n"); | 
|  | enumerated_ref_to_text(&buf, ref, names); | 
|  | printk(KERN_ERR "%s", buf.buf); | 
|  | printbuf_exit(&buf); | 
|  | } | 
|  | } | 
|  |  | 
|  | void enumerated_ref_start(struct enumerated_ref *ref) | 
|  | { | 
|  | #ifndef ENUMERATED_REF_DEBUG | 
|  | percpu_ref_reinit(&ref->ref); | 
|  | #else | 
|  | ref->dying = false; | 
|  | for (unsigned i = 0; i < ref->nr; i++) { | 
|  | BUG_ON(atomic_long_read(&ref->refs[i])); | 
|  | atomic_long_inc(&ref->refs[i]); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void enumerated_ref_exit(struct enumerated_ref *ref) | 
|  | { | 
|  | #ifndef ENUMERATED_REF_DEBUG | 
|  | percpu_ref_exit(&ref->ref); | 
|  | #else | 
|  | kfree(ref->refs); | 
|  | ref->refs = NULL; | 
|  | ref->nr = 0; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | int enumerated_ref_init(struct enumerated_ref *ref, unsigned nr, | 
|  | void (*stop_fn)(struct enumerated_ref *)) | 
|  | { | 
|  | init_completion(&ref->stop_complete); | 
|  | ref->stop_fn = stop_fn; | 
|  |  | 
|  | #ifndef ENUMERATED_REF_DEBUG | 
|  | return percpu_ref_init(&ref->ref, enumerated_ref_kill_cb, | 
|  | PERCPU_REF_INIT_DEAD, GFP_KERNEL); | 
|  | #else | 
|  | ref->refs = kzalloc(sizeof(ref->refs[0]) * nr, GFP_KERNEL); | 
|  | if (!ref->refs) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ref->nr = nr; | 
|  | return 0; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void enumerated_ref_to_text(struct printbuf *out, | 
|  | struct enumerated_ref *ref, | 
|  | const char * const names[]) | 
|  | { | 
|  | #ifdef ENUMERATED_REF_DEBUG | 
|  | bch2_printbuf_tabstop_push(out, 32); | 
|  |  | 
|  | for (unsigned i = 0; i < ref->nr; i++) | 
|  | prt_printf(out, "%s\t%li\n", names[i], | 
|  | atomic_long_read(&ref->refs[i])); | 
|  | #else | 
|  | prt_str(out, "(not in debug mode)\n"); | 
|  | #endif | 
|  | } |