mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 00/15] rv: Add support for BPF monitors
@ 2026-10-01 15:20 Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
                   ` (14 more replies)
  0 siblings, 15 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf
  Cc: Gabriele Monaco, Steven Rostedt, Alexei Starovoitov, Nam Cao,
	Wen Yang, Tobias Schaffner, Viktor Malik

Extend the rv userspace tool to load BPF monitors, those can be found in
specific locations (e.g. /usr/share/rv/bpf_monitors/) and are plain
object files including BTF data.

This type of BPF monitors can be generated from rvgen using the -b flag
just like in-kernel monitors and, after manual adaptation, can be built
and run transparently by the rv userspace tool.

Only DA monitors are supported (as they are the only class the rv tool
currenly supports) and the tool aims to work in the same way for both
in-kernel and BPF monitors.

The da_monitor header is adapted to be included directly in BPF programs
and share as much logic as possible.

BPF monitors are implemented purely in BPF and as such require the rv
userspace tool for any operation:
 * listing: find properly formatted objects in specific locations
 * running: load trace handlers for events
 * tracing: read event/errors from the ring buffer
 * reactors: link a react() function over the object

Selftests (make -C tools/verification/rv check) are present to validate
and demonstrate the usage, which is mostly equivalent to the standard rv
tool usage.

Patch 1 adds tracepoints required in a later monitor example.
Patch 2 improves the rv tool selftest reporting.
Patches 3-4 prepare the kernel headers for BPF support.
Patch 5 adds a feature check for tools/build.
Patch 6-8 prepare and add support for BPF monitors in the rv tool.
Patch 9-11 prepare and include BPF monitors examples.
Patch 12 adds support for BPF reactors.
Patch 13 adds support for generating BPF monitors in rvgen.
Patch 14-15 adds BPF selftests (make check) for the rv and rvgen tools.

Changes since RFC [1]:
* Drop struct_ops integration to favour a BPF-only implementation.
* Implement BPF reactors with a dynamic linker.
* Don't pin programs and maps.
* Improve handler performance when not tracing.

[1] - https://lore.kernel.org/lkml/20260831090524.106845-1-gmonaco@redhat.com

To: linux-trace-kernel@vger.kernel.org
To: bpf@vger.kernel.org
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: Nam Cao <namcao@linutronix.de>
Cc: Wen Yang <wen.yang@linux.dev>
Cc: Tobias Schaffner <tobias.schaffner@siemens.com>
Cc: Viktor Malik <vmalik@redhat.com>

Gabriele Monaco (14):
  tools/rv: Skip empty pid error in selftest if command failed
  rv: Refactor da_trace() functions to get strings internally
  rv: Cast result of model_get_*_name()
  tools/rv: Move argument parsing from in_kernel to utils
  tools/build: Add a feature test for bpftool-btf
  tools/rv: Implement BPF monitor discovery and listing
  tools/rv: Implement BPF monitor loading and tracing
  tools/rv: Copy stripped bpf_atomic.h from libarena
  tools/rv: Add BPF monitors
  tools/rv: Define CONFIG_X86_64 statically for BPF monitors
  tools/rv: Add reactors support to BPF monitors
  verification/rvgen: Add support for BPF monitors
  tools/rv: Add selftest for rv bpf monitors
  verification/rvgen: Add selftest for rvgen -b

Nam Cao (1):
  sched: Add task enqueue/dequeue trace points

 include/rv/automata.h                         |   4 +-
 include/rv/da_monitor.h                       |  61 +-
 include/trace/events/sched.h                  |   8 +
 kernel/sched/core.c                           |  12 +-
 kernel/sched/sched.h                          |   2 +
 tools/build/Makefile.feature                  |   1 +
 tools/build/feature/Makefile                  |   7 +-
 tools/verification/models/nohz.dot            |  16 +
 tools/verification/models/tqueue.dot          |  15 +
 tools/verification/rv/Makefile                |  54 +-
 tools/verification/rv/Makefile.config         |  49 +
 tools/verification/rv/Makefile.rv             |  10 +
 tools/verification/rv/bpf_monitors/.gitignore |   2 +
 .../verification/rv/bpf_monitors/bpf_atomic.h | 105 ++
 .../rv/bpf_monitors/da_monitor_bpf.h          | 374 +++++++
 tools/verification/rv/bpf_monitors/nohz.c     |  42 +
 tools/verification/rv/bpf_monitors/nohz.h     |  49 +
 tools/verification/rv/bpf_monitors/tqueue.c   |  30 +
 tools/verification/rv/bpf_monitors/tqueue.h   |  47 +
 tools/verification/rv/bpf_reactors/.gitignore |   2 +
 tools/verification/rv/bpf_reactors/panic.c    |  15 +
 tools/verification/rv/bpf_reactors/printk.c   |  13 +
 tools/verification/rv/include/bpf_monitor.h   |  22 +
 tools/verification/rv/include/utils.h         |  19 +-
 tools/verification/rv/src/Build               |   5 +
 tools/verification/rv/src/bpf_monitor.c       | 982 ++++++++++++++++++
 tools/verification/rv/src/in_kernel.c         | 176 +---
 tools/verification/rv/src/rv.c                |   5 +
 tools/verification/rv/src/utils.c             |  98 +-
 tools/verification/rv/tests/rv_list.t         |   6 +-
 tools/verification/rv/tests/rv_mon.t          |  61 +-
 tools/verification/rvgen/__main__.py          |  15 +-
 tools/verification/rvgen/rvgen/dot2c.py       |  15 +-
 tools/verification/rvgen/rvgen/dot2k.py       |  22 +-
 tools/verification/rvgen/rvgen/generator.py   |  26 +-
 .../rvgen/rvgen/templates/dot2k/main_bpf.c    |  21 +
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.c      |  35 +
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.h      |  47 +
 .../tests/golden/da_bpf_obj/da_bpf_obj.c      |  49 +
 .../tests/golden/da_bpf_obj/da_bpf_obj.h      |  47 +
 .../verification/rvgen/tests/rvgen_monitor.t  |  11 +
 tools/verification/tests/engine.sh            |   5 +-
 42 files changed, 2392 insertions(+), 193 deletions(-)
 create mode 100644 tools/verification/models/nohz.dot
 create mode 100644 tools/verification/models/tqueue.dot
 create mode 100644 tools/verification/rv/bpf_monitors/.gitignore
 create mode 100644 tools/verification/rv/bpf_monitors/bpf_atomic.h
 create mode 100644 tools/verification/rv/bpf_monitors/da_monitor_bpf.h
 create mode 100644 tools/verification/rv/bpf_monitors/nohz.c
 create mode 100644 tools/verification/rv/bpf_monitors/nohz.h
 create mode 100644 tools/verification/rv/bpf_monitors/tqueue.c
 create mode 100644 tools/verification/rv/bpf_monitors/tqueue.h
 create mode 100644 tools/verification/rv/bpf_reactors/.gitignore
 create mode 100644 tools/verification/rv/bpf_reactors/panic.c
 create mode 100644 tools/verification/rv/bpf_reactors/printk.c
 create mode 100644 tools/verification/rv/include/bpf_monitor.h
 create mode 100644 tools/verification/rv/src/bpf_monitor.c
 create mode 100644 tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h


base-commit: 238650ef6c7c7cca08e032527329424c9fbd70e5
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:49   ` Peter Zijlstra
  2026-10-01 15:20 ` [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Masami Hiramatsu, Ingo Molnar, Peter Zijlstra
  Cc: Nam Cao, K Prateek Nayak, Gabriele Monaco, Alexei Starovoitov,
	Wen Yang, Tobias Schaffner, Viktor Malik

From: Nam Cao <namcao@linutronix.de>

Add trace points into enqueue_task() and dequeue_task().

Signed-off-by: Nam Cao <namcao@linutronix.de>
Suggested-by: Peter Zijlstra <peterz@infradead.org>
Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
Co-developed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/trace/events/sched.h |  8 ++++++++
 kernel/sched/core.c          | 12 +++++++++++-
 kernel/sched/sched.h         |  2 ++
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
index 535860581f15..d6e41edc25d2 100644
--- a/include/trace/events/sched.h
+++ b/include/trace/events/sched.h
@@ -896,6 +896,14 @@ DECLARE_TRACE(sched_set_need_resched,
 	TP_PROTO(struct task_struct *tsk, int cpu, int tif),
 	TP_ARGS(tsk, cpu, tif));
 
+DECLARE_TRACE(sched_enqueue,
+	TP_PROTO(struct task_struct *tsk, int cpu),
+	TP_ARGS(tsk, cpu));
+
+DECLARE_TRACE(sched_dequeue,
+	TP_PROTO(struct task_struct *tsk, int cpu),
+	TP_ARGS(tsk, cpu));
+
 #define DL_OTHER 0
 #define DL_TASK 1
 #define DL_SERVER_FAIR 2
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 7885ff76e69f..debe5290b72f 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -121,6 +121,8 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_enqueue_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dequeue_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_throttle_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_replenish_tp);
 EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_update_tp);
@@ -2181,6 +2183,9 @@ unsigned long get_wchan(struct task_struct *p)
 
 void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
 {
+	if (trace_sched_enqueue_tp_enabled() && !(flags & ENQUEUE_DELAYED))
+		trace_call__sched_enqueue_tp(p, cpu_of(rq));
+
 	if (!(flags & ENQUEUE_NOCLOCK))
 		update_rq_clock(rq);
 
@@ -2207,6 +2212,8 @@ void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
  */
 inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
 {
+	bool ret;
+
 	if (sched_core_enabled(rq))
 		sched_core_dequeue(rq, p, flags);
 
@@ -2223,7 +2230,10 @@ inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
 	 * and mark the task ->sched_delayed.
 	 */
 	uclamp_rq_dec(rq, p);
-	return p->sched_class->dequeue_task(rq, p, flags);
+	ret = p->sched_class->dequeue_task(rq, p, flags);
+	if (trace_sched_dequeue_tp_enabled() && !(flags & DEQUEUE_SLEEP))
+		trace_call__sched_dequeue_tp(p, cpu_of(rq));
+	return ret;
 }
 
 void activate_task(struct rq *rq, struct task_struct *p, int flags)
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf8..a2cb06beafe6 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -3070,6 +3070,8 @@ static inline void sub_nr_running(struct rq *rq, unsigned count)
 
 static inline void __block_task(struct rq *rq, struct task_struct *p)
 {
+	trace_sched_dequeue_tp(p, cpu_of(rq));
+
 	if (p->sched_contributes_to_load)
 		rq->nr_uninterruptible++;
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Some rv userspace selftests rely on the pid of the rv process to match
with the output and fail prematurely in case the pid was not found.
If the rv process failed, however, it's likely it will terminate before
catching the pid, and the test reports empty pid ignoring the error that
caused the process to fail.

Report empty pid only when the rv process succeeds (exit code not what
was expected) and continue showing exit code and output otherwise.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/tests/engine.sh | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
index cfdf2180aad8..4dad43b0b822 100644
--- a/tools/verification/tests/engine.sh
+++ b/tools/verification/tests/engine.sh
@@ -60,8 +60,9 @@ _check() {
 	failbuf=''
 	fail=0
 
-	# Suppress any other error if a needed pid is empty
-	if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns"; then
+	# Suppress any other error if a needed pid is empty and there was no other error
+	if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns" \
+		&& [ $exitcode -eq "$expected_exitcode" ]; then
 		result=''
 		failure "# Empty pid for $command"
 		return 1
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 04/15] rv: Cast result of model_get_*_name() Gabriele Monaco
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

The da_trace_event() and da_trace_error() functions are currently called
with strings and the callers are converting the state/event enums to the
corresponding string representation.
This is unnecessary and is problematic if an alternative implementation
is needed (e.g. BPF without using strings).

Change the functions to accept enums and pass the string representations
to the tracepoints only internally.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/rv/da_monitor.h | 40 ++++++++++++++++++++++------------------
 1 file changed, 22 insertions(+), 18 deletions(-)

diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 6fc5ef8142ac..7f0bdfd7cce3 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -633,19 +633,22 @@ static inline void da_monitor_destroy(void)
  */
 
 static inline void da_trace_event(struct da_monitor *da_mon,
-				  char *curr_state, char *event,
-				  char *next_state, bool is_final,
+				  enum states curr_state, enum events event,
+				  enum states next_state,
 				  da_id_type id)
 {
-	CONCATENATE(trace_event_, MONITOR_NAME)(curr_state, event, next_state,
-						is_final);
+	CONCATENATE(trace_event_, MONITOR_NAME)(model_get_state_name(curr_state),
+						model_get_event_name(event),
+						model_get_state_name(next_state),
+						model_is_final_state(next_state));
 }
 
 static inline void da_trace_error(struct da_monitor *da_mon,
-				  char *curr_state, char *event,
+				  enum states curr_state, enum events event,
 				  da_id_type id)
 {
-	CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event);
+	CONCATENATE(trace_error_, MONITOR_NAME)(model_get_state_name(curr_state),
+						model_get_event_name(event));
 }
 
 /*
@@ -662,19 +665,24 @@ static inline da_id_type da_get_id(struct da_monitor *da_mon)
  */
 
 static inline void da_trace_event(struct da_monitor *da_mon,
-				  char *curr_state, char *event,
-				  char *next_state, bool is_final,
+				  enum states curr_state, enum events event,
+				  enum states next_state,
 				  da_id_type id)
 {
-	CONCATENATE(trace_event_, MONITOR_NAME)(id, curr_state, event,
-						next_state, is_final);
+	CONCATENATE(trace_event_, MONITOR_NAME)(id,
+						model_get_state_name(curr_state),
+						model_get_event_name(event),
+						model_get_state_name(next_state),
+						model_is_final_state(next_state));
 }
 
 static inline void da_trace_error(struct da_monitor *da_mon,
-				  char *curr_state, char *event,
+				  enum states curr_state, enum events event,
 				  da_id_type id)
 {
-	CONCATENATE(trace_error_, MONITOR_NAME)(id, curr_state, event);
+	CONCATENATE(trace_error_, MONITOR_NAME)(id,
+						model_get_state_name(curr_state),
+						model_get_event_name(event));
 }
 #endif /* RV_MON_TYPE */
 
@@ -695,17 +703,13 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_
 		next_state = model_get_next_state(curr_state, event);
 		if (next_state == INVALID_STATE) {
 			react(curr_state, event);
-			da_trace_error(da_mon, model_get_state_name(curr_state),
-				       model_get_event_name(event), id);
+			da_trace_error(da_mon, curr_state, event, id);
 			return false;
 		}
 		if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
 			if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id))
 				return false;
-			da_trace_event(da_mon, model_get_state_name(curr_state),
-				       model_get_event_name(event),
-				       model_get_state_name(next_state),
-				       model_is_final_state(next_state), id);
+			da_trace_event(da_mon, curr_state, event, next_state, id);
 			return true;
 		}
 	}
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 04/15] rv: Cast result of model_get_*_name()
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (2 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Functions like model_get_event_name() and model_get_state_name() are
shared with BPF monitors, however those programs don't play nice with
string pointers without fixed length and the event_name and state_name
arrays need to be defined differently. This gets the compiler to notice
those are const char and the above function discard the const qualifier.

Drop the warning by casting the result to (char *). Note, a cleaner
solution would require to modify all callers, but that would require
changing every single monitor's tracepoint and the advantage is minimal.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/rv/automata.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/rv/automata.h b/include/rv/automata.h
index 4a4eb40cf09a..3c7070e91d11 100644
--- a/include/rv/automata.h
+++ b/include/rv/automata.h
@@ -27,7 +27,7 @@ static char *model_get_state_name(enum states state)
 	if ((state < 0) || (state >= STATE_MAX))
 		return "INVALID";
 
-	return RV_AUTOMATON_NAME.state_names[state];
+	return (char *)RV_AUTOMATON_NAME.state_names[state];
 }
 
 /*
@@ -38,7 +38,7 @@ static char *model_get_event_name(enum events event)
 	if ((event < 0) || (event >= EVENT_MAX))
 		return "INVALID";
 
-	return RV_AUTOMATON_NAME.event_names[event];
+	return (char *)RV_AUTOMATON_NAME.event_names[event];
 }
 
 /*
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (3 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 04/15] rv: Cast result of model_get_*_name() Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

The configuration of the in-kernel RV monitor is relying on global
variables, some static and some extern and the argument parsing occurs
there. That's not scalable especially if the configuration needs to be
shared by other monitor implementations (e.g. BPF).

Move the common argument parsing in utils and use a global struct for
the configuration. Implementation-specific configuration such as the
reactors are still handled in the in-kernel source.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/include/utils.h |  17 ++-
 tools/verification/rv/src/in_kernel.c | 176 ++++++--------------------
 tools/verification/rv/src/utils.c     |  95 +++++++++++++-
 3 files changed, 149 insertions(+), 139 deletions(-)

diff --git a/tools/verification/rv/include/utils.h b/tools/verification/rv/include/utils.h
index f24ae8282bd2..61f77a72a1a2 100644
--- a/tools/verification/rv/include/utils.h
+++ b/tools/verification/rv/include/utils.h
@@ -1,8 +1,23 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <stdbool.h>
+
 #define MAX_PATH		1024
 
 void debug_msg(const char *fmt, ...);
 void err_msg(const char *fmt, ...);
+void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...);
+int parse_arguments(char *monitor_name, int argc, char **argv);
+
+void ikm_usage_print_reactors(void);
 
-extern int config_debug;
+struct config {
+	bool debug;
+	bool is_container;
+	bool trace;
+	int has_id;
+	int my_pid;
+	char *initial_reactor;
+	char *reactor;
+};
+extern struct config config;
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index e6dea4040f8f..90c340a73c76 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -4,7 +4,6 @@
  *
  * Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
  */
-#include <getopt.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -16,14 +15,6 @@
 #include <utils.h>
 #include <rv.h>
 
-static int config_has_id;
-static int config_is_container;
-static int config_my_pid;
-static int config_trace;
-
-static char *config_initial_reactor;
-static char *config_reactor;
-
 /*
  * __ikm_read_enable - reads monitor's enable status
  *
@@ -66,7 +57,7 @@ static int __ikm_find_monitor_name(char *monitor_name, char *out_name)
 	if (!available_monitors)
 		return -1;
 
-	config_is_container = 0;
+	config.is_container = 0;
 	cursor = available_monitors;
 	while ((line = strsep(&cursor, "\n"))) {
 		char *colon = strchr(line, ':');
@@ -83,7 +74,7 @@ static int __ikm_find_monitor_name(char *monitor_name, char *out_name)
 			/* If there are children, they are on the next line. */
 			line = strsep(&cursor, "\n");
 			if (line && !strncmp(line, monitor_name, len) && line[len] == ':')
-				config_is_container = 1;
+				config.is_container = 1;
 		}
 
 		found = 1;
@@ -391,7 +382,7 @@ int ikm_list_monitors(char *container)
 static void ikm_print_header(struct trace_seq *s)
 {
 	trace_seq_printf(s, "%16s-%-8s %5s %5s ", "<TASK>", "PID", "[CPU]", "TYPE");
-	if (config_has_id)
+	if (config.has_id)
 		trace_seq_printf(s, "%8s ", "ID");
 
 	trace_seq_printf(s, "%24s x %-24s -> %-24s %s\n",
@@ -402,7 +393,7 @@ static void ikm_print_header(struct trace_seq *s)
 
 	trace_seq_printf(s, "%16s %-8s %5s %5s ", " | ", " | ", " | ", " | ");
 
-	if (config_has_id)
+	if (config.has_id)
 		trace_seq_printf(s, "%8s ", " | ");
 
 	trace_seq_printf(s, "%24s   %-24s    %-24s %s\n",
@@ -431,25 +422,25 @@ ikm_event_handler(struct trace_seq *s, struct tep_record *record,
 	int val;
 	bool missing_id;
 
-	if (config_has_id)
+	if (config.has_id)
 		missing_id = tep_get_field_val(s, trace_event, "id", record, &id, 1);
 
 	tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1);
 
-	if (config_has_id && (config_my_pid == id))
+	if (config.has_id && (config.my_pid == id))
 		return 0;
-	else if (config_my_pid == pid)
+	else if (config.my_pid == pid)
 		return 0;
 
 	tep_print_event(trace_event->tep, s, record, "%16s-%-8d [%.3d] ",
 			TEP_PRINT_COMM, TEP_PRINT_PID, TEP_PRINT_CPU);
 
-	if (config_is_container)
+	if (config.is_container)
 		tep_print_event(trace_event->tep, s, record, "%s ", TEP_PRINT_NAME);
 	else
 		trace_seq_printf(s, "event ");
 
-	if (config_has_id) {
+	if (config.has_id) {
 		if (missing_id)
 			/* placeholder if we are dealing with a mixed-type container*/
 			trace_seq_printf(s, "        ");
@@ -490,24 +481,24 @@ ikm_error_handler(struct trace_seq *s, struct tep_record *record,
 	int val;
 	bool missing_id;
 
-	if (config_has_id)
+	if (config.has_id)
 		missing_id = tep_get_field_val(s, trace_event, "id", record, &id, 1);
 
 	tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1);
 
-	if (config_has_id && config_my_pid == id)
+	if (config.has_id && config.my_pid == id)
 		return 0;
-	else if (config_my_pid == pid)
+	else if (config.my_pid == pid)
 		return 0;
 
 	trace_seq_printf(s, "%8lld [%03d] ", pid, cpu);
 
-	if (config_is_container)
+	if (config.is_container)
 		tep_print_event(trace_event->tep, s, record, "%s ", TEP_PRINT_NAME);
 	else
 		trace_seq_printf(s, "error ");
 
-	if (config_has_id) {
+	if (config.has_id) {
 		if (missing_id)
 			/* placeholder if we are dealing with a mixed-type container*/
 			trace_seq_printf(s, "        ");
@@ -548,8 +539,8 @@ static int ikm_enable_trace_events(char *monitor_name, struct trace_instance *in
 				   ikm_error_handler, NULL);
 
 	/* set if at least 1 monitor has id in case of a container */
-	config_has_id = ikm_has_id(monitor_name);
-	if (config_has_id < 0)
+	config.has_id = ikm_has_id(monitor_name);
+	if (config.has_id < 0)
 		return -1;
 
 
@@ -596,7 +587,7 @@ static struct trace_instance *ikm_setup_trace_instance(char *monitor_name)
 	struct trace_instance *inst;
 	int retval;
 
-	if (!config_trace)
+	if (!config.trace)
 		return NULL;
 
 	/* alloc data */
@@ -610,7 +601,7 @@ static struct trace_instance *ikm_setup_trace_instance(char *monitor_name)
 	if (retval)
 		goto out_free;
 
-	if (config_is_container)
+	if (config.is_container)
 		retval = ikm_enable_trace_container(monitor_name, inst);
 	else
 		retval = ikm_enable_trace_events(monitor_name, inst);
@@ -645,7 +636,7 @@ static void ikm_destroy_trace_instance(struct trace_instance *inst)
 /*
  * ikm_usage_print_reactors - print all available reactors, one per line.
  */
-static void ikm_usage_print_reactors(void)
+void ikm_usage_print_reactors(void)
 {
 	char *reactors = tracefs_instance_file_read(NULL, "rv/available_reactors", NULL);
 	char *start, *end;
@@ -669,106 +660,6 @@ static void ikm_usage_print_reactors(void)
 
 	fprintf(stderr, "\n");
 }
-/*
- * ikm_usage - print usage
- */
-static void ikm_usage(int exit_val, char *monitor_name, const char *fmt, ...)
-{
-
-	char message[1024];
-	va_list ap;
-	int i;
-
-	static const char *const usage[] = {
-		"",
-		"	-h/--help: print this menu and the reactor list",
-		"	-r/--reactor 'reactor': enables the 'reactor'",
-		"	-s/--self: when tracing (-t), also trace rv command",
-		"	-t/--trace: trace monitor's event",
-		"	-v/--verbose: print debug messages",
-		"",
-		NULL,
-	};
-
-	va_start(ap, fmt);
-	vsnprintf(message, sizeof(message), fmt, ap);
-	va_end(ap);
-
-	fprintf(stderr, "  %s\n", message);
-
-	fprintf(stderr, "\n  usage: rv mon %s [-h] [-q] [-r reactor] [-s] [-v]", monitor_name);
-
-	for (i = 0; usage[i]; i++)
-		fprintf(stderr, "%s\n", usage[i]);
-
-	ikm_usage_print_reactors();
-	exit(exit_val);
-}
-
-/*
- * parse_arguments - parse arguments and set config
- */
-static int parse_arguments(char *monitor_name, int argc, char **argv)
-{
-	int c, retval;
-
-	config_my_pid = getpid();
-
-	while (1) {
-		static struct option long_options[] = {
-			{"help",		no_argument,		0, 'h'},
-			{"reactor",		required_argument,	0, 'r'},
-			{"self",		no_argument,		0, 's'},
-			{"trace",		no_argument,		0, 't'},
-			{"verbose",		no_argument,		0, 'v'},
-			{0, 0, 0, 0}
-		};
-
-		/* getopt_long stores the option index here. */
-		int option_index = 0;
-
-		c = getopt_long(argc, argv, "hr:stv", long_options, &option_index);
-
-		/* detect the end of the options. */
-		if (c == -1)
-			break;
-
-		switch (c) {
-		case 'h':
-			ikm_usage(0, monitor_name, "help:");
-			break;
-		case 'r':
-			config_reactor = optarg;
-			break;
-		case 's':
-			config_my_pid = -1;
-			break;
-		case 't':
-			config_trace = 1;
-			break;
-		case 'v':
-			config_debug = 1;
-			break;
-		}
-	}
-
-	if (config_reactor) {
-		config_initial_reactor = ikm_get_current_reactor(monitor_name);
-		if (!config_initial_reactor)
-			ikm_usage(1, monitor_name,
-				  "ikm: failed to read current reactor, are reactors enabled?");
-
-		retval = ikm_write_reactor(monitor_name, config_reactor);
-		if (retval <= 0)
-			ikm_usage(1, monitor_name,
-				  "ikm: failed to set %s reactor, is it available?",
-				  config_reactor);
-	}
-
-	debug_msg("ikm: my pid is %d\n", config_my_pid);
-
-	return 0;
-}
 
 /**
  * ikm_run_monitor - apply configs and run the monitor
@@ -805,9 +696,22 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
 	/* we should be good to go */
 	retval = parse_arguments(full_name, argc, argv);
 	if (retval)
-		ikm_usage(1, nested_name, "ikm: failed parsing arguments");
+		mon_usage(1, nested_name, "ikm: failed parsing arguments");
+
+	if (config.reactor) {
+		config.initial_reactor = ikm_get_current_reactor(full_name);
+		if (!config.initial_reactor)
+			mon_usage(1, full_name,
+				  "ikm: failed to read current reactor, are reactors enabled?");
+
+		retval = ikm_write_reactor(full_name, config.reactor);
+		if (retval <= 0)
+			mon_usage(1, full_name,
+				  "ikm: failed to set %s reactor, is it available?",
+				  config.reactor);
+	}
 
-	if (config_trace) {
+	if (config.trace) {
 		inst = ikm_setup_trace_instance(nested_name);
 		if (!inst)
 			goto out_free_instance;
@@ -817,11 +721,11 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
 	if (retval < 0)
 		goto out_free_instance;
 
-	if (config_trace)
+	if (config.trace)
 		ikm_print_header(inst->seq);
 
 	while (!should_stop()) {
-		if (config_trace) {
+		if (config.trace) {
 			retval = tracefs_iterate_raw_events(inst->tep,
 							    inst->inst,
 							    NULL,
@@ -840,14 +744,14 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
 	ikm_disable(full_name);
 	ikm_destroy_trace_instance(inst);
 
-	if (config_reactor && config_initial_reactor)
-		ikm_write_reactor(full_name, config_initial_reactor);
+	if (config.reactor && config.initial_reactor)
+		ikm_write_reactor(full_name, config.initial_reactor);
 
 	return 1;
 
 out_free_instance:
 	ikm_destroy_trace_instance(inst);
-	if (config_reactor && config_initial_reactor)
-		ikm_write_reactor(full_name, config_initial_reactor);
+	if (config.reactor && config.initial_reactor)
+		ikm_write_reactor(full_name, config.initial_reactor);
 	return -1;
 }
diff --git a/tools/verification/rv/src/utils.c b/tools/verification/rv/src/utils.c
index 5677b439dc2f..e19dd65ec789 100644
--- a/tools/verification/rv/src/utils.c
+++ b/tools/verification/rv/src/utils.c
@@ -5,11 +5,14 @@
  * Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
  */
 
+#include <getopt.h>
 #include <stdarg.h>
+#include <stdlib.h>
 #include <stdio.h>
+#include <unistd.h>
 #include <utils.h>
 
-int config_debug;
+struct config config;
 
 #define MAX_MSG_LENGTH	1024
 
@@ -36,7 +39,7 @@ void debug_msg(const char *fmt, ...)
 	char message[MAX_MSG_LENGTH];
 	va_list ap;
 
-	if (!config_debug)
+	if (!config.debug)
 		return;
 
 	va_start(ap, fmt);
@@ -45,3 +48,91 @@ void debug_msg(const char *fmt, ...)
 
 	fprintf(stderr, "%s", message);
 }
+
+/*
+ * mon_usage - print usage
+ */
+void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...)
+{
+
+	char message[1024];
+	va_list ap;
+	int i;
+
+	static const char *const usage[] = {
+		"",
+		"	-h/--help: print this menu and the reactor list",
+		"	-r/--reactor 'reactor': enables the 'reactor'",
+		"	-s/--self: when tracing (-t), also trace rv command",
+		"	-t/--trace: trace monitor's event",
+		"	-v/--verbose: print debug messages",
+		"",
+		NULL,
+	};
+
+	va_start(ap, fmt);
+	vsnprintf(message, sizeof(message), fmt, ap);
+	va_end(ap);
+
+	fprintf(stderr, "  %s\n", message);
+
+	fprintf(stderr, "\n  usage: rv mon %s [-h] [-q] [-r reactor] [-s] [-v]", monitor_name);
+
+	for (i = 0; usage[i]; i++)
+		fprintf(stderr, "%s\n", usage[i]);
+
+	ikm_usage_print_reactors();
+	exit(exit_val);
+}
+
+/*
+ * parse_arguments - parse arguments and set config
+ */
+int parse_arguments(char *monitor_name, int argc, char **argv)
+{
+	int c;
+
+	config.my_pid = getpid();
+
+	while (1) {
+		static struct option long_options[] = {
+			{"help",		no_argument,		0, 'h'},
+			{"reactor",		required_argument,	0, 'r'},
+			{"self",		no_argument,		0, 's'},
+			{"trace",		no_argument,		0, 't'},
+			{"verbose",		no_argument,		0, 'v'},
+			{0, 0, 0, 0}
+		};
+
+		/* getopt_long stores the option index here. */
+		int option_index = 0;
+
+		c = getopt_long(argc, argv, "hr:stv", long_options, &option_index);
+
+		/* detect the end of the options. */
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			mon_usage(0, monitor_name, "help:");
+			break;
+		case 'r':
+			config.reactor = optarg;
+			break;
+		case 's':
+			config.my_pid = -1;
+			break;
+		case 't':
+			config.trace = 1;
+			break;
+		case 'v':
+			config.debug = 1;
+			break;
+		}
+	}
+
+	debug_msg("ikm: my pid is %d\n", config.my_pid);
+
+	return 0;
+}
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (4 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing Gabriele Monaco
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf
  Cc: Gabriele Monaco, Steven Rostedt, Alexei Starovoitov, Nam Cao,
	Wen Yang, Tobias Schaffner, Viktor Malik

RV can run bpf monitors and also build them, only the latter requires
the vmlinux.h from bpftool btf.

Add a feature so that RV can be built with BPF support without having to
build in-tree BPF monitors if not supported on the system.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/build/Makefile.feature | 1 +
 tools/build/feature/Makefile | 7 ++++++-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature
index 99eb0ea09537..18e3a7ae893a 100644
--- a/tools/build/Makefile.feature
+++ b/tools/build/Makefile.feature
@@ -129,6 +129,7 @@ FEATURE_TESTS_EXTRA :=                  \
          libdebuginfod			\
          clang-bpf-co-re		\
          bpftool-skeletons		\
+         bpftool-btf			\
          libunwind			\
          libunwind-debug-frame		\
          $(foreach arch,$(LIBUNWIND_ARCHS),libunwind-$(arch) libunwind-debug-frame-$(arch))
diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile
index 7d165018116a..2f01203dda5c 100644
--- a/tools/build/feature/Makefile
+++ b/tools/build/feature/Makefile
@@ -71,7 +71,8 @@ FILES=                                          \
          test-libpfm4.bin			\
          test-rust.bin				\
          test-libopenssl.bin			\
-         test-bpftool-skeletons.bin
+         test-bpftool-skeletons.bin		\
+         test-bpftool-btf.bin
 
 FILES := $(addprefix $(OUTPUT),$(FILES))
 
@@ -383,6 +384,10 @@ $(OUTPUT)test-bpftool-skeletons.bin:
 	{ $(SYSTEM_BPFTOOL) version | grep '^features:.*skeletons'; } \
 		> $(@:.bin=.make.output) 2>&1 && touch $@
 
+$(OUTPUT)test-bpftool-btf.bin:
+	$(SYSTEM_BPFTOOL) btf help \
+		> $(@:.bin=.make.output) 2>&1 && touch $@
+
 # Testing Rust is special: we don't compile anything, it's enough to check the
 # compiler presence. Compiling a test code for this purposes is problematic,
 # because Rust will emit a dependency file without any external references,
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (5 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Implement the BPF monitor listing functionality that scans for compiled
BPF monitor object files and displays them in the monitor list.

If enabled, running rv list shows in-kernel and BPF monitors found in
/usr/share/rv/bpf_monitors/ or in ./bpf_monitors/ relative to the RV
binary's location (used during development).

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/Makefile              |   4 +-
 tools/verification/rv/Makefile.config       |  23 ++
 tools/verification/rv/include/bpf_monitor.h |  14 ++
 tools/verification/rv/src/Build             |   5 +
 tools/verification/rv/src/bpf_monitor.c     | 223 ++++++++++++++++++++
 tools/verification/rv/src/rv.c              |   2 +
 6 files changed, 270 insertions(+), 1 deletion(-)
 create mode 100644 tools/verification/rv/include/bpf_monitor.h
 create mode 100644 tools/verification/rv/src/bpf_monitor.c

diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 8ae5fc0d1d17..0a8c7a656f7f 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -32,8 +32,10 @@ DOCSRC		:= ../../../Documentation/tools/rv/
 
 FEATURE_TESTS	:= libtraceevent
 FEATURE_TESTS	+= libtracefs
+FEATURE_TESTS	+= libbpf
 FEATURE_DISPLAY	:= libtraceevent
 FEATURE_DISPLAY	+= libtracefs
+FEATURE_DISPLAY	+= libbpf
 
 all: $(RV)
 
@@ -57,7 +59,7 @@ endif
 
 CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES)
 
-export CFLAGS OUTPUT srctree
+export CFLAGS OUTPUT srctree BUILD_BPF
 
 $(RV): $(RV_IN)
 	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV) $(RV_IN) $(EXTLIBS)
diff --git a/tools/verification/rv/Makefile.config b/tools/verification/rv/Makefile.config
index 066302230eb2..0600170ac217 100644
--- a/tools/verification/rv/Makefile.config
+++ b/tools/verification/rv/Makefile.config
@@ -43,6 +43,29 @@ else
   $(info libtracefs is missing. Please install libtracefs-dev/libtracefs-devel)
 endif
 
+ifndef BUILD_BPF
+  # BPF monitors are optional but enabled by default
+  BUILD_BPF := 1
+endif
+
+ifeq ($(BUILD_BPF),0)
+  $(info BPF monitor support disabled, building without BPF monitor support.)
+endif
+
+$(call feature_check,libbpf)
+ifeq ($(feature-libbpf), 1)
+  $(call detected,CONFIG_LIBBPF)
+else
+  $(info libbpf is missing, building without BPF monitor support.)
+  $(info Please install libbpf-dev/libbpf-devel)
+  BUILD_BPF := 0
+endif
+
+ifeq ($(BUILD_BPF),1)
+  CFLAGS += -DHAVE_LIBBPF
+  $(call lib_setup,bpf)
+endif
+
 ifeq ($(STOP_ERROR),1)
   $(error Please, check the errors above.)
 endif
diff --git a/tools/verification/rv/include/bpf_monitor.h b/tools/verification/rv/include/bpf_monitor.h
new file mode 100644
index 000000000000..e58bc45c5f75
--- /dev/null
+++ b/tools/verification/rv/include/bpf_monitor.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef _BPF_MONITOR_H
+#define _BPF_MONITOR_H
+
+#ifdef HAVE_LIBBPF
+int bpf_list_monitors(char *container);
+#else
+static inline int bpf_list_monitors(char *container)
+{
+	return 0;
+}
+#endif /* HAVE_LIBBPF */
+
+#endif
diff --git a/tools/verification/rv/src/Build b/tools/verification/rv/src/Build
index d781983c1a79..326503c22915 100644
--- a/tools/verification/rv/src/Build
+++ b/tools/verification/rv/src/Build
@@ -1,4 +1,9 @@
 rv-y += trace.o
 rv-y += utils.o
 rv-y += in_kernel.o
+
+ifeq ($(BUILD_BPF),1)
+  rv-y += bpf_monitor.o
+endif
+
 rv-y += rv.o
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
new file mode 100644
index 000000000000..a1de0c157712
--- /dev/null
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF monitor support: allows rv to control BPF monitors.
+ *
+ * Copyright (C) 2026 Red Hat Inc, Gabriele Monaco <gmonaco@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <errno.h>
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include <bpf/btf.h>
+
+#include <bpf_monitor.h>
+#include <utils.h>
+#include <rv.h>
+
+static char bpf_base_paths[][MAX_PATH] = {
+	".",
+	"/etc/rv",
+	"/usr/local/share/rv",
+	"/usr/share/rv",
+	"", /* Marker */
+};
+
+/* Path used for development, searched first */
+#define DEVEL_PATH 0
+
+
+/*
+ * bpf_read_enable - reads monitor's enable status
+ *
+ * Iterate through all BPF maps in the system, if the rv_mon_NAME map is
+ * loaded, the monitor is enabled.
+ * Since map names have limited size and may get truncated, check that also the
+ * corresponding BTF matches.
+ */
+static int bpf_read_enable(const char *monitor_name)
+{
+	char ringbuf_name[2 * MAX_DA_NAME_LEN];
+	uint32_t id = 0;
+
+	snprintf(ringbuf_name, sizeof(ringbuf_name),
+		 "rv_mon_%s", monitor_name);
+
+	while (bpf_map_get_next_id(id, &id) == 0) {
+		struct bpf_map_info info = { 0 };
+		uint32_t info_len = sizeof(info);
+		struct btf *btf;
+		int type_id;
+		int fd = bpf_map_get_fd_by_id(id);
+
+		if (fd < 0)
+			continue;
+
+		if (bpf_map_get_info_by_fd(fd, &info, &info_len) != 0) {
+			close(fd);
+			continue;
+		}
+		close(fd);
+
+		if (strncmp(info.name, ringbuf_name, BPF_OBJ_NAME_LEN - 1) != 0)
+			continue;
+
+		if (!info.btf_id)
+			continue;
+		btf = btf__load_from_kernel_by_id(info.btf_id);
+		if (!btf)
+			continue;
+
+		type_id = btf__find_by_name_kind(btf, ringbuf_name, BTF_KIND_VAR);
+		btf__free(btf);
+		if (type_id > 0)
+			return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * bpf_read_desc - read monitors' description
+ *
+ * Return the provided string containing the monitor's description, NULL
+ * otherwise.
+ */
+static char *bpf_read_desc(char *desc, struct bpf_object *obj, const char *monitor_name)
+{
+	struct bpf_map *map = bpf_object__find_map_by_name(obj, ".rodata.description");
+	const char *desc_data;
+	size_t desc_size;
+
+	if (!map) {
+		debug_msg("bpf: cannot find description for %s\n",
+			  monitor_name);
+		return NULL;
+	}
+	desc_data = bpf_map__initial_value(map, &desc_size);
+	if (!desc_data || desc_size == 0) {
+		debug_msg("bpf: empty description for %s\n", monitor_name);
+		*desc = 0;
+		return desc;
+	}
+
+	if (desc_size >= MAX_DESCRIPTION)
+		desc_size = MAX_DESCRIPTION - 1;
+	strncpy(desc, desc_data, desc_size);
+	desc[desc_size] = '\0';
+
+	return desc;
+}
+
+/*
+ * bpf_fill_base_paths - fill the path for development builds
+ *
+ * RV searches for BPF monitors on absolute paths on the system as well
+ * as in the same directory of the rv binary. This is useful when running
+ * rv from the kernel tree. This function resolves the right location.
+ */
+static void bpf_fill_base_paths(void)
+{
+	char tmp_path[MAX_PATH], *dir;
+	ssize_t len;
+
+	len = readlink("/proc/self/exe", tmp_path, MAX_PATH);
+	if (len > 0 && len != MAX_PATH) {
+		tmp_path[len] = '\0';
+		dir = dirname(tmp_path);
+		snprintf(bpf_base_paths[DEVEL_PATH], MAX_PATH, "%s", dir);
+	}
+}
+
+static void bpf_object_iterate_path(const char *base_path, const char *subdir,
+				    void (*action)(const char *name, struct bpf_object *obj))
+{
+	char path[MAX_PATH];
+	struct dirent *entry;
+	DIR *dir;
+	char *ext;
+
+	snprintf(path, sizeof(path), "%s/%s", base_path, subdir);
+	dir = opendir(path);
+	if (!dir) {
+		debug_msg("bpf: error opening directory: %s\n", path);
+		return;
+	}
+
+	while ((entry = readdir(dir)) != NULL) {
+		size_t size;
+		struct bpf_object *obj;
+		char name[MAX_DA_NAME_LEN], obj_path[MAX_PATH];
+
+		if (entry->d_name[0] == '.')
+			continue;
+
+		ext = strrchr(entry->d_name, '.');
+		if (!ext || strcmp(ext, ".o") != 0)
+			continue;
+
+		size = snprintf(obj_path, sizeof(obj_path), "%s/%s", path,
+				entry->d_name);
+		obj = bpf_object__open_file(obj_path, NULL);
+		if (!obj || size > MAX_PATH) {
+			err_msg("bpf: error opening object file %s: %s\n",
+				obj_path, strerror(errno));
+			continue;
+		}
+
+		strncpy(name, entry->d_name, sizeof(name));
+		ext = strrchr(name, '.');
+		if (ext)
+			*ext = '\0';
+
+		action(name, obj);
+
+		bpf_object__close(obj);
+	}
+
+	closedir(dir);
+}
+
+static void list_monitor_action(const char *name, struct bpf_object *obj)
+{
+	char desc[MAX_DESCRIPTION];
+
+	if (!bpf_read_desc(desc, obj, name)) {
+		err_msg("bpf: monitor %s does not have desc map, bug?\n", name);
+		return;
+	}
+
+	printf("%-*s %s %s\n", MAX_DA_NAME_LEN, name,
+	       desc, bpf_read_enable(name) ? "[ON]" : "[OFF]");
+}
+
+/*
+ * list_monitors_from_path - list monitors from a specific base path
+ */
+static void list_monitors_from_path(const char *base_path)
+{
+	bpf_object_iterate_path(base_path, "bpf_monitors", list_monitor_action);
+}
+
+/*
+ * bpf_list_monitors - list available BPF monitors from all sources
+ *
+ * @container: BPF monitors are not nested, skip listing.
+ *
+ * Returns 0 on success
+ */
+int bpf_list_monitors(char *container)
+{
+	if (container)
+		return 0;
+	bpf_fill_base_paths();
+	for (int i = 0; bpf_base_paths[i][0]; i++)
+		list_monitors_from_path(bpf_base_paths[i]);
+
+	return 0;
+}
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index 09e0d8598619..7c4a2e49ff9d 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -13,6 +13,7 @@
 #include <trace.h>
 #include <utils.h>
 #include <in_kernel.h>
+#include <bpf_monitor.h>
 
 static int stop_session;
 
@@ -76,6 +77,7 @@ static void rv_list(int argc, char **argv)
 	}
 
 	ikm_list_monitors(container);
+	bpf_list_monitors(container);
 
 	exit(EXIT_SUCCESS);
 }
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (6 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Implement BPF monitor loading, execution, and optional tracing
functionality.

Running rv mon MON loads the MON monitor into the kernel and passing the
-t parameter also attaches to the event ring buffer printing events and
errors to standard output.

Event and state names as well as the ring buffer entry format are
dynamically parsed from BTF.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/include/bpf_monitor.h |   8 +
 tools/verification/rv/include/utils.h       |   2 +
 tools/verification/rv/src/bpf_monitor.c     | 650 ++++++++++++++++++++
 tools/verification/rv/src/rv.c              |   3 +
 tools/verification/rv/src/utils.c           |   5 +-
 5 files changed, 667 insertions(+), 1 deletion(-)

diff --git a/tools/verification/rv/include/bpf_monitor.h b/tools/verification/rv/include/bpf_monitor.h
index e58bc45c5f75..55b2f05666a3 100644
--- a/tools/verification/rv/include/bpf_monitor.h
+++ b/tools/verification/rv/include/bpf_monitor.h
@@ -4,11 +4,19 @@
 
 #ifdef HAVE_LIBBPF
 int bpf_list_monitors(char *container);
+int bpf_run_monitor(char *monitor_name, int argc, char **argv);
 #else
 static inline int bpf_list_monitors(char *container)
 {
 	return 0;
 }
+
+static inline int bpf_run_monitor(char *monitor_name, int argc, char **argv)
+{
+	return 0;
+}
+
+void bpf_usage_print_reactors(void) { }
 #endif /* HAVE_LIBBPF */
 
 #endif
diff --git a/tools/verification/rv/include/utils.h b/tools/verification/rv/include/utils.h
index 61f77a72a1a2..d6b4e60df7ff 100644
--- a/tools/verification/rv/include/utils.h
+++ b/tools/verification/rv/include/utils.h
@@ -10,11 +10,13 @@ void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...);
 int parse_arguments(char *monitor_name, int argc, char **argv);
 
 void ikm_usage_print_reactors(void);
+void bpf_usage_print_reactors(void);
 
 struct config {
 	bool debug;
 	bool is_container;
 	bool trace;
+	bool is_bpf;
 	int has_id;
 	int my_pid;
 	char *initial_reactor;
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index a1de0c157712..9a3f00febfeb 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -8,10 +8,12 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <fcntl.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <libgen.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <bpf/libbpf.h>
 #include <bpf/bpf.h>
 #include <bpf/btf.h>
@@ -31,6 +33,62 @@ static char bpf_base_paths[][MAX_PATH] = {
 /* Path used for development, searched first */
 #define DEVEL_PATH 0
 
+#define MAX_ENUMS 64
+#define MAX_LINKS 16
+#define PROG_ENABLE_MON "enable_monitor"
+#define RV_TRACE_STRUCT "rv_trace_entry"
+#define RV_TRACE_TYPE_ENUM "rv_trace_type"
+
+enum trace_type_id {
+	TRACE_TYPE_ERROR,
+	TRACE_TYPE_EVENT,
+	TRACE_TYPE_MAX,
+};
+
+static const char *const trace_type_names[] = {
+	[TRACE_TYPE_ERROR] = "RV_TRACE_ERROR",
+	[TRACE_TYPE_EVENT] = "RV_TRACE_EVENT",
+};
+
+enum field_id {
+	FIELD_EVENT_TYPE,
+	FIELD_ID,
+	FIELD_CPU,
+	FIELD_PID,
+	FIELD_COMM,
+	FIELD_IS_FINAL,
+	FIELD_CURR_STATE,
+	FIELD_EVENT,
+	FIELD_NEXT_STATE,
+	FIELD_MAX,
+};
+
+static const char *const field_names[] = {
+	[FIELD_EVENT_TYPE] = "event_type",
+	[FIELD_ID] = "id",
+	[FIELD_CPU] = "cpu",
+	[FIELD_PID] = "pid",
+	[FIELD_COMM] = "comm",
+	[FIELD_IS_FINAL] = "is_final",
+	[FIELD_CURR_STATE] = "curr_state",
+	[FIELD_EVENT] = "event",
+	[FIELD_NEXT_STATE] = "next_state",
+};
+
+struct field {
+	size_t offset;
+	size_t size;
+};
+
+struct bpf_monitor_ctx {
+	char monitor_name[MAX_DA_NAME_LEN];
+	char state_names[MAX_ENUMS][MAX_DA_NAME_LEN];
+	char event_names[MAX_ENUMS][MAX_DA_NAME_LEN];
+	int num_states;
+	int num_events;
+	struct field field_metadata[FIELD_MAX];
+	int trace_types[TRACE_TYPE_MAX];
+};
 
 /*
  * bpf_read_enable - reads monitor's enable status
@@ -221,3 +279,595 @@ int bpf_list_monitors(char *container)
 
 	return 0;
 }
+
+static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
+			   va_list args)
+{
+	if (level == LIBBPF_DEBUG && !config.debug)
+		return 0;
+	return vfprintf(stderr, format, args);
+}
+
+/*
+ * Helper functions for state/event name lookup
+ */
+static const char *get_state_name(struct bpf_monitor_ctx *ctx, uint32_t state)
+{
+	if (state < ctx->num_states)
+		return ctx->state_names[state];
+
+	return "<invalid>";
+}
+
+static const char *get_event_name(struct bpf_monitor_ctx *ctx, uint32_t event)
+{
+	if (event < ctx->num_events)
+		return ctx->event_names[event];
+
+	return "<invalid>";
+}
+
+/*
+ * extract_field_metadata - extract field metadata from BTF for efficient parsing
+ *
+ * Introspect the rv_trace_entry structure via BTF and store field offsets and
+ * sizes for direct memory access during event processing.
+ * This allows for parsing known fields without having to stick to a particular
+ * memory layout in future releases.
+ *
+ * Returns 0 on success, -1 on error
+ */
+static int extract_field_metadata(const struct btf *btf, struct bpf_monitor_ctx *ctx)
+{
+	const struct btf_type *trace_type;
+	const struct btf_member *members;
+	int type_id, vlen;
+
+	type_id = btf__find_by_name_kind(btf, RV_TRACE_STRUCT, BTF_KIND_STRUCT);
+	if (type_id <= 0) {
+		debug_msg("bpf: could not find struct '%s' in BTF\n", RV_TRACE_STRUCT);
+		return -1;
+	}
+
+	trace_type = btf__type_by_id(btf, type_id);
+	if (!trace_type) {
+		debug_msg("bpf: could not get type for '%s'\n", RV_TRACE_STRUCT);
+		return -1;
+	}
+
+	members = btf_members(trace_type);
+	vlen = btf_vlen(trace_type);
+
+	for (int i = 0; i < vlen; i++) {
+		const char *name = btf__name_by_offset(btf, members[i].name_off);
+		size_t offset = btf_member_bit_offset(trace_type, i) / 8;
+		size_t size = btf__resolve_size(btf, members[i].type);
+
+		if (!name || (ssize_t)size < 0)
+			continue;
+
+		debug_msg("bpf: field '%s' at offset %zu, size %lld\n", name,
+			  offset, (long long)size);
+
+		for (int j = 0; j < FIELD_MAX; j++) {
+			if (strcmp(name, field_names[j]) == 0) {
+				ctx->field_metadata[j].offset = offset;
+				ctx->field_metadata[j].size = size;
+				if (j == FIELD_ID)
+					config.has_id = true;
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * extract_trace_type_metadata - extract trace type enum values from BTF
+ *
+ * Introspect the rv_trace_type enum via BTF and store enum values for
+ * event type dispatch and configuration.
+ *
+ * Returns 0 on success, -1 on error
+ */
+static int extract_trace_type_metadata(const struct btf *btf, struct bpf_monitor_ctx *ctx)
+{
+	const struct btf_type *enum_type;
+	const struct btf_enum *enums;
+	int type_id, vlen, filled = 0;
+
+	for (int i = 0; i < TRACE_TYPE_MAX; i++)
+		ctx->trace_types[i] = -1;
+
+	type_id = btf__find_by_name_kind(btf, RV_TRACE_TYPE_ENUM, BTF_KIND_ENUM);
+	if (type_id <= 0) {
+		debug_msg("bpf: could not find enum '%s' in BTF\n", RV_TRACE_TYPE_ENUM);
+		return -1;
+	}
+
+	enum_type = btf__type_by_id(btf, type_id);
+	if (!enum_type) {
+		debug_msg("bpf: could not get type for '%s'\n", RV_TRACE_TYPE_ENUM);
+		return -1;
+	}
+
+	enums = btf_enum(enum_type);
+	vlen = btf_vlen(enum_type);
+
+	for (int i = 0; i < vlen; i++) {
+		const char *name = btf__name_by_offset(btf, enums[i].name_off);
+
+		if (!name)
+			continue;
+
+		for (int j = 0; j < TRACE_TYPE_MAX; j++) {
+			if (strcmp(name, trace_type_names[j]) == 0) {
+				ctx->trace_types[j] = enums[i].val;
+				++filled;
+				break;
+			}
+		}
+	}
+
+	if (filled < TRACE_TYPE_MAX) {
+		debug_msg("bpf: could not find all trace types in BTF\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * bpf_print_header - print trace output header
+ */
+static void bpf_print_header(void)
+{
+	printf("%16s-%-8s %5s %5s ", "<TASK>", "PID", "[CPU]", "TYPE");
+	if (config.has_id)
+		printf(" %8s", "ID");
+
+	printf("%24s x %-24s -> %-24s %s\n",
+		"STATE",
+		"EVENT",
+		"NEXT_STATE",
+		"FINAL");
+
+	printf("%16s %-8s %5s %5s ", " | ", " | ", " | ", " | ");
+
+	if (config.has_id)
+		printf(" %8s", " | ");
+	printf("%24s   %-24s    %-24s %s\n", " | ", " | ", " | ", "|");
+}
+
+static inline uint64_t read_field(uint64_t *entry, enum field_id id,
+				  const uint8_t *raw,
+				  const struct bpf_monitor_ctx *ctx)
+{
+	const struct field *field = &ctx->field_metadata[id];
+
+	switch (field->size) {
+	case 1:
+		return entry[id] = *(const uint8_t *)(raw + field->offset);
+	case 2:
+		return entry[id] = *(const uint16_t *)(raw + field->offset);
+	case 4:
+		return entry[id] = *(const uint32_t *)(raw + field->offset);
+	case 8:
+		return entry[id] = *(const uint64_t *)(raw + field->offset);
+	}
+	return 0;
+}
+
+/*
+ * handle_event - ring buffer callback for trace events
+ */
+static int handle_event(void *ctx, void *data, size_t data_sz)
+{
+	struct bpf_monitor_ctx *mon_ctx = ctx;
+	const uint8_t *raw = data;
+	uint64_t entry[FIELD_MAX] = {0};
+	const char *comm;
+
+	if (should_stop())
+		return 1;
+
+	if (config.has_id)
+		read_field(entry, FIELD_ID, raw, mon_ctx);
+	read_field(entry, FIELD_PID, raw, mon_ctx);
+
+	if (config.has_id && (config.my_pid == entry[FIELD_ID]))
+		return 0;
+	else if (config.my_pid == entry[FIELD_PID])
+		return 0;
+
+	read_field(entry, FIELD_EVENT_TYPE, raw, mon_ctx);
+	read_field(entry, FIELD_CPU, raw, mon_ctx);
+	comm = (const char *)(raw + mon_ctx->field_metadata[FIELD_COMM].offset);
+	read_field(entry, FIELD_CURR_STATE, raw, mon_ctx);
+	read_field(entry, FIELD_EVENT, raw, mon_ctx);
+
+	printf("%16s-%-8"PRIu64" [%.3"PRIu64"] ", comm, entry[FIELD_PID], entry[FIELD_CPU]);
+	if (entry[FIELD_EVENT_TYPE] == mon_ctx->trace_types[TRACE_TYPE_ERROR]) {
+		printf("error ");
+		if (config.has_id)
+			printf(" %8"PRIu64"", entry[FIELD_ID]);
+		printf(" %24s x %-24s\n",
+		       get_state_name(mon_ctx, entry[FIELD_CURR_STATE]),
+		       get_event_name(mon_ctx, entry[FIELD_EVENT]));
+	} else if (entry[FIELD_EVENT_TYPE] == mon_ctx->trace_types[TRACE_TYPE_EVENT]) {
+		printf("event ");
+		read_field(entry, FIELD_IS_FINAL, raw, mon_ctx);
+		read_field(entry, FIELD_NEXT_STATE, raw, mon_ctx);
+
+		if (config.has_id)
+			printf(" %8"PRIu64"", entry[FIELD_ID]);
+		printf(" %24s x %-24s -> %-24s %c\n",
+		       get_state_name(mon_ctx, entry[FIELD_CURR_STATE]),
+		       get_event_name(mon_ctx, entry[FIELD_EVENT]),
+		       get_state_name(mon_ctx, entry[FIELD_NEXT_STATE]),
+		       entry[FIELD_IS_FINAL] ? 'Y' : 'N');
+	}
+
+	return 0;
+}
+
+/*
+ * extract_enum_names - extract names from a BTF enum
+ *
+ * Reads enum member names from BTF and stores them in dest array.
+ * Returns the number of enum members extracted (excluding the
+ * {state/event}_max_NAME entry and trimming the _NAME padding)
+ * on success, or -1 on error.
+ */
+static int extract_enum_names(const struct btf *btf, const char *enum_kind,
+			       char dest[][MAX_DA_NAME_LEN], struct bpf_monitor_ctx *ctx)
+{
+	const struct btf_type *enum_type;
+	const struct btf_enum *enums;
+	char buf[2 * MAX_DA_NAME_LEN];
+	bool arrived_at_last = false;
+	int type_id, vlen;
+	int count = 0;
+
+	snprintf(buf, sizeof(buf), "%ss_%s", enum_kind, ctx->monitor_name);
+	type_id = btf__find_by_name_kind(btf, buf, BTF_KIND_ENUM);
+	if (type_id <= 0) {
+		debug_msg("bpf: could not find enum '%s' in BTF\n", buf);
+		return -1;
+	}
+	enum_type = btf__type_by_id(btf, type_id);
+	if (!enum_type)
+		return -1;
+
+	enums = btf_enum(enum_type);
+	vlen = btf_vlen(enum_type);
+
+	snprintf(buf, sizeof(buf), "%s_max_%s", enum_kind, ctx->monitor_name);
+	for (int i = 0; i < vlen; i++) {
+		const char *name = btf__name_by_offset(btf, enums[i].name_off);
+		const char *padding;
+		size_t name_len;
+
+		if (!name || count >= MAX_ENUMS)
+			break;
+
+		/* max value must be the last */
+		if (!strcmp(name, buf)) {
+			if (i == vlen - 1)
+				arrived_at_last = true;
+			break;
+		}
+
+		padding = strrchr(name, '_');
+		name_len = strlen(name);
+		if (padding && !strcmp(ctx->monitor_name, padding + 1))
+			name_len = (size_t)(padding - name);
+
+		if (!name_len)
+			break;
+
+		if (name_len >= MAX_DA_NAME_LEN)
+			name_len = MAX_DA_NAME_LEN - 1;
+		strncpy(dest[count], name, name_len);
+		dest[count][name_len] = '\0';
+		count++;
+	}
+
+	if (!arrived_at_last) {
+		debug_msg("bpf: malformed %ss enum, could fill %d\n", enum_kind, count);
+		return -1;
+	}
+
+	return count;
+}
+
+/*
+ * extract_btf_info - extract BTF types information from the monitor
+ *
+ * Extract state and event names from enums using BTF and extract field
+ * offsets for flexible event parsing.
+ */
+static int extract_btf_info(struct bpf_object *obj, struct bpf_monitor_ctx *ctx)
+{
+	const struct btf *btf;
+
+	btf = bpf_object__btf(obj);
+	if (!btf) {
+		err_msg("bpf: no BTF found in BPF object\n");
+		return -1;
+	}
+
+	if (extract_trace_type_metadata(btf, ctx)) {
+		err_msg("bpf: failed to extract trace type metadata\n");
+		return -1;
+	}
+
+	if (extract_field_metadata(btf, ctx)) {
+		err_msg("bpf: failed to extract field metadata\n");
+		return -1;
+	}
+
+	ctx->num_states = extract_enum_names(btf, "state", ctx->state_names, ctx);
+	ctx->num_events = extract_enum_names(btf, "event", ctx->event_names, ctx);
+	if (ctx->num_states < 0 || ctx->num_events < 0) {
+		err_msg("bpf: failed to extract states (%d) or events names (%d)\n",
+			ctx->num_states, ctx->num_events);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * find_bpf_file - search for a BPF object file in a specific subdirectory
+ */
+static int find_bpf_file(const char *subdir, const char *name, char *path_out, size_t path_len)
+{
+	char path[MAX_PATH];
+
+	for (int i = 0; bpf_base_paths[i][0]; i++) {
+		size_t size = snprintf(path, sizeof(path), "%s/%s/%s.o",
+				       bpf_base_paths[i], subdir, name);
+
+		if (size < MAX_PATH && access(path, R_OK) == 0) {
+			strncpy(path_out, path, path_len - 1);
+			path_out[path_len - 1] = '\0';
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * find_bpf_monitor - search for BPF monitor object file in all directories
+ */
+static int find_bpf_monitor(const char *monitor_name, char *path_out, size_t path_len)
+{
+	return find_bpf_file("bpf_monitors", monitor_name, path_out, path_len);
+}
+
+/*
+ * bpf_setup_ring_buffer - set up the ring buffer to trace events
+ *
+ * Find the ring buffer map and set up the events handler.
+ */
+static struct ring_buffer *bpf_setup_ring_buffer(struct bpf_object *obj,
+						 struct bpf_monitor_ctx *ctx)
+{
+	struct ring_buffer *rb;
+	struct bpf_map *map;
+	char ringbuf_name[2 * MAX_DA_NAME_LEN];
+
+	snprintf(ringbuf_name, sizeof(ringbuf_name), "rv_rb_%s", ctx->monitor_name);
+	map = bpf_object__find_map_by_name(obj, ringbuf_name);
+	if (!map) {
+		err_msg("bpf: error finding ring buffer %s\n", ringbuf_name);
+		return NULL;
+	}
+
+	rb = ring_buffer__new(bpf_map__fd(map), handle_event, ctx, NULL);
+	if (!rb) {
+		err_msg("bpf: error opening ring buffer: %s\n", strerror(errno));
+		return NULL;
+	}
+
+	return rb;
+}
+
+/*
+ * bpf_usage_print_reactors - print available BPF reactors
+ */
+void bpf_usage_print_reactors(void)
+{
+	fprintf(stderr, "  available BPF reactors: nop\n");
+}
+
+/*
+ * bpf_enable_tracing - set the trace_level variable in .rodata
+ *
+ * Find trace_level in a special section of .rodata and set its value before
+ * loading.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int bpf_enable_tracing(struct bpf_object *obj, int val)
+{
+	struct bpf_map *map = bpf_object__find_map_by_name(obj, ".rodata.trace_level");
+	size_t data_size;
+	int *trace_level;
+
+	if (!map)
+		return -1;
+
+	trace_level = bpf_map__initial_value(map, &data_size);
+	if (!trace_level || data_size != sizeof(*trace_level))
+		return -1;
+
+	*trace_level = val;
+	return 0;
+}
+
+/*
+ * open_bpf_monitor - open and load a BPF monitor object from a file path
+ *
+ * Returns loaded BPF object on success, NULL on error.
+ */
+static struct bpf_object *open_bpf_monitor(const char *path, struct bpf_monitor_ctx *ctx)
+{
+	struct bpf_object *obj = NULL;
+	int res;
+
+	obj = bpf_object__open_file(path, NULL);
+	if (!obj) {
+		err_msg("bpf: error opening object: %s\n", strerror(errno));
+		return NULL;
+	}
+
+	if (config.trace) {
+		res = extract_btf_info(obj, ctx);
+		if (res || bpf_enable_tracing(obj, ctx->trace_types[TRACE_TYPE_EVENT])) {
+			err_msg("bpf: failed to enable tracing\n");
+			bpf_object__close(obj);
+			return NULL;
+		}
+	}
+
+	res = bpf_object__load(obj);
+	if (res) {
+		err_msg("bpf: error loading object: %s\n", strerror(-res));
+		bpf_object__close(obj);
+		return NULL;
+	}
+
+	return obj;
+}
+
+/*
+ * attach_bpf_handlers - attach all BPF programs
+ *
+ * Attaches all non-struct_ops programs and stores links in the provided array.
+ *
+ * Returns fd of enable program on success, -1 on error.
+ */
+static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
+				struct bpf_link **links, int *link_count)
+{
+	struct bpf_program *prog;
+	int enable_mon_fd = -1;
+
+	bpf_object__for_each_program(prog, obj) {
+		struct bpf_link *link = NULL;
+		const char *prog_name;
+
+		/* Special program to initialise the monitor */
+		if (!strcmp(bpf_program__name(prog), PROG_ENABLE_MON)) {
+			enable_mon_fd = bpf_program__fd(prog);
+			continue;
+		}
+
+		if (*link_count >= MAX_LINKS) {
+			err_msg("bpf: too many programs to attach (%d)\n", *link_count);
+			return -1;
+		}
+
+		prog_name = bpf_program__name(prog);
+		link = bpf_program__attach(prog);
+		if (!link) {
+			err_msg("bpf: error attaching program '%s': %s\n",
+				prog_name, strerror(errno));
+			return -1;
+		}
+		links[(*link_count)++] = link;
+	}
+	if (enable_mon_fd < 0)
+		err_msg("bpf: could not find program %s\n", PROG_ENABLE_MON);
+	return enable_mon_fd;
+}
+
+/*
+ * bpf_run_monitor - load and run a BPF monitor
+ *
+ * Returns 1 if monitor was found and executed, 0 if not found, -1 on error
+ */
+int bpf_run_monitor(char *monitor_name, int argc, char **argv)
+{
+	struct bpf_link *links[MAX_LINKS] = {0};
+	struct bpf_monitor_ctx ctx = {0};
+	struct ring_buffer *rb = NULL;
+	struct bpf_object *obj = NULL;
+	int res, link_count = 0, enable_mon_fd, retval = -1;
+	char monitor_path[MAX_PATH];
+
+	libbpf_set_print(libbpf_print_fn);
+	bpf_fill_base_paths();
+
+	if (!find_bpf_monitor(monitor_name, monitor_path, sizeof(monitor_path)))
+		return 0;
+
+	config.is_bpf = true;
+
+	res = bpf_read_enable(monitor_name);
+	if (res) {
+		err_msg("bpf: monitor %s (BPF) is already enabled\n", monitor_name);
+		return -1;
+	}
+
+	/* we should be good to go */
+	res = parse_arguments(monitor_name, argc, argv);
+	if (res)
+		mon_usage(1, monitor_name, "bpf: failed parsing arguments");
+
+	strncpy(ctx.monitor_name, monitor_name, sizeof(ctx.monitor_name) - 1);
+
+
+	obj = open_bpf_monitor(monitor_path, &ctx);
+	if (!obj)
+		goto cleanup;
+
+	if (config.trace) {
+		rb = bpf_setup_ring_buffer(obj, &ctx);
+		if (!rb)
+			goto cleanup;
+	}
+
+	enable_mon_fd = attach_bpf_handlers(monitor_name, obj, links, &link_count);
+	if (enable_mon_fd < 0)
+		goto cleanup;
+
+	res = bpf_prog_test_run_opts(enable_mon_fd, NULL);
+	if (res) {
+		err_msg("bpf: error enabling the monitor: %s\n", strerror(-res));
+		goto cleanup;
+	}
+
+	if (config.trace)
+		bpf_print_header();
+
+	while (!should_stop()) {
+		if (!config.trace) {
+			sleep(1);
+			continue;
+		}
+		res = ring_buffer__poll(rb, 100);
+		if (res == -EINTR)
+			break;
+		if (res < 0) {
+			err_msg("bpf: error polling ring buffer: %s\n", strerror(-res));
+			goto cleanup;
+		}
+	}
+	retval = 1;
+
+cleanup:
+	for (int i = 0; i < link_count; i++)
+		bpf_link__destroy(links[i]);
+
+	if (config.trace)
+		ring_buffer__free(rb);
+
+	bpf_object__close(obj);
+
+	return retval;
+}
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index 7c4a2e49ff9d..571c9a14c1a2 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -127,6 +127,9 @@ static void rv_mon(int argc, char **argv)
 	 */
 	run += ikm_run_monitor(monitor_name, argc-1, &argv[1]);
 
+	if (!run)
+		run += bpf_run_monitor(monitor_name, argc-1, &argv[1]);
+
 	if (!run)
 		err_msg("rv: monitor %s does not exist\n", monitor_name);
 	exit(run > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
diff --git a/tools/verification/rv/src/utils.c b/tools/verification/rv/src/utils.c
index e19dd65ec789..8ab5d1560223 100644
--- a/tools/verification/rv/src/utils.c
+++ b/tools/verification/rv/src/utils.c
@@ -81,7 +81,10 @@ void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...)
 	for (i = 0; usage[i]; i++)
 		fprintf(stderr, "%s\n", usage[i]);
 
-	ikm_usage_print_reactors();
+	if (config.is_bpf)
+		bpf_usage_print_reactors();
+	else
+		ikm_usage_print_reactors();
 	exit(exit_val);
 }
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (7 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 10/15] tools/rv: Add BPF monitors Gabriele Monaco
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add bpf_atomic.h including atomic read/write macros like READ_ONCE() and
try_cmpxchg(). This version is a stripped down copy of the one currently
present in selftests/bpf/libarena.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 .../verification/rv/bpf_monitors/bpf_atomic.h | 105 ++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 tools/verification/rv/bpf_monitors/bpf_atomic.h

diff --git a/tools/verification/rv/bpf_monitors/bpf_atomic.h b/tools/verification/rv/bpf_monitors/bpf_atomic.h
new file mode 100644
index 000000000000..f834ea91c9d3
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/bpf_atomic.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+#ifndef BPF_ATOMIC_H
+#define BPF_ATOMIC_H
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+extern bool CONFIG_X86_64 __kconfig __weak;
+
+/*
+ * __unqual_typeof(x) - Declare an unqualified scalar type, leaving
+ *			non-scalar types unchanged,
+ *
+ * Prefer C11 _Generic for better compile-times and simpler code. Note: 'char'
+ * is not type-compatible with 'signed char', and we define a separate case.
+ *
+ * This is copied verbatim from kernel's include/linux/compiler_types.h, but
+ * with default expression (for pointers) changed from (x) to (typeof(x)0).
+ *
+ * This is because LLVM has a bug where for lvalue (x), it does not get rid of
+ * an extra address_space qualifier, but does in case of rvalue (typeof(x)0).
+ * Hence, for pointers, we need to create an rvalue expression to get the
+ * desired type. See https://github.com/llvm/llvm-project/issues/53400.
+ */
+#define __scalar_type_to_expr_cases(type) \
+	unsigned type : (unsigned type)0, signed type : (signed type)0
+
+#define __unqual_typeof(x)                              \
+	typeof(_Generic((x),                            \
+		char: (char)0,                          \
+		__scalar_type_to_expr_cases(char),      \
+		__scalar_type_to_expr_cases(short),     \
+		__scalar_type_to_expr_cases(int),       \
+		__scalar_type_to_expr_cases(long),      \
+		__scalar_type_to_expr_cases(long long), \
+		default: (typeof(x))0))
+
+/* No-op for BPF */
+#define cpu_relax() ({})
+
+#define READ_ONCE(x) (*(volatile typeof(x) *)&(x))
+
+#ifndef WRITE_ONCE
+#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *)&(x)) = (val))
+#endif
+
+#define cmpxchg(p, old, new) __sync_val_compare_and_swap((p), old, new)
+
+#define try_cmpxchg(p, pold, new)                                 \
+	({                                                        \
+		__unqual_typeof(*(pold)) __o = *(pold);           \
+		__unqual_typeof(*(p)) __r = cmpxchg(p, __o, new); \
+		if (__r != __o)                                   \
+			*(pold) = __r;                            \
+		__r == __o;                                       \
+	})
+
+#define try_cmpxchg_relaxed(p, pold, new) try_cmpxchg(p, pold, new)
+
+#define try_cmpxchg_acquire(p, pold, new) try_cmpxchg(p, pold, new)
+
+#define smp_mb()                                 \
+	({                                       \
+		volatile unsigned long __val;    \
+		__sync_fetch_and_add(&__val, 0); \
+	})
+
+#define smp_rmb()                   \
+	({                          \
+		if (!CONFIG_X86_64) \
+			smp_mb();   \
+		else                \
+			barrier();  \
+	})
+
+#define smp_wmb()                   \
+	({                          \
+		if (!CONFIG_X86_64) \
+			smp_mb();   \
+		else                \
+			barrier();  \
+	})
+
+/* Control dependency provides LOAD->STORE, provide LOAD->LOAD */
+#define smp_acquire__after_ctrl_dep() ({ smp_rmb(); })
+
+#define smp_load_acquire(p)                                  \
+	({                                                   \
+		__unqual_typeof(*(p)) __v = READ_ONCE(*(p)); \
+		if (!CONFIG_X86_64)                          \
+			smp_mb();                            \
+		barrier();                                   \
+		__v;                                         \
+	})
+
+#define smp_store_release(p, val)      \
+	({                             \
+		if (!CONFIG_X86_64)    \
+			smp_mb();      \
+		barrier();             \
+		WRITE_ONCE(*(p), val); \
+	})
+
+#endif /* BPF_ATOMIC_H */
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 10/15] tools/rv: Add BPF monitors
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (8 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add the code for 2 initial BPF monitors, both DA (the only currently
supported type):

* queue: per-task monitor stating tasks alternate enqueue and dequeue.
* nohz: per-cpu monitor stating the scheduler tick don't run when
  stopped and can run only after being resumed.

BPF monitors can include the in-kernel da_monitor.h, which is now
adapted to share as much common code and conditionally compile what
needs to be BPF or kernel specific.

Integrate BPF monitor building into main rv Makefile, if all tools are
available on the system.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/rv/da_monitor.h                       |  21 +-
 tools/verification/models/nohz.dot            |  16 +
 tools/verification/models/tqueue.dot          |  15 +
 tools/verification/rv/Makefile                |  42 +-
 tools/verification/rv/Makefile.config         |  26 ++
 tools/verification/rv/Makefile.rv             |   5 +
 tools/verification/rv/bpf_monitors/.gitignore |   2 +
 .../rv/bpf_monitors/da_monitor_bpf.h          | 368 ++++++++++++++++++
 tools/verification/rv/bpf_monitors/nohz.c     |  42 ++
 tools/verification/rv/bpf_monitors/nohz.h     |  49 +++
 tools/verification/rv/bpf_monitors/tqueue.c   |  30 ++
 tools/verification/rv/bpf_monitors/tqueue.h   |  47 +++
 12 files changed, 655 insertions(+), 8 deletions(-)
 create mode 100644 tools/verification/models/nohz.dot
 create mode 100644 tools/verification/models/tqueue.dot
 create mode 100644 tools/verification/rv/bpf_monitors/.gitignore
 create mode 100644 tools/verification/rv/bpf_monitors/da_monitor_bpf.h
 create mode 100644 tools/verification/rv/bpf_monitors/nohz.c
 create mode 100644 tools/verification/rv/bpf_monitors/nohz.h
 create mode 100644 tools/verification/rv/bpf_monitors/tqueue.c
 create mode 100644 tools/verification/rv/bpf_monitors/tqueue.h

diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 7f0bdfd7cce3..ee1cec73ec85 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -14,14 +14,19 @@
 #ifndef _RV_DA_MONITOR_H
 #define _RV_DA_MONITOR_H
 
-#include <rv/automata.h>
-#include <linux/rv.h>
+#ifndef __BPF__
+/* Kernel includes */
 #include <rv/kunit.h>
-#include <linux/stringify.h>
 #include <linux/bug.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/hashtable.h>
+#endif /* __BPF__ */
+
+#include <linux/args.h>
+#include <rv/automata.h>
+#include <linux/rv.h>
+#include <linux/stringify.h>
 
 /*
  * Per-cpu variables require a unique name although static in some
@@ -77,6 +82,9 @@ static struct rv_monitor rv_this;
 #define da_id_type int
 #endif
 
+#ifdef __BPF__
+#include "da_monitor_bpf.h"
+#else
 static void react(enum states curr_state, enum events event)
 {
 	rv_react(&rv_this,
@@ -85,6 +93,7 @@ static void react(enum states curr_state, enum events event)
 		 model_get_event_name(event),
 		 model_get_state_name(curr_state));
 }
+#endif
 
 /*
  * da_monitor_reset_state - reset a monitor and setting it to init state
@@ -159,6 +168,7 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
 	return 1;
 }
 
+#ifndef __BPF__
 #if RV_MON_TYPE == RV_MON_GLOBAL
 /*
  * Functions to define, init and get a global monitor.
@@ -685,6 +695,7 @@ static inline void da_trace_error(struct da_monitor *da_mon,
 						model_get_event_name(event));
 }
 #endif /* RV_MON_TYPE */
+#endif /* __BPF__ */
 
 /*
  * da_event - handle an event for the da_mon
@@ -806,7 +817,7 @@ static inline bool da_handle_start_run_event(enum events event)
 	return __da_handle_start_run_event(da_get_monitor(), event, 0);
 }
 
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif !defined(__BPF__) && RV_MON_TYPE == RV_MON_PER_TASK
 /*
  * Handle event for per task.
  */
@@ -847,7 +858,7 @@ static inline bool da_handle_start_run_event(struct task_struct *tsk,
 	return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid);
 }
 
-#elif RV_MON_TYPE == RV_MON_PER_OBJ
+#elif !defined(__BPF__) && RV_MON_TYPE == RV_MON_PER_OBJ
 /*
  * Handle event for per object.
  */
diff --git a/tools/verification/models/nohz.dot b/tools/verification/models/nohz.dot
new file mode 100644
index 000000000000..49adb25e32d8
--- /dev/null
+++ b/tools/verification/models/nohz.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+	{node [shape = circle] "stopped"};
+	{node [shape = plaintext, style=invis, label=""] "__init_running"};
+	{node [shape = doublecircle] "running"};
+	{node [shape = circle] "running"};
+	"__init_running" -> "running";
+	"stopped" [label = "stopped"];
+	"stopped" -> "running" [ label = "tick_restart" ];
+	"running" [label = "running"];
+	"running" -> "running" [ label = "sched_tick" ];
+	"running" -> "stopped" [ label = "tick_stop" ];
+	{ rank = min ;
+		"__init_running";
+		"running";
+	}
+}
diff --git a/tools/verification/models/tqueue.dot b/tools/verification/models/tqueue.dot
new file mode 100644
index 000000000000..7aadf99ed29d
--- /dev/null
+++ b/tools/verification/models/tqueue.dot
@@ -0,0 +1,15 @@
+digraph state_automaton {
+	{node [shape = circle] "enqueued"};
+	{node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
+	{node [shape = doublecircle] "dequeued"};
+	{node [shape = circle] "dequeued"};
+	"__init_dequeued" -> "dequeued";
+	"enqueued" [label = "enqueued"];
+	"enqueued" -> "dequeued" [ label = "sched_dequeue" ];
+	"dequeued" [label = "dequeued"];
+	"dequeued" -> "enqueued" [ label = "sched_enqueue" ];
+	{ rank = min ;
+		"__init_dequeued";
+		"dequeued";
+	}
+}
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 0a8c7a656f7f..bb81593acd71 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -33,9 +33,13 @@ DOCSRC		:= ../../../Documentation/tools/rv/
 FEATURE_TESTS	:= libtraceevent
 FEATURE_TESTS	+= libtracefs
 FEATURE_TESTS	+= libbpf
+FEATURE_TESTS	+= clang-bpf-co-re
+FEATURE_TESTS	+= bpftool-btf
 FEATURE_DISPLAY	:= libtraceevent
 FEATURE_DISPLAY	+= libtracefs
 FEATURE_DISPLAY	+= libbpf
+FEATURE_DISPLAY	+= clang-bpf-co-re
+FEATURE_DISPLAY	+= bpftool-btf
 
 all: $(RV)
 
@@ -43,7 +47,7 @@ include $(srctree)/tools/build/Makefile.include
 include Makefile.rv
 
 # check for dependencies only on required targets
-NON_CONFIG_TARGETS := clean install doc doc_clean doc_install
+NON_CONFIG_TARGETS := clean install doc doc_clean doc_install bpf
 
 config		:= 1
 ifdef MAKECMDGOALS
@@ -68,19 +72,51 @@ static: $(RV_IN)
 	$(eval LDFLAGS += -static)
 	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
 
+BPF_DIR		:= bpf_monitors
+BPF_SOURCES	:= $(wildcard $(BPF_DIR)/*.c)
+BPF_OBJECTS	:= $(BPF_SOURCES:.c=.o)
+VMLINUX_H	:= $(BPF_DIR)/vmlinux.h
+
+BPF_INCLUDES	:= -I$(srctree)/include/uapi
+BPF_INCLUDES	+= -I$(srctree)/include
+BPF_INCLUDES	+= -I$(BPF_DIR)
+
+BPF_CFLAGS	:= -g -O2 -target bpf -MMD -MP $(BPF_INCLUDES)
+BPF_CFLAGS	+= -Wall -Wno-unused-function -Wno-missing-declarations
+
+VMLINUX		:= ../../../vmlinux
+VMLINUX_BTF	?= $(if $(wildcard $(VMLINUX)) , $(VMLINUX), /sys/kernel/btf/vmlinux)
+
+$(VMLINUX_H): $(VMLINUX_BTF)
+	$(QUIET_GEN)$(SYSTEM_BPFTOOL) btf dump file $< format c > $@
+
+$(BPF_DIR)/%.o: $(BPF_DIR)/%.c $(VMLINUX_H)
+	$(QUIET_CLANG)$(CLANG) $(BPF_CFLAGS) -c $< -o $@
+	$(Q)$(LLVM_STRIP) -g $@
+	$(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
+
+-include $(BPF_SOURCES:.c=.d)
+
+bpf: $(BPF_OBJECTS)
+
 rv.%: fixdep FORCE
 	make -f $(srctree)/tools/build/Makefile.build dir=. $@
 
 $(RV_IN): fixdep FORCE
 	make $(build)=rv
 
+ifeq ($(BUILD_BPF_OBJS),1)
+all: bpf
+endif
+
 clean: doc_clean fixdep-clean
 	$(call QUIET_CLEAN, rv)
-	$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
+	$(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '*.d' -delete
 	$(Q)rm -f rv rv-static fixdep FEATURE-DUMP rv-*
+	$(Q)rm -f $(VMLINUX_H)
 	$(Q)rm -rf feature
 
 check: $(RV)
 	RV=$(RV) prove -o --directives -f tests/
 
-.PHONY: FORCE clean check
+.PHONY: FORCE clean check bpf
diff --git a/tools/verification/rv/Makefile.config b/tools/verification/rv/Makefile.config
index 0600170ac217..1caad39ab0c9 100644
--- a/tools/verification/rv/Makefile.config
+++ b/tools/verification/rv/Makefile.config
@@ -66,6 +66,32 @@ ifeq ($(BUILD_BPF),1)
   $(call lib_setup,bpf)
 endif
 
+# Building BPF monitors requires clang and bpftool, RV with BPF monitors
+# support can still run without building monitors
+ifndef BUILD_BPF_OBJS
+  BUILD_BPF_OBJS := $(BUILD_BPF)
+endif
+
+ifeq ($(BUILD_BPF_OBJS),1)
+  $(call feature_check,clang-bpf-co-re)
+  ifeq ($(feature-clang-bpf-co-re), 1)
+    $(call detected,CONFIG_CLANG_BPF_CO_RE)
+  else
+    $(info clang is missing or does not support BPF CO-RE, cannot build BPF monitors.)
+    $(info Please install clang)
+    BUILD_BPF_OBJS := 0
+  endif
+
+  $(call feature_check,bpftool-btf)
+  ifeq ($(feature-bpftool-btf), 1)
+    $(call detected,CONFIG_BPFTOOL_BTF)
+  else
+    $(info bpftool is missing or does not support btf, cannot build BPF monitors.)
+    $(info Please install bpftool)
+    BUILD_BPF_OBJS := 0
+  endif
+endif
+
 ifeq ($(STOP_ERROR),1)
   $(error Please, check the errors above.)
 endif
diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
index 2497fb96c83d..418748bc3aa1 100644
--- a/tools/verification/rv/Makefile.rv
+++ b/tools/verification/rv/Makefile.rv
@@ -33,12 +33,17 @@ INSTALL		:= install
 MKDIR		:= mkdir
 STRIP		:= strip
 BINDIR		:= /usr/bin
+BPF_MON_DIR	:= /usr/share/rv/bpf_monitors
 
 .PHONY: install
 install: doc_install
 	$(Q)$(MKDIR) -p $(DESTDIR)$(BINDIR)
 	$(call QUIET_INSTALL,rv)$(INSTALL) $(OUTPUT)rv -m 755 $(DESTDIR)$(BINDIR)
 	$(Q)@$(STRIP) $(DESTDIR)$(BINDIR)/rv
+ifneq ($(wildcard bpf_monitors/*.o),)
+	$(Q)$(MKDIR) -p $(DESTDIR)$(BPF_MON_DIR)
+	$(call QUIET_INSTALL,bpf_monitors)$(INSTALL) bpf_monitors/*.o -m 644 $(DESTDIR)$(BPF_MON_DIR)
+endif
 
 .PHONY: doc doc_clean doc_install
 doc:
diff --git a/tools/verification/rv/bpf_monitors/.gitignore b/tools/verification/rv/bpf_monitors/.gitignore
new file mode 100644
index 000000000000..e5add9141ef5
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/.gitignore
@@ -0,0 +1,2 @@
+*.d
+vmlinux.h
diff --git a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
new file mode 100644
index 000000000000..12f0f78b9c9d
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * BPF support for DA monitors.
+ *
+ * BPF programs can include the in-kernel da_monitor directly, this
+ * header contains all the BPF compatibility layer.
+ *
+ * Copyright (C) 2026 Red Hat Inc, Gabriele Monaco <gmonaco@redhat.com>
+ */
+
+#ifndef _DA_MONITOR_BPF_H
+#define _DA_MONITOR_BPF_H
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_atomic.h"
+
+/* BPF monitors don't support these */
+#define rv_react(...) do {} while (0)
+#define trace_rv_retries_error(...) do {} while (0)
+#define pr_warn(fmt, ...) bpf_printk(fmt, ##__VA_ARGS__)
+#define rv_monitoring_on() 1
+#define da_implicit_guard()
+#define IS_ENABLED(conf) 0
+
+/*
+ * Also used to filter out events higher values imply lower ones.
+ * Userspace will parse this enum but expects EVENT to imply ERROR(s) and ERROR
+ * to imply any other ERROR* type.
+ */
+enum rv_trace_type {
+	RV_TRACE_NONE,
+	RV_TRACE_ERROR,
+	RV_TRACE_EVENT,
+};
+
+/* Configuration set from userspace before loading */
+const volatile SEC(".rodata.trace_level") enum rv_trace_type trace_level = RV_TRACE_NONE;
+
+/*
+ * BPF ring buffer for trace events
+ * Events and errors are sent to userspace via this ringbuf
+ */
+struct rv_trace_entry {
+	uint8_t event_type;
+	uint8_t is_final;
+	char comm[TASK_COMM_LEN];
+#if RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
+	uint32_t id;
+#endif
+	uint32_t pid;
+	uint32_t cpu;
+	uint32_t curr_state;
+	uint32_t event;
+	uint32_t next_state;
+};
+
+#define rv_mon_map CONCATENATE(rv_mon_, MONITOR_NAME)
+#define rv_rb CONCATENATE(rv_rb_, MONITOR_NAME)
+
+struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 256 * 1024);
+} rv_rb SEC(".maps");
+
+#ifndef __used
+#define __used __attribute__((used))
+#endif
+
+/* Force types to be included in BTF for userspace parsing */
+static const enum states __used _btf_states;
+static const enum events __used _btf_events;
+static const struct rv_trace_entry __used *_btf_trace;
+
+static inline void da_monitor_reset(struct da_monitor *da_mon);
+
+/* BPF monitors do not use reactors */
+static inline void react(enum states curr_state, enum events event) { }
+
+/*
+ * BPF monitor implementations
+ * These use BPF maps instead of kernel data structures
+ */
+
+#if RV_MON_TYPE == RV_MON_GLOBAL
+/*
+ * BPF Global monitor - uses a single-entry BPF array map
+ */
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, union rv_task_monitor);
+} rv_mon_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor(void)
+{
+	__u32 key = 0;
+	union rv_task_monitor *mon = bpf_map_lookup_elem(&rv_mon_map, &key);
+
+	return &mon->da_mon;
+}
+
+#elif RV_MON_TYPE == RV_MON_PER_CPU
+/*
+ * BPF Per-CPU monitor - uses BPF per-cpu array map
+ */
+
+struct {
+	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, union rv_task_monitor);
+} rv_mon_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor(void)
+{
+	__u32 key = 0;
+	union rv_task_monitor *mon = bpf_map_lookup_elem(&rv_mon_map, &key);
+
+	return &mon->da_mon;
+}
+
+#elif RV_MON_TYPE == RV_MON_PER_OBJ || RV_MON_TYPE == RV_MON_PER_TASK
+/*
+ * BPF Per-Object monitor - uses BPF hash map
+ * Note: monitor_target_bpf type must be compatible with BPF
+ * Types and structs must be different not to collide with vmlinux.h
+ */
+
+#if RV_MON_TYPE == RV_MON_PER_TASK
+/*
+ * BPF Per-Task monitor - uses BPF hash map indexed by PID
+ *
+ * Just a special case of per-object monitor with API consistent with in-kernel
+ * monitors (no need to pass the id).
+ */
+
+#define da_get_monitor(tsk) da_get_monitor_bpf(BPF_CORE_READ(tsk, pid), tsk)
+#define da_handle_event(tsk, event) \
+	da_handle_event_bpf(BPF_CORE_READ(tsk, pid), tsk, event)
+#define da_handle_start_event(tsk, event) \
+	da_handle_start_event_bpf(BPF_CORE_READ(tsk, pid), tsk, event)
+#define da_handle_start_run_event(tsk, event) \
+	da_handle_start_run_event_bpf(BPF_CORE_READ(tsk, pid), tsk, event)
+
+typedef struct task_struct *monitor_target_bpf;
+static inline void da_destroy_storage(da_id_type id);
+
+SEC("tp_btf/sched_process_exit")
+int BPF_PROG(handle_obj_cleanup, struct task_struct *p, bool group_dead)
+{
+	da_destroy_storage(p->pid);
+	return 0;
+}
+
+#else
+
+#define da_get_monitor da_get_monitor_bpf
+#define da_handle_event da_handle_event_bpf
+#define da_handle_start_event da_handle_start_event_bpf
+#define da_handle_start_run_event da_handle_start_run_event_bpf
+
+#endif /* RV_MON_PER_TASK */
+
+struct da_monitor_storage_bpf {
+	da_id_type id;
+	monitor_target_bpf target;
+	union rv_task_monitor rv;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, da_id_type);
+	__type(value, struct da_monitor_storage_bpf);
+} rv_mon_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor_bpf(da_id_type id, monitor_target_bpf target)
+{
+	struct da_monitor_storage_bpf *storage;
+
+	storage = bpf_map_lookup_elem(&rv_mon_map, &id);
+	return storage ? &storage->rv.da_mon : NULL;
+}
+
+static inline struct da_monitor *da_create_storage(da_id_type id,
+						    monitor_target_bpf target,
+						    struct da_monitor *da_mon)
+{
+	struct da_monitor_storage_bpf new_storage = {
+		.id = id,
+		.target = target,
+	};
+
+	if (da_mon)
+		return da_mon;
+
+	bpf_map_update_elem(&rv_mon_map, &id, &new_storage, BPF_NOEXIST);
+	return da_get_monitor_bpf(id, target);
+}
+
+static inline void da_destroy_storage(da_id_type id)
+{
+	bpf_map_delete_elem(&rv_mon_map, &id);
+}
+
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+	return container_of(da_mon, struct da_monitor_storage_bpf, rv.da_mon)->id;
+}
+
+static inline monitor_target_bpf da_get_target(struct da_monitor *da_mon)
+{
+	return container_of(da_mon, struct da_monitor_storage_bpf, rv.da_mon)->target;
+}
+
+/*
+ * Handle event for per object and per task
+ */
+
+static inline void __da_handle_event(struct da_monitor *da_mon,
+				     enum events event, da_id_type id);
+static inline bool __da_handle_start_event(struct da_monitor *da_mon,
+					   enum events event, da_id_type id);
+static inline bool __da_handle_start_run_event(struct da_monitor *da_mon,
+					       enum events event, da_id_type id);
+
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event_bpf(da_id_type id, monitor_target_bpf target, enum events event)
+{
+	struct da_monitor *da_mon;
+
+	da_mon = da_get_monitor_bpf(id, target);
+	if (likely(da_mon))
+		__da_handle_event(da_mon, event, id);
+}
+
+/*
+ * da_handle_start_event - start monitoring or handle event
+ *
+ * This function is used to notify the monitor that the system is returning
+ * to the initial state, so the monitor can start monitoring in the next event.
+ * Thus:
+ *
+ * If the monitor already started, handle the event.
+ * If the monitor did not start yet, start the monitor but skip the event.
+ */
+static inline bool da_handle_start_event_bpf(da_id_type id, monitor_target_bpf target,
+					 enum events event)
+{
+	struct da_monitor *da_mon;
+
+	da_mon = da_get_monitor_bpf(id, target);
+	da_mon = da_create_storage(id, target, da_mon);
+	if (unlikely(!da_mon))
+		return 0;
+	return __da_handle_start_event(da_mon, event, id);
+}
+
+/*
+ * da_handle_start_run_event - start monitoring and handle event
+ *
+ * This function is used to notify the monitor that the system is in the
+ * initial state, so the monitor can start monitoring and handling event.
+ */
+static inline bool da_handle_start_run_event_bpf(da_id_type id, monitor_target_bpf target,
+					     enum events event)
+{
+	struct da_monitor *da_mon;
+
+	da_mon = da_get_monitor_bpf(id, target);
+	da_mon = da_create_storage(id, target, da_mon);
+	if (unlikely(!da_mon))
+		return 0;
+	return __da_handle_start_run_event(da_mon, event, id);
+}
+
+static inline void da_reset_bpf(da_id_type id, monitor_target_bpf target)
+{
+	struct da_monitor *da_mon;
+
+	da_mon = da_get_monitor_bpf(id, target);
+	if (likely(da_mon))
+		da_monitor_reset(da_mon);
+}
+
+#endif /* RV_MON_TYPE */
+
+static inline void *_da_trace_common(enum states curr_state, enum events event,
+				     enum rv_trace_type type)
+{
+	struct rv_trace_entry *entry;
+	static const char stub_comm[] = "<XXX>";
+
+	if (trace_level < type)
+		return NULL;
+
+	entry = bpf_ringbuf_reserve(&rv_rb, sizeof(*entry), 0);
+	if (!entry)
+		return NULL;
+	entry->event_type = type;
+	entry->cpu = bpf_get_smp_processor_id();
+	entry->pid = bpf_get_current_pid_tgid() & 0xffffffff;
+	if (bpf_get_current_comm(entry->comm, TASK_COMM_LEN))
+		__builtin_memcpy(entry->comm, stub_comm, sizeof(stub_comm));
+	entry->curr_state = curr_state;
+	entry->event = event;
+
+	return entry;
+}
+
+#if RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
+static inline void _da_trace_id(struct rv_trace_entry *entry, da_id_type id)
+{
+	entry->id = id;
+}
+#else
+static inline void _da_trace_id(struct rv_trace_entry *entry, da_id_type id) { }
+#endif
+
+/*
+ * BPF trace events implementation using ring buffer
+ */
+static inline void da_trace_event(struct da_monitor *da_mon,
+				  enum states curr_state, enum events event,
+				  enum states next_state,
+				  da_id_type id)
+{
+	struct rv_trace_entry *entry = _da_trace_common(curr_state, event, RV_TRACE_EVENT);
+
+	if (!entry)
+		return;
+	_da_trace_id(entry, id);
+	entry->is_final = model_is_final_state(next_state);
+	entry->next_state = next_state;
+
+	bpf_ringbuf_submit(entry, 0);
+}
+
+static inline void da_trace_error(struct da_monitor *da_mon,
+				  enum states curr_state, enum events event,
+				  da_id_type id)
+{
+	struct rv_trace_entry *entry = _da_trace_common(curr_state, event, RV_TRACE_ERROR);
+
+	if (!entry)
+		return;
+	_da_trace_id(entry, id);
+
+	bpf_ringbuf_submit(entry, 0);
+}
+
+/*
+ * enable_monitor - BPF program to be called manually after all handlers are attached
+ */
+SEC("syscall")
+int enable_monitor(void *ctx)
+{
+	rv_this.enabled = 1;
+	return 0;
+}
+
+#endif // _DA_MONITOR_BPF_H
diff --git a/tools/verification/rv/bpf_monitors/nohz.c b/tools/verification/rv/bpf_monitors/nohz.c
new file mode 100644
index 000000000000..cb19944fad77
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/nohz.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "nohz.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This monitor is broken on purpose to test errors, sched_tick can run with
+ * stopped ticks for one last time (deferred tick reprogram).
+ * A way to fix this monitor is to handle the sched_tick event only when
+ * tick_nohz_handler returns HRTIMER_RESTART (i.e. it isn't stopping the tick).
+ */
+SEC("fentry/sched_tick")
+int BPF_PROG(handle_sched_tick)
+{
+	da_handle_start_event(sched_tick_nohz);
+	return 0;
+}
+
+SEC("fentry/tick_nohz_restart_sched_tick")
+int BPF_PROG(handle_tick_restart)
+{
+	da_handle_start_event(tick_restart_nohz);
+	return 0;
+}
+
+SEC("tp_btf/tick_stop")
+int BPF_PROG(handle_tick_stop, int success, int dependency)
+{
+	if (success)
+		da_handle_event(tick_stop_nohz);
+	return 0;
+}
+
+static struct rv_monitor rv_this = {
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "tick does not run when stopped.";
diff --git a/tools/verification/rv/bpf_monitors/nohz.h b/tools/verification/rv/bpf_monitors/nohz.h
new file mode 100644
index 000000000000..ef1c88f31f51
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/nohz.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of nohz automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME nohz
+
+enum states_nohz {
+	running_nohz,
+	stopped_nohz,
+	state_max_nohz,
+};
+
+#define INVALID_STATE state_max_nohz
+
+enum events_nohz {
+	sched_tick_nohz,
+	tick_restart_nohz,
+	tick_stop_nohz,
+	event_max_nohz,
+};
+
+struct automaton_nohz {
+	char state_names[state_max_nohz][32];
+	char event_names[event_max_nohz][32];
+	unsigned char function[state_max_nohz][event_max_nohz];
+	unsigned char initial_state;
+	bool final_states[state_max_nohz];
+};
+
+static const struct automaton_nohz automaton_nohz = {
+	.state_names = {
+		"running",
+		"stopped",
+	},
+	.event_names = {
+		"sched_tick",
+		"tick_restart",
+		"tick_stop",
+	},
+	.function = {
+		{       running_nohz,      INVALID_STATE,       stopped_nohz },
+		{      INVALID_STATE,       running_nohz,      INVALID_STATE },
+	},
+	.initial_state = running_nohz,
+	.final_states = { 1, 0 },
+};
diff --git a/tools/verification/rv/bpf_monitors/tqueue.c b/tools/verification/rv/bpf_monitors/tqueue.c
new file mode 100644
index 000000000000..e05c5c13c634
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/tqueue.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_TASK
+#include "tqueue.h"
+#include <rv/da_monitor.h>
+#define PF_EXITING 0x00000004
+
+SEC("tp_btf/sched_dequeue_tp")
+int BPF_PROG(handle_sched_dequeue, struct task_struct *tsk, int cpu)
+{
+	if (!(BPF_CORE_READ(tsk, flags) & PF_EXITING))
+		da_handle_start_event(tsk, sched_dequeue_tqueue);
+	return 0;
+}
+
+SEC("tp_btf/sched_enqueue_tp")
+int BPF_PROG(handle_sched_enqueue, struct task_struct *tsk, int cpu)
+{
+	da_handle_event(tsk, sched_enqueue_tqueue);
+	return 0;
+}
+
+static struct rv_monitor rv_this = {
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "enqueue and dequeue tasks.";
diff --git a/tools/verification/rv/bpf_monitors/tqueue.h b/tools/verification/rv/bpf_monitors/tqueue.h
new file mode 100644
index 000000000000..1dac577d6bc4
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/tqueue.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of tqueue automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME tqueue
+
+enum states_tqueue {
+	dequeued_tqueue,
+	enqueued_tqueue,
+	state_max_tqueue,
+};
+
+#define INVALID_STATE state_max_tqueue
+
+enum events_tqueue {
+	sched_dequeue_tqueue,
+	sched_enqueue_tqueue,
+	event_max_tqueue,
+};
+
+struct automaton_tqueue {
+	char state_names[state_max_tqueue][32];
+	char event_names[event_max_tqueue][32];
+	unsigned char function[state_max_tqueue][event_max_tqueue];
+	unsigned char initial_state;
+	bool final_states[state_max_tqueue];
+};
+
+static const struct automaton_tqueue automaton_tqueue = {
+	.state_names = {
+		"dequeued",
+		"enqueued",
+	},
+	.event_names = {
+		"sched_dequeue",
+		"sched_enqueue",
+	},
+	.function = {
+		{        INVALID_STATE,      enqueued_tqueue },
+		{      dequeued_tqueue,        INVALID_STATE },
+	},
+	.initial_state = dequeued_tqueue,
+	.final_states = { 1, 0 },
+};
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for BPF monitors
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (9 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 10/15] tools/rv: Add BPF monitors Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 12/15] tools/rv: Add reactors support to " Gabriele Monaco
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

BPF atomic macros require CONFIG_X86_64 to determine barrier
instructions. Reading this from the system Kconfig can fail if the
configuration is missing or in an unexpected location, causing the
loading phase to fail.

Since the rv tool is compiled for the target architecture, the
architecture is known at compile-time. Define the CONFIG_X86_64 value
statically via the libbpf open options to bypass Kconfig dependency and
ensure reliable loading.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/src/bpf_monitor.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index 9a3f00febfeb..d43ee75d3904 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -719,7 +719,16 @@ static struct bpf_object *open_bpf_monitor(const char *path, struct bpf_monitor_
 	struct bpf_object *obj = NULL;
 	int res;
 
-	obj = bpf_object__open_file(path, NULL);
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		/* Define statically as arch is known, Kconfig may not be available */
+#ifdef __x86_64__
+		.kconfig = "CONFIG_X86_64=y\n",
+#else
+		.kconfig = "CONFIG_X86_64=n\n",
+#endif
+	);
+
+	obj = bpf_object__open_file(path, &opts);
 	if (!obj) {
 		err_msg("bpf: error opening object: %s\n", strerror(errno));
 		return NULL;
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 12/15] tools/rv: Add reactors support to BPF monitors
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (10 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 13/15] verification/rvgen: Add support for " Gabriele Monaco
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

The BPF implementation of RV monitors cannot use standard reactors in
the kernel and currently reactions are skipped.

Add rv_react() to build the printk format using BPF macros and pass that
to a BPF function with a single message parameter, since BPF doesn't
support variable arguments.
This function is defined as weak, so the rv tool can link a different
implementation on top at load time.

Implement a printk reactor that is simply reimplementing this function
passing the message to bpf_printk().

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/rv/da_monitor.h                       |   4 +-
 tools/verification/rv/Makefile                |  12 ++-
 tools/verification/rv/Makefile.rv             |   5 +
 .../rv/bpf_monitors/da_monitor_bpf.h          |  12 ++-
 tools/verification/rv/bpf_reactors/.gitignore |   2 +
 tools/verification/rv/bpf_reactors/panic.c    |  15 +++
 tools/verification/rv/bpf_reactors/printk.c   |  13 +++
 tools/verification/rv/src/bpf_monitor.c       | 102 +++++++++++++++++-
 8 files changed, 157 insertions(+), 8 deletions(-)
 create mode 100644 tools/verification/rv/bpf_reactors/.gitignore
 create mode 100644 tools/verification/rv/bpf_reactors/panic.c
 create mode 100644 tools/verification/rv/bpf_reactors/printk.c

diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index ee1cec73ec85..28cff26aecfe 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -84,7 +84,8 @@ static struct rv_monitor rv_this;
 
 #ifdef __BPF__
 #include "da_monitor_bpf.h"
-#else
+#endif
+
 static void react(enum states curr_state, enum events event)
 {
 	rv_react(&rv_this,
@@ -93,7 +94,6 @@ static void react(enum states curr_state, enum events event)
 		 model_get_event_name(event),
 		 model_get_state_name(curr_state));
 }
-#endif
 
 /*
  * da_monitor_reset_state - reset a monitor and setting it to init state
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index bb81593acd71..44b0e3af1488 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -73,8 +73,11 @@ static: $(RV_IN)
 	$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
 
 BPF_DIR		:= bpf_monitors
+BPF_REACT_DIR   := bpf_reactors
 BPF_SOURCES	:= $(wildcard $(BPF_DIR)/*.c)
 BPF_OBJECTS	:= $(BPF_SOURCES:.c=.o)
+BPF_REACT_SOURCES := $(wildcard $(BPF_REACT_DIR)/*.c)
+BPF_REACT_OBJECTS := $(BPF_REACT_SOURCES:.c=.o)
 VMLINUX_H	:= $(BPF_DIR)/vmlinux.h
 
 BPF_INCLUDES	:= -I$(srctree)/include/uapi
@@ -95,9 +98,14 @@ $(BPF_DIR)/%.o: $(BPF_DIR)/%.c $(VMLINUX_H)
 	$(Q)$(LLVM_STRIP) -g $@
 	$(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
 
--include $(BPF_SOURCES:.c=.d)
+$(BPF_REACT_DIR)/%.o: $(BPF_REACT_DIR)/%.c $(VMLINUX_H)
+	$(QUIET_CLANG)$(CLANG) $(BPF_CFLAGS) -c $< -o $@
+	$(Q)$(LLVM_STRIP) -g $@
+	$(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
+
+-include $(BPF_SOURCES:.c=.d) $(BPF_REACT_SOURCES:.c=.d)
 
-bpf: $(BPF_OBJECTS)
+bpf: $(BPF_OBJECTS) $(BPF_REACT_OBJECTS)
 
 rv.%: fixdep FORCE
 	make -f $(srctree)/tools/build/Makefile.build dir=. $@
diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
index 418748bc3aa1..3ac329851914 100644
--- a/tools/verification/rv/Makefile.rv
+++ b/tools/verification/rv/Makefile.rv
@@ -34,6 +34,7 @@ MKDIR		:= mkdir
 STRIP		:= strip
 BINDIR		:= /usr/bin
 BPF_MON_DIR	:= /usr/share/rv/bpf_monitors
+BPF_REACT_DIR	:= /usr/share/rv/bpf_reactors
 
 .PHONY: install
 install: doc_install
@@ -44,6 +45,10 @@ ifneq ($(wildcard bpf_monitors/*.o),)
 	$(Q)$(MKDIR) -p $(DESTDIR)$(BPF_MON_DIR)
 	$(call QUIET_INSTALL,bpf_monitors)$(INSTALL) bpf_monitors/*.o -m 644 $(DESTDIR)$(BPF_MON_DIR)
 endif
+ifneq ($(wildcard bpf_reactors/*.o),)
+	$(Q)$(MKDIR) -p $(DESTDIR)$(BPF_REACT_DIR)
+	$(call QUIET_INSTALL,bpf_reactors)$(INSTALL) bpf_reactors/*.o -m 644 $(DESTDIR)$(BPF_REACT_DIR)
+endif
 
 .PHONY: doc doc_clean doc_install
 doc:
diff --git a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
index 12f0f78b9c9d..a37d11fef6f1 100644
--- a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
+++ b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
@@ -17,7 +17,6 @@
 #include "bpf_atomic.h"
 
 /* BPF monitors don't support these */
-#define rv_react(...) do {} while (0)
 #define trace_rv_retries_error(...) do {} while (0)
 #define pr_warn(fmt, ...) bpf_printk(fmt, ##__VA_ARGS__)
 #define rv_monitoring_on() 1
@@ -75,8 +74,15 @@ static const struct rv_trace_entry __used *_btf_trace;
 
 static inline void da_monitor_reset(struct da_monitor *da_mon);
 
-/* BPF monitors do not use reactors */
-static inline void react(enum states curr_state, enum events event) { }
+__noinline __weak void bpf_rv_react(char *msg) { }
+
+#define rv_react(mon, fmt, ...)							  \
+	({									  \
+		char ___msg[256];						  \
+										  \
+		if (BPF_SNPRINTF(___msg, sizeof(___msg), fmt, ##__VA_ARGS__) > 0) \
+			bpf_rv_react(___msg);					  \
+	})
 
 /*
  * BPF monitor implementations
diff --git a/tools/verification/rv/bpf_reactors/.gitignore b/tools/verification/rv/bpf_reactors/.gitignore
new file mode 100644
index 000000000000..e5add9141ef5
--- /dev/null
+++ b/tools/verification/rv/bpf_reactors/.gitignore
@@ -0,0 +1,2 @@
+*.d
+vmlinux.h
diff --git a/tools/verification/rv/bpf_reactors/panic.c b/tools/verification/rv/bpf_reactors/panic.c
new file mode 100644
index 000000000000..775f91a300f0
--- /dev/null
+++ b/tools/verification/rv/bpf_reactors/panic.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+void bpf_rv_react(char *msg)
+{
+	struct pt_regs regs = { 0 };
+
+	crash_kexec(&regs);
+}
+
+char LICENSE[] SEC("license") = "GPL";
+static char DESCRIPTION[] SEC(".rodata.description") =
+	"panic the system if an exception is found.";
diff --git a/tools/verification/rv/bpf_reactors/printk.c b/tools/verification/rv/bpf_reactors/printk.c
new file mode 100644
index 000000000000..580410d2a1fc
--- /dev/null
+++ b/tools/verification/rv/bpf_reactors/printk.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+void bpf_rv_react(char *msg)
+{
+	bpf_printk("%s", msg);
+}
+
+char LICENSE[] SEC("license") = "GPL";
+static char DESCRIPTION[] SEC(".rodata.description") =
+	"prints the exception msg to the trace buffer.";
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index d43ee75d3904..35bd1f40a109 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -5,6 +5,7 @@
  * Copyright (C) 2026 Red Hat Inc, Gabriele Monaco <gmonaco@redhat.com>
  */
 
+#define _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -14,6 +15,7 @@
 #include <libgen.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <sys/mman.h>
 #include <bpf/libbpf.h>
 #include <bpf/bpf.h>
 #include <bpf/btf.h>
@@ -36,6 +38,7 @@ static char bpf_base_paths[][MAX_PATH] = {
 #define MAX_ENUMS 64
 #define MAX_LINKS 16
 #define PROG_ENABLE_MON "enable_monitor"
+#define BPF_REACTOR "bpf_rv_react"
 #define RV_TRACE_STRUCT "rv_trace_entry"
 #define RV_TRACE_TYPE_ENUM "rv_trace_type"
 
@@ -676,12 +679,31 @@ static struct ring_buffer *bpf_setup_ring_buffer(struct bpf_object *obj,
 	return rb;
 }
 
+static void list_reactor_action(const char *name, struct bpf_object *obj)
+{
+	const struct btf *btf = bpf_object__btf(obj);
+
+	if (btf__find_by_name_kind(btf, BPF_REACTOR, BTF_KIND_FUNC) >= 0)
+		fprintf(stderr, "%s ", name);
+}
+
+/*
+ * list_reactors_from_path - list reactors from a specific base path
+ */
+static void list_reactors_from_path(const char *base_path)
+{
+	bpf_object_iterate_path(base_path, "bpf_reactors", list_reactor_action);
+}
+
 /*
  * bpf_usage_print_reactors - print available BPF reactors
  */
 void bpf_usage_print_reactors(void)
 {
-	fprintf(stderr, "  available BPF reactors: nop\n");
+	fprintf(stderr, "  available BPF reactors: nop ");
+	for (int i = 0; bpf_base_paths[i][0]; i++)
+		list_reactors_from_path(bpf_base_paths[i]);
+	fprintf(stderr, "\n");
 }
 
 /*
@@ -720,6 +742,7 @@ static struct bpf_object *open_bpf_monitor(const char *path, struct bpf_monitor_
 	int res;
 
 	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.object_name = ctx->monitor_name,
 		/* Define statically as arch is known, Kconfig may not be available */
 #ifdef __x86_64__
 		.kconfig = "CONFIG_X86_64=y\n",
@@ -795,6 +818,65 @@ static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
 	return enable_mon_fd;
 }
 
+static int find_bpf_reactor(const char *reactor_name, char *path_out, size_t path_len)
+{
+	return find_bpf_file("bpf_reactors", reactor_name, path_out, path_len);
+}
+
+/*
+ * link_bpf_reactor - link the reactor function to the monitor
+ *
+ * Reactors are objects defining the BPF_REACTOR function, link that over the
+ * weak definition present in the monitor and return a file descriptor to the
+ * final linked object in memory.
+ *
+ * Returns memfd of final object on success, -1 on error.
+ */
+static int link_bpf_reactor(const char *monitor_path, const char *reactor_path)
+{
+	struct bpf_linker *linker = NULL;
+	int memfd = -1;
+	int err = 0;
+
+	memfd = memfd_create("linked_bpf", 0);
+	if (memfd < 0) {
+		err_msg("bpf: failed to create memfd: %s\n", strerror(errno));
+		return -1;
+	}
+
+	linker = bpf_linker__new_fd(memfd, NULL);
+	if (!linker) {
+		err_msg("bpf: failed to create BPF linker\n");
+		goto out;
+	}
+
+	err = bpf_linker__add_file(linker, monitor_path, NULL);
+	if (err) {
+		err_msg("bpf: failed to add monitor file to linker: %s\n", strerror(-err));
+		goto out;
+	}
+
+	err = bpf_linker__add_file(linker, reactor_path, NULL);
+	if (err) {
+		err_msg("bpf: failed to add reactor file to linker: %s\n", strerror(-err));
+		goto out;
+	}
+
+	err = bpf_linker__finalize(linker);
+	if (err) {
+		err_msg("bpf: failed to finalize BPF linker: %s\n", strerror(-err));
+		goto out;
+	}
+
+	bpf_linker__free(linker);
+	return memfd;
+
+out:
+	bpf_linker__free(linker);
+	close(memfd);
+	return -1;
+}
+
 /*
  * bpf_run_monitor - load and run a BPF monitor
  *
@@ -808,6 +890,7 @@ int bpf_run_monitor(char *monitor_name, int argc, char **argv)
 	struct bpf_object *obj = NULL;
 	int res, link_count = 0, enable_mon_fd, retval = -1;
 	char monitor_path[MAX_PATH];
+	int memfd = -1;
 
 	libbpf_set_print(libbpf_print_fn);
 	bpf_fill_base_paths();
@@ -830,8 +913,25 @@ int bpf_run_monitor(char *monitor_name, int argc, char **argv)
 
 	strncpy(ctx.monitor_name, monitor_name, sizeof(ctx.monitor_name) - 1);
 
+	if (config.reactor && strcmp(config.reactor, "nop")) {
+		char reactor_path[MAX_PATH];
+
+		if (!find_bpf_reactor(config.reactor, reactor_path, sizeof(reactor_path))) {
+			mon_usage(1, monitor_name,
+				  "bpf: failed to set %s reactor, is it available?",
+				  config.reactor);
+			goto cleanup;
+		}
+
+		memfd = link_bpf_reactor(monitor_path, reactor_path);
+		if (memfd < 0)
+			goto cleanup;
+
+		snprintf(monitor_path, sizeof(monitor_path), "/proc/self/fd/%d", memfd);
+	}
 
 	obj = open_bpf_monitor(monitor_path, &ctx);
+	close(memfd);
 	if (!obj)
 		goto cleanup;
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 13/15] verification/rvgen: Add support for BPF monitors
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (11 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 12/15] tools/rv: Add reactors support to " Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add the -b flag to rvgen monitor to generate a bpf monitor.
By default the code is generated in a folder with the same name as the
model just like in-kernel monitors, although only the source and header
are meaningful here. Passing -a moves the sources to
tools/verification/rv/bpf_monitors/ where they can be built directly.

Currently BPF monitors are only supported for DA.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rvgen/__main__.py          | 15 ++++++++---
 tools/verification/rvgen/rvgen/dot2c.py       | 15 ++++++++---
 tools/verification/rvgen/rvgen/dot2k.py       | 22 +++++++++++++---
 tools/verification/rvgen/rvgen/generator.py   | 26 ++++++++++++++-----
 .../rvgen/rvgen/templates/dot2k/main_bpf.c    | 21 +++++++++++++++
 5 files changed, 80 insertions(+), 19 deletions(-)
 create mode 100644 tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c

diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
index 246b43fa29f1..16ce9b512fbb 100644
--- a/tools/verification/rvgen/__main__.py
+++ b/tools/verification/rvgen/__main__.py
@@ -39,6 +39,8 @@ if __name__ == '__main__':
                                 help="Monitor specification file")
     monitor_parser.add_argument('-t', "--monitor_type", dest="monitor_type", required=True,
                                 help=f"Available options: {', '.join(Monitor.monitor_types.keys())}")
+    monitor_parser.add_argument('-b', "--bpf", dest="bpf", action="store_true",
+                                required=False, help="Generate a BPF monitor")
 
     container_parser = subparsers.add_parser("container", parents=[parent_parser])
     container_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
@@ -50,6 +52,9 @@ if __name__ == '__main__':
 
     params = parser.parse_args()
 
+    if params.subcmd == "monitor" and params.bpf and params.monitor_class != "da":
+        parser.error("BPF monitors (-b/--bpf) are only supported for deterministic automaton (-c da)")
+
     try:
         if params.subcmd == "monitor":
             print(f"Opening and parsing the specification file {params.spec}")
@@ -80,7 +85,9 @@ if __name__ == '__main__':
     print("Almost done, checklist")
     if params.subcmd == "monitor":
         print(f"  - Edit the {monitor.name}/{monitor.name}.c to add the instrumentation")
-        print(monitor.fill_tracepoint_tooltip())
-    print(monitor.fill_makefile_tooltip())
-    print(monitor.fill_kconfig_tooltip())
-    print(monitor.fill_monitor_tooltip())
+        if not params.bpf:
+            print(monitor.fill_tracepoint_tooltip())
+    if not params.subcmd == "monitor" or not params.bpf:
+        print(monitor.fill_makefile_tooltip())
+        print(monitor.fill_kconfig_tooltip())
+        print(monitor.fill_monitor_tooltip())
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index 22938ce1bf6c..5be9fe44a19b 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -111,10 +111,17 @@ class Dot2c(Automata):
         min_type = self.get_minimun_type()
         buff = []
         buff.append(f"struct {self.struct_automaton_def} {{")
-        buff.append(f"\tchar *state_names[state_max{self.enum_suffix}];")
-        buff.append(f"\tchar *event_names[event_max{self.enum_suffix}];")
-        if self.is_hybrid_automata():
-            buff.append(f"\tchar *env_names[env_max{self.enum_suffix}];")
+        if self.bpf:
+            # BPF struggles with non-fixed string pointers
+            buff.append(f"\tchar state_names[state_max{self.enum_suffix}][32];")
+            buff.append(f"\tchar event_names[event_max{self.enum_suffix}][32];")
+            if self.is_hybrid_automata():
+                buff.append(f"\tchar env_names[env_max{self.enum_suffix}][32];")
+        else:
+            buff.append(f"\tchar *state_names[state_max{self.enum_suffix}];")
+            buff.append(f"\tchar *event_names[event_max{self.enum_suffix}];")
+            if self.is_hybrid_automata():
+                buff.append(f"\tchar *env_names[env_max{self.enum_suffix}];")
         buff.append(f"\t{min_type} function[state_max{self.enum_suffix}][event_max{self.enum_suffix}];")
         buff.append(f"\t{min_type} initial_state;")
         buff.append(f"\tbool final_states[state_max{self.enum_suffix}];")
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index fd3254ea5b4d..6600bfa0f20f 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -30,14 +30,19 @@ class dot2k(Monitor, Dot2c):
         buff = [ self.monitor_type.upper() ]
         buff += self._fill_timer_type()
         if self.monitor_type == "per_obj":
-            buff.append("typedef /* XXX: define the target type */ *monitor_target;")
+            pad = "_bpf" if self.bpf else ""
+            buff.append(f"typedef /* XXX: define the target type */ *monitor_target{pad};")
         return "\n".join(buff)
 
     def fill_tracepoint_handlers_skel(self) -> str:
         buff = []
         buff += self._fill_hybrid_definitions()
         for event in self.events:
-            buff.append(f"static void handle_{event}(void *data, /* XXX: fill header */)")
+            if self.bpf:
+                buff.append("SEC(/* XXX: tracepoint or other probe */)")
+                buff.append(f"int BPF_PROG(handle_{event}, /* XXX: fill header */)")
+            else:
+                buff.append(f"static void handle_{event}(void *data, /* XXX: fill header */)")
             buff.append("{")
             handle = "handle_event"
             if self.is_start_event(event):
@@ -50,19 +55,28 @@ class dot2k(Monitor, Dot2c):
                 buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;")
                 buff.append(f"\tda_{handle}(p, {event}{self.enum_suffix});")
             elif self.monitor_type == "per_obj":
+                pad = "_bpf" if self.bpf else ""
                 buff.append("\tint id = /* XXX: how do I get the id? */;")
-                buff.append("\tmonitor_target t = /* XXX: how do I get t? */;")
+                buff.append(f"\tmonitor_target{pad} t = /* XXX: how do I get t? */;")
                 buff.append(f"\tda_{handle}(id, t, {event}{self.enum_suffix});")
             else:
                 buff.append(f"\tda_{handle}({event}{self.enum_suffix});")
+            if self.bpf:
+                buff.append("\treturn 0;")
             buff.append("}")
             buff.append("")
         if self.monitor_type == "per_obj":
             buff.append("/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */")
-            buff.append(f"static void handle_{self.cleanup_marker}(void *data, /* XXX: fill header */)")
+            if self.bpf:
+                buff.append("SEC(/* XXX: tracepoint or other probe */)")
+                buff.append(f"int BPF_PROG(handle_{self.cleanup_marker}, /* XXX: fill header */)")
+            else:
+                buff.append(f"static void handle_{self.cleanup_marker}(void *data, /* XXX: fill header */)")
             buff.append("{")
             buff.append("\tint id = /* XXX: how do I get the id? */;")
             buff.append("\tda_destroy_storage(id);")
+            if self.bpf:
+                buff.append("\treturn 0;")
             buff.append("}")
             buff.append("")
         return '\n'.join(buff)
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 45e2bab26cb5..b7985ff84521 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -11,18 +11,22 @@ from pathlib import Path
 
 class RVGenerator:
     rv_dir = "kernel/trace/rv"
+    rv_tool_dir = "../../../tools/verification/rv/bpf_monitors/"
 
     def __init__(self, extra_params={}):
         self.name = extra_params.get("model_name")
         self.parent = extra_params.get("parent")
+        self.bpf = extra_params.get("bpf")
         self.abs_template_dir = \
             Path(__file__).resolve().parent / "templates" / self.template_dir
-        self.main_c = self._read_template_file("main.c")
+        self.main_c = self._read_template_file("main.c" if not self.bpf
+                                               else "main_bpf.c")
         self.kconfig = self._read_template_file("Kconfig")
         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.rv_tool_dir = (Path(self.rv_dir) / self.rv_tool_dir).resolve()
 
     def _fill_rv_kernel_dir(self):
         # find the kernel tree root relative to this file's location
@@ -191,6 +195,9 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
     def __create_directory(self):
         path = Path(self.name)
         if self.auto_patch:
+            if self.bpf:
+                # no directory for BPF monitors
+                return
             path = Path(self.rv_dir) / "monitors" / path
         path.mkdir(exist_ok=True)
 
@@ -202,6 +209,8 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
         path = Path(self.name) / file_name
         if self.auto_patch:
             path = Path(self.rv_dir) / "monitors" / self.name / file_name
+            if self.bpf:
+                path = Path(self.rv_tool_dir) / file_name
         self.__write_file(path, content)
 
     def print_files(self):
@@ -216,8 +225,9 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
         path = f"{self.name}.h"
         self._create_file(path, model_h)
 
-        kconfig = self.fill_kconfig()
-        self._create_file("Kconfig", kconfig)
+        if not self.bpf:
+            kconfig = self.fill_kconfig()
+            self._create_file("Kconfig", kconfig)
 
 
 class Monitor(RVGenerator):
@@ -225,7 +235,8 @@ class Monitor(RVGenerator):
 
     def __init__(self, extra_params={}):
         super().__init__(extra_params)
-        self.trace_h = self._read_template_file("trace.h")
+        if not self.bpf:
+            self.trace_h = self._read_template_file("trace.h")
 
     def fill_trace_h(self):
         trace_h = self.trace_h
@@ -245,6 +256,7 @@ class Monitor(RVGenerator):
 
     def print_files(self):
         super().print_files()
-        trace_h = self.fill_trace_h()
-        path = f"{self.name}_trace.h"
-        self._create_file(path, trace_h)
+        if not self.bpf:
+            trace_h = self.fill_trace_h()
+            path = f"{self.name}_trace.h"
+            self._create_file(path, trace_h)
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c b/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
new file mode 100644
index 000000000000..1a83f3a2159a
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_%%MONITOR_TYPE%%
+#include "%%MODEL_NAME%%.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.
+ */
+%%TRACEPOINT_HANDLERS_SKEL%%
+static struct rv_monitor rv_this = {
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "%%DESCRIPTION%%";
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (12 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 13/15] verification/rvgen: Add support for " Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  2026-10-01 15:20 ` [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add selftest cases for the rv userspace tool BPF monitors, test BPF
monitors listing and functionality, including traces and reactors.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/tests/rv_list.t |  6 ++-
 tools/verification/rv/tests/rv_mon.t  | 61 ++++++++++++++++++++++++++-
 2 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/tools/verification/rv/tests/rv_list.t b/tools/verification/rv/tests/rv_list.t
index 201af33a52cc..3c94f2af2109 100644
--- a/tools/verification/rv/tests/rv_list.t
+++ b/tools/verification/rv/tests/rv_list.t
@@ -6,6 +6,8 @@ test_begin
 set_timeout 30s
 
 RVDIR=/sys/kernel/tracing/rv/
+# only works when running in the kernel tree
+RVTOOL=$(dirname "$RV")
 
 # Help and basic tests
 check "verify help page" \
@@ -17,13 +19,15 @@ check "verify list subcommand help" \
 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')
+all_bpf=$(find "$RVTOOL/bpf_monitors" -name "*.o" -exec basename \{} .o \; | 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}"
+line_bpf="\($all_bpf\)${description_state}"
 
 # List monitors and containers
 check "list all monitors" \
-	"$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\)$"
+	"$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\|$line_bpf\)$"
 
 check_if_exists "list container" \
 	"$RV list sched" "$RVDIR/monitors/sched" \
diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t
index cbc346c74c71..8e14471661c7 100644
--- a/tools/verification/rv/tests/rv_mon.t
+++ b/tools/verification/rv/tests/rv_mon.t
@@ -6,6 +6,7 @@ test_begin
 set_timeout 30s
 
 RVDIR=/sys/kernel/tracing/rv/
+RVTOOL=$(dirname "$RV")
 
 # Help and basic tests
 check "verify mon subcommand help" \
@@ -23,13 +24,34 @@ 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 "invalid BPF reactor name" \
+	"$RV mon nohz -r invalid" 1 "failed to set invalid reactor, is it available?"
+
+check "invalid BPF reactor name check available" \
+	"$RV mon nohz -r invalid" 1 "available BPF reactors: nop [a-z]\+" \
+	"available reactors:"
+
 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
+[ -n "$TEST_COUNT" ] && echo 0 > $RVDIR/monitors/wwnr/enable
+
+fi
+
+if [ -f "$RVTOOL/bpf_monitors/tqueue.o" ]; then
+
+[ -n "$TEST_COUNT" ] && { $RV mon tqueue & disown ; tmp=$! ; sleep 1 ; }
+check "already enabled BPF monitor returns error" \
+	"$RV mon tqueue" 1 "monitor tqueue (BPF) is already enabled"
+
+[ -n "$TEST_COUNT" ] && { kill -9 "$tmp" && sleep 1 ; }
+set_expected_timeout 1s
+
+check "crashed BPF monitor does not leak resources" \
+	"$RV mon tqueue" 0 "" "monitor tqueue (BPF) is already enabled"
 
 fi
 
@@ -40,6 +62,12 @@ set_expected_timeout 2s
 check_if_exists "run the monitor without parameters" \
 	"$RV mon wwnr" "$RVDIR/monitors/wwnr" "" "."
 
+check_if_exists "run a BPF monitor without parameters" \
+	"$RV mon nohz" "$RVTOOL/bpf_monitors/nohz.o" "" "."
+
+check_if_exists "run a per-task BPF monitor without parameters" \
+	"$RV mon tqueue" "$RVTOOL/bpf_monitors/tqueue.o" "" "."
+
 check_if_exists "run the monitor as verbose" \
 	"$RV mon wwnr -v" "$RVDIR/monitors/wwnr" \
 	"my pid is \$pid" "\(event\|error\)"
@@ -60,6 +88,20 @@ 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\]"
 
+TRACE=/sys/kernel/tracing/trace
+
+[ -n "$TEST_COUNT" ] && echo -n > $TRACE
+check_if_exists "run a BPF monitor with a reactor" \
+	"$RV mon nohz -r printk && cat $TRACE" "$RVTOOL/bpf_monitors/nohz.o" \
+	"rv: monitor nohz does not allow event [a-z_]\+ on state [a-z_]\+"
+
+# Give some time for maps from previous run to be cleaned up
+[ -n "$TEST_COUNT" ] && sleep 1
+[ -n "$TEST_COUNT" ] && echo -n > $TRACE
+check_if_exists "run BPF monitors with nop reactor" \
+	"$RV mon nohz -r nop && cat $TRACE" "$RVTOOL/bpf_monitors/nohz.o" \
+	"" "rv: monitor nohz does not allow event"
+
 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$"
@@ -92,4 +134,21 @@ 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\)"
 
+check_if_exists "run per-task BPF monitor with tracing" \
+	"$RV mon tqueue -t" "$RVTOOL/bpf_monitors/tqueue.o" \
+	"$header" "$trace_task_self" "\($header\|$trace_task\)"
+
+check_if_exists "run per-task BPF monitor tracing also self" \
+	"$RV mon tqueue -t -s" "$RVTOOL/bpf_monitors/tqueue.o" \
+	"$trace_task_self" "" "\($header\|$trace_task\)"
+
+check_if_exists "run per-cpu BPF monitor with tracing" \
+	"$RV mon nohz -t" "$RVTOOL/bpf_monitors/nohz.o" \
+	"$header" "$trace_cpu_self" "\($header\|$trace_cpu\)"
+
+# This is unstable, we may never see events from self
+#check_if_exists "run per-cpu BPF monitor tracing also self" \
+#	"$RV mon nohz -t -s" "$RVTOOL/bpf_monitors/nohz.o" \
+#	"$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+
 test_end
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b
  2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
                   ` (13 preceding siblings ...)
  2026-10-01 15:20 ` [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
  14 siblings, 0 replies; 17+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add selftest cases for BPF monitors generation.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.c      | 35 +++++++++++++
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.h      | 47 ++++++++++++++++++
 .../tests/golden/da_bpf_obj/da_bpf_obj.c      | 49 +++++++++++++++++++
 .../tests/golden/da_bpf_obj/da_bpf_obj.h      | 47 ++++++++++++++++++
 .../verification/rvgen/tests/rvgen_monitor.t  | 11 +++++
 5 files changed, 189 insertions(+)
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
 create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h

diff --git a/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
new file mode 100644
index 000000000000..5a652ce9b461
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "da_bpf_cpu.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.
+ */
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_1, /* XXX: fill header */)
+{
+	da_handle_event(event_1_da_bpf_cpu);
+	return 0;
+}
+
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_2, /* XXX: fill header */)
+{
+	/* XXX: validate that this event always leads to the initial state */
+	da_handle_start_event(event_2_da_bpf_cpu);
+	return 0;
+}
+
+static struct rv_monitor rv_this = {
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "auto-generated";
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
new file mode 100644
index 000000000000..fd8125118d81
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_bpf_cpu automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_bpf_cpu
+
+enum states_da_bpf_cpu {
+	state_a_da_bpf_cpu,
+	state_b_da_bpf_cpu,
+	state_max_da_bpf_cpu,
+};
+
+#define INVALID_STATE state_max_da_bpf_cpu
+
+enum events_da_bpf_cpu {
+	event_1_da_bpf_cpu,
+	event_2_da_bpf_cpu,
+	event_max_da_bpf_cpu,
+};
+
+struct automaton_da_bpf_cpu {
+	char state_names[state_max_da_bpf_cpu][32];
+	char event_names[event_max_da_bpf_cpu][32];
+	unsigned char function[state_max_da_bpf_cpu][event_max_da_bpf_cpu];
+	unsigned char initial_state;
+	bool final_states[state_max_da_bpf_cpu];
+};
+
+static const struct automaton_da_bpf_cpu automaton_da_bpf_cpu = {
+	.state_names = {
+		"state_a",
+		"state_b",
+	},
+	.event_names = {
+		"event_1",
+		"event_2",
+	},
+	.function = {
+		{       state_b_da_bpf_cpu,       state_a_da_bpf_cpu },
+		{            INVALID_STATE,       state_a_da_bpf_cpu },
+	},
+	.initial_state = state_a_da_bpf_cpu,
+	.final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
new file mode 100644
index 000000000000..a7d465f84f05
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+typedef /* XXX: define the target type */ *monitor_target_bpf;
+#include "da_bpf_obj.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.
+ */
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_1, /* XXX: fill header */)
+{
+	int id = /* XXX: how do I get the id? */;
+	monitor_target_bpf t = /* XXX: how do I get t? */;
+	da_handle_event(id, t, event_1_da_bpf_obj);
+	return 0;
+}
+
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_2, /* XXX: fill header */)
+{
+	/* XXX: validate that this event always leads to the initial state */
+	int id = /* XXX: how do I get the id? */;
+	monitor_target_bpf t = /* XXX: how do I get t? */;
+	da_handle_start_event(id, t, event_2_da_bpf_obj);
+	return 0;
+}
+
+/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_obj_cleanup, /* XXX: fill header */)
+{
+	int id = /* XXX: how do I get the id? */;
+	da_destroy_storage(id);
+	return 0;
+}
+
+static struct rv_monitor rv_this = {
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "auto-generated";
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h
new file mode 100644
index 000000000000..385006098049
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_bpf_obj automaton
+ * For further information about this format, see kernel documentation:
+ *   Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_bpf_obj
+
+enum states_da_bpf_obj {
+	state_a_da_bpf_obj,
+	state_b_da_bpf_obj,
+	state_max_da_bpf_obj,
+};
+
+#define INVALID_STATE state_max_da_bpf_obj
+
+enum events_da_bpf_obj {
+	event_1_da_bpf_obj,
+	event_2_da_bpf_obj,
+	event_max_da_bpf_obj,
+};
+
+struct automaton_da_bpf_obj {
+	char state_names[state_max_da_bpf_obj][32];
+	char event_names[event_max_da_bpf_obj][32];
+	unsigned char function[state_max_da_bpf_obj][event_max_da_bpf_obj];
+	unsigned char initial_state;
+	bool final_states[state_max_da_bpf_obj];
+};
+
+static const struct automaton_da_bpf_obj automaton_da_bpf_obj = {
+	.state_names = {
+		"state_a",
+		"state_b",
+	},
+	.event_names = {
+		"event_1",
+		"event_2",
+	},
+	.function = {
+		{       state_b_da_bpf_obj,       state_a_da_bpf_obj },
+		{            INVALID_STATE,       state_a_da_bpf_obj },
+	},
+	.initial_state = state_a_da_bpf_obj,
+	.final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/rvgen_monitor.t b/tools/verification/rvgen/tests/rvgen_monitor.t
index 5f2562600bad..3d71685a7ad5 100644
--- a/tools/verification/rvgen/tests/rvgen_monitor.t
+++ b/tools/verification/rvgen/tests/rvgen_monitor.t
@@ -47,6 +47,17 @@ 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"
 
+# BPF monitor test
+check_and_compare_folder "DA BPF per_cpu" \
+	"$RVGEN monitor -b -c da -s tests/specs/test_da.dot -t per_cpu -n da_bpf_cpu" \
+	"da_bpf_cpu" "Edit the da_bpf_cpu/da_bpf_cpu.c to add the instrumentation" \
+	"Edit kernel/trace/rv/Makefile"
+
+check_and_compare_folder "DA BPF per_obj" \
+	"$RVGEN monitor -b -c da -s tests/specs/test_da.dot -t per_obj -n da_bpf_obj" \
+	"da_bpf_obj" "Edit the da_bpf_obj/da_bpf_obj.c to add the instrumentation" \
+	"Edit kernel/trace/rv/Kconfig"
+
 # Error handling tests
 check "missing required spec argument" \
 	"$RVGEN monitor -c da -t per_cpu" 2 \
-- 
2.55.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points
  2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
@ 2026-10-01 15:49   ` Peter Zijlstra
  0 siblings, 0 replies; 17+ messages in thread
From: Peter Zijlstra @ 2026-10-01 15:49 UTC (permalink / raw)
  To: Gabriele Monaco
  Cc: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Masami Hiramatsu, Ingo Molnar, Nam Cao, K Prateek Nayak,
	Alexei Starovoitov, Wen Yang, Tobias Schaffner, Viktor Malik

On Thu, Oct 01, 2026 at 05:20:28PM +0200, Gabriele Monaco wrote:
> From: Nam Cao <namcao@linutronix.de>
> 
> Add trace points into enqueue_task() and dequeue_task().
> 
> Signed-off-by: Nam Cao <namcao@linutronix.de>
> Suggested-by: Peter Zijlstra <peterz@infradead.org>

Obviously I don't remember anything at all. But also, you 'forgot' to
send me the rest of the patches which might hold a clue. As is I'm
clueless as to why we want this, since the Changelog also offers none.

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-10-01 15:49 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
2026-10-01 15:49   ` Peter Zijlstra
2026-10-01 15:20 ` [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 04/15] rv: Cast result of model_get_*_name() Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 10/15] tools/rv: Add BPF monitors Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 12/15] tools/rv: Add reactors support to " Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 13/15] verification/rvgen: Add support for " Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®