| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KUnit helpers for backtrace suppression |
| * |
| * Copyright (C) 2025 Alessandro Carminati <acarmina@redhat.com> |
| * Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net> |
| */ |
| |
| #include <kunit/resource.h> |
| #include <linux/export.h> |
| #include <linux/rculist.h> |
| #include <linux/sched.h> |
| #include <linux/sched/task.h> |
| #include <linux/spinlock.h> |
| |
| #include "hooks-impl.h" |
| |
| struct kunit_suppressed_warning { |
| struct list_head node; |
| struct task_struct *task; |
| struct kunit *test; |
| atomic_t counter; |
| }; |
| |
| static LIST_HEAD(suppressed_warnings); |
| static DEFINE_SPINLOCK(suppressed_warnings_lock); |
| |
| static void kunit_suppress_warning_remove(struct kunit_suppressed_warning *w) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&suppressed_warnings_lock, flags); |
| list_del_rcu(&w->node); |
| spin_unlock_irqrestore(&suppressed_warnings_lock, flags); |
| put_task_struct(w->task); |
| } |
| |
| KUNIT_DEFINE_ACTION_WRAPPER(kunit_suppress_warning_cleanup, |
| kunit_suppress_warning_remove, |
| struct kunit_suppressed_warning *); |
| |
| bool kunit_has_active_suppress_warning(void) |
| { |
| return __kunit_is_suppressed_warning_impl(false); |
| } |
| EXPORT_SYMBOL_GPL(kunit_has_active_suppress_warning); |
| |
| struct kunit_suppressed_warning * |
| kunit_start_suppress_warning(struct kunit *test) |
| { |
| struct kunit_suppressed_warning *w; |
| unsigned long flags; |
| int ret; |
| |
| if (kunit_has_active_suppress_warning()) { |
| KUNIT_FAIL(test, "Another suppression block is already active"); |
| return NULL; |
| } |
| |
| w = kunit_kzalloc(test, sizeof(*w), GFP_KERNEL); |
| if (!w) { |
| KUNIT_FAIL(test, "Failed to allocate suppression handle."); |
| return NULL; |
| } |
| |
| w->task = get_task_struct(current); |
| w->test = test; |
| |
| spin_lock_irqsave(&suppressed_warnings_lock, flags); |
| list_add_rcu(&w->node, &suppressed_warnings); |
| spin_unlock_irqrestore(&suppressed_warnings_lock, flags); |
| |
| ret = kunit_add_action_or_reset(test, |
| kunit_suppress_warning_cleanup, w); |
| if (ret) { |
| KUNIT_FAIL(test, "Failed to add suppression cleanup action."); |
| return NULL; |
| } |
| |
| return w; |
| } |
| EXPORT_SYMBOL_GPL(kunit_start_suppress_warning); |
| |
| void kunit_end_suppress_warning(struct kunit *test, |
| struct kunit_suppressed_warning *w) |
| { |
| if (!w) |
| return; |
| kunit_release_action(test, kunit_suppress_warning_cleanup, w); |
| } |
| EXPORT_SYMBOL_GPL(kunit_end_suppress_warning); |
| |
| void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp) |
| { |
| if (*wp) |
| kunit_end_suppress_warning((*wp)->test, *wp); |
| } |
| EXPORT_SYMBOL_GPL(__kunit_suppress_auto_cleanup); |
| |
| int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w) |
| { |
| return w ? atomic_read(&w->counter) : 0; |
| } |
| EXPORT_SYMBOL_GPL(kunit_suppressed_warning_count); |
| |
| bool __kunit_is_suppressed_warning_impl(bool count) |
| { |
| struct kunit_suppressed_warning *w; |
| |
| guard(rcu)(); |
| list_for_each_entry_rcu(w, &suppressed_warnings, node) { |
| if (w->task == current) { |
| if (count) |
| atomic_inc(&w->counter); |
| return true; |
| } |
| } |
| |
| return false; |
| } |