On Wed, May 6, 2026 at 11:40 AM David Gow <[email protected]> wrote:
>
> Le 04/05/2026 à 3:41 PM, Albert Esteve a écrit :
> > From: Alessandro Carminati <[email protected]>
> >
> > Some unit tests intentionally trigger warning backtraces by passing bad
> > parameters to kernel API functions. Such unit tests typically check the
> > return value from such calls, not the existence of the warning backtrace.
> >
> > Such intentionally generated warning backtraces are neither desirable
> > nor useful for a number of reasons:
> > - They can result in overlooked real problems.
> > - A warning that suddenly starts to show up in unit tests needs to be
> > investigated and has to be marked to be ignored, for example by
> > adjusting filter scripts. Such filters are ad hoc because there is
> > no real standard format for warnings. On top of that, such filter
> > scripts would require constant maintenance.
> >
> > Solve the problem by providing a means to suppress warning backtraces
> > originating from the current kthread while executing test code. Since
> > each KUnit test runs in its own kthread, this effectively scopes
> > suppression to the test that enabled it. Limit changes to generic code
> > to the absolute minimum.
> >
> > Implementation details:
> > Suppression is integrated into the existing KUnit hooks infrastructure
> > in test-bug.h, reusing the kunit_running static branch for zero
> > overhead when no tests are running.
> >
> > Suppression is checked at three points in the warning path:
> > - In warn_slowpath_fmt(), the check runs before any output, fully
> > suppressing both message and backtrace. This covers architectures
> > without __WARN_FLAGS.
> > - In __warn_printk(), the check suppresses the warning message text.
> > This covers architectures that define __WARN_FLAGS but not their own
> > __WARN_printf (arm64, loongarch, parisc, powerpc, riscv, sh), where
> > the message is printed before the trap enters __report_bug().
> > - In __report_bug(), the check runs before __warn() is called,
> > suppressing the backtrace and stack dump.
> >
> > To avoid double-counting on architectures where both __warn_printk()
> > and __report_bug() run for the same warning, kunit_is_suppressed_warning()
> > takes a bool parameter: true to increment the suppression counter
> > (used in warn_slowpath_fmt and __report_bug), false to check only
> > (used in __warn_printk).
> >
> > The suppression state is dynamically allocated via kunit_kzalloc() and
> > tied to the KUnit test lifecycle via kunit_add_action(), ensuring
> > automatic cleanup at test exit. Writer-side access to the global
> > suppression list is serialized with a spinlock; readers use RCU.
> >
> > Three API forms are provided:
> > - kunit_warning_suppress(test) { ... }: scoped, uses __cleanup for
> > automatic teardown on scope exit, kunit_add_action() as safety net
> > for abnormal exits (e.g. kthread_exit from failed assertions).
> > Suppression handle is only accessible inside the block.
> > - KUNIT_START/END_SUPPRESSED_WARNING(test): manual macros for larger
> > blocks or when warning counts need to be checked after suppression
> > ends. Limited to one pair per scope.
> > - kunit_start/end_suppress_warning(test): direct functions returning
> > an explicit handle, for retaining the handle within the test,
> > or for cross-function usage.
> >
> > Signed-off-by: Guenter Roeck <[email protected]>
> > Signed-off-by: Alessandro Carminati <[email protected]>
> > Reviewed-by: Kees Cook <[email protected]>
> > Signed-off-by: Albert Esteve <[email protected]>
> > ---
>
> This looks pretty good to me, thanks.
>
> Reviewed-by: David Gow <[email protected]>
>
> It's maybe slightly over-the-top to now have three different ways of
> enabling warning suppression: I'd probably personally get rid of
> KUNIT_START/END_SUPPRESSED_WARNING() if we had to lose one. But if
> there's a real reason to prefer keeping all three, it's not actually a
> problem to do so.
Thanks for the review!
I think the three forms earn their keep: the scoped form is the go-to
for most cases, but the macros avoid indentation without requiring
users to manage a raw pointer. I initially removed the macros and
added them back later. Direct calls to the functions will be less
frequent, used only when you need the handle.
That said, if it becomes a maintenance burden, the macros are the
easiest to drop since they're thin wrappers. Let me know if you prefer
them to be dropped, and I will send a v9 with that and the
`KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT` additions to the drm test
patch.
BR,
Albert.
>
> Regardless, this series is looking pretty ready to me. Let me know if
> you're planning a v9, otherwise we'll take this when you're ready.
>
> Cheers,
> -- David
>
> > include/kunit/test-bug.h | 25 +++++++++
> > include/kunit/test.h | 138
> > +++++++++++++++++++++++++++++++++++++++++++++++
> > kernel/panic.c | 15 +++++-
> > lib/bug.c | 10 ++++
> > lib/kunit/Makefile | 3 +-
> > lib/kunit/bug.c | 115 +++++++++++++++++++++++++++++++++++++++
> > lib/kunit/hooks-impl.h | 2 +
> > 7 files changed, 305 insertions(+), 3 deletions(-)
> >
> > diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
> > index 47aa8f21ccce8..6237e48ceadfd 100644
> > --- a/include/kunit/test-bug.h
> > +++ b/include/kunit/test-bug.h
> > @@ -23,6 +23,7 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
> > extern struct kunit_hooks_table {
> > __printf(3, 4) void (*fail_current_test)(const char*, int, const
> > char*, ...);
> > void *(*get_static_stub_address)(struct kunit *test, void
> > *real_fn_addr);
> > + bool (*is_suppressed_warning)(bool count);
> > } kunit_hooks;
> >
> > /**
> > @@ -60,9 +61,33 @@ static inline struct kunit *kunit_get_current_test(void)
> > }
> > \
> > } while (0)
> >
> > +/**
> > + * kunit_is_suppressed_warning() - Check if warnings are being suppressed
> > + * by the current KUnit test.
> > + * @count: if true, increment the suppression counter on match.
> > + *
> > + * Returns true if the current task has active warning suppression.
> > + * Uses the kunit_running static branch for zero overhead when no tests
> > run.
> > + *
> > + * A single WARN*() may traverse multiple call sites in the warning path
> > + * (e.g., __warn_printk() and __report_bug()). Pass @count = true at the
> > + * primary suppression point to count each warning exactly once, and
> > + * @count = false at secondary points to suppress output without
> > + * inflating the count.
> > + */
> > +static inline bool kunit_is_suppressed_warning(bool count)
> > +{
> > + if (!static_branch_unlikely(&kunit_running))
> > + return false;
> > +
> > + return kunit_hooks.is_suppressed_warning &&
> > + kunit_hooks.is_suppressed_warning(count);
> > +}
> > +
> > #else
> >
> > static inline struct kunit *kunit_get_current_test(void) { return NULL; }
> > +static inline bool kunit_is_suppressed_warning(bool count) { return false;
> > }
> >
> > #define kunit_fail_current_test(fmt, ...) do {} while (0)
> >
> > diff --git a/include/kunit/test.h b/include/kunit/test.h
> > index 9cd1594ab697d..f278ec028019c 100644
> > --- a/include/kunit/test.h
> > +++ b/include/kunit/test.h
> > @@ -1795,4 +1795,142 @@ do {
> > \
> > // include resource.h themselves if they need it.
> > #include <kunit/resource.h>
> >
> > +/*
> > + * Warning backtrace suppression API.
> > + *
> > + * Suppresses WARN*() backtraces on the current task while active. Three
> > forms
> > + * are provided, in order of convenience:
> > + *
> > + * - Scoped: kunit_warning_suppress(test) { ... }
> > + * Suppression is active for the duration of the block. On normal exit,
> > + * the for-loop increment deactivates suppression. On early exit (break,
> > + * return, goto), the __cleanup attribute fires. On kthread_exit() (e.g.,
> > + * a failed KUnit assertion), kunit_add_action() cleans up at test
> > + * teardown. The suppression handle is only accessible inside the block,
> > + * so warning counts must be checked before the block exits.
> > + *
> > + * - Manual macros: KUNIT_[START|END]_SUPPRESSED_WARNING(test)
> > + * Suppression spans an explicit range in the same scope.
> > kunit_add_action()
> > + * guarantees cleanup even if KUNIT_END_SUPPRESSED_WARNING() is not
> > reached.
> > + * Prefer this form when suppressing warnings across a large block where
> > + * extra indentation is undesirable, or when the warning count needs to
> > be
> > + * checked after suppression ends. Limited to one pair per scope.
> > + *
> > + * - Direct: kunit_start_suppress_warning() / kunit_end_suppress_warning()
> > + * The underlying functions, returning an explicit handle pointer. Use
> > + * when the handle needs to be retained (e.g., for post-suppression
> > + * count checks) or passed across helper functions.
> > + */
> > +struct kunit_suppressed_warning;
> > +
> > +struct kunit_suppressed_warning *
> > +kunit_start_suppress_warning(struct kunit *test);
> > +void kunit_end_suppress_warning(struct kunit *test,
> > + struct kunit_suppressed_warning *w);
> > +int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w);
> > +void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp);
> > +bool kunit_has_active_suppress_warning(void);
> > +
> > +/**
> > + * kunit_warning_suppress() - Suppress WARN*() backtraces for the duration
> > + * of a block.
> > + * @test: The test context object.
> > + *
> > + * Scoped form of the suppression API. Suppression starts when the block is
> > + * entered and ends automatically when the block exits through any path.
> > See
> > + * the section comment above for the cleanup guarantees on each exit path.
> > + * Fails the test if suppression is already active; nesting is not
> > supported.
> > + *
> > + * The warning count can be checked inside the block via
> > + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(). The handle is not accessible
> > + * after the block exits.
> > + *
> > + * Example::
> > + *
> > + * kunit_warning_suppress(test) {
> > + * trigger_warning();
> > + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> > + * }
> > + */
> > +#define kunit_warning_suppress(test) \
> > + for (struct kunit_suppressed_warning *__kunit_suppress \
> > + __cleanup(__kunit_suppress_auto_cleanup) = \
> > + kunit_start_suppress_warning(test); \
> > + __kunit_suppress; \
> > + kunit_end_suppress_warning(test, __kunit_suppress), \
> > + __kunit_suppress = NULL)
> > +
> > +/**
> > + * KUNIT_START_SUPPRESSED_WARNING() - Begin suppressing WARN*() backtraces.
> > + * @test: The test context object.
> > + *
> > + * Manual form of the suppression API. Must be paired with
> > + * KUNIT_END_SUPPRESSED_WARNING() in the same scope. See the section
> > comment
> > + * above for cleanup guarantees. Fails the test if suppression is already
> > + * active; nesting is not supported. Limited to one pair per scope; use
> > + * sequential kunit_warning_suppress() blocks or the direct function API
> > + * when more than one suppression region is needed.
> > + *
> > + * Example::
> > + *
> > + * KUNIT_START_SUPPRESSED_WARNING(test);
> > + * trigger_code_that_should_warn_once();
> > + * KUNIT_END_SUPPRESSED_WARNING(test);
> > + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
> > + */
> > +#define KUNIT_START_SUPPRESSED_WARNING(test) \
> > + struct kunit_suppressed_warning *__kunit_suppress = \
> > + kunit_start_suppress_warning(test)
> > +
> > +/**
> > + * KUNIT_END_SUPPRESSED_WARNING() - End suppressing WARN*() backtraces.
> > + * @test: The test context object.
> > + *
> > + * Deactivates suppression started by KUNIT_START_SUPPRESSED_WARNING().
> > + * The warning count remains readable via KUNIT_SUPPRESSED_WARNING_COUNT()
> > + * after this call.
> > + */
> > +#define KUNIT_END_SUPPRESSED_WARNING(test) \
> > + kunit_end_suppress_warning(test, __kunit_suppress)
> > +
> > +/**
> > + * KUNIT_SUPPRESSED_WARNING_COUNT() - Returns the suppressed warning count.
> > + *
> > + * Returns the number of WARN*() calls suppressed since the current
> > + * suppression block started, or 0 if the handle is NULL. Usable inside a
> > + * kunit_warning_suppress() block or after KUNIT_END_SUPPRESSED_WARNING().
> > + */
> > +#define KUNIT_SUPPRESSED_WARNING_COUNT() \
> > + kunit_suppressed_warning_count(__kunit_suppress)
> > +
> > +/**
> > + * KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT() - Sets an expectation that the
> > + * suppressed warning count
> > equals
> > + * @expected.
> > + * @test: The test context object.
> > + * @expected: an expression that evaluates to the expected warning count.
> > + *
> > + * Sets an expectation that the number of suppressed WARN*() calls equals
> > + * @expected. This is semantically equivalent to
> > + * KUNIT_EXPECT_EQ(@test, KUNIT_SUPPRESSED_WARNING_COUNT(), @expected).
> > + * See KUNIT_EXPECT_EQ() for more information.
> > + */
> > +#define KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected) \
> > + KUNIT_EXPECT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
> > +
> > +/**
> > + * KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT() - Sets an assertion that the
> > + * suppressed warning count
> > equals
> > + * @expected.
> > + * @test: The test context object.
> > + * @expected: an expression that evaluates to the expected warning count.
> > + *
> > + * Sets an assertion that the number of suppressed WARN*() calls equals
> > + * @expected. This is the same as KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(),
> > + * except it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the
> > + * assertion is not met.
> > + */
> > +#define KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT(test, expected) \
> > + KUNIT_ASSERT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)
> > +
> > #endif /* _KUNIT_TEST_H */
> > diff --git a/kernel/panic.c b/kernel/panic.c
> > index c78600212b6c1..697d8ca054bef 100644
> > --- a/kernel/panic.c
> > +++ b/kernel/panic.c
> > @@ -39,6 +39,7 @@
> > #include <linux/sys_info.h>
> > #include <trace/events/error_report.h>
> > #include <asm/sections.h>
> > +#include <kunit/test-bug.h>
> >
> > #define PANIC_TIMER_STEP 100
> > #define PANIC_BLINK_SPD 18
> > @@ -1080,9 +1081,14 @@ void __warn(const char *file, int line, void
> > *caller, unsigned taint,
> > void warn_slowpath_fmt(const char *file, int line, unsigned taint,
> > const char *fmt, ...)
> > {
> > - bool rcu = warn_rcu_enter();
> > + bool rcu;
> > struct warn_args args;
> >
> > + if (kunit_is_suppressed_warning(true))
> > + return;
> > +
> > + rcu = warn_rcu_enter();
> > +
> > pr_warn(CUT_HERE);
> >
> > if (!fmt) {
> > @@ -1102,9 +1108,14 @@ EXPORT_SYMBOL(warn_slowpath_fmt);
> > #else
> > void __warn_printk(const char *fmt, ...)
> > {
> > - bool rcu = warn_rcu_enter();
> > + bool rcu;
> > va_list args;
> >
> > + if (kunit_is_suppressed_warning(false))
> > + return;
> > +
> > + rcu = warn_rcu_enter();
> > +
> > pr_warn(CUT_HERE);
> >
> > va_start(args, fmt);
> > diff --git a/lib/bug.c b/lib/bug.c
> > index 623c467a8b76c..a5cebde554ed8 100644
> > --- a/lib/bug.c
> > +++ b/lib/bug.c
> > @@ -48,6 +48,7 @@
> > #include <linux/rculist.h>
> > #include <linux/ftrace.h>
> > #include <linux/context_tracking.h>
> > +#include <kunit/test-bug.h>
> >
> > extern struct bug_entry __start___bug_table[], __stop___bug_table[];
> >
> > @@ -223,6 +224,15 @@ static enum bug_trap_type __report_bug(struct
> > bug_entry *bug, unsigned long buga
> > no_cut = bug->flags & BUGFLAG_NO_CUT_HERE;
> > has_args = bug->flags & BUGFLAG_ARGS;
> >
> > +#ifdef CONFIG_KUNIT
> > + /*
> > + * Before the once logic so suppressed warnings do not consume
> > + * the single-fire budget of WARN_ON_ONCE().
> > + */
> > + if (warning && kunit_is_suppressed_warning(true))
> > + return BUG_TRAP_TYPE_WARN;
> > +#endif
> > +
> > if (warning && once) {
> > if (done)
> > return BUG_TRAP_TYPE_WARN;
> > diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> > index 656f1fa35abcc..4592f9d0aa8dd 100644
> > --- a/lib/kunit/Makefile
> > +++ b/lib/kunit/Makefile
> > @@ -10,7 +10,8 @@ kunit-objs += test.o \
> > executor.o \
> > attributes.o \
> > device.o \
> > - platform.o
> > + platform.o \
> > + bug.o
> >
> > ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
> > kunit-objs += debugfs.o
> > diff --git a/lib/kunit/bug.c b/lib/kunit/bug.c
> > new file mode 100644
> > index 0000000000000..b0b6778d7399a
> > --- /dev/null
> > +++ b/lib/kunit/bug.c
> > @@ -0,0 +1,115 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * KUnit helpers for backtrace suppression
> > + *
> > + * Copyright (C) 2025 Alessandro Carminati <[email protected]>
> > + * Copyright (C) 2024 Guenter Roeck <[email protected]>
> > + */
> > +
> > +#include <kunit/resource.h>
> > +#include <linux/export.h>
> > +#include <linux/rculist.h>
> > +#include <linux/sched.h>
> > +#include <linux/spinlock.h>
> > +
> > +#include "hooks-impl.h"
> > +
> > +struct kunit_suppressed_warning {
> > + struct list_head node;
> > + struct task_struct *task;
> > + struct kunit *test;
> > + int 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);
> > + synchronize_rcu(); /* Wait for readers to finish */
> > +}
> > +
> > +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)
> > + return NULL;
> > +
> > + w->task = 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)
> > + 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 ? 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)
> > + w->counter++;
> > + return true;
> > + }
> > + }
> > +
> > + return false;
> > +}
> > diff --git a/lib/kunit/hooks-impl.h b/lib/kunit/hooks-impl.h
> > index 4e71b2d0143ba..d8720f2616925 100644
> > --- a/lib/kunit/hooks-impl.h
> > +++ b/lib/kunit/hooks-impl.h
> > @@ -19,6 +19,7 @@ void __printf(3, 4) __kunit_fail_current_test_impl(const
> > char *file,
> > int line,
> > const char *fmt, ...);
> > void *__kunit_get_static_stub_address_impl(struct kunit *test, void
> > *real_fn_addr);
> > +bool __kunit_is_suppressed_warning_impl(bool count);
> >
> > /* Code to set all of the function pointers. */
> > static inline void kunit_install_hooks(void)
> > @@ -26,6 +27,7 @@ static inline void kunit_install_hooks(void)
> > /* Install the KUnit hook functions. */
> > kunit_hooks.fail_current_test = __kunit_fail_current_test_impl;
> > kunit_hooks.get_static_stub_address =
> > __kunit_get_static_stub_address_impl;
> > + kunit_hooks.is_suppressed_warning =
> > __kunit_is_suppressed_warning_impl;
> > }
> >
> > #endif /* _KUNIT_HOOKS_IMPL_H */
> >
>