* [PATCH v9 02/10] x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires
From: Masami Hiramatsu (Google) @ 2026-07-17 14:19 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add CONFIG_HAVE_POST_BREAKPOINT_HOOK which indicates the hw_breakpoint
on that architecture fires after the target memory has been modified.
This is currently x86 only behavior.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
arch/Kconfig | 10 ++++++++++
arch/x86/Kconfig | 1 +
kernel/trace/Kconfig | 1 +
3 files changed, 12 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig
index fa7507ac8e13..959aee9568ff 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -457,6 +457,16 @@ config HAVE_MIXED_BREAKPOINTS_REGS
Select this option if your arch implements breakpoints under the
latter fashion.
+config HAVE_POST_BREAKPOINT_HOOK
+ bool
+ depends on HAVE_HW_BREAKPOINT
+ help
+ Depending on the arch implementation of hardware breakpoints,
+ some of them provide breakpoint hook after the target memory
+ is modified.
+ Select this option if your arch implements breakpoints overflow
+ handler hooks after the target memory is modified.
+
config HAVE_USER_RETURN_NOTIFIER
bool
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index bdad90f210e4..6b7e14ef8cfb 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -246,6 +246,7 @@ config X86
select HAVE_FUNCTION_TRACER
select HAVE_GCC_PLUGINS
select HAVE_HW_BREAKPOINT
+ select HAVE_POST_BREAKPOINT_HOOK
select HAVE_IOREMAP_PROT
select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64
select HAVE_IRQ_TIME_ACCOUNTING
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index b58c2565024f..d9b6fa5c35d9 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -866,6 +866,7 @@ config WPROBE_EVENTS
bool "Enable wprobe-based dynamic events"
depends on TRACING
depends on HAVE_HW_BREAKPOINT
+ depends on HAVE_POST_BREAKPOINT_HOOK
select PROBE_EVENTS
select DYNAMIC_EVENTS
help
^ permalink raw reply related
* [PATCH v9 03/10] selftests: tracing: Add a basic testcase for wprobe
From: Masami Hiramatsu (Google) @ 2026-07-17 14:20 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add 'add_remove_wprobe.tc' testcase for testing wprobe event that
tests adding and removing operations of the wprobe event.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v9:
- Fix command check logic to prevent early exit under 'set -e'
(errexit) when grep or test fails.
- Simplify enable/disable status checks by removing cat pipes.
Changes in v8:
- Fixed silently test failure path.
---
tools/testing/selftests/ftrace/config | 1
.../ftrace/test.d/dynevent/add_remove_wprobe.tc | 63 ++++++++++++++++++++
2 files changed, 64 insertions(+)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc
diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftests/ftrace/config
index 544de0db5f58..d2f503722020 100644
--- a/tools/testing/selftests/ftrace/config
+++ b/tools/testing/selftests/ftrace/config
@@ -27,3 +27,4 @@ CONFIG_STACK_TRACER=y
CONFIG_TRACER_SNAPSHOT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
+CONFIG_WPROBE_EVENTS=y
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc
new file mode 100644
index 000000000000..647c37d5e4c8
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc
@@ -0,0 +1,63 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Generic dynamic event - add/remove wprobe events
+# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README
+
+echo 0 > events/enable
+echo > dynamic_events
+
+# Use jiffies as a variable that is frequently written to.
+TARGET=jiffies
+
+echo "w:my_wprobe w@$TARGET" >> dynamic_events
+
+if ! grep -q my_wprobe dynamic_events; then
+ echo "Failed to create wprobe event"
+ exit_fail
+fi
+
+if [ ! -d events/wprobes/my_wprobe ]; then
+ echo "Failed to create wprobe event directory"
+ exit_fail
+fi
+
+echo 1 > events/wprobes/my_wprobe/enable
+
+# Check if the event is enabled
+if ! grep -q 1 events/wprobes/my_wprobe/enable; then
+ echo "Failed to enable wprobe event"
+ exit_fail
+fi
+
+# Let some time pass to trigger the breakpoint
+sleep 1
+
+# Check if we got any trace output
+if ! grep -q my_wprobe trace; then
+ echo "wprobe event was not triggered"
+ exit_fail
+fi
+
+echo 0 > events/wprobes/my_wprobe/enable
+
+# Check if the event is disabled
+if ! grep -q 0 events/wprobes/my_wprobe/enable; then
+ echo "Failed to disable wprobe event"
+ exit_fail
+fi
+
+echo "-:my_wprobe" >> dynamic_events
+
+if grep -q my_wprobe dynamic_events; then
+ echo "Failed to remove wprobe event"
+ exit_fail
+fi
+
+if [ -d events/wprobes/my_wprobe ]; then
+ echo "Failed to remove wprobe event directory"
+ exit_fail
+fi
+
+clear_trace
+
+exit 0
^ permalink raw reply related
* [PATCH v9 04/10] selftests: tracing: Add syntax testcase for wprobe
From: Masami Hiramatsu (Google) @ 2026-07-17 14:20 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add "wprobe_syntax_errors.tc" testcase for testing syntax errors
of the watch probe events.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
.../test.d/dynevent/wprobes_syntax_errors.tc | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc
new file mode 100644
index 000000000000..56ac579d60ae
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.tc
@@ -0,0 +1,20 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Watch probe event parser error log check
+# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README
+
+check_error() { # command-with-error-pos-by-^
+ ftrace_errlog_check 'wprobe' "$1" 'dynamic_events'
+}
+
+check_error 'w ^symbol' # BAD_ACCESS_FMT
+check_error 'w ^a@symbol' # BAD_ACCESS_TYPE
+check_error 'w w@^symbol' # BAD_ACCESS_ADDR
+check_error 'w w@jiffies^+offset' # BAD_ACCESS_ADDR
+check_error 'w w@jiffies:^100' # BAD_ACCESS_LEN
+check_error 'w w@jiffies ^$arg1' # BAD_VAR
+check_error 'w w@jiffies ^$retval' # BAD_VAR
+check_error 'w w@jiffies ^$stack' # BAD_VAR
+check_error 'w w@jiffies ^%ax' # BAD_VAR
+
+exit 0
^ permalink raw reply related
* [PATCH v9 05/10] x86/hw_breakpoint: Unify breakpoint install/uninstall
From: Masami Hiramatsu (Google) @ 2026-07-17 14:20 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Jinchao Wang <wangjinchao600@gmail.com>
Consolidate breakpoint management to reduce code duplication.
The diffstat was misleading, so the stripped code size is compared instead.
After refactoring, it is reduced from 11976 bytes to 11448 bytes on my
x86_64 system built with clang.
This also makes it easier to introduce arch_reinstall_hw_breakpoint().
In addition, including linux/types.h to fix a missing build dependency.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v8:
- Add missing barrier() on the disable path of setup_hwbp() to prevent
the compiler from reordering this_cpu_write(cpu_dr7, ...) before
set_debugreg(dr7, 7).
- Clear existing slot control and enable bits in cpu_dr7 inside
setup_hwbp() using __encode_dr7() before setting new ones to prevent
register state corruption on update/reinstall.
---
arch/x86/include/asm/hw_breakpoint.h | 6 +
arch/x86/kernel/hw_breakpoint.c | 144 +++++++++++++++++++---------------
2 files changed, 86 insertions(+), 64 deletions(-)
diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw_breakpoint.h
index 0bc931cd0698..aa6adac6c3a2 100644
--- a/arch/x86/include/asm/hw_breakpoint.h
+++ b/arch/x86/include/asm/hw_breakpoint.h
@@ -5,6 +5,7 @@
#include <uapi/asm/hw_breakpoint.h>
#define __ARCH_HW_BREAKPOINT_H
+#include <linux/types.h>
/*
* The name should probably be something dealt in
@@ -18,6 +19,11 @@ struct arch_hw_breakpoint {
u8 type;
};
+enum bp_slot_action {
+ BP_SLOT_ACTION_INSTALL,
+ BP_SLOT_ACTION_UNINSTALL,
+};
+
#include <linux/kdebug.h>
#include <linux/percpu.h>
#include <linux/list.h>
diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoint.c
index f846c15f21ca..c323c2aab2af 100644
--- a/arch/x86/kernel/hw_breakpoint.c
+++ b/arch/x86/kernel/hw_breakpoint.c
@@ -49,7 +49,6 @@ static DEFINE_PER_CPU(unsigned long, cpu_debugreg[HBP_NUM]);
*/
static DEFINE_PER_CPU(struct perf_event *, bp_per_reg[HBP_NUM]);
-
static inline unsigned long
__encode_dr7(int drnum, unsigned int len, unsigned int type)
{
@@ -86,96 +85,113 @@ int decode_dr7(unsigned long dr7, int bpnum, unsigned *len, unsigned *type)
}
/*
- * Install a perf counter breakpoint.
- *
- * We seek a free debug address register and use it for this
- * breakpoint. Eventually we enable it in the debug control register.
- *
- * Atomic: we hold the counter->ctx->lock and we only handle variables
- * and registers local to this cpu.
+ * We seek a slot and change it or keep it based on the action.
+ * Returns slot number on success, negative error on failure.
+ * Must be called with IRQs disabled.
*/
-int arch_install_hw_breakpoint(struct perf_event *bp)
+static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action)
{
- struct arch_hw_breakpoint *info = counter_arch_bp(bp);
- unsigned long *dr7;
- int i;
-
- lockdep_assert_irqs_disabled();
+ struct perf_event *old_bp;
+ struct perf_event *new_bp;
+ int slot;
+
+ switch (action) {
+ case BP_SLOT_ACTION_INSTALL:
+ old_bp = NULL;
+ new_bp = bp;
+ break;
+ case BP_SLOT_ACTION_UNINSTALL:
+ old_bp = bp;
+ new_bp = NULL;
+ break;
+ default:
+ return -EINVAL;
+ }
- for (i = 0; i < HBP_NUM; i++) {
- struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
+ for (slot = 0; slot < HBP_NUM; slot++) {
+ struct perf_event **curr = this_cpu_ptr(&bp_per_reg[slot]);
- if (!*slot) {
- *slot = bp;
- break;
+ if (*curr == old_bp) {
+ *curr = new_bp;
+ return slot;
}
}
- if (WARN_ONCE(i == HBP_NUM, "Can't find any breakpoint slot"))
- return -EBUSY;
+ if (old_bp) {
+ WARN_ONCE(1, "Can't find matching breakpoint slot");
+ return -EINVAL;
+ }
- set_debugreg(info->address, i);
- __this_cpu_write(cpu_debugreg[i], info->address);
+ WARN_ONCE(1, "No free breakpoint slots");
+ return -EBUSY;
+}
- dr7 = this_cpu_ptr(&cpu_dr7);
- *dr7 |= encode_dr7(i, info->len, info->type);
+static void setup_hwbp(struct arch_hw_breakpoint *info, int slot, bool enable)
+{
+ unsigned long dr7;
+
+ set_debugreg(info->address, slot);
+ __this_cpu_write(cpu_debugreg[slot], info->address);
+
+ dr7 = this_cpu_read(cpu_dr7);
+ dr7 &= ~(__encode_dr7(slot, 0xc, 0x3) |
+ (DR_LOCAL_ENABLE << (slot * DR_ENABLE_SIZE)));
+ if (enable)
+ dr7 |= encode_dr7(slot, info->len, info->type);
/*
- * Ensure we first write cpu_dr7 before we set the DR7 register.
- * This ensures an NMI never see cpu_dr7 0 when DR7 is not.
+ * Enabling:
+ * Ensure we first write cpu_dr7 before we set the DR7 register.
+ * This ensures an NMI never see cpu_dr7 0 when DR7 is not.
*/
+ if (enable)
+ this_cpu_write(cpu_dr7, dr7);
+
barrier();
- set_debugreg(*dr7, 7);
- if (info->mask)
- amd_set_dr_addr_mask(info->mask, i);
+ set_debugreg(dr7, 7);
+
+ amd_set_dr_addr_mask(enable ? info->mask : 0, slot);
- return 0;
+ barrier();
+
+ /*
+ * Disabling:
+ * Ensure the write to cpu_dr7 is after we've set the DR7 register.
+ * This ensures an NMI never see cpu_dr7 0 when DR7 is not.
+ */
+ if (!enable)
+ this_cpu_write(cpu_dr7, dr7);
}
/*
- * Uninstall the breakpoint contained in the given counter.
- *
- * First we search the debug address register it uses and then we disable
- * it.
- *
- * Atomic: we hold the counter->ctx->lock and we only handle variables
- * and registers local to this cpu.
+ * find suitable breakpoint slot and set it up based on the action
*/
-void arch_uninstall_hw_breakpoint(struct perf_event *bp)
+static int arch_manage_bp(struct perf_event *bp, enum bp_slot_action action)
{
- struct arch_hw_breakpoint *info = counter_arch_bp(bp);
- unsigned long dr7;
- int i;
+ struct arch_hw_breakpoint *info;
+ int slot;
lockdep_assert_irqs_disabled();
- for (i = 0; i < HBP_NUM; i++) {
- struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
-
- if (*slot == bp) {
- *slot = NULL;
- break;
- }
- }
+ slot = manage_bp_slot(bp, action);
+ if (slot < 0)
+ return slot;
- if (WARN_ONCE(i == HBP_NUM, "Can't find any breakpoint slot"))
- return;
+ info = counter_arch_bp(bp);
+ setup_hwbp(info, slot, action != BP_SLOT_ACTION_UNINSTALL);
- dr7 = this_cpu_read(cpu_dr7);
- dr7 &= ~__encode_dr7(i, info->len, info->type);
-
- set_debugreg(dr7, 7);
- if (info->mask)
- amd_set_dr_addr_mask(0, i);
+ return 0;
+}
- /*
- * Ensure the write to cpu_dr7 is after we've set the DR7 register.
- * This ensures an NMI never see cpu_dr7 0 when DR7 is not.
- */
- barrier();
+int arch_install_hw_breakpoint(struct perf_event *bp)
+{
+ return arch_manage_bp(bp, BP_SLOT_ACTION_INSTALL);
+}
- this_cpu_write(cpu_dr7, dr7);
+void arch_uninstall_hw_breakpoint(struct perf_event *bp)
+{
+ arch_manage_bp(bp, BP_SLOT_ACTION_UNINSTALL);
}
static int arch_bp_generic_len(int x86_len)
^ permalink raw reply related
* [PATCH v9 06/10] x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
From: Masami Hiramatsu (Google) @ 2026-07-17 14:20 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Jinchao Wang <wangjinchao600@gmail.com>
The new arch_reinstall_hw_breakpoint() function can be used in an
atomic context, unlike the more expensive free and re-allocation path.
This allows callers to efficiently re-establish an existing breakpoint.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v9:
- Update commit message.
- Temporarily disable the active slot in setup_hwbp() before updating
the address register to avoid spurious debug exceptions.
---
arch/x86/include/asm/hw_breakpoint.h | 2 ++
arch/x86/kernel/hw_breakpoint.c | 34 ++++++++++++++++++++++++++++------
2 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw_breakpoint.h
index aa6adac6c3a2..c22cc4e87fc5 100644
--- a/arch/x86/include/asm/hw_breakpoint.h
+++ b/arch/x86/include/asm/hw_breakpoint.h
@@ -21,6 +21,7 @@ struct arch_hw_breakpoint {
enum bp_slot_action {
BP_SLOT_ACTION_INSTALL,
+ BP_SLOT_ACTION_REINSTALL,
BP_SLOT_ACTION_UNINSTALL,
};
@@ -65,6 +66,7 @@ extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
int arch_install_hw_breakpoint(struct perf_event *bp);
+int arch_reinstall_hw_breakpoint(struct perf_event *bp);
void arch_uninstall_hw_breakpoint(struct perf_event *bp);
void hw_breakpoint_pmu_read(struct perf_event *bp);
void hw_breakpoint_pmu_unthrottle(struct perf_event *bp);
diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoint.c
index c323c2aab2af..0df3ff556f47 100644
--- a/arch/x86/kernel/hw_breakpoint.c
+++ b/arch/x86/kernel/hw_breakpoint.c
@@ -100,6 +100,10 @@ static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action)
old_bp = NULL;
new_bp = bp;
break;
+ case BP_SLOT_ACTION_REINSTALL:
+ old_bp = bp;
+ new_bp = bp;
+ break;
case BP_SLOT_ACTION_UNINSTALL:
old_bp = bp;
new_bp = NULL;
@@ -129,23 +133,36 @@ static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action)
static void setup_hwbp(struct arch_hw_breakpoint *info, int slot, bool enable)
{
unsigned long dr7;
-
- set_debugreg(info->address, slot);
- __this_cpu_write(cpu_debugreg[slot], info->address);
+ bool enabled;
dr7 = this_cpu_read(cpu_dr7);
+ enabled = dr7 & ((DR_LOCAL_ENABLE | DR_GLOBAL_ENABLE) << (slot * DR_ENABLE_SIZE));
dr7 &= ~(__encode_dr7(slot, 0xc, 0x3) |
(DR_LOCAL_ENABLE << (slot * DR_ENABLE_SIZE)));
- if (enable)
- dr7 |= encode_dr7(slot, info->len, info->type);
+
+ /*
+ * If the slot is currently enabled, disable it first before updating
+ * the address register to prevent spurious debug exceptions.
+ */
+ if (enable && enabled) {
+ barrier();
+ set_debugreg(dr7, 7);
+ barrier();
+ this_cpu_write(cpu_dr7, dr7);
+ }
+
+ set_debugreg(info->address, slot);
+ __this_cpu_write(cpu_debugreg[slot], info->address);
/*
* Enabling:
* Ensure we first write cpu_dr7 before we set the DR7 register.
* This ensures an NMI never see cpu_dr7 0 when DR7 is not.
*/
- if (enable)
+ if (enable) {
+ dr7 |= encode_dr7(slot, info->len, info->type);
this_cpu_write(cpu_dr7, dr7);
+ }
barrier();
@@ -189,6 +206,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
return arch_manage_bp(bp, BP_SLOT_ACTION_INSTALL);
}
+int arch_reinstall_hw_breakpoint(struct perf_event *bp)
+{
+ return arch_manage_bp(bp, BP_SLOT_ACTION_REINSTALL);
+}
+
void arch_uninstall_hw_breakpoint(struct perf_event *bp)
{
arch_manage_bp(bp, BP_SLOT_ACTION_UNINSTALL);
^ permalink raw reply related
* [PATCH v9 07/10] HWBP: Add modify_wide_hw_breakpoint_local() API
From: Masami Hiramatsu (Google) @ 2026-07-17 14:20 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add modify_wide_hw_breakpoint_local() arch-wide interface which allows
hwbp users to update watch address on-line. This is available if the
arch supports CONFIG_HAVE_REINSTALL_HW_BREAKPOINT.
Note that this allows to change the type only for compatible types,
because it does not release and reserve the hwbp slot based on type.
For instance, you can not change HW_BREAKPOINT_W to HW_BREAKPOINT_X.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v9:
- Add lockdep_assert_irqs_disabled() to enforce and document the
interrupt-disabled calling context requirement.
- Implement state rollback logic in modify_wide_hw_breakpoint_local()
to prevent inconsistent state on architecture update failure.
Changes in v8:
- Parse new attributes into a temporary struct arch_hw_breakpoint
copy to prevent in-place mutation and corruption on parse error paths.
- Synchronize logical perf_event attributes by updating
bp->attr.bp_type and bp->attr.bp_len in modify_wide_hw_breakpoint_local().
Changes in v7:
- Update bp->attr.bp_attr so that we can correctly check the
address on it.
- Use -EOPNOTSUPP instead of -ENOSYS.
Changes in v4:
- Update kerneldoc comment about modify_wide_hw_breakpoint_local
according to Randy's comment.
Changes in v2:
- Check type compatibility by checking slot. (Thanks Jinchao!)
---
arch/Kconfig | 10 +++++++
arch/x86/Kconfig | 1 +
include/linux/hw_breakpoint.h | 6 ++++
kernel/events/hw_breakpoint.c | 61 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+)
diff --git a/arch/Kconfig b/arch/Kconfig
index 959aee9568ff..4a87e843bb4c 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -467,6 +467,16 @@ config HAVE_POST_BREAKPOINT_HOOK
Select this option if your arch implements breakpoints overflow
handler hooks after the target memory is modified.
+config HAVE_REINSTALL_HW_BREAKPOINT
+ bool
+ depends on HAVE_HW_BREAKPOINT
+ help
+ Depending on the arch implementation of hardware breakpoints,
+ some of them are able to update the breakpoint configuration
+ without release and reserve the hardware breakpoint register.
+ What configuration is able to update depends on hardware and
+ software implementation.
+
config HAVE_USER_RETURN_NOTIFIER
bool
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 6b7e14ef8cfb..588218da8f41 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -247,6 +247,7 @@ config X86
select HAVE_GCC_PLUGINS
select HAVE_HW_BREAKPOINT
select HAVE_POST_BREAKPOINT_HOOK
+ select HAVE_REINSTALL_HW_BREAKPOINT
select HAVE_IOREMAP_PROT
select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64
select HAVE_IRQ_TIME_ACCOUNTING
diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h
index db199d653dd1..6754ffbee9ed 100644
--- a/include/linux/hw_breakpoint.h
+++ b/include/linux/hw_breakpoint.h
@@ -81,6 +81,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,
perf_overflow_handler_t triggered,
void *context);
+extern int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr);
+
extern int register_perf_hw_breakpoint(struct perf_event *bp);
extern void unregister_hw_breakpoint(struct perf_event *bp);
extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events);
@@ -124,6 +127,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,
perf_overflow_handler_t triggered,
void *context) { return NULL; }
static inline int
+modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr) { return -EOPNOTSUPP; }
+static inline int
register_perf_hw_breakpoint(struct perf_event *bp) { return -ENOSYS; }
static inline void unregister_hw_breakpoint(struct perf_event *bp) { }
static inline void
diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c
index 789add0c185a..63570a5079bf 100644
--- a/kernel/events/hw_breakpoint.c
+++ b/kernel/events/hw_breakpoint.c
@@ -888,6 +888,67 @@ void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events)
}
EXPORT_SYMBOL_GPL(unregister_wide_hw_breakpoint);
+/**
+ * modify_wide_hw_breakpoint_local - update breakpoint config for local CPU
+ * @bp: the hwbp perf event for this CPU
+ * @attr: the new attribute for @bp
+ *
+ * This does not release and reserve the slot of a HWBP; it just reuses the
+ * current slot on local CPU. So the users must update the other CPUs by
+ * themselves.
+ * Also, since this does not release/reserve the slot, this can not change the
+ * type to incompatible type of the HWBP.
+ * Return err if attr is invalid or the CPU fails to update debug register
+ * for new @attr.
+ */
+#ifdef CONFIG_HAVE_REINSTALL_HW_BREAKPOINT
+int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr)
+{
+ struct arch_hw_breakpoint info, old_info;
+ unsigned long old_addr;
+ unsigned int old_type, old_len;
+ int ret;
+
+ lockdep_assert_irqs_disabled();
+
+ if (find_slot_idx(bp->attr.bp_type) != find_slot_idx(attr->bp_type))
+ return -EINVAL;
+
+ ret = hw_breakpoint_arch_parse(bp, attr, &info);
+ if (ret)
+ return ret;
+
+ old_info = *counter_arch_bp(bp);
+ old_addr = bp->attr.bp_addr;
+ old_type = bp->attr.bp_type;
+ old_len = bp->attr.bp_len;
+
+ *counter_arch_bp(bp) = info;
+ bp->attr.bp_addr = attr->bp_addr;
+ bp->attr.bp_type = attr->bp_type;
+ bp->attr.bp_len = attr->bp_len;
+
+ ret = arch_reinstall_hw_breakpoint(bp);
+ if (ret) {
+ /* Rollback to the original state */
+ *counter_arch_bp(bp) = old_info;
+ bp->attr.bp_addr = old_addr;
+ bp->attr.bp_type = old_type;
+ bp->attr.bp_len = old_len;
+ }
+
+ return ret;
+}
+#else
+int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+ struct perf_event_attr *attr)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+EXPORT_SYMBOL_GPL(modify_wide_hw_breakpoint_local);
+
/**
* hw_breakpoint_is_used - check if breakpoints are currently used
*
^ permalink raw reply related
* [PATCH v9 08/10] tracing: wprobe: Add wprobe event trigger
From: Masami Hiramatsu (Google) @ 2026-07-17 14:21 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add wprobe event trigger to set and clear the watch event dynamically.
This allows us to set an watchpoint on a given local variables and
a slab object instead of static objects.
The trigger syntax is below:
- set_wprobe:WPROBE:FIELD[+OFFSET] [if FILTER]
- clear_wprobe:WPROBE[:FIELD[+OFFSET]] [if FILTER]
set_wprobe sets the address pointed by FIELD[+offset] to the WPROBE
event. The FIELD is the field name of trigger event.
clear_wprobe clears the watch address of WPROBE event. If the FIELD
option is specified, it clears only if the current watch address is
same as the given FIELD[+OFFSET] value.
The set_wprobe trigger does not change the type and length, these
must be set when creating a new wprobe.
Also, the WPROBE event must be disabled when setting the new trigger
and it will be busy afterwards. Recommended usage is to add a new
wprobe at NULL address and keep disabled.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v9:
- Make event_trigger_free() non-static to solve build dependency.
- Sync irq_work and work inside __unregister_trace_wprobe() before
clearing tw->bp_event to prevent concurrent NULL pointer dereference.
- Introduce private_free destructor in struct event_trigger_data to
safely release wprobe_trigger_data after tracepoint readers exit.
- Fix filter memory leak in wprobe_trigger_cmd_parse() on the error
handling path of trace_event_try_get_ref() failure.
- Avoid overwriting tw->addr on clear_wprobe trigger registration to
prevent active watchpoint corruption and hardware breakpoint leak.
Changes in v8:
- Redesign wprobe_trigger() to be safe in NMI/hardirq contexts by
deferring register updates to a workqueue via irq_work.
- Skip trigger execution and increment an atomic missed count
(tw->missed) if the tracepoint runs in NMI context to prevent
recursive spinlock deadlocks. (this is currently hidden counter)
- Prohibit attaching wprobe triggers to kprobe_events by checking
TRACE_EVENT_FL_KPROBE in wprobe_trigger_cmd_parse().
- Use call_rcu() in trigger deactivation path and add
synchronize_rcu() in parse failure path to ensure safe RCU
lifetime cleanup.
- Acquire the target tracepoint's module reference via
trace_event_try_get_ref() to prevent module refcount underflows.
- Fix event_trigger_data memory leak by properly freeing the
initial refcount in wprobe_trigger_cmd_parse() on success.
- Call on_each_cpu() with wait=true in wprobe_work_func() to
prevent use-after-free during trigger unregistration.
- Synchronize concurrent wprobe triggers on the same event by
using a shared raw spinlock (tw->lock).
- Drop the support of kprobe events (that should be done later).
Changes in v7:
- Use kzalloc_obj().
- Update sample code in document.
Changes in v6:
- Update according to the latest change of trigger ops.
Changes in v5:
- Following the suggestions, the documentation was revised to suit rst.
Changes in v3:
- Add FIELD option support for clear_wprobe and update document.
- Fix to unregister/free event_trigger_data on file correctly.
- Fix syntax comments.
Changes in v2:
- Getting local cpu perf_event from trace_wprobe directly.
- Remove trace_wprobe_local_perf() because it is conditionally unused.
- Make CONFIG_WPROBE_TRIGGERS a hidden config.
---
Documentation/trace/wprobetrace.rst | 93 +++++++
include/linux/trace_events.h | 1
kernel/trace/Kconfig | 10 +
kernel/trace/trace.h | 2
kernel/trace/trace_events_trigger.c | 12 +
kernel/trace/trace_wprobe.c | 438 +++++++++++++++++++++++++++++++++++
6 files changed, 553 insertions(+), 3 deletions(-)
diff --git a/Documentation/trace/wprobetrace.rst b/Documentation/trace/wprobetrace.rst
index eb4f10607530..a4c0f0e676fd 100644
--- a/Documentation/trace/wprobetrace.rst
+++ b/Documentation/trace/wprobetrace.rst
@@ -68,3 +68,96 @@ Here is an example to add a wprobe event on a variable `jiffies`.
<idle>-0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_update_jiffies64+0xbe/0x130)
You can see the code which writes to `jiffies` is `tick_do_update_jiffies64()`.
+
+Combination with trigger action
+-------------------------------
+The event trigger action can extend the utilization of this wprobe.
+
+- set_wprobe:WPEVENT:FIELD[+|-ADJUST]
+- clear_wprobe:WPEVENT[:FIELD[+|-]ADJUST]
+
+Set these triggers to the target event, then the WPROBE event will be
+setup to trace the memory access at FIELD[+|-ADJUST] address.
+When clear_wprobe is hit, if FIELD is NOT specified, the WPEVENT is
+forcibly cleared. If FIELD[[+|-]ADJUST] is set, it clears WPEVENT only
+if its watching address is the same as the FIELD[[+|-]ADJUST] value.
+
+Notes:
+The set_wprobe trigger does not change the type and length, these
+must be set when creating a new wprobe.
+
+The WPROBE event must be disabled when setting the new trigger
+and it will be busy afterwards. Recommended usage is to add a new
+wprobe at NULL address and keep disabled.
+
+Wprobe triggers are not supported on kprobe_events, because kprobes
+themselves can use software breakpoints which conflicts with wprobe
+operation.
+
+
+For example, trace the first 8 bytes of the dentry data structure passed
+to do_truncate() until it is deleted by dentry_kill().
+(Note: all tracefs setup uses '>>' so that it does not kick do_truncate())
+::
+
+ # echo 'w:watch rw@0:8 address=$addr value=+0($addr)' >> dynamic_events
+ # echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events
+ # echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+ # echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events
+ # echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger
+ # echo 1 >> events/fprobes/truncate/enable
+ # echo 1 >> events/fprobes/dentry_kill/enable
+
+ # echo aaa > /tmp/hoge
+ # echo bbb > /tmp/hoge
+ # echo ccc > /tmp/hoge
+ # rm /tmp/hoge
+
+Then, the trace data will show::
+
+ # tracer: nop
+ #
+ # entries-in-buffer/entries-written: 32/32 #P:8
+ #
+ # _-----=> irqs-off/BH-disabled
+ # / _----=> need-resched
+ # | / _---=> hardirq/softirq
+ # || / _--=> preempt-depth
+ # ||| / _-=> migrate-disable
+ # |||| / delay
+ # TASK-PID CPU# ||||| TIMESTAMP FUNCTION
+ # | | | ||||| | |
+ sh-107 [004] ...1. 9.990418: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618
+ sh-107 [004] ...1. 9.990914: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b3de78
+ sh-107 [004] ...1. 9.993175: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049ddd40
+ sh-107 [004] ..... 9.995198: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880048083a8
+ sh-107 [004] ...1. 9.995389: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db998
+ sh-107 [004] ..Zff 9.997503: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..Zff 9.997509: watch: (path_openat+0x211/0xda0) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..Zff 9.997514: watch: (path_openat+0xa56/0xda0) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..Zff 9.997518: watch: (path_openat+0xae2/0xda0) address=0xffff8880048083a8 value=0x8200080
+ sh-107 [004] ..... 9.997521: truncate: (do_truncate+0x4/0x120) dentry=0xffff8880048083a8
+ sh-107 [004] ...1. 9.997582: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004808270
+ sh-107 [004] ...1. 9.999365: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db728
+ sh-107 [004] ...1. 9.999388: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b1c000
+ rm-113 [005] ..Zff 10.000965: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.000971: watch: (path_lookupat+0x97/0x1e0) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.000984: watch: (lookup_fast+0xaa/0x150) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.000988: watch: (path_lookupat+0x97/0x1e0) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001010: watch: (lookup_one_qstr_excl+0x28/0x140) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001014: watch: (lookup_one_qstr_excl+0xd1/0x140) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001018: watch: (may_delete_dentry+0x1c/0x200) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001021: watch: (may_delete_dentry+0x195/0x200) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] ..Zff 10.001031: watch: (vfs_unlink+0x5e/0x260) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] d.Z.. 10.001067: watch: (d_make_discardable+0x1b/0x40) address=0xffff8880048083a8 value=0x8200080
+ rm-113 [005] d.Z.. 10.001071: watch: (d_make_discardable+0x29/0x40) address=0xffff8880048083a8 value=0x200080
+ rm-113 [005] ...1. 10.001072: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880048083a8
+ rm-113 [005] ...1. 10.001218: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880048083a8
+ sh-107 [004] ...1. 10.001416: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db110
+ sh-107 [004] ...1. 10.001444: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff8880049db248
+ sh-107 [004] ...1. 10.001500: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618
+ sh-107 [004] ...1. 10.002067: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b41e78
+ sh-107 [004] ...1. 10.904920: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004b41e78
+ sh-107 [004] ...1. 10.905129: dentry_kill: (dentry_kill+0x0/0x2c0) dentry=0xffff888004ad6618
+
+You can see the watch event is correctly configured on the dentry.
diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h
index d1e5ab71d928..e6f3bbcbb9af 100644
--- a/include/linux/trace_events.h
+++ b/include/linux/trace_events.h
@@ -729,6 +729,7 @@ enum event_trigger_type {
ETT_EVENT_HIST = (1 << 4),
ETT_HIST_ENABLE = (1 << 5),
ETT_EVENT_EPROBE = (1 << 6),
+ ETT_EVENT_WPROBE = (1 << 7),
};
extern int filter_match_preds(struct event_filter *filter, void *rec);
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index d9b6fa5c35d9..af570fa2a882 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -876,6 +876,16 @@ config WPROBE_EVENTS
Those events can be inserted wherever hardware breakpoints can be
set, and record accessed memory address and values.
+config WPROBE_TRIGGERS
+ depends on WPROBE_EVENTS
+ depends on HAVE_REINSTALL_HW_BREAKPOINT
+ bool
+ default y
+ help
+ This adds an event trigger which will set the wprobe on a specific
+ field of an event. This allows user to trace the memory access of
+ an address pointed by the event field.
+
config BPF_EVENTS
depends on BPF_SYSCALL
depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 2f07c5c4ffc8..e5964a69057c 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -1939,6 +1939,7 @@ struct event_trigger_data {
struct event_filter __rcu *filter;
char *filter_str;
void *private_data;
+ void (*private_free)(void *private_data);
bool paused;
bool paused_tmp;
struct list_head list;
@@ -1982,6 +1983,7 @@ trigger_data_alloc(struct event_command *cmd_ops, char *cmd, char *param,
void *private_data);
extern void trigger_data_free(struct event_trigger_data *data);
extern int event_trigger_init(struct event_trigger_data *data);
+extern void event_trigger_free(struct event_trigger_data *data);
extern int trace_event_trigger_enable_disable(struct trace_event_file *file,
int trigger_enable);
extern void update_cond_flag(struct trace_event_file *file);
diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c
index 655db2e82513..1e3b667a2edd 100644
--- a/kernel/trace/trace_events_trigger.c
+++ b/kernel/trace/trace_events_trigger.c
@@ -51,8 +51,11 @@ static void trigger_data_free_queued_locked(void)
tracepoint_synchronize_unregister();
- llist_for_each_entry_safe(data, tmp, llnodes, llist)
+ llist_for_each_entry_safe(data, tmp, llnodes, llist) {
+ if (data->private_free)
+ data->private_free(data->private_data);
kfree(data);
+ }
}
/* Bulk garbage collection of event_trigger_data elements */
@@ -74,8 +77,11 @@ static int trigger_kthread_fn(void *ignore)
/* make sure current triggers exit before free */
tracepoint_synchronize_unregister();
- llist_for_each_entry_safe(data, tmp, llnodes, llist)
+ llist_for_each_entry_safe(data, tmp, llnodes, llist) {
+ if (data->private_free)
+ data->private_free(data->private_data);
kfree(data);
+ }
}
return 0;
@@ -582,7 +588,7 @@ int event_trigger_init(struct event_trigger_data *data)
* Usually used directly as the @free method in event trigger
* implementations.
*/
-static void
+void
event_trigger_free(struct event_trigger_data *data)
{
if (WARN_ON_ONCE(data->ref <= 0))
diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c
index 08a2829b9eaa..df099a6eb69e 100644
--- a/kernel/trace/trace_wprobe.c
+++ b/kernel/trace/trace_wprobe.c
@@ -6,7 +6,9 @@
*/
#define pr_fmt(fmt) "trace_wprobe: " fmt
+#include <linux/atomic.h>
#include <linux/compiler.h>
+#include <linux/errno.h>
#include <linux/hw_breakpoint.h>
#include <linux/kallsyms.h>
#include <linux/list.h>
@@ -15,11 +17,16 @@
#include <linux/perf_event.h>
#include <linux/rculist.h>
#include <linux/security.h>
+#include <linux/spinlock.h>
#include <linux/tracepoint.h>
#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <linux/irq_work.h>
+#include <linux/preempt.h>
#include <asm/ptrace.h>
+#include "trace.h"
#include "trace_dynevent.h"
#include "trace_probe.h"
#include "trace_probe_kernel.h"
@@ -50,6 +57,10 @@ struct trace_wprobe {
int len;
int type;
const char *symbol;
+ raw_spinlock_t lock;
+ struct irq_work irq_work;
+ struct work_struct work;
+ atomic_t missed;
struct trace_probe tp;
};
@@ -199,14 +210,49 @@ static int __register_trace_wprobe(struct trace_wprobe *tw)
static void __unregister_trace_wprobe(struct trace_wprobe *tw)
{
if (tw->bp_event) {
+ irq_work_sync(&tw->irq_work);
+ cancel_work_sync(&tw->work);
unregister_wide_hw_breakpoint(tw->bp_event);
tw->bp_event = NULL;
}
}
+static int trace_wprobe_update_local(struct trace_wprobe *tw, unsigned long addr)
+{
+ struct perf_event *bp = *this_cpu_ptr(tw->bp_event);
+ struct perf_event_attr attr = bp->attr;
+
+ attr.bp_addr = addr;
+ return modify_wide_hw_breakpoint_local(bp, &attr);
+}
+
+static void wprobe_smp_update_func(void *info)
+{
+ struct trace_wprobe *tw = info;
+ unsigned long addr = READ_ONCE(tw->addr);
+
+ trace_wprobe_update_local(tw, addr);
+}
+
+static void wprobe_work_func(struct work_struct *work)
+{
+ struct trace_wprobe *tw = container_of(work, struct trace_wprobe, work);
+
+ on_each_cpu(wprobe_smp_update_func, tw, true);
+}
+
+static void wprobe_irq_work_func(struct irq_work *irq_work)
+{
+ struct trace_wprobe *tw = container_of(irq_work, struct trace_wprobe, irq_work);
+
+ schedule_work(&tw->work);
+}
+
static void free_trace_wprobe(struct trace_wprobe *tw)
{
if (tw) {
+ irq_work_sync(&tw->irq_work);
+ cancel_work_sync(&tw->work);
trace_probe_cleanup(&tw->tp);
kfree(tw->symbol);
kfree(tw);
@@ -238,6 +284,10 @@ static struct trace_wprobe *alloc_trace_wprobe(const char *group,
tw->addr = addr;
tw->len = len;
tw->type = type;
+ raw_spin_lock_init(&tw->lock);
+ init_irq_work(&tw->irq_work, wprobe_irq_work_func);
+ INIT_WORK(&tw->work, wprobe_work_func);
+ atomic_set(&tw->missed, 0);
ret = trace_probe_init(&tw->tp, event, group, false, nargs);
if (ret < 0)
@@ -745,3 +795,391 @@ static __init int init_wprobe_trace(void)
}
fs_initcall(init_wprobe_trace);
+#ifdef CONFIG_WPROBE_TRIGGERS
+
+static int wprobe_trigger_global_enabled;
+
+#define SET_WPROBE_STR "set_wprobe"
+#define CLEAR_WPROBE_STR "clear_wprobe"
+#define WPROBE_DEFAULT_CLEAR_ADDRESS ((unsigned long)&wprobe_trigger_global_enabled)
+
+struct wprobe_trigger_data {
+ struct rcu_head rcu;
+ struct trace_event_file *file;
+ struct trace_wprobe *tw;
+ unsigned int offset;
+ long adjust;
+ const char *field;
+ bool clear;
+};
+
+static void wprobe_trigger(struct event_trigger_data *data,
+ struct trace_buffer *buffer, void *rec,
+ struct ring_buffer_event *event)
+{
+ struct wprobe_trigger_data *wprobe_data = data->private_data;
+ struct trace_wprobe *tw = wprobe_data->tw;
+ unsigned long addr, flags;
+ bool changed = false;
+
+ addr = *(unsigned long *)(rec + wprobe_data->offset);
+ addr += wprobe_data->adjust;
+
+ if (in_nmi()) {
+ atomic_inc(&tw->missed);
+ return;
+ }
+
+ raw_spin_lock_irqsave(&tw->lock, flags);
+
+ if (!wprobe_data->clear) {
+ if (tw->addr == WPROBE_DEFAULT_CLEAR_ADDRESS) {
+ tw->addr = addr;
+ changed = true;
+ clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags);
+ }
+ } else {
+ if (tw->addr != WPROBE_DEFAULT_CLEAR_ADDRESS) {
+ if (!wprobe_data->field || tw->addr == addr) {
+ tw->addr = WPROBE_DEFAULT_CLEAR_ADDRESS;
+ changed = true;
+ set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags);
+ }
+ }
+ }
+
+ if (changed)
+ irq_work_queue(&tw->irq_work);
+
+ raw_spin_unlock_irqrestore(&tw->lock, flags);
+}
+
+static void free_wprobe_trigger_data(struct wprobe_trigger_data *wprobe_data)
+{
+ if (wprobe_data) {
+ kfree(wprobe_data->field);
+ kfree(wprobe_data);
+ }
+}
+DEFINE_FREE(free_wprobe_trigger_data, struct wprobe_trigger_data *, free_wprobe_trigger_data(_T));
+
+static void free_private_wprobe_trigger_data(void *data)
+{
+ free_wprobe_trigger_data(data);
+}
+
+static int wprobe_trigger_print(struct seq_file *m,
+ struct event_trigger_data *data)
+{
+ struct wprobe_trigger_data *wprobe_data = data->private_data;
+
+ if (wprobe_data->clear) {
+ seq_printf(m, "%s:%s", CLEAR_WPROBE_STR,
+ trace_event_name(wprobe_data->file->event_call));
+ if (wprobe_data->field) {
+ seq_printf(m, ":%s%+ld",
+ wprobe_data->field, wprobe_data->adjust);
+ }
+ } else
+ seq_printf(m, "%s:%s:%s%+ld", SET_WPROBE_STR,
+ trace_event_name(wprobe_data->file->event_call),
+ wprobe_data->field, wprobe_data->adjust);
+
+ if (data->filter_str)
+ seq_printf(m, " if %s\n", data->filter_str);
+ else
+ seq_putc(m, '\n');
+
+ return 0;
+}
+
+static struct wprobe_trigger_data *
+wprobe_trigger_alloc(struct trace_wprobe *tw, struct trace_event_file *file,
+ bool clear)
+{
+ struct wprobe_trigger_data *wprobe_data;
+
+ wprobe_data = kzalloc_obj(*wprobe_data);
+ if (!wprobe_data)
+ return NULL;
+
+ wprobe_data->tw = tw;
+ wprobe_data->clear = clear;
+ wprobe_data->file = file;
+
+ return wprobe_data;
+}
+
+static void wprobe_trigger_free(struct event_trigger_data *data)
+{
+ struct wprobe_trigger_data *wprobe_data = data->private_data;
+
+ if (WARN_ON_ONCE(data->ref <= 0))
+ return;
+
+ data->ref--;
+ if (!data->ref) {
+ /* Remove the SOFT_MODE flag */
+ trace_event_enable_disable(wprobe_data->file, 0, 1);
+ trace_event_put_ref(wprobe_data->file->event_call);
+ trigger_data_free(data);
+ }
+}
+
+static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops,
+ struct trace_event_file *file,
+ char *glob, char *cmd,
+ char *param_and_filter)
+{
+ /*
+ * set_wprobe:EVENT:FIELD[+OFFS]
+ * clear_wprobe:EVENT[:FIELD[+OFFS]]
+ */
+ struct wprobe_trigger_data *wprobe_data __free(free_wprobe_trigger_data) = NULL;
+ struct event_trigger_data *trigger_data __free(kfree) = NULL;
+ struct ftrace_event_field *field = NULL;
+ struct trace_event_file *wprobe_file;
+ struct trace_array *tr = file->tr;
+ struct trace_event_call *event;
+ char *event_str, *field_str;
+ bool remove, clear = false;
+ struct trace_wprobe *tw;
+ char *param, *filter;
+ int ret;
+
+ remove = event_trigger_check_remove(glob);
+
+ if (!strcmp(cmd, CLEAR_WPROBE_STR))
+ clear = true;
+
+ if (event_trigger_empty_param(param_and_filter))
+ return -EINVAL;
+
+ ret = event_trigger_separate_filter(param_and_filter, ¶m, &filter, true);
+ if (ret)
+ return ret;
+
+ if (file->event_call->flags & TRACE_EVENT_FL_KPROBE)
+ return -EOPNOTSUPP;
+
+ event_str = strsep(¶m, ":");
+
+ /* Find target wprobe */
+ tw = find_trace_wprobe(event_str, WPROBE_EVENT_SYSTEM);
+ if (!tw)
+ return -ENOENT;
+ /* The target wprobe must not be used (unless clear) */
+ if (!remove && !clear && trace_probe_is_enabled(&tw->tp))
+ return -EBUSY;
+
+ wprobe_file = find_event_file(tr, WPROBE_EVENT_SYSTEM, event_str);
+ if (!wprobe_file)
+ return -EINVAL;
+
+ wprobe_data = wprobe_trigger_alloc(tw, wprobe_file, clear);
+ if (!wprobe_data)
+ return -ENOMEM;
+
+ /* Find target field, which must be equivarent to "void *" */
+ field_str = strsep(¶m, ":");
+ /* trigger removing or clear_wprobe does not need field. */
+ if (!remove && !clear && !field_str)
+ return -EINVAL;
+
+ if (field_str) {
+ char *offs;
+
+ offs = strpbrk(field_str, "+-");
+ if (offs) {
+ long val;
+
+ if (kstrtol(offs, 0, &val) < 0)
+ return -EINVAL;
+ wprobe_data->adjust = val;
+ *offs = '\0';
+ }
+
+ event = file->event_call;
+ field = trace_find_event_field(event, field_str);
+ if (!field)
+ return -ENOENT;
+
+ if (field->size != sizeof(void *))
+ return -ENOEXEC;
+ wprobe_data->offset = field->offset;
+ wprobe_data->field = kstrdup(field_str, GFP_KERNEL);
+ if (!wprobe_data->field)
+ return -ENOMEM;
+ }
+
+ trigger_data = trigger_data_alloc(cmd_ops, cmd, param, wprobe_data);
+ if (!trigger_data)
+ return -ENOMEM;
+
+ trigger_data->private_free = free_private_wprobe_trigger_data;
+
+ /* Up the trigger_data count to make sure nothing frees it on failure */
+ event_trigger_init(trigger_data);
+
+ if (remove) {
+ event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
+ return 0;
+ }
+
+ ret = event_trigger_parse_num(param, trigger_data);
+ if (ret)
+ return ret;
+
+ ret = event_trigger_set_filter(cmd_ops, file, filter, trigger_data);
+ if (ret < 0)
+ return ret;
+
+ /* Soft-enable (register) wprobe event on WPROBE_DEFAULT_CLEAR_ADDRESS */
+ if (!trace_event_try_get_ref(wprobe_file->event_call)) {
+ event_trigger_reset_filter(cmd_ops, trigger_data);
+ return -ENODEV;
+ }
+
+ if (!clear)
+ WRITE_ONCE(tw->addr, WPROBE_DEFAULT_CLEAR_ADDRESS);
+ ret = trace_event_enable_disable(wprobe_file, 1, 1);
+ if (ret < 0) {
+ trace_event_put_ref(wprobe_file->event_call);
+ event_trigger_reset_filter(cmd_ops, trigger_data);
+ return ret;
+ }
+ ret = event_trigger_register(cmd_ops, file, glob, trigger_data);
+ if (ret) {
+ event_trigger_reset_filter(cmd_ops, trigger_data);
+ trace_event_enable_disable(wprobe_file, 0, 1);
+ trace_event_put_ref(wprobe_file->event_call);
+ synchronize_rcu();
+ return ret;
+ }
+ /* Make it NULL to avoid freeing trigger_data and wprobe_data by __free() */
+ wprobe_data = NULL;
+ event_trigger_free(trigger_data);
+ trigger_data = NULL;
+
+ return 0;
+}
+
+/* Return event_trigger_data if there is a trigger which points the same wprobe */
+static struct event_trigger_data *
+wprobe_trigger_find_same(struct event_trigger_data *test,
+ struct trace_event_file *file)
+{
+ struct wprobe_trigger_data *test_wprobe_data = test->private_data;
+ struct wprobe_trigger_data *wprobe_data;
+ struct event_trigger_data *iter;
+
+ list_for_each_entry(iter, &file->triggers, list) {
+ wprobe_data = iter->private_data;
+ if (!wprobe_data ||
+ iter->cmd_ops->trigger_type !=
+ test->cmd_ops->trigger_type)
+ continue;
+ if (wprobe_data->tw == test_wprobe_data->tw)
+ return iter;
+ }
+ return NULL;
+}
+
+static int wprobe_register_trigger(char *glob,
+ struct event_trigger_data *data,
+ struct trace_event_file *file)
+{
+ int ret = 0;
+
+ lockdep_assert_held(&event_mutex);
+
+ /* The same wprobe is not accept on the same file (event) */
+ if (wprobe_trigger_find_same(data, file))
+ return -EEXIST;
+
+ if (data->cmd_ops->init) {
+ ret = data->cmd_ops->init(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ list_add_rcu(&data->list, &file->triggers);
+
+ update_cond_flag(file);
+ ret = trace_event_trigger_enable_disable(file, 1);
+ if (ret < 0) {
+ list_del_rcu(&data->list);
+ update_cond_flag(file);
+ }
+ return ret;
+}
+
+static void wprobe_unregister_trigger(char *glob,
+ struct event_trigger_data *test,
+ struct trace_event_file *file)
+{
+ struct event_trigger_data *data;
+
+ lockdep_assert_held(&event_mutex);
+
+ data = wprobe_trigger_find_same(test, file);
+ if (!data)
+ return;
+
+ list_del_rcu(&data->list);
+ trace_event_trigger_enable_disable(file, 0);
+ update_cond_flag(file);
+ if (data->cmd_ops->free)
+ data->cmd_ops->free(data);
+}
+
+static struct event_command trigger_wprobe_set_cmd = {
+ .name = SET_WPROBE_STR,
+ .trigger_type = ETT_EVENT_WPROBE,
+ /* This triggers after when the event is recorded. */
+ .flags = EVENT_CMD_FL_NEEDS_REC,
+ .parse = wprobe_trigger_cmd_parse,
+ .reg = wprobe_register_trigger,
+ .unreg = wprobe_unregister_trigger,
+ .set_filter = set_trigger_filter,
+ .trigger = wprobe_trigger,
+ .count_func = event_trigger_count,
+ .print = wprobe_trigger_print,
+ .init = event_trigger_init,
+ .free = wprobe_trigger_free,
+};
+
+static struct event_command trigger_wprobe_clear_cmd = {
+ .name = CLEAR_WPROBE_STR,
+ .trigger_type = ETT_EVENT_WPROBE,
+ /* This triggers after when the event is recorded. */
+ .flags = EVENT_CMD_FL_NEEDS_REC,
+ .parse = wprobe_trigger_cmd_parse,
+ .reg = wprobe_register_trigger,
+ .unreg = wprobe_unregister_trigger,
+ .set_filter = set_trigger_filter,
+ .trigger = wprobe_trigger,
+ .count_func = event_trigger_count,
+ .print = wprobe_trigger_print,
+ .init = event_trigger_init,
+ .free = wprobe_trigger_free,
+};
+
+static __init int init_trigger_wprobe_cmds(void)
+{
+ int ret;
+
+ ret = register_event_command(&trigger_wprobe_set_cmd);
+ if (WARN_ON(ret < 0))
+ return ret;
+ ret = register_event_command(&trigger_wprobe_clear_cmd);
+ if (WARN_ON(ret < 0))
+ unregister_event_command(&trigger_wprobe_set_cmd);
+
+ if (!ret)
+ wprobe_trigger_global_enabled = 1;
+
+ return ret;
+}
+fs_initcall(init_trigger_wprobe_cmds);
+#endif /* CONFIG_WPROBE_TRIGGERS */
^ permalink raw reply related
* [PATCH v9 09/10] selftests: ftrace: Add wprobe trigger testcase
From: Masami Hiramatsu (Google) @ 2026-07-17 14:21 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a testcase for checking wprobe trigger. This sets set_wprobe and
clear_wprobe triggers on fprobe events to watch dentry access.
So this depends on both wprobe and fprobe.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v9:
- Drop dentry cache after removing tempfile for on-disk
filesystem.
Changes in v8:
- Use TMPDIR for making a test file.
- Ensure the fprobe and target functions exists, if not, exit
with UNRESOLVED (== skip).
Changes in v7:
- Add a newline at the end of file.(style fix)
- Use dentry_kill instead of __dentry_kill.
Changes in v5:
- Enable CONFIG_WPROBE_TRIGGERS in the config for ftrace test.
Changes in v3:
- Newly added.
---
tools/testing/selftests/ftrace/config | 1
.../ftrace/test.d/trigger/trigger-wprobe.tc | 70 ++++++++++++++++++++
2 files changed, 71 insertions(+)
create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftests/ftrace/config
index d2f503722020..ecdee77f360f 100644
--- a/tools/testing/selftests/ftrace/config
+++ b/tools/testing/selftests/ftrace/config
@@ -28,3 +28,4 @@ CONFIG_TRACER_SNAPSHOT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
CONFIG_WPROBE_EVENTS=y
+CONFIG_WPROBE_TRIGGERS=y
diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
new file mode 100644
index 000000000000..0634259e2c65
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe.tc
@@ -0,0 +1,70 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: event trigger - test wprobe trigger
+# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README events/sched/sched_process_fork/trigger
+
+echo 0 > tracing_on
+rm -f $TMPDIR/hoge
+
+:;: "Add a wprobe event used by trigger" ;:
+echo 'w:watch rw@0:8 address=$addr value=+0($addr)' > dynamic_events
+
+# we will skip this test if fprobe is not supported.
+if ! grep -Fq "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README; then
+ echo "UNRESOLVED: fprobe is not supported"
+ exit_unresolved
+fi
+
+# we will skip this test if the target function does not exist.
+if ! grep -wq "do_truncate" /proc/kallsyms; then
+ echo "UNRESOLVED: do_truncate not found"
+ exit_unresolved
+fi
+if ! grep -wq "dentry_kill" /proc/kallsyms; then
+ echo "UNRESOLVED: dentry_kill not found"
+ exit_unresolved
+fi
+
+:;: "Add events for triggering wprobe" ;:
+echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events
+echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events
+
+:;: "Add wprobe triggers" ;:
+echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger
+cat events/fprobes/truncate/trigger | grep ^set_wprobe
+cat events/fprobes/dentry_kill/trigger | grep ^clear_wprobe
+
+:;: "Ensure wprobe is still disabled" ;:
+cat events/wprobes/watch/enable | grep 0
+
+:;: "Enable events for triggers" ;:
+echo 1 >> events/fprobes/truncate/enable
+echo 1 >> events/fprobes/dentry_kill/enable
+
+:;: "Start test workload" ;:
+echo 1 >> tracing_on
+
+echo aaa > $TMPDIR/hoge
+echo bbb > $TMPDIR/hoge
+echo ccc > $TMPDIR/hoge
+rm $TMPDIR/hoge
+
+:;: "Drop dentry caches (for dentry_kill)" ;:
+# Use append (>>) to avoid calling do_truncate again.
+sync && echo 2 >> /proc/sys/vm/drop_caches
+
+:;: "Check trace results" ;:
+cat trace | grep watch
+
+
+:;: "Ensure wprobe becomes disabled again" ;:
+cat events/wprobes/watch/enable | grep 0
+
+:;: "Remove wprobe triggers" ;:
+echo '!set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+echo '!clear_wprobe:watch' >> events/fprobes/dentry_kill/trigger
+! grep ^set_wprobe events/fprobes/truncate/trigger
+! grep ^clear_wprobe events/fprobes/dentry_kill/trigger
+
+exit 0
^ permalink raw reply related
* [PATCH v9 10/10] tracing/wprobe: Support BTF typecast in fetchargs
From: Masami Hiramatsu (Google) @ 2026-07-17 14:21 UTC (permalink / raw)
To: Steven Rostedt, Peter Zijlstra, Ingo Molnar, x86
Cc: Jinchao Wang, Mathieu Desnoyers, Masami Hiramatsu,
Thomas Gleixner, Borislav Petkov, Dave Hansen, H . Peter Anvin,
Alexander Shishkin, Ian Rogers, linux-kernel, linux-trace-kernel,
linux-doc, linux-perf-users
In-Reply-To: <178429796992.157981.3393977217853767915.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Allow BTF typecast syntax (STRUCT)FETCHARG->MEMBER in wprobe event
fetchargs. Previously, handle_typecast() rejected any probe context
that was not a function entry/return or tracepoint event probe.
Wprobe events use (the accessed address) and (the value
at that address). By enabling BTF typecast, users can now cast these
to a concrete struct type and access its fields directly. For example:
echo 'w:watch rw@0:8 dflag=(struct dentry)$addr->d_flags' >> dynamic_events
With a set_wprobe trigger pointing the watchpoint at a dentry address,
the resulting trace shows d_flags being accessed at that location.
Note that and are restricted to kernel-space memory,
which is consistent with the existing TPARG_FL_KERNEL flag used when
parsing wprobe fetchargs.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v9:
- Newly added.
---
kernel/trace/trace_probe.c | 3 +
kernel/trace/trace_probe.h | 5 +
.../test.d/trigger/trigger-wprobe-btf-typecast.tc | 80 ++++++++++++++++++++
3 files changed, 87 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-typecast.tc
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 5d5e9b477b86..8332ff1bb4ff 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -967,7 +967,8 @@ static int handle_typecast(char *arg, struct traceprobe_parse_context *ctx)
if (!(tparg_is_event_probe(ctx->flags) ||
tparg_is_function_entry(ctx->flags) ||
- tparg_is_function_return(ctx->flags))) {
+ tparg_is_function_return(ctx->flags) ||
+ tparg_is_wprobe(ctx->flags))) {
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EOPNOTSUPP;
}
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 7380502a85af..0a83b3fb6128 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -439,6 +439,11 @@ static inline bool tparg_is_event_probe(unsigned int flags)
return !!(flags & TPARG_FL_TEVENT);
}
+static inline bool tparg_is_wprobe(unsigned int flags)
+{
+ return !!(flags & TPARG_FL_WPROBE);
+}
+
/* Each typecast consumes nested level. So the max number of typecast is 8. */
#define TRACEPROBE_MAX_NESTED_LEVEL 8
diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-typecast.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-typecast.tc
new file mode 100644
index 000000000000..8962c91d8428
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-typecast.tc
@@ -0,0 +1,80 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: event trigger - test wprobe trigger with BTF typecast fetchargs
+# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README events/sched/sched_process_fork/trigger "[(structname[,field])]<argname>[->field[->field|.field...]]":README
+
+echo 0 >> tracing_on
+
+rm -f $TMPDIR/hoge
+
+# we will skip this test if fprobe is not supported.
+if ! grep -Fq "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README; then
+ echo "UNRESOLVED: fprobe is not supported"
+ exit_unresolved
+fi
+
+# we will skip this test if the target function does not exist.
+if ! grep -wq "do_truncate" /proc/kallsyms; then
+ echo "UNRESOLVED: do_truncate not found"
+ exit_unresolved
+fi
+if ! grep -wq "dentry_kill" /proc/kallsyms; then
+ echo "UNRESOLVED: dentry_kill not found"
+ exit_unresolved
+fi
+
+:;: "Add a wprobe event with BTF typecast fetchargs" ;:
+# $addr is the address being accessed (= dentry pointer when watching dentry)
+# (dentry)$addr->d_flags reads d_flags from the dentry struct via BTF typecast
+# Note: BTF typecast uses (STRUCT) without the 'struct' keyword, matching
+# the fetcharg syntax used in fprobe/tprobe events.
+echo 'w:watch rw@0:8 address=$addr dflag=(dentry)$addr->d_flags' >> dynamic_events
+
+:;: "Check the wprobe event is registered with dflag field" ;:
+grep -q "dflag" dynamic_events
+
+:;: "Add events for triggering wprobe" ;:
+echo 'f:truncate do_truncate dentry=$arg2' >> dynamic_events
+echo 'f:dentry_kill dentry_kill dentry=$arg1' >> dynamic_events
+
+:;: "Add wprobe triggers" ;:
+echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+echo 'clear_wprobe:watch:dentry' >> events/fprobes/dentry_kill/trigger
+cat events/fprobes/truncate/trigger | grep ^set_wprobe
+cat events/fprobes/dentry_kill/trigger | grep ^clear_wprobe
+
+:;: "Ensure wprobe is still disabled" ;:
+cat events/wprobes/watch/enable | grep 0
+
+:;: "Enable events for triggers" ;:
+echo 1 >> events/fprobes/truncate/enable
+echo 1 >> events/fprobes/dentry_kill/enable
+
+:;: "Start test workload" ;:
+echo 1 >> tracing_on
+
+echo aaa > $TMPDIR/hoge
+sleep 1
+echo bbb > $TMPDIR/hoge
+sleep 1
+echo ccc > $TMPDIR/hoge
+sleep 1
+rm $TMPDIR/hoge
+
+:;: "Drop dentry caches (for dentry_kill)" ;:
+sync && echo 2 >> /proc/sys/vm/drop_caches
+
+:;: "Check trace results include BTF typecast field dflag" ;:
+cat trace > /tmp/test-trace-typecast.log
+cat trace | grep "watch.*dflag="
+
+:;: "Ensure wprobe becomes disabled again" ;:
+cat events/wprobes/watch/enable | grep 0
+
+:;: "Remove wprobe triggers" ;:
+echo '!set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger
+echo '!clear_wprobe:watch' >> events/fprobes/dentry_kill/trigger
+! grep ^set_wprobe events/fprobes/truncate/trigger
+! grep ^clear_wprobe events/fprobes/dentry_kill/trigger
+
+exit 0
^ permalink raw reply related
* [PATCH v4 00/17] rv: Add selftests to tools and KUnit tests
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel
Cc: Gabriele Monaco, Steven Rostedt, Nam Cao, Thomas Weissschuh,
Tomas Glozar, John Kacur, Wen Yang
This series adds support to the make check target in the rv userspace
tool and the rvgen script, this allows to quickly validate its
functionality. The selftest framework is inspired by the one used in
RTLA.
A few bugs in both tools were also discovered and are fixed as part of
this series.
Additionally it adds unit tests for models. This is achieved by running
the handlers functions directly within KUnit, emulating all modules
paths as if real kernel events fired.
Unit tests emulate a series of events that are expected to trigger
violations and checks that a reaction occurred, stub structs and
functions are used so the kernel is not affected by the test.
Finally it adds a few kselftests for new monitors and improves existing
ones.
Difference since V3 [1]:
* Retry multiple times when getting the pid to avoid races in selftests
* Use pathlib instead of os.path in rvgen
* Rename files to backup instead of writing to backup in rvgen kunit
* Use deadline_thresh in nomiss KUnit test
* Special init/destroy for per-task monitors to avoid touching real
tasks and starting tracepoints in KUnit test
* Use actual reactor instead of KUnit stub to support timer reactions
* Wait for dmesg to flush all messages after reactions in wwnr selftest
* Restore original stall threshold after selftest
Difference since V2 [2]:
* Use general rv_this also in LTL monitors
* Refactor KUnit tests to allow build as module
* Add deadline and stall kselftests
* Fix errexit assumption on kselftests
* Adapt rvgen selftests after rebase
Differences since RFC [3]:
* Fix issue with LTL generator printing literals as uppercase
* Add missing state label in selftest dot spec
* Fail selftest if pid was required but not found (harness error)
* Remove useless static keywords in KUnit tests
* Assert after kunit_kzalloc()
* Use RV_KUNIT_EXPECT_REACTION_HERE to avoid false positives
* Prevent running RV monitors and events together with KUnit
* Rearrange KUnit testing headers
* Expect no reaction at the end of KUnit test cases
* Fix broken nomiss test and allocation
[1] - https://lore.kernel.org/lkml/20260625121440.116317-1-gmonaco@redhat.com
[2] - https://lore.kernel.org/lkml/20260514152055.229162-1-gmonaco@redhat.com
[3] - https://lore.kernel.org/lkml/20260427151134.192971-1-gmonaco@redhat.com
To: linux-trace-kernel@vger.kernel.org
To: linux-kernel@vger.kernel.org
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Nam Cao <namcao@linutronix.de>
Cc: Thomas Weissschuh <thomas.weissschuh@linutronix.de>
Cc: Tomas Glozar <tglozar@redhat.com>
Cc: John Kacur <jkacur@redhat.com>
Cc: Wen Yang <wen.yang@linux.dev>
Gabriele Monaco (17):
rv: Use generic rv_this for the rv_monitor variable in LTL
tools/rv: Fix exit status when monitor execution fails
verification/rvgen: Improve rv_dir discovery in RVGenerator
verification/rvgen: Use pathlib instead of os.path
verification/rvgen: Improve consistency in template files
tools/rv: Add selftests
verification/rvgen: Add golden and spec folders for tests
verification/rvgen: Add selftests
verification/rvgen: Add the rvgen kunit subcommand
verification/rvgen: Add selftests for rvgen kunit
rv: Export task monitor slot and react symbols
rv: Add KUnit tests for some DA/HA monitors
rv: Add KUnit stub for current
rv: Add KUnit tests for some LTL monitors
selftests/verification: Fix wrong errexit assumption
selftests/verification: Rearrange the wwnr_printk test
selftests/verification: Add selftests for deadline and stall monitors
include/rv/da_monitor.h | 23 ++
include/rv/ha_monitor.h | 21 ++
include/rv/kunit.h | 72 +++++
include/rv/ltl_monitor.h | 14 +-
kernel/trace/rv/Kconfig | 14 +
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 18 ++
.../trace/rv/monitors/nomiss/nomiss_kunit.c | 38 +++
.../trace/rv/monitors/nomiss/nomiss_kunit.h | 35 +++
kernel/trace/rv/monitors/opid/opid.c | 12 +
kernel/trace/rv/monitors/opid/opid_kunit.c | 33 +++
kernel/trace/rv/monitors/opid/opid_kunit.h | 23 ++
.../trace/rv/monitors/pagefault/pagefault.c | 20 +-
.../rv/monitors/pagefault/pagefault_kunit.c | 34 +++
.../rv/monitors/pagefault/pagefault_kunit.h | 24 ++
kernel/trace/rv/monitors/sco/sco.c | 13 +
kernel/trace/rv/monitors/sco/sco_kunit.c | 29 ++
kernel/trace/rv/monitors/sco/sco_kunit.h | 24 ++
kernel/trace/rv/monitors/sleep/sleep.c | 49 +++-
kernel/trace/rv/monitors/sleep/sleep_kunit.c | 58 ++++
kernel/trace/rv/monitors/sleep/sleep_kunit.h | 30 ++
kernel/trace/rv/monitors/sssw/sssw.c | 14 +
kernel/trace/rv/monitors/sssw/sssw_kunit.c | 33 +++
kernel/trace/rv/monitors/sssw/sssw_kunit.h | 30 ++
kernel/trace/rv/monitors/sts/sts.c | 19 ++
kernel/trace/rv/monitors/sts/sts_kunit.c | 39 +++
kernel/trace/rv/monitors/sts/sts_kunit.h | 33 +++
kernel/trace/rv/rv.c | 50 ++++
kernel/trace/rv/rv_monitors_test.c | 188 +++++++++++++
kernel/trace/rv/rv_reactors.c | 1 +
.../verification/test.d/rv_deadline.tc | 21 ++
.../test.d/rv_monitor_enable_disable.tc | 10 +-
.../verification/test.d/rv_monitor_reactor.tc | 4 +-
.../selftests/verification/test.d/rv_stall.tc | 33 +++
.../verification/test.d/rv_wwnr_printk.tc | 27 +-
tools/verification/rv/Makefile | 5 +-
tools/verification/rv/src/rv.c | 24 +-
tools/verification/rv/tests/rv_list.t | 48 ++++
tools/verification/rv/tests/rv_mon.t | 95 +++++++
tools/verification/rvgen/Makefile | 5 +
tools/verification/rvgen/__main__.py | 15 +-
tools/verification/rvgen/rvgen/generator.py | 51 ++--
tools/verification/rvgen/rvgen/kunit.py | 194 +++++++++++++
tools/verification/rvgen/rvgen/ltl2k.py | 2 +-
.../rvgen/rvgen/templates/container/main.c | 2 +-
.../rvgen/rvgen/templates/dot2k/main.c | 2 +-
.../rvgen/rvgen/templates/kunit.c | 33 +++
.../rvgen/rvgen/templates/ltl2k/main.c | 8 +-
.../rvgen/tests/golden/da_global/Kconfig | 9 +
.../rvgen/tests/golden/da_global/da_global.c | 95 +++++++
.../rvgen/tests/golden/da_global/da_global.h | 47 ++++
.../tests/golden/da_global/da_global_trace.h | 15 +
.../tests/golden/da_perobj_parent/Kconfig | 11 +
.../da_perobj_parent/da_perobj_parent.c | 110 ++++++++
.../da_perobj_parent/da_perobj_parent.h | 64 +++++
.../da_perobj_parent/da_perobj_parent_trace.h | 15 +
.../tests/golden/da_pertask_desc/Kconfig | 9 +
.../golden/da_pertask_desc/da_pertask_desc.c | 105 +++++++
.../golden/da_pertask_desc/da_pertask_desc.h | 64 +++++
.../da_pertask_desc/da_pertask_desc_trace.h | 15 +
.../rvgen/tests/golden/ha_percpu/Kconfig | 9 +
.../rvgen/tests/golden/ha_percpu/ha_percpu.c | 244 ++++++++++++++++
.../rvgen/tests/golden/ha_percpu/ha_percpu.h | 72 +++++
.../tests/golden/ha_percpu/ha_percpu_trace.h | 19 ++
.../rvgen/tests/golden/ltl_pertask/Kconfig | 9 +
.../tests/golden/ltl_pertask/ltl_pertask.c | 107 +++++++
.../tests/golden/ltl_pertask/ltl_pertask.h | 108 ++++++++
.../golden/ltl_pertask/ltl_pertask_trace.h | 14 +
.../rvgen/tests/golden/test_bak_kunit/Kconfig | 9 +
.../golden/test_bak_kunit/test_bak_kunit.c | 107 +++++++
.../golden/test_bak_kunit/test_bak_kunit.h | 108 ++++++++
.../test_bak_kunit/test_bak_kunit_kunit.c | 33 +++
.../test_bak_kunit/test_bak_kunit_kunit.c.bak | 1 +
.../test_bak_kunit/test_bak_kunit_kunit.h | 22 ++
.../test_bak_kunit/test_bak_kunit_trace.h | 14 +
.../rvgen/tests/golden/test_container/Kconfig | 5 +
.../golden/test_container/test_container.c | 35 +++
.../golden/test_container/test_container.h | 3 +
.../rvgen/tests/golden/test_da/Kconfig | 9 +
.../rvgen/tests/golden/test_da/test_da.c | 95 +++++++
.../rvgen/tests/golden/test_da/test_da.h | 47 ++++
.../tests/golden/test_da/test_da_trace.h | 15 +
.../rvgen/tests/golden/test_da_kunit/Kconfig | 9 +
.../golden/test_da_kunit/test_da_kunit.c | 107 +++++++
.../golden/test_da_kunit/test_da_kunit.h | 47 ++++
.../test_da_kunit/test_da_kunit_kunit.c | 33 +++
.../test_da_kunit/test_da_kunit_kunit.h | 23 ++
.../test_da_kunit/test_da_kunit_trace.h | 15 +
.../rvgen/tests/golden/test_ha/Kconfig | 9 +
.../rvgen/tests/golden/test_ha/test_ha.c | 247 +++++++++++++++++
.../rvgen/tests/golden/test_ha/test_ha.h | 72 +++++
.../tests/golden/test_ha/test_ha_trace.h | 19 ++
.../rvgen/tests/golden/test_ha_kunit/Kconfig | 9 +
.../golden/test_ha_kunit/test_ha_kunit.c | 260 ++++++++++++++++++
.../golden/test_ha_kunit/test_ha_kunit.h | 88 ++++++
.../test_ha_kunit/test_ha_kunit_kunit.c | 33 +++
.../test_ha_kunit/test_ha_kunit_kunit.h | 24 ++
.../test_ha_kunit/test_ha_kunit_trace.h | 19 ++
.../rvgen/tests/golden/test_ltl/Kconfig | 11 +
.../rvgen/tests/golden/test_ltl/test_ltl.c | 108 ++++++++
.../rvgen/tests/golden/test_ltl/test_ltl.h | 108 ++++++++
.../tests/golden/test_ltl/test_ltl_trace.h | 14 +
.../rvgen/tests/golden/test_ltl_kunit/Kconfig | 9 +
.../golden/test_ltl_kunit/test_ltl_kunit.c | 107 +++++++
.../golden/test_ltl_kunit/test_ltl_kunit.h | 108 ++++++++
.../test_ltl_kunit/test_ltl_kunit_kunit.c | 33 +++
.../test_ltl_kunit/test_ltl_kunit_kunit.h | 22 ++
.../test_ltl_kunit/test_ltl_kunit_trace.h | 14 +
.../rvgen/tests/rvgen_container.t | 20 ++
tools/verification/rvgen/tests/rvgen_kunit.t | 41 +++
.../verification/rvgen/tests/rvgen_monitor.t | 87 ++++++
.../rvgen/tests/specs/test_da.dot | 16 ++
.../rvgen/tests/specs/test_da2.dot | 19 ++
.../rvgen/tests/specs/test_ha.dot | 27 ++
.../rvgen/tests/specs/test_invalid.dot | 8 +
.../rvgen/tests/specs/test_invalid.ltl | 1 +
.../rvgen/tests/specs/test_invalid_ha.dot | 16 ++
.../rvgen/tests/specs/test_ltl.ltl | 1 +
tools/verification/tests/engine.sh | 175 ++++++++++++
119 files changed, 5093 insertions(+), 81 deletions(-)
create mode 100644 include/rv/kunit.h
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
create mode 100644 kernel/trace/rv/monitors/opid/opid_kunit.c
create mode 100644 kernel/trace/rv/monitors/opid/opid_kunit.h
create mode 100644 kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
create mode 100644 kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
create mode 100644 kernel/trace/rv/monitors/sco/sco_kunit.c
create mode 100644 kernel/trace/rv/monitors/sco/sco_kunit.h
create mode 100644 kernel/trace/rv/monitors/sleep/sleep_kunit.c
create mode 100644 kernel/trace/rv/monitors/sleep/sleep_kunit.h
create mode 100644 kernel/trace/rv/monitors/sssw/sssw_kunit.c
create mode 100644 kernel/trace/rv/monitors/sssw/sssw_kunit.h
create mode 100644 kernel/trace/rv/monitors/sts/sts_kunit.c
create mode 100644 kernel/trace/rv/monitors/sts/sts_kunit.h
create mode 100644 kernel/trace/rv/rv_monitors_test.c
create mode 100644 tools/testing/selftests/verification/test.d/rv_deadline.tc
create mode 100644 tools/testing/selftests/verification/test.d/rv_stall.tc
create mode 100644 tools/verification/rv/tests/rv_list.t
create mode 100644 tools/verification/rv/tests/rv_mon.t
create mode 100644 tools/verification/rvgen/rvgen/kunit.py
create mode 100644 tools/verification/rvgen/rvgen/templates/kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/da_global/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.c
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.h
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_container/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.c
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/rvgen_container.t
create mode 100644 tools/verification/rvgen/tests/rvgen_kunit.t
create mode 100644 tools/verification/rvgen/tests/rvgen_monitor.t
create mode 100644 tools/verification/rvgen/tests/specs/test_da.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_da2.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.ltl
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ltl.ltl
create mode 100644 tools/verification/tests/engine.sh
base-commit: a13c140cc289c0b7b3770bce5b3ad42ab35074aa
--
2.55.0
^ permalink raw reply
* [PATCH v4 01/17] rv: Use generic rv_this for the rv_monitor variable in LTL
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Align the rv_monitor variable name in LTL to the generic rv_this as it
is already done for DA/HA monitors. This improves consistency and eases
assumptions across model classes.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/ltl_monitor.h | 5 ++---
kernel/trace/rv/monitors/pagefault/pagefault.c | 6 +++---
kernel/trace/rv/monitors/sleep/sleep.c | 6 +++---
tools/verification/rvgen/rvgen/templates/ltl2k/main.c | 6 +++---
4 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h
index 38e792401f76..56e83edcf0c4 100644
--- a/include/rv/ltl_monitor.h
+++ b/include/rv/ltl_monitor.h
@@ -16,8 +16,7 @@
#error "Please include $(MODEL_NAME).h generated by rvgen"
#endif
-#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME)
-static struct rv_monitor RV_MONITOR_NAME;
+static struct rv_monitor rv_this;
static int ltl_monitor_slot = RV_PER_TASK_MONITOR_INIT;
@@ -85,7 +84,7 @@ static void ltl_monitor_destroy(void)
static void ltl_illegal_state(struct task_struct *task, struct ltl_monitor *mon)
{
CONCATENATE(trace_error_, MONITOR_NAME)(task);
- rv_react(&RV_MONITOR_NAME, "rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n",
+ rv_react(&rv_this, "rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n",
task->comm, task->pid);
}
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index 9fe6123b2200..5e1a2a606783 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -63,7 +63,7 @@ static void disable_pagefault(void)
ltl_monitor_destroy();
}
-static struct rv_monitor rv_pagefault = {
+static struct rv_monitor rv_this = {
.name = "pagefault",
.description = "Monitor that RT tasks do not raise page faults",
.enable = enable_pagefault,
@@ -72,12 +72,12 @@ static struct rv_monitor rv_pagefault = {
static int __init register_pagefault(void)
{
- return rv_register_monitor(&rv_pagefault, &rv_rtapp);
+ return rv_register_monitor(&rv_this, &rv_rtapp);
}
static void __exit unregister_pagefault(void)
{
- rv_unregister_monitor(&rv_pagefault);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_pagefault);
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 8dfe5ec13e19..12328ce663f5 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -224,7 +224,7 @@ static void disable_sleep(void)
ltl_monitor_destroy();
}
-static struct rv_monitor rv_sleep = {
+static struct rv_monitor rv_this = {
.name = "sleep",
.description = "Monitor that RT tasks do not undesirably sleep",
.enable = enable_sleep,
@@ -233,12 +233,12 @@ static struct rv_monitor rv_sleep = {
static int __init register_sleep(void)
{
- return rv_register_monitor(&rv_sleep, &rv_rtapp);
+ return rv_register_monitor(&rv_this, &rv_rtapp);
}
static void __exit unregister_sleep(void)
{
- rv_unregister_monitor(&rv_sleep);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_sleep);
diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
index f85d076fbf78..31258b9ea083 100644
--- a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
@@ -77,7 +77,7 @@ static void disable_%%MODEL_NAME%%(void)
/*
* This is the monitor register section.
*/
-static struct rv_monitor rv_%%MODEL_NAME%% = {
+static struct rv_monitor rv_this = {
.name = "%%MODEL_NAME%%",
.description = "%%DESCRIPTION%%",
.enable = enable_%%MODEL_NAME%%,
@@ -86,12 +86,12 @@ static struct rv_monitor rv_%%MODEL_NAME%% = {
static int __init register_%%MODEL_NAME%%(void)
{
- return rv_register_monitor(&rv_%%MODEL_NAME%%, %%PARENT%%);
+ return rv_register_monitor(&rv_this, %%PARENT%%);
}
static void __exit unregister_%%MODEL_NAME%%(void)
{
- rv_unregister_monitor(&rv_%%MODEL_NAME%%);
+ rv_unregister_monitor(&rv_this);
}
module_init(register_%%MODEL_NAME%%);
--
2.55.0
^ permalink raw reply related
* [PATCH v4 02/17] tools/rv: Fix exit status when monitor execution fails
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
When running "rv mon" on a monitor that is already enabled, the tool
fails to start but incorrectly exits with a success status (0).
Fix the exit condition to ensure it returns a failure code on any
execution error. Also use the standard EXIT_SUCCESS/EXIT_FAILURE macros
throughout the file.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/src/rv.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index b8fe24a87d97..09e0d8598619 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -50,23 +50,23 @@ static void rv_list(int argc, char **argv)
" [container]: list only monitors in this container",
NULL,
};
- int i, print_help = 0, retval = 0;
+ int i, print_help = 0, retval = EXIT_SUCCESS;
char *container = NULL;
if (argc == 2) {
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
print_help = 1;
- retval = 0;
+ retval = EXIT_SUCCESS;
} else if (argv[1][0] == '-') {
/* assume invalid option */
print_help = 1;
- retval = 1;
+ retval = EXIT_FAILURE;
} else
container = argv[1];
} else if (argc > 2) {
/* more than 2 is always usage */
print_help = 1;
- retval = 1;
+ retval = EXIT_FAILURE;
}
if (print_help) {
fprintf(stderr, "rv version %s\n", VERSION);
@@ -77,7 +77,7 @@ static void rv_list(int argc, char **argv)
ikm_list_monitors(container);
- exit(0);
+ exit(EXIT_SUCCESS);
}
/*
@@ -108,14 +108,14 @@ static void rv_mon(int argc, char **argv)
for (i = 0; usage[i]; i++)
fprintf(stderr, "%s\n", usage[i]);
- exit(1);
+ exit(EXIT_FAILURE);
} else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "rv version %s\n", VERSION);
for (i = 0; usage[i]; i++)
fprintf(stderr, "%s\n", usage[i]);
- exit(0);
+ exit(EXIT_SUCCESS);
}
monitor_name = argv[1];
@@ -127,7 +127,7 @@ static void rv_mon(int argc, char **argv)
if (!run)
err_msg("rv: monitor %s does not exist\n", monitor_name);
- exit(!run);
+ exit(run > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
static void usage(int exit_val, const char *fmt, ...)
@@ -174,13 +174,13 @@ static void usage(int exit_val, const char *fmt, ...)
int main(int argc, char **argv)
{
if (geteuid())
- usage(1, "%s needs root permission", argv[0]);
+ usage(EXIT_FAILURE, "%s needs root permission", argv[0]);
if (argc <= 1)
- usage(1, "%s requires a command", argv[0]);
+ usage(EXIT_FAILURE, "%s requires a command", argv[0]);
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
- usage(0, "help");
+ usage(EXIT_SUCCESS, "help");
if (!strcmp(argv[1], "list"))
rv_list(--argc, &argv[1]);
@@ -197,5 +197,5 @@ int main(int argc, char **argv)
}
/* invalid sub-command */
- usage(1, "%s does not know the %s command, old version?", argv[0], argv[1]);
+ usage(EXIT_FAILURE, "%s does not know the %s command, old version?", argv[0], argv[1]);
}
--
2.55.0
^ permalink raw reply related
* [PATCH v4 03/17] verification/rvgen: Improve rv_dir discovery in RVGenerator
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
The RVGenerator class can find the RV directory (kernel/trace/rv) in the
kernel tree to do some auto patching. This works by assuming PWD is
either the kernel tree or tools/verification, which isn't always the
case (e.g. when running from selftests).
Make discovery more robust by relying on the absolute path of the
current script and traversing backwards the right number of times.
This should work from any location if rvgen is in the kernel tree.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
Notes:
V4:
* Use pathlib's parents array to climb up directories
tools/verification/rvgen/rvgen/generator.py | 29 ++++++++++++---------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 56f3bd8db850..1c20f7d1905c 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -7,6 +7,7 @@
import platform
import os
+from pathlib import Path
class RVGenerator:
@@ -25,27 +26,29 @@ class RVGenerator:
self.__fill_rv_kernel_dir()
def __fill_rv_kernel_dir(self):
-
- # first try if we are running in the kernel tree root
- if os.path.exists(self.rv_dir):
- return
-
- # offset if we are running inside the kernel tree from verification/dot2
- kernel_path = os.path.join("../..", self.rv_dir)
-
- if os.path.exists(kernel_path):
- self.rv_dir = kernel_path
+ # find the kernel tree root relative to this file's location
+ resolved_path = Path(__file__).resolve()
+ if len(resolved_path.parents) > 4:
+ kernel_root = resolved_path.parents[4]
+ kernel_path = kernel_root / self.rv_dir
+
+ if kernel_path.exists():
+ self.rv_dir = str(kernel_path)
+ return
+
+ # best effort if rvgen is installed and we are at the root of a kernel tree
+ if Path(self.rv_dir).exists():
return
if platform.system() != "Linux":
raise OSError("I can only run on Linux.")
- kernel_path = os.path.join(f"/lib/modules/{platform.release()}/build", self.rv_dir)
+ kernel_path = Path(f"/lib/modules/{platform.release()}/build") / self.rv_dir
# if the current kernel is from a distro this may not be a full kernel tree
# verify that one of the files we are going to modify is available
- if os.path.exists(os.path.join(kernel_path, "rv_trace.h")):
- self.rv_dir = kernel_path
+ if (kernel_path / "rv_trace.h").exists():
+ self.rv_dir = str(kernel_path)
return
raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
--
2.55.0
^ permalink raw reply related
* [PATCH v4 04/17] verification/rvgen: Use pathlib instead of os.path
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Migrate to the newer patlib library, bundled with python since 3.4 to
increase readability over using os.path.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/rvgen/generator.py | 22 +++++++++------------
1 file changed, 9 insertions(+), 13 deletions(-)
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 1c20f7d1905c..f1b37d34b1e9 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -6,7 +6,6 @@
# Abstract class for generating kernel runtime verification monitors from specification file
import platform
-import os
from pathlib import Path
@@ -17,7 +16,7 @@ class RVGenerator:
self.name = extra_params.get("model_name")
self.parent = extra_params.get("parent")
self.abs_template_dir = \
- os.path.join(os.path.dirname(__file__), "templates", self.template_dir)
+ Path(__file__).resolve().parent / "templates" / self.template_dir
self.main_c = self._read_template_file("main.c")
self.kconfig = self._read_template_file("Kconfig")
self.description = extra_params.get("description", self.name) or "auto-generated"
@@ -60,12 +59,12 @@ class RVGenerator:
def _read_template_file(self, file):
try:
- path = os.path.join(self.abs_template_dir, file)
+ path = self.abs_template_dir / file
return self._read_file(path)
except OSError:
# Specific template file not found. Try the generic template file in the template/
# directory, which is one level up
- path = os.path.join(self.abs_template_dir, "..", file)
+ path = self.abs_template_dir.parent / file
return self._read_file(path)
def fill_parent(self):
@@ -136,7 +135,7 @@ class RVGenerator:
def _patch_file(self, file, marker, line):
assert self.auto_patch
- file_to_patch = os.path.join(self.rv_dir, file)
+ file_to_patch = Path(self.rv_dir) / file
content = self._read_file(file_to_patch)
content = content.replace(marker, line + "\n" + marker)
self.__write_file(file_to_patch, content)
@@ -190,22 +189,19 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
return f" - Move {self.name}/ to the kernel's monitor directory ({self.rv_dir}/monitors)"
def __create_directory(self):
- path = self.name
+ path = Path(self.name)
if self.auto_patch:
- path = os.path.join(self.rv_dir, "monitors", path)
- try:
- os.mkdir(path)
- except FileExistsError:
- return
+ path = Path(self.rv_dir) / "monitors" / path
+ path.mkdir(exist_ok=True)
def __write_file(self, file_name, content):
with open(file_name, 'w') as file:
file.write(content)
def _create_file(self, file_name, content):
- path = f"{self.name}/{file_name}"
+ path = Path(self.name) / file_name
if self.auto_patch:
- path = os.path.join(self.rv_dir, "monitors", path)
+ path = Path(self.rv_dir) / "monitors" / self.name / file_name
self.__write_file(path, content)
def print_files(self):
--
2.55.0
^ permalink raw reply related
* [PATCH v4 05/17] verification/rvgen: Improve consistency in template files
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Template files for rvgen had minor inconsistencies in their
placeholders for default author and default tracepoint examples. The
user needs to modify those anyway but keeping consistency may help in
bulk editing or checking.
Change default author from "dot2k: auto-generated" (for DA/containers)
or /* TODO */ (for LTL) to the general "rvgen: auto-generated".
Align the sample tracepoint handler name in LTL template to
handle_example_event, consistently with the rest of the file.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/rvgen/ltl2k.py | 2 +-
tools/verification/rvgen/rvgen/templates/container/main.c | 2 +-
tools/verification/rvgen/rvgen/templates/dot2k/main.c | 2 +-
tools/verification/rvgen/rvgen/templates/ltl2k/main.c | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/tools/verification/rvgen/rvgen/ltl2k.py b/tools/verification/rvgen/rvgen/ltl2k.py
index 81fd1f5ea5ea..f3781a3e0856 100644
--- a/tools/verification/rvgen/rvgen/ltl2k.py
+++ b/tools/verification/rvgen/rvgen/ltl2k.py
@@ -222,7 +222,7 @@ class ltl2k(generator.Monitor):
return f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_example_event);"
def fill_tracepoint_detach_helper(self):
- return f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_sample_event);"
+ return f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_example_event);"
def fill_atoms_init(self):
buff = []
diff --git a/tools/verification/rvgen/rvgen/templates/container/main.c b/tools/verification/rvgen/rvgen/templates/container/main.c
index 5fc89b46f279..e6a20d74886c 100644
--- a/tools/verification/rvgen/rvgen/templates/container/main.c
+++ b/tools/verification/rvgen/rvgen/templates/container/main.c
@@ -31,5 +31,5 @@ module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_AUTHOR("rvgen: auto-generated");
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
index 889446760e3c..bd3e0aab9cc5 100644
--- a/tools/verification/rvgen/rvgen/templates/dot2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
@@ -79,5 +79,5 @@ module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("dot2k: auto-generated");
+MODULE_AUTHOR("rvgen: auto-generated");
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
index 31258b9ea083..c33f21535a7a 100644
--- a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
+++ b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
@@ -98,5 +98,5 @@ module_init(register_%%MODEL_NAME%%);
module_exit(unregister_%%MODEL_NAME%%);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR(/* TODO */);
+MODULE_AUTHOR("rvgen: auto-generated");
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
--
2.55.0
^ permalink raw reply related
* [PATCH v4 06/17] tools/rv: Add selftests
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
The rv tool needs automated testing to catch regressions and verify
correct functionality across different usage scenarios.
Add selftests that validate monitor listing (including containers and
nested monitors), monitor execution with different configurations
(reactors, verbose output, tracing), and trace output format for both
per-task and per-cpu monitors. Error handling paths are also tested.
Tests use a shared engine for common patterns.
Acked-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
Notes:
V4:
* Retry multiple times when getting the pid to avoid races
tools/verification/rv/Makefile | 5 +-
tools/verification/rv/tests/rv_list.t | 48 +++++++++
tools/verification/rv/tests/rv_mon.t | 95 +++++++++++++++++
tools/verification/tests/engine.sh | 141 ++++++++++++++++++++++++++
4 files changed, 288 insertions(+), 1 deletion(-)
create mode 100644 tools/verification/rv/tests/rv_list.t
create mode 100644 tools/verification/rv/tests/rv_mon.t
create mode 100644 tools/verification/tests/engine.sh
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 5b898360ba48..8ae5fc0d1d17 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -78,4 +78,7 @@ clean: doc_clean fixdep-clean
$(Q)rm -f rv rv-static fixdep FEATURE-DUMP rv-*
$(Q)rm -rf feature
-.PHONY: FORCE clean
+check: $(RV)
+ RV=$(RV) prove -o --directives -f tests/
+
+.PHONY: FORCE clean check
diff --git a/tools/verification/rv/tests/rv_list.t b/tools/verification/rv/tests/rv_list.t
new file mode 100644
index 000000000000..201af33a52cc
--- /dev/null
+++ b/tools/verification/rv/tests/rv_list.t
@@ -0,0 +1,48 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv/
+
+# Help and basic tests
+check "verify help page" \
+ "$RV --help" 0 "usage: rv command"
+
+check "verify list subcommand help" \
+ "$RV list --help" 0 "list all available monitors"
+
+all_nested=$(grep : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+all_non_nested=$(grep -v : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+sched_monitors=$(grep sched: $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+description_state="[[:space:]]\+[[:print:]]\+\[\(OFF\|ON\)\]"
+line_nested=" - \($all_nested\)${description_state}"
+line_non_nested="\($all_non_nested\)${description_state}"
+
+# List monitors and containers
+check "list all monitors" \
+ "$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\)$"
+
+check_if_exists "list container" \
+ "$RV list sched" "$RVDIR/monitors/sched" \
+ "" "-- No monitor found in container sched --" \
+ "^\($sched_monitors\)${description_state}$"
+
+check_if_exists "list non-container" \
+ "$RV list wwnr" "$RVDIR/monitors/wwnr" \
+ "-- No monitor found in container wwnr --" \
+ "^\( - \)\?[[:alnum:]]\+${description_state}$"
+
+check "list incomplete container name" \
+ "$RV list s" 0 "-- No monitor found in container s --"
+
+# Error handling tests
+check "no command" \
+ "$RV" 1 "rv requires a command"
+
+check "invalid command" \
+ "$RV invalid" 1 "rv does not know the invalid command"
+
+test_end
diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t
new file mode 100644
index 000000000000..cbc346c74c71
--- /dev/null
+++ b/tools/verification/rv/tests/rv_mon.t
@@ -0,0 +1,95 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv/
+
+# Help and basic tests
+check "verify mon subcommand help" \
+ "$RV mon --help" 0 "run a monitor"
+
+# Error handling tests
+check "mon without monitor name" \
+ "$RV mon" 1 "usage: rv mon"
+
+check "invalid monitor name" \
+ "$RV mon invalid" 1 "monitor invalid does not exist"
+
+if [ -d $RVDIR/monitors/wwnr ]; then
+
+check "invalid reactor name" \
+ "$RV mon wwnr -r invalid" 1 "failed to set invalid reactor, is it available?"
+
+check "monitor name is substring of another monitor" \
+ "$RV mon nr" 1 "monitor nr does not exist"
+
+check "already enabled monitor returns error" \
+ "echo 1 > $RVDIR/monitors/wwnr/enable; $RV mon wwnr" 1 \
+ "monitor wwnr (in-kernel) is already enabled"
+echo 0 > $RVDIR/monitors/wwnr/enable
+
+fi
+
+# rv mon runs until terminated
+set_expected_timeout 2s
+
+# Run monitors with different configurations
+check_if_exists "run the monitor without parameters" \
+ "$RV mon wwnr" "$RVDIR/monitors/wwnr" "" "."
+
+check_if_exists "run the monitor as verbose" \
+ "$RV mon wwnr -v" "$RVDIR/monitors/wwnr" \
+ "my pid is \$pid" "\(event\|error\)"
+
+check_if_exists "run the monitor with a reactor" \
+ "$RV mon wwnr -r printk & sleep .5 && cat $RVDIR/monitors/wwnr/reactors && wait" \
+ "$RVDIR/monitors/wwnr/reactors" "\[printk\]"
+
+check_if_exists "reactor is restored after exit" \
+ "cat $RVDIR/monitors/wwnr/reactors" \
+ "$RVDIR/monitors/wwnr/reactors" "\[nop\]"
+
+check_if_exists "run a nested monitor with a reactor" \
+ "$RV mon snroc -r printk & sleep .5 && cat $RVDIR/monitors/sched/snroc/reactors && wait" \
+ "$RVDIR/monitors/sched/snroc/reactors" "\[printk\]"
+
+check_if_exists "run an explicitly nested monitor with a reactor" \
+ "$RV mon sched:sssw -r printk & sleep .5 && cat $RVDIR/monitors/sched/sssw/reactors && wait" \
+ "$RVDIR/monitors/sched/sssw/reactors" "\[printk\]"
+
+check_if_exists "run container monitor" \
+ "$RV mon sched & sleep .5 && cat $RVDIR/monitors/sched/{sssw,sco}/enable && wait" \
+ "$RVDIR/monitors/sched" "1" "0" "^1$"
+
+# Regexes for the trace
+header="^[[:space:]]\+\(\([][A-Z_x<>-]\+\||\)[[:space:]]*\)\+$"
+type="\(event\|error\)[[:space:]]\+"
+genpid="[0-9]\+[[:space:]]\+"
+selfpid="\$pid[[:space:]]\+"
+cpu="\[[0-9]\{3\}\][[:space:]]\+"
+state="[a-z_]\+ "
+trace_task="${genpid}${cpu}${type}${genpid}${state}"
+trace_task_self="${genpid}${cpu}${type}${selfpid}${state}"
+trace_cpu="${genpid}${cpu}${type}${state}"
+trace_cpu_self="${selfpid}${cpu}${type}${state}"
+
+check_if_exists "run per-task monitor with tracing" \
+ "$RV mon sssw -t" "$RVDIR/monitors/sched/sssw" \
+ "$header" "$trace_task_self" "\($header\|$trace_task\)"
+
+check_if_exists "run per-task monitor tracing also self" \
+ "$RV mon sched:sssw -t -s" "$RVDIR/monitors/sched/sssw" \
+ "$trace_task_self" "" "\($header\|$trace_task\)"
+
+check_if_exists "run per-cpu monitor with tracing" \
+ "$RV mon sched:sco -t" "$RVDIR/monitors/sched/sco" \
+ "$header" "$trace_cpu_self" "\($header\|$trace_cpu\)"
+
+check_if_exists "run per-cpu monitor tracing also self" \
+ "$RV mon sco -t -s" "$RVDIR/monitors/sched/sco" \
+ "$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+
+test_end
diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
new file mode 100644
index 000000000000..57e16dc980b1
--- /dev/null
+++ b/tools/verification/tests/engine.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+test_begin() {
+ # Count tests to allow the test harness to double-check if all were
+ # included correctly.
+ ctr=0
+ [ -z "$RV" ] && RV="../rv/rv"
+ [ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
+}
+
+failure() {
+ fail=1
+ if [ $# -gt 0 ]; then
+ failbuf+="$1"
+ failbuf+=$'\n'
+ fi
+}
+
+report() {
+ local desc="$1"
+
+ if [ "$fail" -eq 0 ]; then
+ echo "ok $ctr - $desc"
+ else
+ # Add output and exit code as comments in case of failure
+ echo "not ok $ctr - $desc"
+ echo -n "$failbuf"
+ echo "$result" | col -b | while read -r line; do echo "# $line"; done
+ printf "#\n# exit code %s\n" "$exitcode"
+ fi
+}
+
+_check() {
+ local command=$2
+ local expected_exitcode=${3:-0}
+ local expected_output=$4
+ local unexpected_output=$5
+ local all_lines_pattern=$6
+ local patterns="$expected_output $unexpected_output $all_lines_pattern"
+ local bgpid pid
+
+ eval "$TIMEOUT" "$command" &> check_output.$$ &
+ bgpid=$!
+
+ if grep -q "\$pid" <<< "$patterns"; then
+ for _ in {1..30}; do
+ pid=$(pgrep -f "${command%%[|;&>]*}" | tail -n1)
+ [ -n "$pid" ] && break
+ sleep 0.1
+ done
+ fi
+
+ wait $bgpid
+ exitcode=$?
+ result=$(tr -d '\0' < check_output.$$)
+ rm -f check_output.$$
+
+ failbuf=''
+ fail=0
+
+ # Suppress any other error if a needed pid is empty
+ if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns"; then
+ result=''
+ failure "# Empty pid for $command"
+ return 1
+ fi
+
+ expected_output="${expected_output//\$pid/$pid}"
+ unexpected_output="${unexpected_output//\$pid/$pid}"
+ all_lines_pattern="${all_lines_pattern//\$pid/$pid}"
+
+ # Test if the results matches if requested
+ if [ -n "$expected_output" ] && ! grep -qe "$expected_output" <<< "$result"; then
+ failure "# Output match failed: \"$expected_output\""
+ fi
+
+ if [ -n "$unexpected_output" ] && grep -qe "$unexpected_output" <<< "$result"; then
+ failure "# Output non-match failed: \"$unexpected_output\""
+ fi
+
+ if [ -n "$all_lines_pattern" ] && grep -vqe "$all_lines_pattern" <<< "$result"; then
+ failure "# All-lines pattern failed: \"$all_lines_pattern\""
+ fi
+
+ if [ $exitcode -ne "$expected_exitcode" ]; then
+ failure "# Expected exit code $expected_exitcode"
+ fi
+}
+
+check() {
+ # Simple check: run the command with given arguments and test exit code.
+ # If TEST_COUNT is set, run the test. Otherwise, just count.
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ _check "$@"
+ report "$1"
+ fi
+}
+
+check_if_exists() {
+ # Conditional check that skips if a file or folder doesn't exist
+ local desc=$1
+ local command=$2
+ local file=$3
+ local expected_output=$4
+ local unexpected_output=$5
+ local all_lines_pattern=$6
+
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ if [ ! -e "$file" ]; then
+ echo "ok $ctr - $desc # SKIP file not found: $file"
+ else
+ _check "$desc" "$command" 0 "$expected_output" \
+ "$unexpected_output" "$all_lines_pattern"
+ report "$desc"
+ fi
+ fi
+}
+
+set_timeout() {
+ TIMEOUT="timeout -v -k 30s $1"
+}
+
+set_expected_timeout() {
+ TIMEOUT="timeout --preserve-status -k 30s $1"
+}
+
+unset_timeout() {
+ unset TIMEOUT
+}
+
+test_end() {
+ # If running without TEST_COUNT, tests are not actually run, just
+ # counted. In that case, re-run the test with the correct count.
+ [ -z "$TEST_COUNT" ] && TEST_COUNT=$ctr exec bash "$0" || true
+}
+
+# Avoid any environmental discrepancies
+export LC_ALL=C
+unset_timeout
--
2.55.0
^ permalink raw reply related
* [PATCH v4 07/17] verification/rvgen: Add golden and spec folders for tests
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Create reference models specifications and generated files in the golded
folder. Those can be used as reference to validate rvgen still generates
files as expected in automated tests.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
.../rvgen/tests/golden/da_global/Kconfig | 9 +
.../rvgen/tests/golden/da_global/da_global.c | 95 +++++++
.../rvgen/tests/golden/da_global/da_global.h | 47 ++++
.../tests/golden/da_global/da_global_trace.h | 15 ++
.../tests/golden/da_perobj_parent/Kconfig | 11 +
.../da_perobj_parent/da_perobj_parent.c | 110 ++++++++
.../da_perobj_parent/da_perobj_parent.h | 64 +++++
.../da_perobj_parent/da_perobj_parent_trace.h | 15 ++
.../tests/golden/da_pertask_desc/Kconfig | 9 +
.../golden/da_pertask_desc/da_pertask_desc.c | 105 ++++++++
.../golden/da_pertask_desc/da_pertask_desc.h | 64 +++++
.../da_pertask_desc/da_pertask_desc_trace.h | 15 ++
.../rvgen/tests/golden/ha_percpu/Kconfig | 9 +
.../rvgen/tests/golden/ha_percpu/ha_percpu.c | 244 +++++++++++++++++
.../rvgen/tests/golden/ha_percpu/ha_percpu.h | 72 +++++
.../tests/golden/ha_percpu/ha_percpu_trace.h | 19 ++
.../rvgen/tests/golden/ltl_pertask/Kconfig | 9 +
.../tests/golden/ltl_pertask/ltl_pertask.c | 107 ++++++++
.../tests/golden/ltl_pertask/ltl_pertask.h | 108 ++++++++
.../golden/ltl_pertask/ltl_pertask_trace.h | 14 +
.../rvgen/tests/golden/test_container/Kconfig | 5 +
.../golden/test_container/test_container.c | 35 +++
.../golden/test_container/test_container.h | 3 +
.../rvgen/tests/golden/test_da/Kconfig | 9 +
.../rvgen/tests/golden/test_da/test_da.c | 95 +++++++
.../rvgen/tests/golden/test_da/test_da.h | 47 ++++
.../tests/golden/test_da/test_da_trace.h | 15 ++
.../rvgen/tests/golden/test_ha/Kconfig | 9 +
.../rvgen/tests/golden/test_ha/test_ha.c | 247 ++++++++++++++++++
.../rvgen/tests/golden/test_ha/test_ha.h | 72 +++++
.../tests/golden/test_ha/test_ha_trace.h | 19 ++
.../rvgen/tests/golden/test_ltl/Kconfig | 11 +
.../rvgen/tests/golden/test_ltl/test_ltl.c | 108 ++++++++
.../rvgen/tests/golden/test_ltl/test_ltl.h | 108 ++++++++
.../tests/golden/test_ltl/test_ltl_trace.h | 14 +
.../rvgen/tests/specs/test_da.dot | 16 ++
.../rvgen/tests/specs/test_da2.dot | 19 ++
.../rvgen/tests/specs/test_ha.dot | 27 ++
.../rvgen/tests/specs/test_invalid.dot | 8 +
.../rvgen/tests/specs/test_invalid.ltl | 1 +
.../rvgen/tests/specs/test_invalid_ha.dot | 16 ++
.../rvgen/tests/specs/test_ltl.ltl | 1 +
42 files changed, 2026 insertions(+)
create mode 100644 tools/verification/rvgen/tests/golden/da_global/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.c
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.h
create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_container/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.c
create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
create mode 100644 tools/verification/rvgen/tests/specs/test_da.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_da2.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.ltl
create mode 100644 tools/verification/rvgen/tests/specs/test_invalid_ha.dot
create mode 100644 tools/verification/rvgen/tests/specs/test_ltl.ltl
diff --git a/tools/verification/rvgen/tests/golden/da_global/Kconfig b/tools/verification/rvgen/tests/golden/da_global/Kconfig
new file mode 100644
index 000000000000..799fbf11c3ac
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_GLOBAL
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "da_global monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.c b/tools/verification/rvgen/tests/golden/da_global/da_global.c
new file mode 100644
index 000000000000..71b26ae2d51c
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_global"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_GLOBAL
+#include "da_global.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_da_global);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_da_global);
+}
+
+static int enable_da_global(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_da_global(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_global",
+ .description = "auto-generated",
+ .enable = enable_da_global,
+ .disable = disable_da_global,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_global(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_da_global(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_global);
+module_exit(unregister_da_global);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("da_global: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.h b/tools/verification/rvgen/tests/golden/da_global/da_global.h
new file mode 100644
index 000000000000..40b1f1c0c681
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_global automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_global
+
+enum states_da_global {
+ state_a_da_global,
+ state_b_da_global,
+ state_max_da_global,
+};
+
+#define INVALID_STATE state_max_da_global
+
+enum events_da_global {
+ event_1_da_global,
+ event_2_da_global,
+ event_max_da_global,
+};
+
+struct automaton_da_global {
+ char *state_names[state_max_da_global];
+ char *event_names[event_max_da_global];
+ unsigned char function[state_max_da_global][event_max_da_global];
+ unsigned char initial_state;
+ bool final_states[state_max_da_global];
+};
+
+static const struct automaton_da_global automaton_da_global = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_da_global, state_a_da_global },
+ { INVALID_STATE, state_a_da_global },
+ },
+ .initial_state = state_a_da_global,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
new file mode 100644
index 000000000000..4d2730b71dd0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_GLOBAL
+DEFINE_EVENT(event_da_monitor, event_da_global,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_da_global,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_DA_GLOBAL */
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
new file mode 100644
index 000000000000..249ba3aee8d7
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_PEROBJ_PARENT
+ depends on RV
+ # XXX: add dependencies if there
+ depends on RV_MON_PARENT_MON
+ default y
+ select DA_MON_EVENTS_ID
+ bool "da_perobj_parent monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
new file mode 100644
index 000000000000..a0f8b5a216a3
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_perobj_parent"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+#include <monitors/parent_mon/parent_mon.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_OBJ
+typedef /* XXX: define the target type */ *monitor_target;
+#include "da_perobj_parent.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event is only valid in the initial state */
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_start_run_event(id, t, event_1_da_perobj_parent);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_2_da_perobj_parent);
+}
+
+static void handle_event_3(void *data, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_3_da_perobj_parent);
+}
+
+static int enable_da_perobj_parent(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2);
+ rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3);
+
+ return 0;
+}
+
+static void disable_da_perobj_parent(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2);
+ rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_perobj_parent",
+ .description = "auto-generated",
+ .enable = enable_da_perobj_parent,
+ .disable = disable_da_perobj_parent,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_perobj_parent(void)
+{
+ return rv_register_monitor(&rv_this, &rv_parent_mon);
+}
+
+static void __exit unregister_da_perobj_parent(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_perobj_parent);
+module_exit(unregister_da_perobj_parent);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("da_perobj_parent: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
new file mode 100644
index 000000000000..3c8dc3b22443
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_perobj_parent automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_perobj_parent
+
+enum states_da_perobj_parent {
+ state_a_da_perobj_parent,
+ state_b_da_perobj_parent,
+ state_c_da_perobj_parent,
+ state_max_da_perobj_parent,
+};
+
+#define INVALID_STATE state_max_da_perobj_parent
+
+enum events_da_perobj_parent {
+ event_1_da_perobj_parent,
+ event_2_da_perobj_parent,
+ event_3_da_perobj_parent,
+ event_max_da_perobj_parent,
+};
+
+struct automaton_da_perobj_parent {
+ char *state_names[state_max_da_perobj_parent];
+ char *event_names[event_max_da_perobj_parent];
+ unsigned char function[state_max_da_perobj_parent][event_max_da_perobj_parent];
+ unsigned char initial_state;
+ bool final_states[state_max_da_perobj_parent];
+};
+
+static const struct automaton_da_perobj_parent automaton_da_perobj_parent = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ "state_c",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ "event_3",
+ },
+ .function = {
+ {
+ state_b_da_perobj_parent,
+ state_c_da_perobj_parent,
+ INVALID_STATE,
+ },
+ {
+ INVALID_STATE,
+ state_a_da_perobj_parent,
+ state_c_da_perobj_parent,
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = state_a_da_perobj_parent,
+ .final_states = { 1, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
new file mode 100644
index 000000000000..59bfca8f73d2
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_PEROBJ_PARENT
+DEFINE_EVENT(event_da_monitor_id, event_da_perobj_parent,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_da_perobj_parent,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+#endif /* CONFIG_RV_MON_DA_PEROBJ_PARENT */
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
new file mode 100644
index 000000000000..c6f350179098
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_DA_PERTASK_DESC
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_ID
+ bool "da_pertask_desc monitor"
+ help
+ Custom description for testing
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
new file mode 100644
index 000000000000..c1ae5078c4f9
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "da_pertask_desc"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+#include "da_pertask_desc.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event is only valid in the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_run_event(p, event_1_da_pertask_desc);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event_2_da_pertask_desc);
+}
+
+static void handle_event_3(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event_3_da_pertask_desc);
+}
+
+static int enable_da_pertask_desc(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2);
+ rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3);
+
+ return 0;
+}
+
+static void disable_da_pertask_desc(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2);
+ rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "da_pertask_desc",
+ .description = "Custom description for testing",
+ .enable = enable_da_pertask_desc,
+ .disable = disable_da_pertask_desc,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_da_pertask_desc(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_da_pertask_desc(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_da_pertask_desc);
+module_exit(unregister_da_pertask_desc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("da_pertask_desc: Custom description for testing");
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
new file mode 100644
index 000000000000..837b238754b0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_pertask_desc automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_pertask_desc
+
+enum states_da_pertask_desc {
+ state_a_da_pertask_desc,
+ state_b_da_pertask_desc,
+ state_c_da_pertask_desc,
+ state_max_da_pertask_desc,
+};
+
+#define INVALID_STATE state_max_da_pertask_desc
+
+enum events_da_pertask_desc {
+ event_1_da_pertask_desc,
+ event_2_da_pertask_desc,
+ event_3_da_pertask_desc,
+ event_max_da_pertask_desc,
+};
+
+struct automaton_da_pertask_desc {
+ char *state_names[state_max_da_pertask_desc];
+ char *event_names[event_max_da_pertask_desc];
+ unsigned char function[state_max_da_pertask_desc][event_max_da_pertask_desc];
+ unsigned char initial_state;
+ bool final_states[state_max_da_pertask_desc];
+};
+
+static const struct automaton_da_pertask_desc automaton_da_pertask_desc = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ "state_c",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ "event_3",
+ },
+ .function = {
+ {
+ state_b_da_pertask_desc,
+ state_c_da_pertask_desc,
+ INVALID_STATE,
+ },
+ {
+ INVALID_STATE,
+ state_a_da_pertask_desc,
+ state_c_da_pertask_desc,
+ },
+ {
+ INVALID_STATE,
+ INVALID_STATE,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = state_a_da_pertask_desc,
+ .final_states = { 1, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
new file mode 100644
index 000000000000..4e6086c4d86e
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_DA_PERTASK_DESC
+DEFINE_EVENT(event_da_monitor_id, event_da_pertask_desc,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_da_pertask_desc,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+#endif /* CONFIG_RV_MON_DA_PERTASK_DESC */
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
new file mode 100644
index 000000000000..0cc185ccfddf
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_HA_PERCPU
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_IMPLICIT
+ bool "ha_percpu monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
new file mode 100644
index 000000000000..f61b2eae691a
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "ha_percpu"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "ha_percpu.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns)
+{
+ if (env == clk_ha_percpu)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_ha_percpu)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_ha_percpu)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns)
+{
+ if (env == clk_ha_percpu)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ * Due to the different representations between invariants and guards, there is
+ * a function to convert it in case invariants or guards are reachable from
+ * another invariant without reset. Those are not present if not required in
+ * the model. This is all automatic but is worth checking because it may show
+ * errors in the model (e.g. missing resets).
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_ha_percpu)
+ return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S2_ha_percpu)
+ return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns);
+ return true;
+}
+
+static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == next_state)
+ return;
+ if (curr_state == S2_ha_percpu)
+ ha_inv_to_guard(ha_mon, clk_ha_percpu, BAR_NS(ha_mon), time_ns);
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_ha_percpu && event == event0_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S0_ha_percpu && event == event1_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S1_ha_percpu && event == event0_ha_percpu)
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ else if (curr_state == S1_ha_percpu && event == event2_ha_percpu) {
+ res = ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ } else if (curr_state == S2_ha_percpu && event == event1_ha_percpu)
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ ha_get_env(ha_mon, clk_ha_percpu, time_ns) < foo_ns;
+ else if (curr_state == S3_ha_percpu && event == event0_ha_percpu)
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_ha_percpu, time_ns) == 0ull);
+ else if (curr_state == S3_ha_percpu && event == event1_ha_percpu) {
+ res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) ||
+ (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < 5000ull &&
+ ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_ha_percpu, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_ha_percpu)
+ return;
+ if (next_state == S0_ha_percpu)
+ ha_start_timer_ns(ha_mon, clk_ha_percpu, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_ha_percpu)
+ ha_start_timer_ns(ha_mon, clk_ha_percpu, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_ha_percpu)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_ha_percpu)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event0_ha_percpu);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event1_ha_percpu);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event2_ha_percpu);
+}
+
+static int enable_ha_percpu(void)
+{
+ int retval;
+
+ retval = ha_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_ha_percpu(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2);
+
+ ha_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "ha_percpu",
+ .description = "auto-generated",
+ .enable = enable_ha_percpu,
+ .disable = disable_ha_percpu,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_ha_percpu(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_ha_percpu(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_ha_percpu);
+module_exit(unregister_ha_percpu);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("ha_percpu: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
new file mode 100644
index 000000000000..2538db4f6a26
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of ha_percpu automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME ha_percpu
+
+enum states_ha_percpu {
+ S0_ha_percpu,
+ S1_ha_percpu,
+ S2_ha_percpu,
+ S3_ha_percpu,
+ state_max_ha_percpu,
+};
+
+#define INVALID_STATE state_max_ha_percpu
+
+enum events_ha_percpu {
+ event0_ha_percpu,
+ event1_ha_percpu,
+ event2_ha_percpu,
+ event_max_ha_percpu,
+};
+
+enum envs_ha_percpu {
+ clk_ha_percpu,
+ env1_ha_percpu,
+ env2_ha_percpu,
+ env_max_ha_percpu,
+ env_max_stored_ha_percpu = env1_ha_percpu,
+};
+
+_Static_assert(env_max_stored_ha_percpu <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_ha_percpu {
+ char *state_names[state_max_ha_percpu];
+ char *event_names[event_max_ha_percpu];
+ char *env_names[env_max_ha_percpu];
+ unsigned char function[state_max_ha_percpu][event_max_ha_percpu];
+ unsigned char initial_state;
+ bool final_states[state_max_ha_percpu];
+};
+
+static const struct automaton_ha_percpu automaton_ha_percpu = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ { S0_ha_percpu, S1_ha_percpu, INVALID_STATE },
+ { S0_ha_percpu, INVALID_STATE, S2_ha_percpu },
+ { INVALID_STATE, S2_ha_percpu, S3_ha_percpu },
+ { S0_ha_percpu, S1_ha_percpu, INVALID_STATE },
+ },
+ .initial_state = S0_ha_percpu,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
new file mode 100644
index 000000000000..074ddff6a60d
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_HA_PERCPU
+DEFINE_EVENT(event_da_monitor, event_ha_percpu,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_ha_percpu,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+
+DEFINE_EVENT(error_env_da_monitor, error_env_ha_percpu,
+ TP_PROTO(char *state, char *event, char *env),
+ TP_ARGS(state, event, env));
+#endif /* CONFIG_RV_MON_HA_PERCPU */
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
new file mode 100644
index 000000000000..b37f46670bfd
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_LTL_PERTASK
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "ltl_pertask monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
new file mode 100644
index 000000000000..2c60b5c5b4e0
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "ltl_pertask"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "ltl_pertask.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_ltl_pertask(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_ltl_pertask(void)
+{
+ rv_detach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "ltl_pertask",
+ .description = "auto-generated",
+ .enable = enable_ltl_pertask,
+ .disable = disable_ltl_pertask,
+};
+
+static int __init register_ltl_pertask(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_ltl_pertask(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_ltl_pertask);
+module_exit(unregister_ltl_pertask);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("ltl_pertask: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
new file mode 100644
index 000000000000..7e5de351b8fa
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME ltl_pertask
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
new file mode 100644
index 000000000000..ebd53621a5b1
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_LTL_PERTASK
+DEFINE_EVENT(event_ltl_monitor_id, event_ltl_pertask,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_ltl_pertask,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_LTL_PERTASK */
diff --git a/tools/verification/rvgen/tests/golden/test_container/Kconfig b/tools/verification/rvgen/tests/golden/test_container/Kconfig
new file mode 100644
index 000000000000..2becb65dddad
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/Kconfig
@@ -0,0 +1,5 @@
+config RV_MON_TEST_CONTAINER
+ depends on RV
+ bool "test_container monitor"
+ help
+ Test container for grouping monitors
diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.c b/tools/verification/rvgen/tests/golden/test_container/test_container.c
new file mode 100644
index 000000000000..e7e34592c6c5
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/test_container.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+
+#define MODULE_NAME "test_container"
+
+#include "test_container.h"
+
+struct rv_monitor rv_test_container = {
+ .name = "test_container",
+ .description = "Test container for grouping monitors",
+ .enable = NULL,
+ .disable = NULL,
+ .reset = NULL,
+ .enabled = 0,
+};
+
+static int __init register_test_container(void)
+{
+ return rv_register_monitor(&rv_test_container, NULL);
+}
+
+static void __exit unregister_test_container(void)
+{
+ rv_unregister_monitor(&rv_test_container);
+}
+
+module_init(register_test_container);
+module_exit(unregister_test_container);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_container: Test container for grouping monitors");
diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.h b/tools/verification/rvgen/tests/golden/test_container/test_container.h
new file mode 100644
index 000000000000..83e434432650
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_container/test_container.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+extern struct rv_monitor rv_test_container;
diff --git a/tools/verification/rvgen/tests/golden/test_da/Kconfig b/tools/verification/rvgen/tests/golden/test_da/Kconfig
new file mode 100644
index 000000000000..0143a148ef34
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_DA
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "test_da monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.c b/tools/verification/rvgen/tests/golden/test_da/test_da.c
new file mode 100644
index 000000000000..59b8dfabbbf1
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_da"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "test_da.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_test_da);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_test_da);
+}
+
+static int enable_test_da(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_test_da(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_da",
+ .description = "auto-generated",
+ .enable = enable_test_da,
+ .disable = disable_test_da,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_da(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_da(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_da);
+module_exit(unregister_test_da);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_da: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.h b/tools/verification/rvgen/tests/golden/test_da/test_da.h
new file mode 100644
index 000000000000..d55795efbb61
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_da automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_da
+
+enum states_test_da {
+ state_a_test_da,
+ state_b_test_da,
+ state_max_test_da,
+};
+
+#define INVALID_STATE state_max_test_da
+
+enum events_test_da {
+ event_1_test_da,
+ event_2_test_da,
+ event_max_test_da,
+};
+
+struct automaton_test_da {
+ char *state_names[state_max_test_da];
+ char *event_names[event_max_test_da];
+ unsigned char function[state_max_test_da][event_max_test_da];
+ unsigned char initial_state;
+ bool final_states[state_max_test_da];
+};
+
+static const struct automaton_test_da automaton_test_da = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_test_da, state_a_test_da },
+ { INVALID_STATE, state_a_test_da },
+ },
+ .initial_state = state_a_test_da,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
new file mode 100644
index 000000000000..8bd67115d244
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_DA
+DEFINE_EVENT(event_da_monitor, event_test_da,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_test_da,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_TEST_DA */
diff --git a/tools/verification/rvgen/tests/golden/test_ha/Kconfig b/tools/verification/rvgen/tests/golden/test_ha/Kconfig
new file mode 100644
index 000000000000..f4048290c774
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_HA
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_ID
+ bool "test_ha monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.c b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c
new file mode 100644
index 000000000000..900370428e5f
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ha"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "test_ha.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns)
+{
+ if (env == clk_test_ha)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_test_ha)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_test_ha)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns)
+{
+ if (env == clk_test_ha)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ * Due to the different representations between invariants and guards, there is
+ * a function to convert it in case invariants or guards are reachable from
+ * another invariant without reset. Those are not present if not required in
+ * the model. This is all automatic but is worth checking because it may show
+ * errors in the model (e.g. missing resets).
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_test_ha)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S2_test_ha)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns);
+ return true;
+}
+
+static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == next_state)
+ return;
+ if (curr_state == S2_test_ha)
+ ha_inv_to_guard(ha_mon, clk_test_ha, BAR_NS(ha_mon), time_ns);
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_test_ha && event == event0_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S0_test_ha && event == event1_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S1_test_ha && event == event0_test_ha)
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ else if (curr_state == S1_test_ha && event == event2_test_ha) {
+ res = ha_get_env(ha_mon, env1_test_ha, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ } else if (curr_state == S2_test_ha && event == event1_test_ha)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ ha_get_env(ha_mon, clk_test_ha, time_ns) < foo_ns;
+ else if (curr_state == S3_test_ha && event == event0_test_ha)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ (ha_get_env(ha_mon, clk_test_ha, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_test_ha, time_ns) == 0ull);
+ else if (curr_state == S3_test_ha && event == event1_test_ha) {
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha) ||
+ (ha_get_env(ha_mon, clk_test_ha, time_ns) < 5000ull &&
+ ha_get_env(ha_mon, env1_test_ha, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_test_ha, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_test_ha)
+ return;
+ if (next_state == S0_test_ha)
+ ha_start_timer_ns(ha_mon, clk_test_ha, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_test_ha)
+ ha_start_timer_ns(ha_mon, clk_test_ha, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_test_ha)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_test_ha)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_event(p, event0_test_ha);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event1_test_ha);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event2_test_ha);
+}
+
+static int enable_test_ha(void)
+{
+ int retval;
+
+ retval = ha_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_test_ha(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2);
+
+ ha_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ha",
+ .description = "auto-generated",
+ .enable = enable_test_ha,
+ .disable = disable_test_ha,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_ha(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ha(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ha);
+module_exit(unregister_test_ha);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ha: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h
new file mode 100644
index 000000000000..949fa4453403
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_ha automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_ha
+
+enum states_test_ha {
+ S0_test_ha,
+ S1_test_ha,
+ S2_test_ha,
+ S3_test_ha,
+ state_max_test_ha,
+};
+
+#define INVALID_STATE state_max_test_ha
+
+enum events_test_ha {
+ event0_test_ha,
+ event1_test_ha,
+ event2_test_ha,
+ event_max_test_ha,
+};
+
+enum envs_test_ha {
+ clk_test_ha,
+ env1_test_ha,
+ env2_test_ha,
+ env_max_test_ha,
+ env_max_stored_test_ha = env1_test_ha,
+};
+
+_Static_assert(env_max_stored_test_ha <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_test_ha {
+ char *state_names[state_max_test_ha];
+ char *event_names[event_max_test_ha];
+ char *env_names[env_max_test_ha];
+ unsigned char function[state_max_test_ha][event_max_test_ha];
+ unsigned char initial_state;
+ bool final_states[state_max_test_ha];
+};
+
+static const struct automaton_test_ha automaton_test_ha = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ { S0_test_ha, S1_test_ha, INVALID_STATE },
+ { S0_test_ha, INVALID_STATE, S2_test_ha },
+ { INVALID_STATE, S2_test_ha, S3_test_ha },
+ { S0_test_ha, S1_test_ha, INVALID_STATE },
+ },
+ .initial_state = S0_test_ha,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
new file mode 100644
index 000000000000..381bafcb3322
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_HA
+DEFINE_EVENT(event_da_monitor_id, event_test_ha,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_test_ha,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha,
+ TP_PROTO(int id, char *state, char *event, char *env),
+ TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TEST_HA */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig
new file mode 100644
index 000000000000..e2d0e721f180
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_LTL
+ depends on RV
+ # XXX: add dependencies if there
+ depends on RV_MON_LTL_PARENT
+ default y
+ select LTL_MON_EVENTS_ID
+ bool "test_ltl monitor"
+ help
+ Simple description
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
new file mode 100644
index 000000000000..dd961c5dc8ad
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ltl"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+#include <monitors/ltl_parent/ltl_parent.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_ltl.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_ltl(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_ltl(void)
+{
+ rv_detach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ltl",
+ .description = "Simple description",
+ .enable = enable_test_ltl,
+ .disable = disable_test_ltl,
+};
+
+static int __init register_test_ltl(void)
+{
+ return rv_register_monitor(&rv_this, &rv_ltl_parent);
+}
+
+static void __exit unregister_test_ltl(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ltl);
+module_exit(unregister_test_ltl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ltl: Simple description");
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
new file mode 100644
index 000000000000..7895f2e233e8
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_ltl
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
new file mode 100644
index 000000000000..3571b004c114
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_LTL
+DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_LTL */
diff --git a/tools/verification/rvgen/tests/specs/test_da.dot b/tools/verification/rvgen/tests/specs/test_da.dot
new file mode 100644
index 000000000000..e555c239b221
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_da.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a"];
+ "state_a" -> "state_a" [ label = "event_2" ];
+ "state_a" -> "state_b" [ label = "event_1" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_da2.dot b/tools/verification/rvgen/tests/specs/test_da2.dot
new file mode 100644
index 000000000000..cdd4192f58ae
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_da2.dot
@@ -0,0 +1,19 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = circle] "state_c"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a"];
+ "state_a" -> "state_b" [ label = "event_1" ];
+ "state_a" -> "state_c" [ label = "event_2" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ "state_b" -> "state_c" [ label = "event_3" ];
+ "state_c" [label = "state_c"];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_ha.dot b/tools/verification/rvgen/tests/specs/test_ha.dot
new file mode 100644
index 000000000000..af18ad7389ec
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_ha.dot
@@ -0,0 +1,27 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = circle] "S1"};
+ {node [shape = plaintext, style=invis, label=""] "__init_S0"};
+ {node [shape = doublecircle] "S0"};
+ {node [shape = circle] "S0"};
+ {node [shape = circle] "S2"};
+ {node [shape = circle] "S3"};
+ "__init_S0" -> "S0";
+ "S0" [label = "S0\nclk < bar_ns()", color = green3];
+ "S1" [label = "S1"];
+ "S2" [label = "S2\nclk < BAR_NS()"];
+ "S3" [label = "S3"];
+ "S1" -> "S0" [ label = "event0;reset(clk)" ];
+ "S0" -> "S1" [ label = "event1;reset(clk)" ];
+ "S0" -> "S0" [ label = "event0;reset(clk)" ];
+ "S1" -> "S2" [ label = "event2;env1 == 0;reset(clk)" ];
+ "S2" -> "S3" [ label = "event2" ];
+ "S2" -> "S2" [ label = "event1;clk < foo_ns" ];
+ "S3" -> "S0" [ label = "event0;clk < FOO_NS && env2 == 0" ];
+ "S3" -> "S1" [ label = "event1;clk < 5us && env1 == 1;reset(clk)" ];
+ { rank = min ;
+ "__init_S0";
+ "S0";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_invalid.dot b/tools/verification/rvgen/tests/specs/test_invalid.dot
new file mode 100644
index 000000000000..17c63fc57f17
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid.dot
@@ -0,0 +1,8 @@
+digraph invalid {
+ {node [shape = circle] "init"};
+ {node [shape = circle] "state1"};
+ "init" [label = "init"];
+ "init" -> "state1" [ label = "event_a" ];
+ "state1" [label = "state1"];
+ "state1" -> "init" [ label = "event_b" ];
+}
diff --git a/tools/verification/rvgen/tests/specs/test_invalid.ltl b/tools/verification/rvgen/tests/specs/test_invalid.ltl
new file mode 100644
index 000000000000..cf36307e003c
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid.ltl
@@ -0,0 +1 @@
+RULE = A invalid B
diff --git a/tools/verification/rvgen/tests/specs/test_invalid_ha.dot b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot
new file mode 100644
index 000000000000..06de6aa8709f
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "state_b"};
+ {node [shape = plaintext, style=invis, label=""] "__init_state_a"};
+ {node [shape = doublecircle] "state_a"};
+ {node [shape = circle] "state_a"};
+ "__init_state_a" -> "state_a";
+ "state_a" [label = "state_a;clk < 1"];
+ "state_a" -> "state_a" [ label = "event_2;reset(clk)" ];
+ "state_a" -> "state_b" [ label = "event_1;wrong_constraint" ];
+ "state_b" [label = "state_b"];
+ "state_b" -> "state_a" [ label = "event_2" ];
+ { rank = min ;
+ "__init_state_a";
+ "state_a";
+ }
+}
diff --git a/tools/verification/rvgen/tests/specs/test_ltl.ltl b/tools/verification/rvgen/tests/specs/test_ltl.ltl
new file mode 100644
index 000000000000..5ed658abd69c
--- /dev/null
+++ b/tools/verification/rvgen/tests/specs/test_ltl.ltl
@@ -0,0 +1 @@
+RULE = always (EVENT_A imply eventually EVENT_B)
--
2.55.0
^ permalink raw reply related
* [PATCH v4 08/17] verification/rvgen: Add selftests
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Wen Yang, Thomas Weissschuh, Tomas Glozar, John Kacur
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
The rvgen code generator needs validation to ensure it produces correct
monitor implementations from input specifications.
Add selftests with golden reference outputs covering all monitor classes
(DA, HA, LTL) and types (global, per_cpu, per_task, per_obj), including
optional features like descriptions and parent monitors. Container
generation and error handling (missing files, invalid specifications,
missing arguments) are also validated against expected output.
Acked-by: Nam Cao <namcao@linutronix.de>
Reviewed-by: Wen Yang <wen.yang@linux.dev>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/Makefile | 4 +
.../rvgen/tests/rvgen_container.t | 20 +++++
.../verification/rvgen/tests/rvgen_monitor.t | 87 +++++++++++++++++++
tools/verification/tests/engine.sh | 34 ++++++++
4 files changed, 145 insertions(+)
create mode 100644 tools/verification/rvgen/tests/rvgen_container.t
create mode 100644 tools/verification/rvgen/tests/rvgen_monitor.t
diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile
index cfc4056c1e87..2a2b9e64ea42 100644
--- a/tools/verification/rvgen/Makefile
+++ b/tools/verification/rvgen/Makefile
@@ -13,6 +13,10 @@ all:
.PHONY: clean
clean:
+.PHONY: check
+check:
+ prove -o --directives -f tests/
+
.PHONY: install
install:
$(INSTALL) rvgen/automata.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/automata.py
diff --git a/tools/verification/rvgen/tests/rvgen_container.t b/tools/verification/rvgen/tests/rvgen_container.t
new file mode 100644
index 000000000000..fa4fb3db8288
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_container.t
@@ -0,0 +1,20 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help tests
+check "verify container subcommand help" \
+ "$RVGEN container -h" 0 "model_name" "class"
+
+check_and_compare_folder "container with description" \
+ "$RVGEN container -n test_container -D 'Test container for grouping monitors'" \
+ "test_container" "Writing the monitor into the directory test_container"
+
+# Error handling tests
+check "missing required model_name" \
+ "$RVGEN container" 2 "the following arguments are required: -n/--model_name"
+
+test_end
diff --git a/tools/verification/rvgen/tests/rvgen_monitor.t b/tools/verification/rvgen/tests/rvgen_monitor.t
new file mode 100644
index 000000000000..261476504eee
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_monitor.t
@@ -0,0 +1,87 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help and basic tests
+check "verify help page" \
+ "$RVGEN --help" 0 "Generate kernel rv monitor"
+
+check "verify monitor subcommand help" \
+ "$RVGEN monitor --help" 0 "Monitor class"
+
+# DA monitor tests - test all monitor types
+check_and_compare_folder "DA per_cpu (default name)" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu" \
+ "test_da" "obj-\$(CONFIG_RV_MON_TEST_DA) += monitors/test_da/test_da.o"
+
+check_and_compare_folder "DA global type" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t global -n da_global" \
+ "da_global" "DA_MON_EVENTS_IMPLICIT"
+
+check_and_compare_folder "DA per_task with description" \
+ "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_task -n da_pertask_desc -D 'Custom description for testing'" \
+ "da_pertask_desc" "#include <monitors/da_pertask_desc/da_pertask_desc_trace.h>"
+
+check_and_compare_folder "DA per_obj with parent" \
+ "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_obj -n da_perobj_parent -p parent_mon" \
+ "da_perobj_parent" "DA_MON_EVENTS_ID"
+
+# HA monitor tests
+check_and_compare_folder "HA per_task (default name)" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task" \
+ "test_ha" "HA_MON_EVENTS_ID"
+
+check_and_compare_folder "HA per_cpu type" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_cpu -n ha_percpu" \
+ "ha_percpu" "HA_MON_EVENTS_IMPLICIT"
+
+# LTL monitor test
+check_and_compare_folder "LTL per_task" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n ltl_pertask" \
+ "ltl_pertask" "source \"kernel/trace/rv/monitors/ltl_pertask/Kconfig\""
+
+check_and_compare_folder "LTL per_task with parent and description (default name)" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -p ltl_parent -D 'Simple description'" \
+ "test_ltl" "LTL_MON_EVENTS_ID"
+
+# Error handling tests
+check "missing required spec argument" \
+ "$RVGEN monitor -c da -t per_cpu" 2 \
+ "the following arguments are required: -s/--spec" "Traceback (most recent call last)"
+
+check "missing required monitor type" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot" 2 \
+ "the following arguments are required: -t/--monitor_type" "Traceback (most recent call last)"
+
+check "missing required monitor class" \
+ "$RVGEN monitor -s tests/specs/test_da.dot -t per_cpu" 2 \
+ "the following arguments are required: -c/--class" "Traceback (most recent call last)"
+
+check "invalid monitor class" \
+ "$RVGEN monitor -c invalid -s tests/specs/test_da.dot -t per_cpu" 1 \
+ "Unknown monitor class" "Traceback (most recent call last)"
+
+check "missing dot file" \
+ "$RVGEN monitor -c da -s tests/specs/nonexistent.dot -t per_cpu" 1 \
+ "No such file or directory" "Traceback (most recent call last)"
+
+check "missing ltl file" \
+ "$RVGEN monitor -c ltl -s tests/specs/nonexistent.ltl -t per_task" 1 \
+ "No such file or directory" "Traceback (most recent call last)"
+
+check "invalid dot file syntax" \
+ "$RVGEN monitor -c da -s tests/specs/test_invalid.dot -t per_cpu" 1 \
+ "Not a valid .dot format" "Traceback (most recent call last)"
+
+check "invalid ha file syntax" \
+ "$RVGEN monitor -c ha -s tests/specs/test_invalid_ha.dot -t per_obj" 1 \
+ "Unrecognised event constraint" "Traceback (most recent call last)"
+
+check "invalid ltl file syntax" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_invalid.ltl -t per_task" 1 \
+ "Illegal character 'i'" "Traceback (most recent call last)"
+
+test_end
diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
index 57e16dc980b1..cfdf2180aad8 100644
--- a/tools/verification/tests/engine.sh
+++ b/tools/verification/tests/engine.sh
@@ -5,6 +5,8 @@ test_begin() {
# included correctly.
ctr=0
[ -z "$RV" ] && RV="../rv/rv"
+ [ -z "$RVGEN" ] && RVGEN="python3 ../rvgen"
+ [ -z "$GOLDEN_DIR" ] && GOLDEN_DIR="tests/golden"
[ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT"
}
@@ -118,6 +120,38 @@ check_if_exists() {
fi
}
+check_and_compare_folder() {
+ # Run command, compare generated folder to golden, and cleanup
+ local desc=$1
+ local command=$2
+ local generated_dir=$3
+ local expected_output=$4
+ local unexpected_output=$5
+ local golden_dir="$GOLDEN_DIR/$generated_dir"
+
+ ctr=$((ctr + 1))
+ if [ -n "$TEST_COUNT" ]; then
+ rm -rf "$generated_dir"
+ _check "$desc" "$command" 0 "$expected_output" "$unexpected_output"
+
+ if [ "$fail" -eq 0 ] && [ ! -d "$generated_dir" ]; then
+ failure "# Generated directory not found: $generated_dir"
+ fi
+
+ if [ "$fail" -ne 0 ]; then
+ :
+ elif ! diff -r "$generated_dir" "$golden_dir" &> /dev/null; then
+ failure "# Directories differ:"
+ failbuf+=$(diff -r "$generated_dir" "$golden_dir" 2>&1 | sed 's/^/# /')
+ failbuf+=$'\n'
+ fi
+
+ report "$1"
+
+ rm -rf "$generated_dir"
+ fi
+}
+
set_timeout() {
TIMEOUT="timeout -v -k 30s $1"
}
--
2.55.0
^ permalink raw reply related
* [PATCH v4 09/17] verification/rvgen: Add the rvgen kunit subcommand
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Add the rvgen kunit subcommand to patch an already generated monitor for
kunit support. It parses the handlers and create the necessary structs
and initialisations.
The only remaining manual steps are importing the test in the runner
and writing the test itself.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
Notes:
V4:
* Use pathlib instead of os.path
* Rename files to backup instead of writing to backup
tools/verification/rvgen/Makefile | 1 +
tools/verification/rvgen/__main__.py | 15 +-
tools/verification/rvgen/rvgen/generator.py | 4 +-
tools/verification/rvgen/rvgen/kunit.py | 194 ++++++++++++++++++
.../rvgen/rvgen/templates/kunit.c | 29 +++
5 files changed, 240 insertions(+), 3 deletions(-)
create mode 100644 tools/verification/rvgen/rvgen/kunit.py
create mode 100644 tools/verification/rvgen/rvgen/templates/kunit.c
diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile
index 2a2b9e64ea42..48d0376a5cc4 100644
--- a/tools/verification/rvgen/Makefile
+++ b/tools/verification/rvgen/Makefile
@@ -23,6 +23,7 @@ install:
$(INSTALL) rvgen/dot2c.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2c.py
$(INSTALL) dot2c -D -m 755 $(DESTDIR)$(bindir)/
$(INSTALL) rvgen/dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2k.py
+ $(INSTALL) rvgen/kunit.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/kunit.py
$(INSTALL) rvgen/container.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/container.py
$(INSTALL) rvgen/generator.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/generator.py
$(INSTALL) rvgen/ltl2ba.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2ba.py
diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
index 5c923dc10d0f..e0ac562fbddd 100644
--- a/tools/verification/rvgen/__main__.py
+++ b/tools/verification/rvgen/__main__.py
@@ -13,6 +13,7 @@ if __name__ == '__main__':
from rvgen.generator import Monitor
from rvgen.container import Container
from rvgen.ltl2k import ltl2k
+ from rvgen.kunit import KUnit, KUnitError
from rvgen.automata import AutomataError
import argparse
import sys
@@ -41,6 +42,11 @@ if __name__ == '__main__':
container_parser = subparsers.add_parser("container", parents=[parent_parser])
container_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
+ kunit_parser = subparsers.add_parser("kunit", parents=[parent_parser])
+ kunit_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
+ kunit_parser.add_argument('-l', "--local", dest="local", action="store_true", required=False,
+ help="Force looking for the monitor in the current directory only")
+
params = parser.parse_args()
try:
@@ -55,11 +61,18 @@ if __name__ == '__main__':
else:
print("Unknown monitor class:", params.monitor_class)
sys.exit(1)
- else:
+ elif params.subcmd == "container":
monitor = Container(vars(params))
+ elif params.subcmd == "kunit":
+ monitor = KUnit(vars(params))
+ monitor.print_files()
+ sys.exit(0)
except AutomataError as e:
print(f"There was an error processing {params.spec}: {e}", file=sys.stderr)
sys.exit(1)
+ except KUnitError as e:
+ print(f"There was an error generating KUnit files: {e}", file=sys.stderr)
+ sys.exit(1)
print(f"Writing the monitor into the directory {monitor.name}")
monitor.print_files()
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index f1b37d34b1e9..45e2bab26cb5 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -22,9 +22,9 @@ class RVGenerator:
self.description = extra_params.get("description", self.name) or "auto-generated"
self.auto_patch = extra_params.get("auto_patch")
if self.auto_patch:
- self.__fill_rv_kernel_dir()
+ self._fill_rv_kernel_dir()
- def __fill_rv_kernel_dir(self):
+ def _fill_rv_kernel_dir(self):
# find the kernel tree root relative to this file's location
resolved_path = Path(__file__).resolve()
if len(resolved_path.parents) > 4:
diff --git a/tools/verification/rvgen/rvgen/kunit.py b/tools/verification/rvgen/rvgen/kunit.py
new file mode 100644
index 000000000000..ed2082d7d3bc
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/kunit.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+#
+# Generator for runtime verification kunit files
+
+import re
+from pathlib import Path
+from . import generator
+
+
+class KUnitError(Exception):
+ """Exception raised for errors in KUnit generation and file handling."""
+
+
+class KUnit(generator.RVGenerator):
+ template_dir = ""
+
+ def __init__(self, extra_params={}):
+ super().__init__(extra_params)
+ self.local = extra_params.get("local", False)
+ self.kunit_c = self._read_template_file("kunit.c")
+ if not self.local:
+ self._fill_rv_kernel_dir()
+ try:
+ self.monitor_path = self.__find_monitor_c_file()
+ with open(self.monitor_path, 'r') as f:
+ self.content = f.read()
+ except OSError as e:
+ raise KUnitError(e) from e
+ self.monitor_class = self.__detect_monitor_class()
+
+ def _read_template_file(self, file):
+ if file in ("main.c", "Kconfig"):
+ return ""
+ return super()._read_template_file(file)
+
+ def __find_monitor_c_file(self) -> str:
+ """Look for the monitor file in the kernel tree or in the current folder."""
+ if not self.local:
+ path = Path(self.rv_dir) / "monitors" / self.name / f"{self.name}.c"
+ if path.exists():
+ return str(path)
+
+ path = Path(self.name) / f"{self.name}.c"
+ if path.exists():
+ return str(path)
+
+ raise FileNotFoundError(f"Could not find monitor C file for '{self.name}'")
+
+ def __extract_function_args(self, handler_name: str) -> str:
+ pattern = re.compile(
+ r'^\s*(.*?)\b' + re.escape(handler_name) + r'\(([^)]*)\)',
+ re.MULTILINE | re.DOTALL
+ )
+ match = pattern.search(self.content)
+ if not match:
+ return "/* XXX: fill handlers argument. */"
+
+ return match.group(2).strip()
+
+ def __parse_attach_handlers(self) -> list[str]:
+ """Find handlers by parsing when they are attached to tracepoints."""
+ probe_pattern = re.compile(
+ r'rv_attach_trace_probe\(.*, ([a-zA-Z0-9_]+)\)'
+ )
+ handlers = []
+ for match in probe_pattern.finditer(self.content):
+ handler = match.group(1)
+ if handler not in handlers:
+ handlers.append(handler)
+ return handlers
+
+ def __detect_monitor_class(self) -> str:
+ for c in ("da", "ha", "ltl"):
+ if f"{c}_monitor.h" in self.content:
+ return c
+ return "da"
+
+ def __fill_kunit_c(self, struct_name: str) -> str:
+ kunit_c = self.kunit_c
+ kunit_c = kunit_c.replace("%%MODEL_NAME%%", self.name)
+ kunit_c = kunit_c.replace("%%MODEL_NAME_UP%%", self.name.upper())
+ kunit_c = kunit_c.replace("%%MONITOR_CLASS%%", self.monitor_class)
+ kunit_c = kunit_c.replace("%%STRUCT_NAME%%", struct_name)
+ return kunit_c
+
+ def __fill_kunit_h(self, struct_name, prototypes) -> str:
+ return f"""/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __{self.name.upper()}_KUNIT_H
+#define __{self.name.upper()}_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct {struct_name} {{
+\tstruct rv_kunit_mon mon;
+\t{"\n\t".join(prototypes)}
+}} {struct_name};
+#endif
+
+#endif /* __{self.name.upper()}_KUNIT_H */
+"""
+
+ def __fill_monitor_handlers(self, struct_name, assignments):
+ struct_definition = f"""#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "{self.name}_kunit.h"
+
+const struct {struct_name} {struct_name} = {{
+\t.mon = RV_MON_OPS_INIT(),
+\t{"\n\t".join(assignments)}
+}};
+EXPORT_SYMBOL_IF_KUNIT({struct_name});
+#endif"""
+
+ if self.auto_patch:
+ try:
+ with open(self.monitor_path, 'w') as f:
+ f.write(f"{self.content}\n{struct_definition}\n")
+ except OSError as e:
+ raise KUnitError(f"Error patching monitor file {self.monitor_path}: {e}") from e
+ else:
+ print(f"Append the following to {self.name}.c:\n")
+ print(struct_definition)
+ print("Now complete the test and add it to rv_monitors_test.c")
+
+ def print_files(self):
+
+ handlers = self.__parse_attach_handlers()
+
+ if not handlers:
+ raise KUnitError(f"No handlers found in {self.monitor_path}")
+
+ prototypes = []
+ assignments = []
+ for handler in handlers:
+ arguments = self.__extract_function_args(handler)
+
+ prototypes.append(f"void (*{handler})({arguments});")
+ assignments.append(f".{handler} = {handler},")
+
+ struct_name = f"rv_{self.name}_ops"
+
+ self.__fill_monitor_handlers(struct_name, assignments)
+
+ dir_path = Path(self.monitor_path).parent
+
+ header_file_path = dir_path / f"{self.name}_kunit.h"
+ kunit_c_file_path = dir_path / f"{self.name}_kunit.c"
+
+ use_backup = True
+ if header_file_path.exists() or kunit_c_file_path.exists():
+ try:
+ response = input("KUnit file(s) already exist. Backup? [Y/n] ")
+ if response.strip().lower() in ("n", "no"):
+ use_backup = False
+ except EOFError:
+ print("Non-interactive session detected, backing up existing files.")
+ else:
+ use_backup = False
+
+ if use_backup:
+ for path in (header_file_path, kunit_c_file_path):
+ if path.exists():
+ try:
+ path.rename(path.with_suffix(path.suffix + ".bak"))
+ except OSError as e:
+ raise KUnitError(f"Error backing up file {path}: {e}") from e
+
+ header_content = self.__fill_kunit_h(struct_name, prototypes)
+ try:
+ with open(header_file_path, 'w') as f:
+ f.write(header_content)
+ print(f"Successfully created KUnit header file: {header_file_path}")
+ except OSError as e:
+ raise KUnitError(f"Error writing to file {header_file_path}: {e}") from e
+
+ kunit_c_content = self.__fill_kunit_c(struct_name)
+ try:
+ with open(kunit_c_file_path, 'w') as f:
+ f.write(kunit_c_content)
+ print(f"Successfully created KUnit C file: {kunit_c_file_path}")
+ except OSError as e:
+ raise KUnitError(f"Error writing to file {kunit_c_file_path}: {e}") from e
diff --git a/tools/verification/rvgen/rvgen/templates/kunit.c b/tools/verification/rvgen/rvgen/templates/kunit.c
new file mode 100644
index 000000000000..d29bbf2ea5c6
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/kunit.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "%%MODEL_NAME%%_kunit.h"
+
+#if IS_ENABLED(CONFIG_RV_MON_%%MODEL_NAME_UP%%)
+
+static void rv_test_%%MODEL_NAME%%(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &%%STRUCT_NAME%%.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * %%STRUCT_NAME%%.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_%%MODEL_NAME%% rv_test_stub
+#endif
--
2.55.0
^ permalink raw reply related
* [PATCH v4 12/17] rv: Add KUnit tests for some DA/HA monitors
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Validate the functionality of DA monitors by injecting events in a
controlled environment (KUnit) and expecting reactions.
Events handlers are exported directly from the monitor source files
without using system events and with dummy arguments (e.g. no real
tasks). If the provided sequence of events incurs a violation, the test
expects the stub version of rv_react() to be called.
This testing method can validate the entire monitor implementation since
it sits between the monitor and the system (in place of the
tracepoints). All sorts of system and timing events can be emulated
without affecting the running kernel.
Handlers and monitor functions are exported as part of a struct to
simplify the process of running KUnit tests from kernel modules.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
Notes:
V4:
* Use deadline_thresh in nomiss KUnit test
* Special init/destroy for per-task monitors to avoid touching real
tasks and starting tracepoints
* Add dummy test to check reactions work as expected
* Use actual reactor instead of stub to support timer reactions
include/rv/da_monitor.h | 22 +++
include/rv/ha_monitor.h | 21 +++
include/rv/kunit.h | 61 ++++++
kernel/trace/rv/Kconfig | 11 ++
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 18 ++
.../trace/rv/monitors/nomiss/nomiss_kunit.c | 38 ++++
.../trace/rv/monitors/nomiss/nomiss_kunit.h | 35 ++++
kernel/trace/rv/monitors/opid/opid.c | 12 ++
kernel/trace/rv/monitors/opid/opid_kunit.c | 33 ++++
kernel/trace/rv/monitors/opid/opid_kunit.h | 23 +++
kernel/trace/rv/monitors/sco/sco.c | 13 ++
kernel/trace/rv/monitors/sco/sco_kunit.c | 29 +++
kernel/trace/rv/monitors/sco/sco_kunit.h | 24 +++
kernel/trace/rv/monitors/sssw/sssw.c | 14 ++
kernel/trace/rv/monitors/sssw/sssw_kunit.c | 33 ++++
kernel/trace/rv/monitors/sssw/sssw_kunit.h | 30 +++
kernel/trace/rv/monitors/sts/sts.c | 19 ++
kernel/trace/rv/monitors/sts/sts_kunit.c | 39 ++++
kernel/trace/rv/monitors/sts/sts_kunit.h | 33 ++++
kernel/trace/rv/rv.c | 40 ++++
kernel/trace/rv/rv_monitors_test.c | 175 ++++++++++++++++++
.../rvgen/rvgen/templates/kunit.c | 4 +
23 files changed, 728 insertions(+)
create mode 100644 include/rv/kunit.h
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
create mode 100644 kernel/trace/rv/monitors/opid/opid_kunit.c
create mode 100644 kernel/trace/rv/monitors/opid/opid_kunit.h
create mode 100644 kernel/trace/rv/monitors/sco/sco_kunit.c
create mode 100644 kernel/trace/rv/monitors/sco/sco_kunit.h
create mode 100644 kernel/trace/rv/monitors/sssw/sssw_kunit.c
create mode 100644 kernel/trace/rv/monitors/sssw/sssw_kunit.h
create mode 100644 kernel/trace/rv/monitors/sts/sts_kunit.c
create mode 100644 kernel/trace/rv/monitors/sts/sts_kunit.h
create mode 100644 kernel/trace/rv/rv_monitors_test.c
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 34b8fba9ecd4..773564720ba1 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -311,6 +311,11 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk)
return &tsk->rv[task_mon_slot].da_mon;
}
+static inline void da_reset(struct task_struct *tsk)
+{
+ da_monitor_reset(da_get_monitor(tsk));
+}
+
/*
* da_get_target - return the task associated to the monitor
*/
@@ -908,4 +913,21 @@ static inline void da_reset(da_id_type id, monitor_target target)
}
#endif /* RV_MON_TYPE */
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#if RV_MON_TYPE == RV_MON_PER_TASK
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .is_per_task = true, \
+ .task_slot = &task_mon_slot, \
+ .task_reset = da_reset, \
+}
+#else
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .monitor_init = da_monitor_init, \
+ .monitor_destroy = da_monitor_destroy, \
+}
+#endif /* RV_MON_TYPE */
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+
#endif
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 28d3c74cabfc..d4a490bf2726 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -558,4 +558,25 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon) { }
#endif
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#ifdef RV_MON_OPS_INIT
+#undef RV_MON_OPS_INIT
+#endif
+
+#if RV_MON_TYPE == RV_MON_PER_TASK
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .is_per_task = true, \
+ .task_slot = &task_mon_slot, \
+ .task_reset = da_reset, \
+}
+#else
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .monitor_init = ha_monitor_init, \
+ .monitor_destroy = ha_monitor_destroy, \
+}
+#endif /* RV_MON_TYPE */
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+
#endif
diff --git a/include/rv/kunit.h b/include/rv/kunit.h
new file mode 100644
index 000000000000..ff98b5137285
--- /dev/null
+++ b/include/rv/kunit.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * Declaration of utilities to run KUnit tests.
+ */
+
+#ifndef _RV_KUNIT_H
+#define _RV_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <kunit/test.h>
+#include <kunit/test-bug.h>
+#include <linux/delay.h>
+
+int rv_set_testing(struct kunit_suite *suite);
+void rv_clear_testing(struct kunit_suite *suite);
+
+#define RV_KUNIT_MAX_MOCK_TASKS 8
+
+struct rv_kunit_ctx {
+ int reactions, expected;
+ int mock_task_count;
+ struct task_struct *mock_tasks[RV_KUNIT_MAX_MOCK_TASKS];
+};
+
+#define RV_KUNIT_EXPECT_REACTION(test, ctx) \
+ do { \
+ KUNIT_EXPECT_EQ(test, ctx->reactions, ++ctx->expected); \
+ if (ctx->reactions != ctx->expected) \
+ ctx->expected = ctx->reactions; \
+ } while (0)
+
+#define RV_KUNIT_EXPECT_NO_REACTION(test, ctx) \
+ do { \
+ KUNIT_EXPECT_EQ(test, ctx->reactions, ctx->expected); \
+ if (ctx->reactions != ctx->expected) \
+ ctx->expected = ctx->reactions; \
+ } while (0)
+
+#define RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) \
+ for (int __done = ({ RV_KUNIT_EXPECT_NO_REACTION(test, ctx); 0; }); \
+ !__done; \
+ __done = ({ RV_KUNIT_EXPECT_REACTION(test, ctx); 1; }))
+
+struct rv_kunit_mon {
+ struct rv_monitor *rv_this;
+ int (*monitor_init)(void);
+ void (*monitor_destroy)(void);
+ bool is_per_task;
+ int *task_slot;
+ void (*task_reset)(struct task_struct *task);
+};
+
+void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon);
+void teardown_test(void *arg);
+struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test);
+
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
+#endif /* _RV_KUNIT_H */
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 3884b14df375..34c1feb35a9b 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -111,3 +111,14 @@ config RV_REACT_PANIC
help
Enables the panic reactor. The panic reactor emits a printk()
message if an exception is found and panic()s the system.
+
+config RV_MONITORS_KUNIT_TEST
+ tristate "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS
+ depends on KUNIT && RV && RV_REACTORS
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for the RV (Runtime Verification) monitors.
+ These tests verify that monitors correctly detect violations by
+ triggering fake events and validating the expected reactions.
+
+ If unsure, say N.
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 94498da35b37..a3502b7fe7f2 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -24,3 +24,4 @@ obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
+obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 8ead8783c29f..beb75965dc8d 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -291,3 +291,21 @@ module_exit(unregister_nomiss);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadline.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "nomiss_kunit.h"
+
+const struct rv_nomiss_ops rv_nomiss_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .deadline_thresh = &deadline_thresh,
+ .handle_dl_replenish = handle_dl_replenish,
+ .handle_dl_throttle = handle_dl_throttle,
+ .handle_dl_server_stop = handle_dl_server_stop,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_sys_enter = handle_sys_enter,
+ .handle_newtask = handle_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_nomiss_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
new file mode 100644
index 000000000000..2b85bbf179c4
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "nomiss_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_NOMISS)
+
+static void rv_test_nomiss(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_nomiss_ops.mon);
+
+ target->pid = 99;
+ target->policy = SCHED_DEADLINE;
+ target->dl.runtime = 10000;
+ target->dl.dl_deadline = 20000;
+
+ rv_nomiss_ops.handle_newtask(NULL, target, 0);
+
+ /* Task gets preempted and can't terminate before deadline */
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ rv_nomiss_ops.handle_dl_replenish(NULL, &target->dl, 0, DL_TASK);
+ udelay(10);
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) {
+ udelay(10 + *rv_nomiss_ops.deadline_thresh / 1000);
+ rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+ }
+}
+
+#else
+#define rv_test_nomiss rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
new file mode 100644
index 000000000000..2be779c5dbaa
--- /dev/null
+++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __NOMISS_KUNIT_H
+#define __NOMISS_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_nomiss_ops {
+ struct rv_kunit_mon mon;
+ const u64 *deadline_thresh;
+ void (*handle_dl_replenish)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_dl_throttle)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_dl_server_stop)(void *data, struct sched_dl_entity *dl_se,
+ int cpu, u8 type);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *tsk);
+ void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id);
+ void (*handle_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_nomiss_ops;
+#endif
+
+#endif /* __NOMISS_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c
index 3b6a85e815b8..9ae619f176fa 100644
--- a/kernel/trace/rv/monitors/opid/opid.c
+++ b/kernel/trace/rv/monitors/opid/opid.c
@@ -115,3 +115,15 @@ module_exit(unregister_opid);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("opid: operations with preemption and irq disabled.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "opid_kunit.h"
+
+const struct rv_opid_ops rv_opid_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_need_resched = handle_sched_need_resched,
+ .handle_sched_waking = handle_sched_waking,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_opid_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.c b/kernel/trace/rv/monitors/opid/opid_kunit.c
new file mode 100644
index 000000000000..3cb087a74241
--- /dev/null
+++ b/kernel/trace/rv/monitors/opid/opid_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "opid_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_OPID)
+
+static void rv_test_opid(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_opid_ops.mon);
+
+ /* Ensure we keep the same per-cpu monitor */
+ guard(migrate)();
+ KUNIT_EXPECT_TRUE(test, preemptible());
+
+ /* Wakeup with preemption and interrupts enabled */
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_opid_ops.handle_sched_waking(NULL, NULL);
+
+ /* Need resched with interrupts enabled */
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) {
+ scoped_guard(preempt)
+ rv_opid_ops.handle_sched_need_resched(NULL, NULL, 0, TIF_NEED_RESCHED);
+ }
+}
+
+#else
+#define rv_test_opid rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.h b/kernel/trace/rv/monitors/opid/opid_kunit.h
new file mode 100644
index 000000000000..4969c6175957
--- /dev/null
+++ b/kernel/trace/rv/monitors/opid/opid_kunit.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __OPID_KUNIT_H
+#define __OPID_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_opid_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_need_resched)(void *data, struct task_struct *tsk, int cpu, int tif);
+ void (*handle_sched_waking)(void *data, struct task_struct *p);
+} rv_opid_ops;
+#endif
+
+#endif /* __OPID_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sco/sco.c b/kernel/trace/rv/monitors/sco/sco.c
index 5a3bd5e16e62..1ef1b96e859d 100644
--- a/kernel/trace/rv/monitors/sco/sco.c
+++ b/kernel/trace/rv/monitors/sco/sco.c
@@ -83,3 +83,16 @@ module_exit(unregister_sco);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sco: scheduling context operations.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sco_kunit.h"
+
+const struct rv_sco_ops rv_sco_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_schedule_entry = handle_schedule_entry,
+ .handle_schedule_exit = handle_schedule_exit,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sco_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.c b/kernel/trace/rv/monitors/sco/sco_kunit.c
new file mode 100644
index 000000000000..5e59bcbfcf0b
--- /dev/null
+++ b/kernel/trace/rv/monitors/sco/sco_kunit.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sco_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SCO)
+
+static void rv_test_sco(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sco_ops.mon);
+
+ /* Ensure we keep the same per-cpu monitor */
+ guard(migrate)();
+
+ /* Set state while scheduling */
+ rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sco_ops.handle_schedule_entry(NULL, false);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+}
+
+#else
+#define rv_test_sco rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.h b/kernel/trace/rv/monitors/sco/sco_kunit.h
new file mode 100644
index 000000000000..567757df6b1d
--- /dev/null
+++ b/kernel/trace/rv/monitors/sco/sco_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SCO_KUNIT_H
+#define __SCO_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sco_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state);
+ void (*handle_schedule_entry)(void *data, bool preempt);
+ void (*handle_schedule_exit)(void *data, bool is_switch);
+} rv_sco_ops;
+#endif
+
+#endif /* __SCO_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sssw/sssw.c b/kernel/trace/rv/monitors/sssw/sssw.c
index a91321c890cd..fbfde32dc136 100644
--- a/kernel/trace/rv/monitors/sssw/sssw.c
+++ b/kernel/trace/rv/monitors/sssw/sssw.c
@@ -112,3 +112,17 @@ module_exit(unregister_sssw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sssw: set state sleep and wakeup.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sssw_kunit.h"
+
+const struct rv_sssw_ops rv_sssw_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_signal_deliver = handle_signal_deliver,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sssw_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.c b/kernel/trace/rv/monitors/sssw/sssw_kunit.c
new file mode 100644
index 000000000000..a95faf859c60
--- /dev/null
+++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sssw_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SSSW)
+
+static void rv_test_sssw(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sssw_ops.mon);
+
+ /* Suspend without setting to sleepable */
+ rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_RUNNING);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+
+ /* Switch in after suspension without wakeup */
+ rv_sssw_ops.handle_sched_wakeup(NULL, target);
+ rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sssw_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING);
+}
+
+#else
+#define rv_test_sssw rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.h b/kernel/trace/rv/monitors/sssw/sssw_kunit.h
new file mode 100644
index 000000000000..6513daa7afba
--- /dev/null
+++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SSSW_KUNIT_H
+#define __SSSW_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sssw_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *p);
+ void (*handle_signal_deliver)(void *data, int sig,
+ struct kernel_siginfo *info,
+ struct k_sigaction *ka);
+} rv_sssw_ops;
+#endif
+
+#endif /* __SSSW_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sts/sts.c b/kernel/trace/rv/monitors/sts/sts.c
index ce031cbf202a..2a044cf925b1 100644
--- a/kernel/trace/rv/monitors/sts/sts.c
+++ b/kernel/trace/rv/monitors/sts/sts.c
@@ -152,3 +152,22 @@ module_exit(unregister_sts);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sts: schedule implies task switch.");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sts_kunit.h"
+
+const struct rv_sts_ops rv_sts_ops = {
+ .mon = RV_MON_OPS_INIT(),
+#ifdef CONFIG_X86_LOCAL_APIC
+ .handle_vector_irq_entry = handle_vector_irq_entry,
+#endif
+ .handle_irq_disable = handle_irq_disable,
+ .handle_irq_enable = handle_irq_enable,
+ .handle_irq_entry = handle_irq_entry,
+ .handle_sched_switch = handle_sched_switch,
+ .handle_schedule_entry = handle_schedule_entry,
+ .handle_schedule_exit = handle_schedule_exit,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sts_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.c b/kernel/trace/rv/monitors/sts/sts_kunit.c
new file mode 100644
index 000000000000..a07316fff091
--- /dev/null
+++ b/kernel/trace/rv/monitors/sts/sts_kunit.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/sched.h>
+#include "sts_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_STS)
+
+static void rv_test_sts(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_sts_ops.mon);
+ /* Per-CPU monitor, make sure we don't change CPU mid-test */
+ guard(migrate)();
+
+ /* Switch without disabling interrupts */
+ rv_sts_ops.handle_schedule_exit(NULL, false);
+ rv_sts_ops.handle_schedule_entry(NULL, false);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+
+ rv_sts_ops.handle_schedule_exit(NULL, false);
+
+ /* Schedule from interrupt context */
+ rv_sts_ops.handle_schedule_entry(NULL, false);
+ rv_sts_ops.handle_irq_disable(NULL, 0, 0);
+ rv_sts_ops.handle_irq_entry(NULL, 0, NULL);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING);
+ rv_sts_ops.handle_irq_enable(NULL, 0, 0);
+}
+
+#else
+#define rv_test_sts rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.h b/kernel/trace/rv/monitors/sts/sts_kunit.h
new file mode 100644
index 000000000000..dede4e098c1f
--- /dev/null
+++ b/kernel/trace/rv/monitors/sts/sts_kunit.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __STS_KUNIT_H
+#define __STS_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sts_ops {
+ struct rv_kunit_mon mon;
+#ifdef CONFIG_X86_LOCAL_APIC
+ void (*handle_vector_irq_entry)(void *data, int vector);
+#endif
+ void (*handle_irq_disable)(void *data, unsigned long ip, unsigned long parent_ip);
+ void (*handle_irq_enable)(void *data, unsigned long ip, unsigned long parent_ip);
+ void (*handle_irq_entry)(void *data, int irq, struct irqaction *action);
+ void (*handle_sched_switch)(void *data, bool preempt,
+ struct task_struct *prev,
+ struct task_struct *next,
+ unsigned int prev_state);
+ void (*handle_schedule_entry)(void *data, bool preempt);
+ void (*handle_schedule_exit)(void *data, bool is_switch);
+} rv_sts_ops;
+#endif
+
+#endif /* __STS_KUNIT_H */
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index 9d58c730821d..cfe950fef3b4 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -854,3 +854,43 @@ int __init rv_init_interface(void)
return 0;
}
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <rv/kunit.h>
+#include <kunit/visibility.h>
+
+/*
+ * rv_set_testing - ensure mutual exclusion between KUnit tests and real monitors
+ *
+ * KUnit tests for RV monitors rely on stubs that are incompatible with
+ * the execution of real monitors. Ensure mutual exclusion by acquiring
+ * the rv_interface_lock for the duration of the suite.
+ *
+ * Returns 0 on success, -EBUSY if any real monitor is already enabled.
+ */
+int rv_set_testing(struct kunit_suite *suite)
+{
+ struct rv_monitor *mon;
+
+ mutex_lock(&rv_interface_lock);
+
+ list_for_each_entry(mon, &rv_monitors_list, list) {
+ if (mon->enabled) {
+ mutex_unlock(&rv_interface_lock);
+ return -EBUSY;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_set_testing);
+
+/*
+ * rv_clear_testing - allow real monitors to run again after KUnit tests
+ */
+void rv_clear_testing(struct kunit_suite *suite)
+{
+ mutex_unlock(&rv_interface_lock);
+}
+EXPORT_SYMBOL_IF_KUNIT(rv_clear_testing);
+#endif
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
new file mode 100644
index 000000000000..2145c85d4c9a
--- /dev/null
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ *
+ * RV monitor kunit tests:
+ * Tests the RV monitors by triggering fake events to verify monitor
+ * behavior and reactions. Tests start from the first defined event and
+ * trigger events in order to verify error detection.
+ */
+#include <rv/kunit.h>
+#include <kunit/static_stub.h>
+#include <kunit/test-bug.h>
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include "rv.h"
+
+/*
+ * An easy way to pass the context is to use kunit_get_current_test()->priv,
+ * but this doesn't always work (e.g. a reactor running from another context
+ * like softirq). Store the current value here whenever a test is running.
+ */
+static struct rv_kunit_ctx *active_ctx;
+
+__printf(1, 0)
+static void rv_kunit_mock_react(const char *msg, va_list args)
+{
+ if (active_ctx)
+ ++active_ctx->reactions;
+}
+
+/*
+ * teardown_test - Disable the monitor for a kunit test
+ *
+ * Since per-task monitors are special, make sure we reset all the ones we
+ * started manually here, if required.
+ */
+void teardown_test(void *arg)
+{
+ const struct rv_kunit_mon *mon = arg;
+ struct kunit *test = kunit_get_current_test();
+
+ if (test) {
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+
+ if (mon->is_per_task && mon->task_reset) {
+ for (int i = 0; i < ctx->mock_task_count; i++)
+ mon->task_reset(ctx->mock_tasks[i]);
+ synchronize_rcu();
+ }
+ }
+
+ mon->rv_this->enabled = 0;
+
+ mon->rv_this->react = NULL;
+ active_ctx = NULL;
+
+ if (mon->is_per_task)
+ *mon->task_slot = RV_PER_TASK_MONITOR_INIT;
+ else
+ mon->monitor_destroy();
+}
+
+/*
+ * prepare_test - Enable the monitor for a kunit test
+ *
+ * Do the bare minimum to set up the monitor, per-task monitors are special as
+ * "real" initialisation/destruction iterates over real tasks, and may register
+ * handlers. All we need is to select the right slot in the task_struct.
+ */
+void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon)
+{
+ KUNIT_ASSERT_FALSE(test, mon->rv_this->enabled);
+
+ active_ctx = test->priv;
+ mon->rv_this->react = rv_kunit_mock_react;
+
+ if (mon->is_per_task)
+ *mon->task_slot = 0;
+ else
+ KUNIT_ASSERT_EQ(test, mon->monitor_init(), 0);
+
+ mon->rv_this->enabled = 1;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ kunit_add_action_or_reset(test, teardown_test, (void *)mon));
+}
+
+struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ struct task_struct *tsk;
+
+ KUNIT_ASSERT_LT(test, ctx->mock_task_count, RV_KUNIT_MAX_MOCK_TASKS);
+
+ tsk = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tsk);
+
+ if (!IS_ENABLED(CONFIG_THREAD_INFO_IN_TASK)) {
+ tsk->stack = kunit_kzalloc(test, sizeof(struct thread_info), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, tsk->stack);
+ }
+
+ ctx->mock_tasks[ctx->mock_task_count++] = tsk;
+ return tsk;
+}
+
+static int rv_mon_test_init(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+ test->priv = ctx;
+
+ return 0;
+}
+
+static void __maybe_unused rv_test_stub(struct kunit *test)
+{
+ kunit_skip(test, "Monitor not enabled\n");
+}
+
+/*
+ * rv_test_dummy - test reactions work as expected
+ */
+static void rv_test_dummy(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ static struct rv_monitor dummy_monitor = {
+ .name = "dummy",
+ .react = rv_kunit_mock_react,
+ };
+
+ active_ctx = ctx;
+
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_react(&dummy_monitor, "dummy");
+ RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
+
+ active_ctx = NULL;
+}
+
+#include "monitors/sco/sco_kunit.c"
+#include "monitors/sssw/sssw_kunit.c"
+#include "monitors/sts/sts_kunit.c"
+#include "monitors/opid/opid_kunit.c"
+#include "monitors/nomiss/nomiss_kunit.c"
+
+static struct kunit_case rv_mon_test_cases[] = {
+ KUNIT_CASE(rv_test_dummy),
+ KUNIT_CASE(rv_test_sco),
+ KUNIT_CASE(rv_test_sssw),
+ KUNIT_CASE(rv_test_sts),
+ KUNIT_CASE(rv_test_opid),
+ KUNIT_CASE(rv_test_nomiss),
+ {}
+};
+
+static struct kunit_suite rv_mon_test_suite = {
+ .name = "rv_mon",
+ .suite_init = rv_set_testing,
+ .suite_exit = rv_clear_testing,
+ .init = rv_mon_test_init,
+ .test_cases = rv_mon_test_cases,
+};
+
+kunit_test_suites(&rv_mon_test_suite);
+
+MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
+MODULE_DESCRIPTION("RV monitor kunit tests: test monitors by triggering reactions");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/tools/verification/rvgen/rvgen/templates/kunit.c b/tools/verification/rvgen/rvgen/templates/kunit.c
index 402b5c8575cf..62092b5cc6d4 100644
--- a/tools/verification/rvgen/rvgen/templates/kunit.c
+++ b/tools/verification/rvgen/rvgen/templates/kunit.c
@@ -13,6 +13,10 @@
static void rv_test_%%MODEL_NAME%%(struct kunit *test)
{
struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
prepare_test(test, &%%STRUCT_NAME%%.mon);
--
2.55.0
^ permalink raw reply related
* [PATCH v4 13/17] rv: Add KUnit stub for current
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Some monitors do not only rely on tracepoint arguments but also on the
currently executing task.
This makes it more challenging to mock events in KUnit.
Define wrapper functions around current, the functionality is stubbed
only during KUnit, however the additional function call is necessary
whenever the KUnit tests are built in.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 1 +
include/rv/kunit.h | 13 +++++++++-
include/rv/ltl_monitor.h | 1 +
kernel/trace/rv/Kconfig | 3 +++
.../trace/rv/monitors/pagefault/pagefault.c | 2 +-
kernel/trace/rv/monitors/sleep/sleep.c | 24 +++++++++----------
kernel/trace/rv/rv.c | 8 +++++++
kernel/trace/rv/rv_monitors_test.c | 9 +++++++
8 files changed, 47 insertions(+), 14 deletions(-)
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 773564720ba1..9f7ba443d777 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -16,6 +16,7 @@
#include <rv/automata.h>
#include <linux/rv.h>
+#include <rv/kunit.h>
#include <linux/stringify.h>
#include <linux/bug.h>
#include <linux/sched.h>
diff --git a/include/rv/kunit.h b/include/rv/kunit.h
index ff98b5137285..6d16a422a80c 100644
--- a/include/rv/kunit.h
+++ b/include/rv/kunit.h
@@ -2,7 +2,10 @@
/*
* Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
*
- * Declaration of utilities to run KUnit tests.
+ * Declaration of wrappers to allow stubbing core functionality, like current,
+ * and other testing utilities.
+ * Necessary only when mocking may be needed. If the RV KUnit test is
+ * enabled, the wrappers incur an additional function call overhead.
*/
#ifndef _RV_KUNIT_H
@@ -16,6 +19,7 @@
int rv_set_testing(struct kunit_suite *suite);
void rv_clear_testing(struct kunit_suite *suite);
+struct task_struct *rv_get_current(void);
#define RV_KUNIT_MAX_MOCK_TASKS 8
@@ -23,6 +27,7 @@ struct rv_kunit_ctx {
int reactions, expected;
int mock_task_count;
struct task_struct *mock_tasks[RV_KUNIT_MAX_MOCK_TASKS];
+ struct task_struct *curr;
};
#define RV_KUNIT_EXPECT_REACTION(test, ctx) \
@@ -57,5 +62,11 @@ void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon);
void teardown_test(void *arg);
struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test);
+#define rv_mock_current(ctx, task) (ctx->curr = task)
+
+#else /* !CONFIG_RV_MONITORS_KUNIT_TEST */
+
+#define rv_get_current() current
+
#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
#endif /* _RV_KUNIT_H */
diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h
index 56e83edcf0c4..d7dc01db4dd9 100644
--- a/include/rv/ltl_monitor.h
+++ b/include/rv/ltl_monitor.h
@@ -9,6 +9,7 @@
#include <linux/stringify.h>
#include <linux/seq_buf.h>
#include <rv/instrumentation.h>
+#include <rv/kunit.h>
#include <trace/events/task.h>
#include <trace/events/sched.h>
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index 34c1feb35a9b..7bae9723cdbf 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -121,4 +121,7 @@ config RV_MONITORS_KUNIT_TEST
These tests verify that monitors correctly detect violations by
triggering fake events and validating the expected reactions.
+ Enabling this may slightly increase overhead of some monitors even
+ when the KUnit test is not running.
+
If unsure, say N.
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index 5e1a2a606783..e52500fd2de0 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -38,7 +38,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo
static void handle_page_fault(void *data, unsigned long address, struct pt_regs *regs,
unsigned long error_code)
{
- ltl_atom_pulse(current, LTL_PAGEFAULT, true);
+ ltl_atom_pulse(rv_get_current(), LTL_PAGEFAULT, true);
}
static int enable_pagefault(void)
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 12328ce663f5..71d2005ce520 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -102,7 +102,7 @@ static void handle_sched_waking(void *data, struct task_struct *task)
if (this_cpu_read(hardirq_context)) {
ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true);
} else if (in_task()) {
- if (current->prio <= task->prio)
+ if (rv_get_current()->prio <= task->prio)
ltl_atom_pulse(task, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, true);
} else if (in_nmi()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_NMI, true);
@@ -112,12 +112,12 @@ static void handle_sched_waking(void *data, struct task_struct *task)
static void handle_contention_begin(void *data, void *lock, unsigned int flags)
{
if (flags & LCB_F_RT)
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, true);
}
static void handle_contention_end(void *data, void *lock, int ret)
{
- ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
+ ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, false);
}
static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
@@ -126,7 +126,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
unsigned long args[6];
int op, cmd;
- mon = ltl_get_monitor(current);
+ mon = ltl_get_monitor(rv_get_current());
switch (id) {
#ifdef __NR_clock_nanosleep
@@ -135,11 +135,11 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_clock_nanosleep_time64
case __NR_clock_nanosleep_time64:
#endif
- syscall_get_arguments(current, regs, args);
+ syscall_get_arguments(rv_get_current(), regs, args);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, true);
break;
#ifdef __NR_futex
@@ -148,25 +148,25 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
#ifdef __NR_futex_time64
case __NR_futex_time64:
#endif
- syscall_get_arguments(current, regs, args);
+ syscall_get_arguments(rv_get_current(), regs, args);
op = args[1];
cmd = op & FUTEX_CMD_MASK;
switch (cmd) {
case FUTEX_LOCK_PI:
case FUTEX_LOCK_PI2:
- ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_LOCK_PI, true);
break;
case FUTEX_WAIT:
case FUTEX_WAIT_BITSET:
case FUTEX_WAIT_REQUEUE_PI:
- ltl_atom_update(current, LTL_FUTEX_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_FUTEX_WAIT, true);
break;
}
break;
#ifdef __NR_epoll_wait
case __NR_epoll_wait:
- ltl_atom_update(current, LTL_EPOLL_WAIT, true);
+ ltl_atom_update(rv_get_current(), LTL_EPOLL_WAIT, true);
break;
#endif
}
@@ -174,7 +174,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
{
- struct ltl_monitor *mon = ltl_get_monitor(current);
+ struct ltl_monitor *mon = ltl_get_monitor(rv_get_current());
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
@@ -182,7 +182,7 @@ static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
- ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false);
+ ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, false);
}
static void handle_kthread_stop(void *data, struct task_struct *task)
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index cfe950fef3b4..edb10812f296 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -142,6 +142,7 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
+#include <kunit/static_stub.h>
#ifdef CONFIG_RV_MON_EVENTS
#define CREATE_TRACE_POINTS
@@ -893,4 +894,11 @@ void rv_clear_testing(struct kunit_suite *suite)
mutex_unlock(&rv_interface_lock);
}
EXPORT_SYMBOL_IF_KUNIT(rv_clear_testing);
+
+struct task_struct *rv_get_current(void)
+{
+ KUNIT_STATIC_STUB_REDIRECT(rv_get_current);
+ return current;
+}
+EXPORT_SYMBOL_GPL(rv_get_current);
#endif
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index 2145c85d4c9a..2108973383b2 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -106,6 +106,13 @@ struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test)
return tsk;
}
+static struct task_struct *stub_rv_get_current(void)
+{
+ if (active_ctx && active_ctx->curr)
+ return active_ctx->curr;
+ return current;
+}
+
static int rv_mon_test_init(struct kunit *test)
{
struct rv_kunit_ctx *ctx;
@@ -115,6 +122,8 @@ static int rv_mon_test_init(struct kunit *test)
test->priv = ctx;
+ kunit_activate_static_stub(test, rv_get_current, stub_rv_get_current);
+
return 0;
}
--
2.55.0
^ permalink raw reply related
* [PATCH v4 14/17] rv: Add KUnit tests for some LTL monitors
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco,
Masami Hiramatsu
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Validate the functionality of LTL monitors by injecting events in a
controlled environment (KUnit) and expecting reactions, just like it is
done in DA monitors.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/ltl_monitor.h | 8 +++
.../trace/rv/monitors/pagefault/pagefault.c | 12 ++++
.../rv/monitors/pagefault/pagefault_kunit.c | 34 +++++++++++
.../rv/monitors/pagefault/pagefault_kunit.h | 24 ++++++++
kernel/trace/rv/monitors/sleep/sleep.c | 19 ++++++
kernel/trace/rv/monitors/sleep/sleep_kunit.c | 58 +++++++++++++++++++
kernel/trace/rv/monitors/sleep/sleep_kunit.h | 30 ++++++++++
kernel/trace/rv/rv_monitors_test.c | 4 ++
8 files changed, 189 insertions(+)
create mode 100644 kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
create mode 100644 kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
create mode 100644 kernel/trace/rv/monitors/sleep/sleep_kunit.c
create mode 100644 kernel/trace/rv/monitors/sleep/sleep_kunit.h
diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h
index d7dc01db4dd9..e9fd8265a3da 100644
--- a/include/rv/ltl_monitor.h
+++ b/include/rv/ltl_monitor.h
@@ -172,3 +172,11 @@ static void __maybe_unused ltl_atom_pulse(struct task_struct *task, enum ltl_ato
ltl_atom_set(mon, atom, !value);
ltl_validate(task, mon);
}
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#define RV_MON_OPS_INIT() { \
+ .rv_this = &rv_this, \
+ .is_per_task = true, \
+ .task_slot = <l_monitor_slot, \
+}
+#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c
index e52500fd2de0..c599fc19fc88 100644
--- a/kernel/trace/rv/monitors/pagefault/pagefault.c
+++ b/kernel/trace/rv/monitors/pagefault/pagefault.c
@@ -86,3 +86,15 @@ module_exit(unregister_pagefault);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("pagefault: Monitor that RT tasks do not raise page faults");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "pagefault_kunit.h"
+
+const struct rv_pagefault_ops rv_pagefault_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_page_fault = handle_page_fault,
+ .handle_task_newtask = handle_task_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_pagefault_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
new file mode 100644
index 000000000000..98ac7d4ec014
--- /dev/null
+++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <linux/sched/deadline.h>
+#include <linux/sched/rt.h>
+#include "pagefault_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_PAGEFAULT)
+
+static void rv_test_pagefault(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+
+ prepare_test(test, &rv_pagefault_ops.mon);
+
+ /* Initial pagefault when non-RT to start the model without failure */
+ target->policy = SCHED_NORMAL;
+ target->prio = MAX_RT_PRIO + 20;
+ rv_pagefault_ops.handle_task_newtask(NULL, target, 0);
+ rv_mock_current(ctx, target);
+ rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0);
+
+ /* RT task has a page fault */
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 1;
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0);
+}
+
+#else
+#define rv_test_pagefault rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
new file mode 100644
index 000000000000..2f9652f08b3f
--- /dev/null
+++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __PAGEFAULT_KUNIT_H
+#define __PAGEFAULT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_pagefault_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_page_fault)(void *data, unsigned long address, struct pt_regs *regs,
+ unsigned long error_code);
+ void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_pagefault_ops;
+#endif
+
+#endif /* __PAGEFAULT_KUNIT_H */
diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c
index 71d2005ce520..f84a61a21825 100644
--- a/kernel/trace/rv/monitors/sleep/sleep.c
+++ b/kernel/trace/rv/monitors/sleep/sleep.c
@@ -247,3 +247,22 @@ module_exit(unregister_sleep);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("sleep: Monitor that RT tasks do not undesirably sleep");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "sleep_kunit.h"
+
+const struct rv_sleep_ops rv_sleep_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_sched_waking = handle_sched_waking,
+ .handle_sched_wakeup = handle_sched_wakeup,
+ .handle_sched_set_state = handle_sched_set_state,
+ .handle_contention_begin = handle_contention_begin,
+ .handle_contention_end = handle_contention_end,
+ .handle_kthread_stop = handle_kthread_stop,
+ .handle_sys_enter = handle_sys_enter,
+ .handle_sys_exit = handle_sys_exit,
+ .handle_task_newtask = handle_task_newtask,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_sleep_ops);
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.c b/kernel/trace/rv/monitors/sleep/sleep_kunit.c
new file mode 100644
index 000000000000..9bbe3822a76d
--- /dev/null
+++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+#include <trace/events/syscalls.h>
+#include <trace/events/sched.h>
+#include <uapi/linux/futex.h>
+#include "sleep_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_SLEEP)
+
+static void rv_test_sleep(struct kunit *test)
+{
+ struct task_struct *target = rv_kunit_alloc_mock_task(test);
+ struct task_struct *other = rv_kunit_alloc_mock_task(test);
+ struct rv_kunit_ctx *ctx = test->priv;
+ unsigned long args[6] = {0};
+ struct pt_regs regs = {0};
+
+ prepare_test(test, &rv_sleep_ops.mon);
+ target->policy = SCHED_FIFO;
+ target->prio = MAX_RT_PRIO - 2;
+ other->policy = SCHED_FIFO;
+ other->prio = MAX_RT_PRIO - 1;
+ rv_sleep_ops.handle_task_newtask(NULL, target, 0);
+
+ /* RT task sleeps on a non RT-friendly nanosleep */
+ rv_mock_current(ctx, target);
+ args[0] = CLOCK_REALTIME;
+ syscall_set_arguments(target, ®s, args);
+#ifdef __NR_clock_nanosleep
+ rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_clock_nanosleep);
+#elif defined(__NR_clock_nanosleep_time64)
+ rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_clock_nanosleep_time64);
+#endif
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_sleep_ops.handle_sys_exit(NULL, NULL, 0);
+
+ /* RT task woken up by lower priority task */
+ args[1] = FUTEX_WAIT;
+ syscall_set_arguments(target, ®s, args);
+ rv_mock_current(ctx, target);
+#ifdef __NR_futex
+ rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_futex);
+#elif defined(__NR_futex_time64)
+ rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_futex_time64);
+#endif
+ rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE);
+ rv_mock_current(ctx, other);
+ rv_sleep_ops.handle_sched_waking(NULL, target);
+ RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ rv_sleep_ops.handle_sched_wakeup(NULL, target);
+}
+
+#else
+#define rv_test_sleep rv_test_stub
+#endif
diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.h b/kernel/trace/rv/monitors/sleep/sleep_kunit.h
new file mode 100644
index 000000000000..2cd61e31a6af
--- /dev/null
+++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __SLEEP_KUNIT_H
+#define __SLEEP_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_sleep_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_sched_waking)(void *data, struct task_struct *task);
+ void (*handle_sched_wakeup)(void *data, struct task_struct *task);
+ void (*handle_sched_set_state)(void *data, struct task_struct *task, int state);
+ void (*handle_contention_begin)(void *data, void *lock, unsigned int flags);
+ void (*handle_contention_end)(void *data, void *lock, int ret);
+ void (*handle_kthread_stop)(void *data, struct task_struct *task);
+ void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id);
+ void (*handle_sys_exit)(void *data, struct pt_regs *regs, long ret);
+ void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags);
+} rv_sleep_ops;
+#endif
+
+#endif /* __SLEEP_KUNIT_H */
diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c
index 2108973383b2..c565bca2b4ae 100644
--- a/kernel/trace/rv/rv_monitors_test.c
+++ b/kernel/trace/rv/rv_monitors_test.c
@@ -157,6 +157,8 @@ static void rv_test_dummy(struct kunit *test)
#include "monitors/sts/sts_kunit.c"
#include "monitors/opid/opid_kunit.c"
#include "monitors/nomiss/nomiss_kunit.c"
+#include "monitors/pagefault/pagefault_kunit.c"
+#include "monitors/sleep/sleep_kunit.c"
static struct kunit_case rv_mon_test_cases[] = {
KUNIT_CASE(rv_test_dummy),
@@ -165,6 +167,8 @@ static struct kunit_case rv_mon_test_cases[] = {
KUNIT_CASE(rv_test_sts),
KUNIT_CASE(rv_test_opid),
KUNIT_CASE(rv_test_nomiss),
+ KUNIT_CASE(rv_test_pagefault),
+ KUNIT_CASE(rv_test_sleep),
{}
};
--
2.55.0
^ permalink raw reply related
* [PATCH v4 15/17] selftests/verification: Fix wrong errexit assumption
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco,
Shuah Khan, linux-kselftest
Cc: Wen Yang, Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
RV selftest rely on bash errexit (set -e) to terminate with error, when
a step is expected to return false, the following syntax is used:
! cmd
This however prevents the test from exiting when cmd is false (desired)
but doesn't exit if cmd is true, since commands prefixed with ! are
explicitly excluded from errexit.
Use the syntax
! cmd || false
Which ends up checking the exit value of ! cmd and supplies a false
command for errexit to evaluate.
Reviewed-by: Wen Yang <wen.yang@linux.dev>
Acked-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
.../verification/test.d/rv_monitor_enable_disable.tc | 10 +++++-----
.../verification/test.d/rv_monitor_reactor.tc | 4 ++--
.../selftests/verification/test.d/rv_wwnr_printk.tc | 4 ++--
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc b/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc
index f29236defb5a..61e2c8b54d9a 100644
--- a/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc
+++ b/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc
@@ -10,7 +10,7 @@ test_simple_monitor() {
grep -q "$monitor$" enabled_monitors
echo 0 > "monitors/$prefix$monitor/enable"
- ! grep -q "$monitor$" enabled_monitors
+ ! grep -q "$monitor$" enabled_monitors || false
echo "$monitor" >> enabled_monitors
grep -q 1 "monitors/$prefix$monitor/enable"
@@ -34,12 +34,12 @@ test_container_monitor() {
test -n "$nested"
echo 0 > "monitors/$monitor/enable"
- ! grep -q "^$monitor$" enabled_monitors
+ ! grep -q "^$monitor$" enabled_monitors || false
for nested_dir in "monitors/$monitor"/*; do
[ -d "$nested_dir" ] || continue
nested=$(basename "$nested_dir")
- ! grep -q "^$monitor:$nested$" enabled_monitors
+ ! grep -q "^$monitor:$nested$" enabled_monitors || false
done
echo "$monitor" >> enabled_monitors
@@ -71,5 +71,5 @@ for monitor_dir in monitors/*; do
fi
done
-! echo non_existent_monitor > enabled_monitors
-! grep -q "^non_existent_monitor$" enabled_monitors
+! echo non_existent_monitor > enabled_monitors || false
+! grep -q "^non_existent_monitor$" enabled_monitors || false
diff --git a/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc b/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc
index 2958bf849338..516a20971390 100644
--- a/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc
+++ b/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc
@@ -64,5 +64,5 @@ done
monitor=$(ls /sys/kernel/tracing/rv/monitors -1 | head -n 1)
test -f "monitors/$monitor/reactors"
-! echo non_existent_reactor > "monitors/$monitor/reactors"
-! grep -q "\\[non_existent_reactor\\]" "monitors/$monitor/reactors"
+! echo non_existent_reactor > "monitors/$monitor/reactors" || false
+! grep -q "\\[non_existent_reactor\\]" "monitors/$monitor/reactors" || false
diff --git a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc
index 5a59432b1d93..96de95edb530 100644
--- a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc
+++ b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc
@@ -17,13 +17,13 @@ echo printk > monitors/wwnr/reactors
load
echo 0 > monitoring_on
-! load
+! load || false
echo 1 > monitoring_on
load
echo 0 > reacting_on
-! load
+! load || false
echo 1 > reacting_on
echo nop > monitors/wwnr/reactors
--
2.55.0
^ permalink raw reply related
* [PATCH v4 17/17] selftests/verification: Add selftests for deadline and stall monitors
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco,
Shuah Khan, linux-kselftest
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
Add selftests to verify deadline monitors don't fail under expected
conditions and the stall monitor report violations only when expected.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
Notes:
V4:
* Restore original stall threshold after selftest
.../verification/test.d/rv_deadline.tc | 21 ++++++++++++
.../selftests/verification/test.d/rv_stall.tc | 33 +++++++++++++++++++
2 files changed, 54 insertions(+)
create mode 100644 tools/testing/selftests/verification/test.d/rv_deadline.tc
create mode 100644 tools/testing/selftests/verification/test.d/rv_stall.tc
diff --git a/tools/testing/selftests/verification/test.d/rv_deadline.tc b/tools/testing/selftests/verification/test.d/rv_deadline.tc
new file mode 100644
index 000000000000..b583096beda6
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/rv_deadline.tc
@@ -0,0 +1,21 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test deadline monitors trigger no reaction
+# requires: available_reactors deadline:monitor printk:reactor stress-ng:program
+
+load() { # returns true if there was a reaction
+ local lines_before
+ lines_before=$(dmesg | wc -l)
+ stress-ng --cpu 2 --sched deadline --sched-period 100000000 --sched-deadline 100000000 --sched-runtime 20000000 -t 5 &
+ stress-ng --cpu 2 --sched rr --sched-prio 50 --cyclic 1 --cyclic-policy rr --cyclic-prio 50 -t 5 &
+ wait
+ dmesg | tail -n +$((lines_before + 1)) | grep -q "rv: monitor [a-z]\+ does not allow event"
+}
+
+echo 1 > monitors/deadline/enable
+echo printk > monitors/deadline/reactors
+
+! load || false
+
+echo nop > monitors/deadline/reactors
+echo 0 > monitors/deadline/enable
diff --git a/tools/testing/selftests/verification/test.d/rv_stall.tc b/tools/testing/selftests/verification/test.d/rv_stall.tc
new file mode 100644
index 000000000000..515a10263ca1
--- /dev/null
+++ b/tools/testing/selftests/verification/test.d/rv_stall.tc
@@ -0,0 +1,33 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test stall monitor
+# requires: available_reactors stall:monitor printk:reactor stress-ng:program
+
+THRESHOLD=/sys/module/stall/parameters/threshold_jiffies
+ORIG_THRESHOLD=$(cat $THRESHOLD)
+trap 'echo $ORIG_THRESHOLD > $THRESHOLD' EXIT
+
+load() { # returns true if there was a reaction
+ local lines_before cpu
+ cpu=$(($(nproc) - 1))
+ lines_before=$(dmesg | wc -l)
+ stress-ng --cpu 1 --taskset "$cpu" --sched rr --sched-prio 1 -t 3 &
+ stress-ng --cpu 5 --taskset "$cpu" -t 3 &
+ wait
+ dmesg | tail -n +$((lines_before + 1)) | grep -q "rv: monitor stall does not allow event"
+}
+
+echo 5000 > $THRESHOLD
+echo 1 > monitors/stall/enable
+echo printk > monitors/stall/reactors
+
+! load || false
+
+echo 0 > monitors/stall/enable
+echo 70 > $THRESHOLD
+echo 1 > monitors/stall/enable
+
+load
+
+echo nop > monitors/stall/reactors
+echo 0 > monitors/stall/enable
--
2.55.0
^ permalink raw reply related
* [PATCH v4 10/17] verification/rvgen: Add selftests for rvgen kunit
From: Gabriele Monaco @ 2026-07-17 15:46 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, Steven Rostedt, Gabriele Monaco
Cc: Nam Cao, Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260717154638.220789-1-gmonaco@redhat.com>
The rvgen kunit command patches monitor files and adds necessary
definitions for kunit tests.
Add a test case validating its behaviour on dummy generated files and
comparing it against reference files, like it's done for rvgen monitor.
Reviewed-by: Nam Cao <namcao@linutronix.de>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
.../rvgen/rvgen/templates/kunit.c | 2 +-
.../rvgen/tests/golden/test_bak_kunit/Kconfig | 9 +
.../golden/test_bak_kunit/test_bak_kunit.c | 107 +++++++
.../golden/test_bak_kunit/test_bak_kunit.h | 108 ++++++++
.../test_bak_kunit/test_bak_kunit_kunit.c | 33 +++
.../test_bak_kunit/test_bak_kunit_kunit.c.bak | 1 +
.../test_bak_kunit/test_bak_kunit_kunit.h | 22 ++
.../test_bak_kunit/test_bak_kunit_trace.h | 14 +
.../rvgen/tests/golden/test_da_kunit/Kconfig | 9 +
.../golden/test_da_kunit/test_da_kunit.c | 107 +++++++
.../golden/test_da_kunit/test_da_kunit.h | 47 ++++
.../test_da_kunit/test_da_kunit_kunit.c | 33 +++
.../test_da_kunit/test_da_kunit_kunit.h | 23 ++
.../test_da_kunit/test_da_kunit_trace.h | 15 +
.../rvgen/tests/golden/test_ha_kunit/Kconfig | 9 +
.../golden/test_ha_kunit/test_ha_kunit.c | 260 ++++++++++++++++++
.../golden/test_ha_kunit/test_ha_kunit.h | 88 ++++++
.../test_ha_kunit/test_ha_kunit_kunit.c | 33 +++
.../test_ha_kunit/test_ha_kunit_kunit.h | 24 ++
.../test_ha_kunit/test_ha_kunit_trace.h | 19 ++
.../rvgen/tests/golden/test_ltl_kunit/Kconfig | 9 +
.../golden/test_ltl_kunit/test_ltl_kunit.c | 107 +++++++
.../golden/test_ltl_kunit/test_ltl_kunit.h | 108 ++++++++
.../test_ltl_kunit/test_ltl_kunit_kunit.c | 33 +++
.../test_ltl_kunit/test_ltl_kunit_kunit.h | 22 ++
.../test_ltl_kunit/test_ltl_kunit_trace.h | 14 +
tools/verification/rvgen/tests/rvgen_kunit.t | 41 +++
27 files changed, 1296 insertions(+), 1 deletion(-)
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h
create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h
create mode 100644 tools/verification/rvgen/tests/rvgen_kunit.t
diff --git a/tools/verification/rvgen/rvgen/templates/kunit.c b/tools/verification/rvgen/rvgen/templates/kunit.c
index d29bbf2ea5c6..402b5c8575cf 100644
--- a/tools/verification/rvgen/rvgen/templates/kunit.c
+++ b/tools/verification/rvgen/rvgen/templates/kunit.c
@@ -8,7 +8,7 @@
*/
#include "%%MODEL_NAME%%_kunit.h"
-#if IS_ENABLED(CONFIG_RV_MON_%%MODEL_NAME_UP%%)
+#if IS_REACHABLE(CONFIG_RV_MON_%%MODEL_NAME_UP%%)
static void rv_test_%%MODEL_NAME%%(struct kunit *test)
{
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig
new file mode 100644
index 000000000000..175a416f8b18
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_BAK_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "test_bak_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c
new file mode 100644
index 000000000000..16579c1c6910
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_bak_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_bak_kunit.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_bak_kunit(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_bak_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_bak_kunit(void)
+{
+ rv_detach_trace_probe("test_bak_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_bak_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_bak_kunit,
+ .disable = disable_test_bak_kunit,
+};
+
+static int __init register_test_bak_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_bak_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_bak_kunit);
+module_exit(unregister_test_bak_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_bak_kunit: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h
new file mode 100644
index 000000000000..2bfe4e37cea7
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_bak_kunit
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c
new file mode 100644
index 000000000000..e2b9354034cc
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_bak_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_BAK_KUNIT)
+
+static void rv_test_test_bak_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_bak_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_bak_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_bak_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak
new file mode 100644
index 000000000000..f747925bf542
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak
@@ -0,0 +1 @@
+DUMMY
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h
new file mode 100644
index 000000000000..585c4803be23
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_BAK_KUNIT_KUNIT_H
+#define __TEST_BAK_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_bak_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_example_event)(void *data, /* XXX: fill header */);
+} rv_test_bak_kunit_ops;
+#endif
+
+#endif /* __TEST_BAK_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h
new file mode 100644
index 000000000000..b984208838c4
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_BAK_KUNIT
+DEFINE_EVENT(event_ltl_monitor_id, event_test_bak_kunit,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_bak_kunit,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_BAK_KUNIT */
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig
new file mode 100644
index 000000000000..6d664ba5624d
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_DA_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select DA_MON_EVENTS_IMPLICIT
+ bool "test_da_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c
new file mode 100644
index 000000000000..effd26548b07
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_da_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "test_da_kunit.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+static void handle_event_1(void *data, /* XXX: fill header */)
+{
+ da_handle_event(event_1_test_da_kunit);
+}
+
+static void handle_event_2(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_test_da_kunit);
+}
+
+static int enable_test_da_kunit(void)
+{
+ int retval;
+
+ retval = da_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_1);
+ rv_attach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_2);
+
+ return 0;
+}
+
+static void disable_test_da_kunit(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_1);
+ rv_detach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_2);
+
+ da_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_da_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_da_kunit,
+ .disable = disable_test_da_kunit,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_da_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_da_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_da_kunit);
+module_exit(unregister_test_da_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_da_kunit: auto-generated");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "test_da_kunit_kunit.h"
+
+const struct rv_test_da_kunit_ops rv_test_da_kunit_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_event_1 = handle_event_1,
+ .handle_event_2 = handle_event_2,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_test_da_kunit_ops);
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h
new file mode 100644
index 000000000000..290a9454caa4
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_da_kunit automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_da_kunit
+
+enum states_test_da_kunit {
+ state_a_test_da_kunit,
+ state_b_test_da_kunit,
+ state_max_test_da_kunit,
+};
+
+#define INVALID_STATE state_max_test_da_kunit
+
+enum events_test_da_kunit {
+ event_1_test_da_kunit,
+ event_2_test_da_kunit,
+ event_max_test_da_kunit,
+};
+
+struct automaton_test_da_kunit {
+ char *state_names[state_max_test_da_kunit];
+ char *event_names[event_max_test_da_kunit];
+ unsigned char function[state_max_test_da_kunit][event_max_test_da_kunit];
+ unsigned char initial_state;
+ bool final_states[state_max_test_da_kunit];
+};
+
+static const struct automaton_test_da_kunit automaton_test_da_kunit = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_test_da_kunit, state_a_test_da_kunit },
+ { INVALID_STATE, state_a_test_da_kunit },
+ },
+ .initial_state = state_a_test_da_kunit,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c
new file mode 100644
index 000000000000..17826a5c47c8
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_da_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_DA_KUNIT)
+
+static void rv_test_test_da_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_da_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_da_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_da_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h
new file mode 100644
index 000000000000..0094215ff4fb
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_DA_KUNIT_KUNIT_H
+#define __TEST_DA_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_da_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_event_1)(void *data, /* XXX: fill header */);
+ void (*handle_event_2)(void *data, /* XXX: fill header */);
+} rv_test_da_kunit_ops;
+#endif
+
+#endif /* __TEST_DA_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h
new file mode 100644
index 000000000000..16804a79e834
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_DA_KUNIT
+DEFINE_EVENT(event_da_monitor, event_test_da_kunit,
+ TP_PROTO(char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor, error_test_da_kunit,
+ TP_PROTO(char *state, char *event),
+ TP_ARGS(state, event));
+#endif /* CONFIG_RV_MON_TEST_DA_KUNIT */
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig
new file mode 100644
index 000000000000..6c48770ace1a
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_HA_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select HA_MON_EVENTS_ID
+ bool "test_ha_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c
new file mode 100644
index 000000000000..99d6244c2539
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ha_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#define RV_MON_TYPE RV_MON_PER_TASK
+/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+#include "test_ha_kunit.h"
+#include <rv/ha_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ *
+ */
+#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */
+
+#define FOO_NS /* XXX: what is FOO_NS? */
+
+static inline u64 bar_ns(struct ha_monitor *ha_mon)
+{
+ return /* XXX: what is bar_ns(ha_mon)? */;
+}
+
+static u64 foo_ns = /* XXX: default value */;
+module_param(foo_ns, ullong, 0644);
+
+/*
+ * These functions define how to read and reset the environment variable.
+ *
+ * Common environment variables like ns-based and jiffy-based clocks have
+ * pre-define getters and resetters you can use. The parser can infer the type
+ * of the environment variable if you supply a measure unit in the constraint.
+ * If you define your own functions, make sure to add appropriate memory
+ * barriers if required.
+ * Some environment variables don't require a storage as they read a system
+ * state (e.g. preemption count). Those variables are never reset, so we don't
+ * define a reset function on monitors only relying on this type of variables.
+ */
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha_kunit env, u64 time_ns)
+{
+ if (env == clk_test_ha_kunit)
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+ else if (env == env1_test_ha_kunit)
+ return /* XXX: how do I read env1? */
+ else if (env == env2_test_ha_kunit)
+ return /* XXX: how do I read env2? */
+ return ENV_INVALID_VALUE;
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha_kunit env, u64 time_ns)
+{
+ if (env == clk_test_ha_kunit)
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+/*
+ * These functions are used to validate state transitions.
+ *
+ * They are generated by parsing the model, there is usually no need to change them.
+ * If the monitor requires a timer, there are functions responsible to arm it when
+ * the next state has a constraint, cancel it in any other case and to check
+ * that it didn't expire before the callback run. Transitions to the same state
+ * without a reset never affect timers.
+ * Due to the different representations between invariants and guards, there is
+ * a function to convert it in case invariants or guards are reachable from
+ * another invariant without reset. Those are not present if not required in
+ * the model. This is all automatic but is worth checking because it may show
+ * errors in the model (e.g. missing resets).
+ */
+static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == S0_test_ha_kunit)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S2_test_ha_kunit)
+ return ha_check_invariant_ns(ha_mon, clk_test_ha_kunit, time_ns);
+ return true;
+}
+
+static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (curr_state == next_state)
+ return;
+ if (curr_state == S2_test_ha_kunit)
+ ha_inv_to_guard(ha_mon, clk_test_ha_kunit, BAR_NS(ha_mon), time_ns);
+}
+
+static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ bool res = true;
+
+ if (curr_state == S0_test_ha_kunit && event == event0_test_ha_kunit)
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S0_test_ha_kunit && event == event1_test_ha_kunit)
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S1_test_ha_kunit && event == event0_test_ha_kunit)
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ else if (curr_state == S1_test_ha_kunit && event == event2_test_ha_kunit) {
+ res = ha_get_env(ha_mon, env1_test_ha_kunit, time_ns) == 0ull;
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ } else if (curr_state == S2_test_ha_kunit && event == event1_test_ha_kunit)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) ||
+ ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < foo_ns;
+ else if (curr_state == S3_test_ha_kunit && event == event0_test_ha_kunit)
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) ||
+ (ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < FOO_NS &&
+ ha_get_env(ha_mon, env2_test_ha_kunit, time_ns) == 0ull);
+ else if (curr_state == S3_test_ha_kunit && event == event1_test_ha_kunit) {
+ res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) ||
+ (ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < 5000ull &&
+ ha_get_env(ha_mon, env1_test_ha_kunit, time_ns) == 1ull);
+ ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns);
+ }
+ return res;
+}
+
+static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (next_state == curr_state && event != event0_test_ha_kunit)
+ return;
+ if (next_state == S0_test_ha_kunit)
+ ha_start_timer_ns(ha_mon, clk_test_ha_kunit, bar_ns(ha_mon), time_ns);
+ else if (next_state == S2_test_ha_kunit)
+ ha_start_timer_ns(ha_mon, clk_test_ha_kunit, BAR_NS(ha_mon), time_ns);
+ else if (curr_state == S0_test_ha_kunit)
+ ha_cancel_timer(ha_mon);
+ else if (curr_state == S2_test_ha_kunit)
+ ha_cancel_timer(ha_mon);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
+
+ if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
+ return false;
+
+ ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
+
+ return true;
+}
+
+static void handle_event0(void *data, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_start_event(p, event0_test_ha_kunit);
+}
+
+static void handle_event1(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event1_test_ha_kunit);
+}
+
+static void handle_event2(void *data, /* XXX: fill header */)
+{
+ struct task_struct *p = /* XXX: how do I get p? */;
+ da_handle_event(p, event2_test_ha_kunit);
+}
+
+static int enable_test_ha_kunit(void)
+{
+ int retval;
+
+ retval = ha_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event0);
+ rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event1);
+ rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event2);
+
+ return 0;
+}
+
+static void disable_test_ha_kunit(void)
+{
+ rv_this.enabled = 0;
+
+ rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event0);
+ rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event1);
+ rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event2);
+
+ ha_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ha_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_ha_kunit,
+ .disable = disable_test_ha_kunit,
+ .reset = da_monitor_reset_all,
+ .enabled = 0,
+};
+
+static int __init register_test_ha_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ha_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ha_kunit);
+module_exit(unregister_test_ha_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ha_kunit: auto-generated");
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "test_ha_kunit_kunit.h"
+
+const struct rv_test_ha_kunit_ops rv_test_ha_kunit_ops = {
+ .mon = RV_MON_OPS_INIT(),
+ .handle_event0 = handle_event0,
+ .handle_event1 = handle_event1,
+ .handle_event2 = handle_event2,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_test_ha_kunit_ops);
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h
new file mode 100644
index 000000000000..5c428f818bdf
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of test_ha_kunit automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME test_ha_kunit
+
+enum states_test_ha_kunit {
+ S0_test_ha_kunit,
+ S1_test_ha_kunit,
+ S2_test_ha_kunit,
+ S3_test_ha_kunit,
+ state_max_test_ha_kunit,
+};
+
+#define INVALID_STATE state_max_test_ha_kunit
+
+enum events_test_ha_kunit {
+ event0_test_ha_kunit,
+ event1_test_ha_kunit,
+ event2_test_ha_kunit,
+ event_max_test_ha_kunit,
+};
+
+enum envs_test_ha_kunit {
+ clk_test_ha_kunit,
+ env1_test_ha_kunit,
+ env2_test_ha_kunit,
+ env_max_test_ha_kunit,
+ env_max_stored_test_ha_kunit = env1_test_ha_kunit,
+};
+
+_Static_assert(env_max_stored_test_ha_kunit <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_CLK_NS
+
+struct automaton_test_ha_kunit {
+ char *state_names[state_max_test_ha_kunit];
+ char *event_names[event_max_test_ha_kunit];
+ char *env_names[env_max_test_ha_kunit];
+ unsigned char function[state_max_test_ha_kunit][event_max_test_ha_kunit];
+ unsigned char initial_state;
+ bool final_states[state_max_test_ha_kunit];
+};
+
+static const struct automaton_test_ha_kunit automaton_test_ha_kunit = {
+ .state_names = {
+ "S0",
+ "S1",
+ "S2",
+ "S3",
+ },
+ .event_names = {
+ "event0",
+ "event1",
+ "event2",
+ },
+ .env_names = {
+ "clk",
+ "env1",
+ "env2",
+ },
+ .function = {
+ {
+ S0_test_ha_kunit,
+ S1_test_ha_kunit,
+ INVALID_STATE,
+ },
+ {
+ S0_test_ha_kunit,
+ INVALID_STATE,
+ S2_test_ha_kunit,
+ },
+ {
+ INVALID_STATE,
+ S2_test_ha_kunit,
+ S3_test_ha_kunit,
+ },
+ {
+ S0_test_ha_kunit,
+ S1_test_ha_kunit,
+ INVALID_STATE,
+ },
+ },
+ .initial_state = S0_test_ha_kunit,
+ .final_states = { 1, 0, 0, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c
new file mode 100644
index 000000000000..6214a4aa6d25
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_ha_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_HA_KUNIT)
+
+static void rv_test_test_ha_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_ha_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_ha_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_ha_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h
new file mode 100644
index 000000000000..0b2030cb644a
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_HA_KUNIT_KUNIT_H
+#define __TEST_HA_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_ha_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_event0)(void *data, /* XXX: fill header */);
+ void (*handle_event1)(void *data, /* XXX: fill header */);
+ void (*handle_event2)(void *data, /* XXX: fill header */);
+} rv_test_ha_kunit_ops;
+#endif
+
+#endif /* __TEST_HA_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h
new file mode 100644
index 000000000000..6c13ee0068d3
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_HA_KUNIT
+DEFINE_EVENT(event_da_monitor_id, event_test_ha_kunit,
+ TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
+ TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_test_ha_kunit,
+ TP_PROTO(int id, char *state, char *event),
+ TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha_kunit,
+ TP_PROTO(int id, char *state, char *event, char *env),
+ TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TEST_HA_KUNIT */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig
new file mode 100644
index 000000000000..3e334c344261
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TEST_LTL_KUNIT
+ depends on RV
+ # XXX: add dependencies if there
+ select LTL_MON_EVENTS_ID
+ bool "test_ltl_kunit monitor"
+ help
+ auto-generated
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c
new file mode 100644
index 000000000000..c1d58ce435a8
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/ftrace.h>
+#include <linux/tracepoint.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+#include <rv/instrumentation.h>
+
+#define MODULE_NAME "test_ltl_kunit"
+
+/*
+ * XXX: include required tracepoint headers, e.g.,
+ * #include <trace/events/sched.h>
+ */
+#include <rv_trace.h>
+
+
+/*
+ * This is the self-generated part of the monitor. Generally, there is no need
+ * to touch this section.
+ */
+#include "test_ltl_kunit.h"
+#include <rv/ltl_monitor.h>
+
+static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
+{
+ /*
+ * This is called everytime the Buchi automaton is triggered.
+ *
+ * This function could be used to fetch the atomic propositions which
+ * are expensive to trace. It is possible only if the atomic proposition
+ * does not need to be updated at precise time.
+ *
+ * It is recommended to use tracepoints and ltl_atom_update() instead.
+ */
+}
+
+static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
+{
+ /*
+ * This should initialize as many atomic propositions as possible.
+ *
+ * @task_creation indicates whether the task is being created. This is
+ * false if the task is already running before the monitor is enabled.
+ */
+ ltl_atom_set(mon, LTL_EVENT_A, true/false);
+ ltl_atom_set(mon, LTL_EVENT_B, true/false);
+}
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+static void handle_example_event(void *data, /* XXX: fill header */)
+{
+ ltl_atom_update(task, LTL_EVENT_A, true/false);
+}
+
+static int enable_test_ltl_kunit(void)
+{
+ int retval;
+
+ retval = ltl_monitor_init();
+ if (retval)
+ return retval;
+
+ rv_attach_trace_probe("test_ltl_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ return 0;
+}
+
+static void disable_test_ltl_kunit(void)
+{
+ rv_detach_trace_probe("test_ltl_kunit", /* XXX: tracepoint */, handle_example_event);
+
+ ltl_monitor_destroy();
+}
+
+/*
+ * This is the monitor register section.
+ */
+static struct rv_monitor rv_this = {
+ .name = "test_ltl_kunit",
+ .description = "auto-generated",
+ .enable = enable_test_ltl_kunit,
+ .disable = disable_test_ltl_kunit,
+};
+
+static int __init register_test_ltl_kunit(void)
+{
+ return rv_register_monitor(&rv_this, NULL);
+}
+
+static void __exit unregister_test_ltl_kunit(void)
+{
+ rv_unregister_monitor(&rv_this);
+}
+
+module_init(register_test_ltl_kunit);
+module_exit(unregister_test_ltl_kunit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("rvgen: auto-generated");
+MODULE_DESCRIPTION("test_ltl_kunit: auto-generated");
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h
new file mode 100644
index 000000000000..acc503b56e87
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * C implementation of Buchi automaton, automatically generated by
+ * tools/verification/rvgen from the linear temporal logic specification.
+ * For further information, see kernel documentation:
+ * Documentation/trace/rv/linear_temporal_logic.rst
+ */
+
+#include <linux/rv.h>
+
+#define MONITOR_NAME test_ltl_kunit
+
+enum ltl_atom {
+ LTL_EVENT_A,
+ LTL_EVENT_B,
+ LTL_NUM_ATOM
+};
+static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
+
+static const char *ltl_atom_str(enum ltl_atom atom)
+{
+ static const char *const names[] = {
+ "ev_a",
+ "ev_b",
+ };
+
+ return names[atom];
+}
+
+enum ltl_buchi_state {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ RV_NUM_BA_STATES
+};
+static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
+
+static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ if (val1)
+ __set_bit(S0, mon->states);
+ if (true)
+ __set_bit(S1, mon->states);
+ if (event_b)
+ __set_bit(S4, mon->states);
+}
+
+static void
+ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
+{
+ bool event_b = test_bit(LTL_EVENT_B, mon->atoms);
+ bool event_a = test_bit(LTL_EVENT_A, mon->atoms);
+ bool val1 = !event_a;
+
+ switch (state) {
+ case S0:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S1:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S2:
+ if (true)
+ __set_bit(S1, next);
+ if (true && val1)
+ __set_bit(S2, next);
+ if (event_b && val1)
+ __set_bit(S3, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S3:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ case S4:
+ if (val1)
+ __set_bit(S0, next);
+ if (true)
+ __set_bit(S1, next);
+ if (event_b)
+ __set_bit(S4, next);
+ break;
+ }
+}
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c
new file mode 100644
index 000000000000..37dab5dfdebc
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/rv.h>
+#include <rv/kunit.h>
+/*
+ * XXX: include required headers, e.g.,
+ * #include <linux/sched.h>
+ */
+#include "test_ltl_kunit_kunit.h"
+
+#if IS_REACHABLE(CONFIG_RV_MON_TEST_LTL_KUNIT)
+
+static void rv_test_test_ltl_kunit(struct kunit *test)
+{
+ struct rv_kunit_ctx *ctx = test->priv;
+ /*
+ * If you need to create task_structs with rv_kunit_alloc_mock_task()
+ * do it BEFORE preparing the test.
+ */
+
+ prepare_test(test, &rv_test_ltl_kunit_ops.mon);
+
+ /*
+ * XXX: write the test here
+ * e.g.
+ * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx)
+ * rv_test_ltl_kunit_ops.handle_event(args);
+ */
+}
+
+#else
+#define rv_test_test_ltl_kunit rv_test_stub
+#endif
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h
new file mode 100644
index 000000000000..b2ca34be327f
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Automatically generated by rvgen kunit.
+ * May need manual intervention for function prototypes that couldn't be
+ * found (e.g. are in another file) or variables to be exported.
+ */
+
+#ifndef __TEST_LTL_KUNIT_KUNIT_H
+#define __TEST_LTL_KUNIT_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/rv.h>
+#include <rv/kunit.h>
+
+extern const struct rv_test_ltl_kunit_ops {
+ struct rv_kunit_mon mon;
+ void (*handle_example_event)(void *data, /* XXX: fill header */);
+} rv_test_ltl_kunit_ops;
+#endif
+
+#endif /* __TEST_LTL_KUNIT_KUNIT_H */
diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h
new file mode 100644
index 000000000000..a054d5b2c0ea
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TEST_LTL_KUNIT
+DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl_kunit,
+ TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
+ TP_ARGS(task, states, atoms, next));
+DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl_kunit,
+ TP_PROTO(struct task_struct *task),
+ TP_ARGS(task));
+#endif /* CONFIG_RV_MON_TEST_LTL_KUNIT */
diff --git a/tools/verification/rvgen/tests/rvgen_kunit.t b/tools/verification/rvgen/tests/rvgen_kunit.t
new file mode 100644
index 000000000000..d27d9175f562
--- /dev/null
+++ b/tools/verification/rvgen/tests/rvgen_kunit.t
@@ -0,0 +1,41 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+# Help tests
+check "verify kunit subcommand help" \
+ "$RVGEN kunit -h" 0 "model_name" "spec"
+
+check_and_compare_folder "KUnit generation with local lookup and test_da_kunit" \
+ "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu -n test_da_kunit && $RVGEN kunit -a -l -n test_da_kunit" \
+ "test_da_kunit" "Now complete the test and add it to rv_monitors_test.c" "RV_MON_OPS_INIT"
+
+check_and_compare_folder "KUnit generation with local lookup and test_ha_kunit" \
+ "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task -n test_ha_kunit && $RVGEN kunit -a -l -n test_ha_kunit" \
+ "test_ha_kunit" "Successfully created KUnit" "Append the following to"
+
+check_and_compare_folder "KUnit generation with local lookup and test_ltl_kunit" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n test_ltl_kunit && $RVGEN kunit -l -n test_ltl_kunit" \
+ "test_ltl_kunit" "RV_MON_OPS_INIT"
+
+check_and_compare_folder "KUnit generation with backup file" \
+ "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n test_bak_kunit && echo DUMMY > test_bak_kunit/test_bak_kunit_kunit.c && $RVGEN kunit -l -n test_bak_kunit" \
+ "test_bak_kunit" "KUnit file(s) already exist.*backing up existing files"
+
+# Error handling tests
+check "missing required model_name" \
+ "$RVGEN kunit" 2 "the following arguments are required: -n/--model_name"
+
+check "non-existent model_name with auto_patch" \
+ "$RVGEN kunit -a -n nonexistent" 1 \
+ "Could not find monitor C file" "Traceback (most recent call last)"
+
+check "monitor without handlers" \
+ "mkdir -p nohandler ; echo DUMMY > nohandler/nohandler.c ; $RVGEN kunit -l -n nohandler" 1 \
+ "No handlers found" "Traceback (most recent call last)"
+rm -rf nohandler
+
+test_end
--
2.55.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox