14e66934eSEric Dumazet // SPDX-License-Identifier: GPL-2.0-or-later 24e66934eSEric Dumazet #include <linux/export.h> 34e66934eSEric Dumazet #include <linux/ref_tracker.h> 44e66934eSEric Dumazet #include <linux/slab.h> 54e66934eSEric Dumazet #include <linux/stacktrace.h> 64e66934eSEric Dumazet #include <linux/stackdepot.h> 74e66934eSEric Dumazet 84e66934eSEric Dumazet #define REF_TRACKER_STACK_ENTRIES 16 94e66934eSEric Dumazet 104e66934eSEric Dumazet struct ref_tracker { 114e66934eSEric Dumazet struct list_head head; /* anchor into dir->list or dir->quarantine */ 124e66934eSEric Dumazet bool dead; 134e66934eSEric Dumazet depot_stack_handle_t alloc_stack_handle; 144e66934eSEric Dumazet depot_stack_handle_t free_stack_handle; 154e66934eSEric Dumazet }; 164e66934eSEric Dumazet 174e66934eSEric Dumazet void ref_tracker_dir_exit(struct ref_tracker_dir *dir) 184e66934eSEric Dumazet { 194e66934eSEric Dumazet struct ref_tracker *tracker, *n; 204e66934eSEric Dumazet unsigned long flags; 214e66934eSEric Dumazet bool leak = false; 224e66934eSEric Dumazet 234e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 244e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->quarantine, head) { 254e66934eSEric Dumazet list_del(&tracker->head); 264e66934eSEric Dumazet kfree(tracker); 274e66934eSEric Dumazet dir->quarantine_avail++; 284e66934eSEric Dumazet } 294e66934eSEric Dumazet list_for_each_entry_safe(tracker, n, &dir->list, head) { 304e66934eSEric Dumazet pr_err("leaked reference.\n"); 314e66934eSEric Dumazet if (tracker->alloc_stack_handle) 324e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 334e66934eSEric Dumazet leak = true; 344e66934eSEric Dumazet list_del(&tracker->head); 354e66934eSEric Dumazet kfree(tracker); 364e66934eSEric Dumazet } 374e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 384e66934eSEric Dumazet WARN_ON_ONCE(leak); 394e66934eSEric Dumazet WARN_ON_ONCE(refcount_read(&dir->untracked) != 1); 404e66934eSEric Dumazet } 414e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_exit); 424e66934eSEric Dumazet 434e66934eSEric Dumazet void ref_tracker_dir_print(struct ref_tracker_dir *dir, 444e66934eSEric Dumazet unsigned int display_limit) 454e66934eSEric Dumazet { 464e66934eSEric Dumazet struct ref_tracker *tracker; 474e66934eSEric Dumazet unsigned long flags; 484e66934eSEric Dumazet unsigned int i = 0; 494e66934eSEric Dumazet 504e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 514e66934eSEric Dumazet list_for_each_entry(tracker, &dir->list, head) { 524e66934eSEric Dumazet if (i < display_limit) { 534e66934eSEric Dumazet pr_err("leaked reference.\n"); 544e66934eSEric Dumazet if (tracker->alloc_stack_handle) 554e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 564e66934eSEric Dumazet i++; 574e66934eSEric Dumazet } else { 584e66934eSEric Dumazet break; 594e66934eSEric Dumazet } 604e66934eSEric Dumazet } 614e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 624e66934eSEric Dumazet } 634e66934eSEric Dumazet EXPORT_SYMBOL(ref_tracker_dir_print); 644e66934eSEric Dumazet 654e66934eSEric Dumazet int ref_tracker_alloc(struct ref_tracker_dir *dir, 664e66934eSEric Dumazet struct ref_tracker **trackerp, 674e66934eSEric Dumazet gfp_t gfp) 684e66934eSEric Dumazet { 694e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 704e66934eSEric Dumazet struct ref_tracker *tracker; 714e66934eSEric Dumazet unsigned int nr_entries; 72*c12837d1SEric Dumazet gfp_t gfp_mask = gfp; 734e66934eSEric Dumazet unsigned long flags; 744e66934eSEric Dumazet 75*c12837d1SEric Dumazet if (gfp & __GFP_DIRECT_RECLAIM) 76*c12837d1SEric Dumazet gfp_mask |= __GFP_NOFAIL; 77*c12837d1SEric Dumazet *trackerp = tracker = kzalloc(sizeof(*tracker), gfp_mask); 784e66934eSEric Dumazet if (unlikely(!tracker)) { 794e66934eSEric Dumazet pr_err_once("memory allocation failure, unreliable refcount tracker.\n"); 804e66934eSEric Dumazet refcount_inc(&dir->untracked); 814e66934eSEric Dumazet return -ENOMEM; 824e66934eSEric Dumazet } 834e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 844e66934eSEric Dumazet nr_entries = filter_irq_stacks(entries, nr_entries); 854e66934eSEric Dumazet tracker->alloc_stack_handle = stack_depot_save(entries, nr_entries, gfp); 864e66934eSEric Dumazet 874e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 884e66934eSEric Dumazet list_add(&tracker->head, &dir->list); 894e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 904e66934eSEric Dumazet return 0; 914e66934eSEric Dumazet } 924e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_alloc); 934e66934eSEric Dumazet 944e66934eSEric Dumazet int ref_tracker_free(struct ref_tracker_dir *dir, 954e66934eSEric Dumazet struct ref_tracker **trackerp) 964e66934eSEric Dumazet { 974e66934eSEric Dumazet unsigned long entries[REF_TRACKER_STACK_ENTRIES]; 984e66934eSEric Dumazet struct ref_tracker *tracker = *trackerp; 994e66934eSEric Dumazet depot_stack_handle_t stack_handle; 1004e66934eSEric Dumazet unsigned int nr_entries; 1014e66934eSEric Dumazet unsigned long flags; 1024e66934eSEric Dumazet 1034e66934eSEric Dumazet if (!tracker) { 1044e66934eSEric Dumazet refcount_dec(&dir->untracked); 1054e66934eSEric Dumazet return -EEXIST; 1064e66934eSEric Dumazet } 1074e66934eSEric Dumazet nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1); 1084e66934eSEric Dumazet nr_entries = filter_irq_stacks(entries, nr_entries); 1094e66934eSEric Dumazet stack_handle = stack_depot_save(entries, nr_entries, GFP_ATOMIC); 1104e66934eSEric Dumazet 1114e66934eSEric Dumazet spin_lock_irqsave(&dir->lock, flags); 1124e66934eSEric Dumazet if (tracker->dead) { 1134e66934eSEric Dumazet pr_err("reference already released.\n"); 1144e66934eSEric Dumazet if (tracker->alloc_stack_handle) { 1154e66934eSEric Dumazet pr_err("allocated in:\n"); 1164e66934eSEric Dumazet stack_depot_print(tracker->alloc_stack_handle); 1174e66934eSEric Dumazet } 1184e66934eSEric Dumazet if (tracker->free_stack_handle) { 1194e66934eSEric Dumazet pr_err("freed in:\n"); 1204e66934eSEric Dumazet stack_depot_print(tracker->free_stack_handle); 1214e66934eSEric Dumazet } 1224e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1234e66934eSEric Dumazet WARN_ON_ONCE(1); 1244e66934eSEric Dumazet return -EINVAL; 1254e66934eSEric Dumazet } 1264e66934eSEric Dumazet tracker->dead = true; 1274e66934eSEric Dumazet 1284e66934eSEric Dumazet tracker->free_stack_handle = stack_handle; 1294e66934eSEric Dumazet 1304e66934eSEric Dumazet list_move_tail(&tracker->head, &dir->quarantine); 1314e66934eSEric Dumazet if (!dir->quarantine_avail) { 1324e66934eSEric Dumazet tracker = list_first_entry(&dir->quarantine, struct ref_tracker, head); 1334e66934eSEric Dumazet list_del(&tracker->head); 1344e66934eSEric Dumazet } else { 1354e66934eSEric Dumazet dir->quarantine_avail--; 1364e66934eSEric Dumazet tracker = NULL; 1374e66934eSEric Dumazet } 1384e66934eSEric Dumazet spin_unlock_irqrestore(&dir->lock, flags); 1394e66934eSEric Dumazet 1404e66934eSEric Dumazet kfree(tracker); 1414e66934eSEric Dumazet return 0; 1424e66934eSEric Dumazet } 1434e66934eSEric Dumazet EXPORT_SYMBOL_GPL(ref_tracker_free); 144