bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 00/20] rv: Add support for BPF monitors
@ 2026-08-31  9:05 Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points Gabriele Monaco
                   ` (20 more replies)
  0 siblings, 21 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf
  Cc: Gabriele Monaco, Steven Rostedt, 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 using the struct_ops framework to integrate
as much as possible with in-kernel monitor. After registering, they
look like standard monitors in the tracefs, although to properly enable
them we need to load handlers from userspace and this is done by the rv
tool. BPF monitors cannot generate tracepoints so the rv -t for those
monitors is reading events from a ringbuffer.

Selftests (make -C tools/verification/rv check) are present to validate
and demonstrate the usage, which boils down to:

  rv bpf register          # load struct_ops and maps for all monitors
  rv list                  # unmodified, list BPF monitors transparently
  rv mon -t <mon>          # run the monitor with tracing (ringbuffer)
  rv mon -r printk <mon>   # run the monitor with a reactor
  rv bpf unregister        # remove all BPF monitors

Patch 1 adds tracepoints required in a later monitor example.
Patch 2 improves the rv tool selftest reporting.
Patches 3-9 prepare and add support for BPF monitors in the kernel.
Patch 10 adds a feature check for tools/build
Patch 11-14 prepare and add support for BPF monitors in the rv tool.
Patch 15-17 prepare and include BPF monitors examples
Patch 18 adds support for generating BPF monitors in rvgen
Patch 19-20 adds BPF selftests (make check) for the rv and rvgen tools

To: linux-trace-kernel@vger.kernel.org
To: bpf@vger.kernel.org
Cc: Steven Rostedt <rostedt@goodmis.org>
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 (19):
  tools/rv: Skip empty pid error in selftest if command failed
  rv: Refactor da_trace() functions to get strings internally
  rv: Use static arrays for rv_monitor name and description
  rv: Add in-kernel support for BPF monitors
  rv: Add rv_get_monitor_by_name()
  rv: Add reactors support to BPF monitors
  rv: Cast result of model_get_*_name()
  rv: Handle unregistered monitors safely in tracefs
  tools/build: Add a feature test for bpftool-btf
  tools/rv: Move argument parsing from in_kernel to utils
  tools/rv: Export functionality for in_kernel monitors
  tools/rv: Implement BPF monitor loading and tracing
  tools/rv: Implement BPF monitor registration logic
  tools/rv: Copy stripped bpf_atomic.h from libarena
  tools/rv: Add BPF monitors
  tools/rv: Define CONFIG_X86_64 statically for BPF monitors
  verification/rvgen: Add support for BPF monitors
  tools/rv: Add selftest for rv bpf
  verification/rvgen: Add selftest for rvgen -b

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

 include/linux/rv.h                            |    9 +-
 include/rv/automata.h                         |    4 +-
 include/rv/da_monitor.h                       |   67 +-
 include/trace/events/sched.h                  |    8 +
 kernel/sched/core.c                           |   12 +-
 kernel/sched/sched.h                          |    2 +
 kernel/trace/rv/Kconfig                       |   10 +
 kernel/trace/rv/Makefile                      |    1 +
 kernel/trace/rv/rv.c                          |   68 +-
 kernel/trace/rv/rv.h                          |    5 +-
 kernel/trace/rv/rv_bpf.c                      |  174 +++
 kernel/trace/rv/rv_reactors.c                 |    6 +
 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                |   51 +-
 tools/verification/rv/Makefile.config         |   49 +
 tools/verification/rv/Makefile.rv             |    5 +
 tools/verification/rv/bpf_monitors/.gitignore |    2 +
 .../verification/rv/bpf_monitors/bpf_atomic.h |  105 ++
 .../rv/bpf_monitors/da_monitor_bpf.h          |  400 +++++++
 tools/verification/rv/bpf_monitors/nohz.c     |   47 +
 tools/verification/rv/bpf_monitors/nohz.h     |   49 +
 tools/verification/rv/bpf_monitors/tqueue.c   |   35 +
 tools/verification/rv/bpf_monitors/tqueue.h   |   47 +
 tools/verification/rv/include/bpf_monitor.h   |   21 +
 tools/verification/rv/include/in_kernel.h     |    5 +
 tools/verification/rv/include/utils.h         |   17 +-
 tools/verification/rv/src/Build               |    5 +
 tools/verification/rv/src/bpf_monitor.c       | 1025 +++++++++++++++++
 tools/verification/rv/src/in_kernel.c         |  205 ++--
 tools/verification/rv/src/rv.c                |   10 +-
 tools/verification/rv/src/utils.c             |   95 +-
 tools/verification/rv/tests/rv_bpf.t          |  104 ++
 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    |   26 +
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.c      |   40 +
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.h      |   47 +
 .../tests/golden/da_bpf_obj/da_bpf_obj.c      |   54 +
 .../tests/golden/da_bpf_obj/da_bpf_obj.h      |   47 +
 .../verification/rvgen/tests/rvgen_monitor.t  |   11 +
 tools/verification/tests/engine.sh            |    5 +-
 46 files changed, 2770 insertions(+), 220 deletions(-)
 create mode 100644 kernel/trace/rv/rv_bpf.c
 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/include/bpf_monitor.h
 create mode 100644 tools/verification/rv/src/bpf_monitor.c
 create mode 100644 tools/verification/rv/tests/rv_bpf.t
 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: cee9395acd8043be0644b25c34bfa86623f2b935
-- 
2.55.0


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

* [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:32   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 02/20] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
                   ` (19 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 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, 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 f78275192036..8561ca324943 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_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_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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 02/20] tools/rv: Skip empty pid error in selftest if command failed
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 03/20] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
                   ` (18 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 03/20] rv: Refactor da_trace() functions to get strings internally
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 02/20] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 04/20] rv: Use static arrays for rv_monitor name and description Gabriele Monaco
                   ` (17 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 04/20] rv: Use static arrays for rv_monitor name and description
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (2 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 03/20] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 05/20] rv: Add in-kernel support for BPF monitors Gabriele Monaco
                   ` (16 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco, Masami Hiramatsu
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Name and description in struct rv_monitor are currently defined as
pointers, which are assigned during initialisation. This means they are
not clearly in the structure and this can be a problem if the struct
needs to be reused in other contexts (like BPF's struct_opts).

Make the fields static arrays with defined size, limit the description
to 128 bytes (much longer than any current description). This slightly
increases the kernel size as the full space for the strings is reserved,
but that's a minor increase as each monitor has a single instance of
struct rv_monitor, total increase around 100B per monitor, uncompressed.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 include/linux/rv.h   |  9 ++++++---
 kernel/trace/rv/rv.c | 10 +++++-----
 kernel/trace/rv/rv.h |  3 ---
 3 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/include/linux/rv.h b/include/linux/rv.h
index 541ba404926a..a3a332b49747 100644
--- a/include/linux/rv.h
+++ b/include/linux/rv.h
@@ -7,7 +7,10 @@
 #ifndef _LINUX_RV_H
 #define _LINUX_RV_H
 
-#define MAX_DA_NAME_LEN			32
+#define MAX_RV_MONITOR_NAME_SIZE	32
+#define MAX_RV_REACTOR_NAME_SIZE	32
+#define MAX_RV_DESCRIPTION_SIZE		128
+
 #define MAX_DA_RETRY_RACING_EVENTS	3
 
 #define RV_MON_GLOBAL   0
@@ -137,8 +140,8 @@ struct rv_reactor {
 #endif
 
 struct rv_monitor {
-	const char		*name;
-	const char		*description;
+	char			name[MAX_RV_MONITOR_NAME_SIZE];
+	char			description[MAX_RV_DESCRIPTION_SIZE];
 	bool			enabled;
 	int			(*enable)(void);
 	void			(*disable)(void);
diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index 29f155c6968b..f77c5865f41a 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -392,11 +392,11 @@ static ssize_t monitor_desc_read_data(struct file *filp, char __user *user_buf,
 				      loff_t *ppos)
 {
 	struct rv_monitor *mon = filp->private_data;
-	char buff[256];
+	char buff[MAX_RV_DESCRIPTION_SIZE + 2];
 
 	memset(buff, 0, sizeof(buff));
 
-	snprintf(buff, sizeof(buff), "%s\n", mon->description);
+	snprintf(buff, sizeof(buff), "%.*s\n", MAX_RV_DESCRIPTION_SIZE, mon->description);
 
 	return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff) + 1);
 }
@@ -756,9 +756,9 @@ int rv_register_monitor(struct rv_monitor *monitor, struct rv_monitor *parent)
 	struct rv_monitor *r;
 	int retval = 0;
 
-	if (strlen(monitor->name) >= MAX_RV_MONITOR_NAME_SIZE) {
-		pr_info("Monitor %s has a name longer than %d\n", monitor->name,
-			MAX_RV_MONITOR_NAME_SIZE);
+	if (strnlen(monitor->name, MAX_RV_MONITOR_NAME_SIZE) == MAX_RV_MONITOR_NAME_SIZE) {
+		pr_info("Monitor %.*s has a name longer than %d\n", MAX_RV_MONITOR_NAME_SIZE,
+			monitor->name, MAX_RV_MONITOR_NAME_SIZE);
 		return -EINVAL;
 	}
 
diff --git a/kernel/trace/rv/rv.h b/kernel/trace/rv/rv.h
index 2c0f51ff9d5c..07512046e5cc 100644
--- a/kernel/trace/rv/rv.h
+++ b/kernel/trace/rv/rv.h
@@ -19,9 +19,6 @@ struct rv_interface {
 
 DEFINE_FREE(rv_remove, struct dentry *, if (_T) rv_remove(_T));
 
-#define MAX_RV_MONITOR_NAME_SIZE	32
-#define MAX_RV_REACTOR_NAME_SIZE	32
-
 extern struct mutex rv_interface_lock;
 extern struct list_head rv_monitors_list;
 
-- 
2.55.0


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

* [RFC PATCH 05/20] rv: Add in-kernel support for BPF monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (3 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 04/20] rv: Use static arrays for rv_monitor name and description Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 06/20] rv: Add rv_get_monitor_by_name() Gabriele Monaco
                   ` (15 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco, Masami Hiramatsu
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add support for BPF monitors via the struct_ops mechanism, which allows
BPF monitors to be loaded and appear almost like in-kernel modules.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/trace/rv/Kconfig  |  10 ++++
 kernel/trace/rv/Makefile |   1 +
 kernel/trace/rv/rv_bpf.c | 118 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+)
 create mode 100644 kernel/trace/rv/rv_bpf.c

diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index efa930f94ea4..be11e5c85a60 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -126,3 +126,13 @@ config RV_MONITORS_KUNIT_TEST
 	  unrelated KUnit test is running.
 
 	  If unsure, say N.
+
+config RV_MON_BPF_STRUCT_OPS
+	bool "BPF struct_ops support for RV monitors"
+	depends on RV && BPF_SYSCALL && BPF_JIT
+	help
+	  Enable BPF programs to register as Runtime Verification monitors
+	  using the BPF struct_ops mechanism. This allows BPF monitors to
+	  be managed through the standard RV interface in sysfs alongside
+	  kernel monitors.
+	  It has no performance impact if not used.
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index cdbf68c84f5a..635461395cba 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -26,3 +26,4 @@ obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
 obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
 obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
 obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
+obj-$(CONFIG_RV_MON_BPF_STRUCT_OPS) += rv_bpf.o
diff --git a/kernel/trace/rv/rv_bpf.c b/kernel/trace/rv/rv_bpf.c
new file mode 100644
index 000000000000..0450bcaf1b79
--- /dev/null
+++ b/kernel/trace/rv/rv_bpf.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF struct_ops support for Runtime Verification monitors
+ *
+ * Allows BPF programs to register as RV monitors via struct_ops.
+ * BPF monitors appear in /sys/kernel/tracing/rv/ alongside kernel monitors.
+ *
+ * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco <gmonaco@redhat.com>
+ */
+
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/filter.h>
+#include <linux/rv.h>
+
+static int bpf_rv_monitor_init(struct btf *btf)
+{
+	return 0;
+}
+
+static int bpf_rv_monitor_init_member(const struct btf_type *t,
+				      const struct btf_member *member,
+				      void *kdata, const void *udata)
+{
+	const struct rv_monitor *umon = udata;
+	struct rv_monitor *kmon = kdata;
+	u32 moff = __btf_member_bit_offset(t, member) / 8;
+	int ret;
+
+	switch (moff) {
+	case offsetof(struct rv_monitor, name):
+		ret = bpf_obj_name_cpy(kmon->name, umon->name,
+				       sizeof(kmon->name));
+		if (ret < 0)
+			return ret;
+		if (ret == 0)
+			return -EINVAL;
+		return 1;
+	case offsetof(struct rv_monitor, description):
+		ret = strscpy(kmon->description, umon->description);
+		if (ret < 0)
+			return ret;
+		if (ret == 0)
+			return -EINVAL;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int bpf_rv_monitor_reg(void *kdata, struct bpf_link *link)
+{
+	struct rv_monitor *mon = kdata;
+
+	pr_info("rv: Registering BPF monitor %s\n", mon->name);
+	return rv_register_monitor(mon, NULL);
+}
+
+static void bpf_rv_monitor_unreg(void *kdata, struct bpf_link *link)
+{
+	struct rv_monitor *mon = kdata;
+
+	pr_info("rv: Unregistering BPF monitor %s\n", mon->name);
+	rv_unregister_monitor(mon);
+}
+
+static int bpf_rv_monitor_validate(void *kdata)
+{
+	struct rv_monitor *mon = kdata;
+
+	if (!mon->enable)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct bpf_verifier_ops bpf_rv_monitor_verifier_ops = {
+	.get_func_proto = bpf_base_func_proto,
+	.is_valid_access = NULL,
+};
+
+static int rv_ops__mon_enable(void)
+{
+	return 0;
+}
+
+static void rv_ops__mon_disable(void) { }
+
+static void rv_ops__mon_reset(void) { }
+
+static struct rv_monitor __bpf_ops_rv_monitor = {
+	.name = "rv_monitor",
+	.description = "stub BPF monitor.",
+	.enable = rv_ops__mon_enable,
+	.disable = rv_ops__mon_disable,
+	.reset = rv_ops__mon_reset,
+	.enabled = 0,
+};
+
+static struct bpf_struct_ops bpf_rv_monitor_ops = {
+	.verifier_ops = &bpf_rv_monitor_verifier_ops,
+	.init = bpf_rv_monitor_init,
+	.init_member = bpf_rv_monitor_init_member,
+	.reg = bpf_rv_monitor_reg,
+	.unreg = bpf_rv_monitor_unreg,
+	.validate = bpf_rv_monitor_validate,
+	.name = "rv_monitor",
+	.cfi_stubs = &__bpf_ops_rv_monitor,
+	.owner = THIS_MODULE,
+};
+
+static int __init bpf_rv_monitor_init_ops(void)
+{
+	return register_bpf_struct_ops(&bpf_rv_monitor_ops, rv_monitor);
+}
+late_initcall(bpf_rv_monitor_init_ops);
-- 
2.55.0


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

* [RFC PATCH 06/20] rv: Add rv_get_monitor_by_name()
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (4 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 05/20] rv: Add in-kernel support for BPF monitors Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:24   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 07/20] rv: Add reactors support to BPF monitors Gabriele Monaco
                   ` (14 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco, Masami Hiramatsu
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add a helper function to get the rv_monitor pointer for a registered
monitor given its name and add its first user in rv_register_monitor().

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/trace/rv/rv.c | 28 +++++++++++++++++++---------
 kernel/trace/rv/rv.h |  1 +
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index f77c5865f41a..33919c5fbd6a 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -142,6 +142,7 @@
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/rculist.h>
 
 #ifdef CONFIG_RV_MON_EVENTS
 #define CREATE_TRACE_POINTS
@@ -229,6 +230,18 @@ bool rv_is_container_monitor(struct rv_monitor *mon)
 	return next->parent == mon || !mon->enable;
 }
 
+struct rv_monitor *rv_get_monitor_by_name(const char *name)
+{
+	struct rv_monitor *m;
+
+	list_for_each_entry_rcu(m, &rv_monitors_list, list,
+				lockdep_is_held(&rv_interface_lock)) {
+		if (strcmp(name, m->name) == 0)
+			return m;
+	}
+	return NULL;
+}
+
 /*
  * This section collects the monitor/ files and folders.
  */
@@ -753,7 +766,6 @@ static void destroy_monitor_dir(struct rv_monitor *mon)
  */
 int rv_register_monitor(struct rv_monitor *monitor, struct rv_monitor *parent)
 {
-	struct rv_monitor *r;
 	int retval = 0;
 
 	if (strnlen(monitor->name, MAX_RV_MONITOR_NAME_SIZE) == MAX_RV_MONITOR_NAME_SIZE) {
@@ -764,11 +776,9 @@ int rv_register_monitor(struct rv_monitor *monitor, struct rv_monitor *parent)
 
 	guard(mutex)(&rv_interface_lock);
 
-	list_for_each_entry(r, &rv_monitors_list, list) {
-		if (strcmp(monitor->name, r->name) == 0) {
-			pr_info("Monitor %s is already registered\n", monitor->name);
-			return -EEXIST;
-		}
+	if (rv_get_monitor_by_name(monitor->name)) {
+		pr_info("Monitor %s is already registered\n", monitor->name);
+		return -EEXIST;
 	}
 
 	if (parent && rv_is_nested_monitor(parent)) {
@@ -785,9 +795,9 @@ int rv_register_monitor(struct rv_monitor *monitor, struct rv_monitor *parent)
 
 	/* keep children close to the parent for easier visualisation */
 	if (parent)
-		list_add(&monitor->list, &parent->list);
+		list_add_rcu(&monitor->list, &parent->list);
 	else
-		list_add_tail(&monitor->list, &rv_monitors_list);
+		list_add_tail_rcu(&monitor->list, &rv_monitors_list);
 
 	return 0;
 }
@@ -803,7 +813,7 @@ int rv_unregister_monitor(struct rv_monitor *monitor)
 	guard(mutex)(&rv_interface_lock);
 
 	rv_disable_monitor(monitor);
-	list_del(&monitor->list);
+	list_del_rcu(&monitor->list);
 	destroy_monitor_dir(monitor);
 
 	return 0;
diff --git a/kernel/trace/rv/rv.h b/kernel/trace/rv/rv.h
index 07512046e5cc..4d2c9cb284c9 100644
--- a/kernel/trace/rv/rv.h
+++ b/kernel/trace/rv/rv.h
@@ -27,6 +27,7 @@ int rv_disable_monitor(struct rv_monitor *mon);
 int rv_enable_monitor(struct rv_monitor *mon);
 bool rv_is_container_monitor(struct rv_monitor *mon);
 bool rv_is_nested_monitor(struct rv_monitor *mon);
+struct rv_monitor *rv_get_monitor_by_name(const char *name);
 
 #ifdef CONFIG_RV_REACTORS
 int reactor_populate_monitor(struct rv_monitor *mon, struct dentry *root);
-- 
2.55.0


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

* [RFC PATCH 07/20] rv: Add reactors support to BPF monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (5 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 06/20] rv: Add rv_get_monitor_by_name() Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 08/20] rv: Cast result of model_get_*_name() Gabriele Monaco
                   ` (13 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco, Masami Hiramatsu
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

The struct_ops system allows BPF monitors to be assigned reactors
transparently, but calling rv_react() is something the monitors should
do and BPF monitors cannot, as well as they cannot easily get a pointer
to the rv_monitor structure.

Add a bpf_rv_react kfunc that finds the monitor pointer given its name
and triggers a reaction. Since variadic functions are complicated in
BPF, this kfunc receives a plain string, that should be populated by the
caller with some sort of sprintf().

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/trace/rv/rv_bpf.c | 62 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 3 deletions(-)

diff --git a/kernel/trace/rv/rv_bpf.c b/kernel/trace/rv/rv_bpf.c
index 0450bcaf1b79..90ac7af1edff 100644
--- a/kernel/trace/rv/rv_bpf.c
+++ b/kernel/trace/rv/rv_bpf.c
@@ -13,7 +13,9 @@
 #include <linux/btf.h>
 #include <linux/btf_ids.h>
 #include <linux/filter.h>
+#include <linux/uaccess.h>
 #include <linux/rv.h>
+#include "rv.h"
 
 static int bpf_rv_monitor_init(struct btf *btf)
 {
@@ -111,8 +113,62 @@ static struct bpf_struct_ops bpf_rv_monitor_ops = {
 	.owner = THIS_MODULE,
 };
 
-static int __init bpf_rv_monitor_init_ops(void)
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_rv_react - trigger a reactor from BPF
+ * @name: monitor name
+ * @msg: pre-formatted message string
+ * @msg__sz: size of the msg buffer
+ *
+ * This kfunc allows BPF monitors to trigger reactors with a message.
+ * The message should be pre-formatted by the BPF program using bpf_snprintf.
+ */
+__bpf_kfunc void bpf_rv_react(char *name__str, char *msg, u32 msg__sz)
+{
+	struct rv_monitor *monitor;
+	char safe_msg[256];
+
+	if (msg__sz == 0)
+		return;
+	msg__sz = min_t(u32, msg__sz, sizeof(safe_msg));
+	if (strncpy_from_kernel_nofault(safe_msg, msg, msg__sz) < 0)
+		return;
+	safe_msg[msg__sz - 1] = '\0';
+
+	guard(rcu)();
+	monitor = rv_get_monitor_by_name(name__str);
+
+	if (monitor)
+		rv_react(monitor, "%s", safe_msg);
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(rv_kfunc_set_ids)
+BTF_ID_FLAGS(func, bpf_rv_react)
+BTF_KFUNCS_END(rv_kfunc_set_ids)
+
+static const struct btf_kfunc_id_set rv_kfunc_set = {
+	.owner = THIS_MODULE,
+	.set = &rv_kfunc_set_ids,
+};
+
+static int __init rv_monitor_init_bpf(void)
 {
-	return register_bpf_struct_ops(&bpf_rv_monitor_ops, rv_monitor);
+	int ret;
+
+	ret = register_bpf_struct_ops(&bpf_rv_monitor_ops, rv_monitor);
+	if (ret) {
+		pr_err("rv: Failed to register struct_ops (%pe)\n", ERR_PTR(ret));
+		return ret;
+	}
+
+	ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &rv_kfunc_set);
+	if (ret) {
+		pr_err("rv: Failed to register reactor kfunc (%pe)\n", ERR_PTR(ret));
+		return ret;
+	}
+	return 0;
 }
-late_initcall(bpf_rv_monitor_init_ops);
+late_initcall(rv_monitor_init_bpf);
-- 
2.55.0


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

* [RFC PATCH 08/20] rv: Cast result of model_get_*_name()
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (6 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 07/20] rv: Add reactors support to BPF monitors Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 09/20] rv: Handle unregistered monitors safely in tracefs Gabriele Monaco
                   ` (12 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 09/20] rv: Handle unregistered monitors safely in tracefs
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (7 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 08/20] rv: Cast result of model_get_*_name() Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:24   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 10/20] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
                   ` (11 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco, Masami Hiramatsu
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

With the introduction of pluggable BPF monitors, a monitor can be
dynamically unregistered while its tracefs files are still open by a
userspace process. This leaves a dangling pointer in the tracefs file
descriptor's private_data.

Validate that the monitor pointer still exists in the rv_monitors_list
before dereferencing it during read/write operations and return an error
otherwise.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 kernel/trace/rv/rv.c          | 32 +++++++++++++++++++++++++++++---
 kernel/trace/rv/rv.h          |  1 +
 kernel/trace/rv/rv_reactors.c |  6 ++++++
 3 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
index 33919c5fbd6a..6ddd3b92da1d 100644
--- a/kernel/trace/rv/rv.c
+++ b/kernel/trace/rv/rv.c
@@ -245,13 +245,31 @@ struct rv_monitor *rv_get_monitor_by_name(const char *name)
 /*
  * This section collects the monitor/ files and folders.
  */
+
+bool rv_is_monitor_registered(struct rv_monitor *mon)
+{
+	struct rv_monitor *m;
+
+	list_for_each_entry_rcu(m, &rv_monitors_list, list,
+				lockdep_is_held(&rv_interface_lock)) {
+		if (m == mon)
+			return true;
+	}
+	return false;
+}
+
 static ssize_t monitor_enable_read_data(struct file *filp, char __user *user_buf, size_t count,
 					loff_t *ppos)
 {
 	struct rv_monitor *mon = filp->private_data;
 	const char *buff;
 
-	buff = mon->enabled ? "1\n" : "0\n";
+	scoped_guard(rcu) {
+		if (!rv_is_monitor_registered(mon))
+			return -ENODEV;
+
+		buff = mon->enabled ? "1\n" : "0\n";
+	}
 
 	return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff)+1);
 }
@@ -384,6 +402,9 @@ static ssize_t monitor_enable_write_data(struct file *filp, const char __user *u
 
 	guard(mutex)(&rv_interface_lock);
 
+	if (!rv_is_monitor_registered(mon))
+		return -ENODEV;
+
 	if (val)
 		retval = rv_enable_monitor(mon);
 	else
@@ -407,9 +428,14 @@ static ssize_t monitor_desc_read_data(struct file *filp, char __user *user_buf,
 	struct rv_monitor *mon = filp->private_data;
 	char buff[MAX_RV_DESCRIPTION_SIZE + 2];
 
-	memset(buff, 0, sizeof(buff));
+	scoped_guard(rcu) {
+		if (!rv_is_monitor_registered(mon))
+			return -ENODEV;
 
-	snprintf(buff, sizeof(buff), "%.*s\n", MAX_RV_DESCRIPTION_SIZE, mon->description);
+		memset(buff, 0, sizeof(buff));
+		snprintf(buff, sizeof(buff), "%.*s\n", MAX_RV_DESCRIPTION_SIZE,
+			 mon->description);
+	}
 
 	return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff) + 1);
 }
diff --git a/kernel/trace/rv/rv.h b/kernel/trace/rv/rv.h
index 4d2c9cb284c9..6650ee9de308 100644
--- a/kernel/trace/rv/rv.h
+++ b/kernel/trace/rv/rv.h
@@ -28,6 +28,7 @@ int rv_enable_monitor(struct rv_monitor *mon);
 bool rv_is_container_monitor(struct rv_monitor *mon);
 bool rv_is_nested_monitor(struct rv_monitor *mon);
 struct rv_monitor *rv_get_monitor_by_name(const char *name);
+bool rv_is_monitor_registered(struct rv_monitor *mon);
 
 #ifdef CONFIG_RV_REACTORS
 int reactor_populate_monitor(struct rv_monitor *mon, struct dentry *root);
diff --git a/kernel/trace/rv/rv_reactors.c b/kernel/trace/rv/rv_reactors.c
index 2f5fc8d18dea..e23e0b5cc0ca 100644
--- a/kernel/trace/rv/rv_reactors.c
+++ b/kernel/trace/rv/rv_reactors.c
@@ -142,6 +142,9 @@ static int monitor_reactor_show(struct seq_file *m, void *p)
 	struct rv_monitor *mon = m->private;
 	struct rv_reactor *reactor = container_of(p, struct rv_reactor, list);
 
+	if (!rv_is_monitor_registered(mon))
+		return -ENODEV;
+
 	if (mon->reactor == reactor)
 		seq_printf(m, "[%s]\n", reactor->name);
 	else
@@ -235,6 +238,9 @@ monitor_reactors_write(struct file *file, const char __user *user_buf,
 
 	guard(mutex)(&rv_interface_lock);
 
+	if (!rv_is_monitor_registered(mon))
+		return -ENODEV;
+
 	list_for_each_entry(reactor, &rv_reactors_list, list) {
 		if (strcmp(ptr, reactor->name) != 0)
 			continue;
-- 
2.55.0


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

* [RFC PATCH 10/20] tools/build: Add a feature test for bpftool-btf
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (8 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 09/20] rv: Handle unregistered monitors safely in tracefs Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 11/20] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
                   ` (10 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf
  Cc: Gabriele Monaco, Steven Rostedt, 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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 11/20] tools/rv: Move argument parsing from in_kernel to utils
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (9 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 10/20] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 12/20] tools/rv: Export functionality for in_kernel monitors Gabriele Monaco
                   ` (9 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 12/20] tools/rv: Export functionality for in_kernel monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (10 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 11/20] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 13/20] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
                   ` (8 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Export enable/disable and reactor set/reset functionality from in-kernel
monitor to be used by other compatible monitor types (BPF).
Use helpers to simplify the reactor set/reset actions.

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/include/in_kernel.h |  5 ++
 tools/verification/rv/src/in_kernel.c     | 61 ++++++++++++++++-------
 2 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/tools/verification/rv/include/in_kernel.h b/tools/verification/rv/include/in_kernel.h
index f3bfd3b9895f..16d68caa84bd 100644
--- a/tools/verification/rv/include/in_kernel.h
+++ b/tools/verification/rv/include/in_kernel.h
@@ -1,3 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0
 int ikm_list_monitors(char *container);
 int ikm_run_monitor(char *monitor, int argc, char **argv);
+int __ikm_read_enable(char *monitor_name);
+int ikm_enable(char *monitor_name);
+int ikm_disable(char *monitor_name);
+void ikm_set_reactor(char *monitor_name);
+void ikm_reset_reactor(char *monitor_name);
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index 90c340a73c76..3e8272d55a3a 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -23,7 +23,7 @@
  * Returns the current status, or -1 if the monitor does not exist,
  * __hence not logging errors.
  */
-static int __ikm_read_enable(char *monitor_name)
+int __ikm_read_enable(char *monitor_name)
 {
 	char path[MAX_PATH];
 	long long enabled;
@@ -132,7 +132,7 @@ static int ikm_write_enable(char *monitor_name, char *enable_disable)
  *
  * Returns -1 on failure. Success otherwise.
  */
-static int ikm_enable(char *monitor_name)
+int ikm_enable(char *monitor_name)
 {
 	return ikm_write_enable(monitor_name, "1");
 }
@@ -142,7 +142,7 @@ static int ikm_enable(char *monitor_name)
  *
  * Returns -1 on failure. Success otherwise.
  */
-static int ikm_disable(char *monitor_name)
+int ikm_disable(char *monitor_name)
 {
 	return ikm_write_enable(monitor_name, "0");
 }
@@ -304,6 +304,42 @@ static char *ikm_get_current_reactor(char *monitor_name)
 	return curr_reactor;
 }
 
+/*
+ * ikm_set_reactor - set the configured reactor and store the initial one
+ *
+ * Do nothing if we should not set reactors.
+ */
+void ikm_set_reactor(char *monitor_name)
+{
+	int retval;
+
+	if (!config.reactor)
+		return;
+
+	config.initial_reactor = ikm_get_current_reactor(monitor_name);
+	if (!config.initial_reactor)
+		mon_usage(1, monitor_name,
+			  "ikm: failed to read current reactor, are reactors enabled?");
+
+	retval = ikm_write_reactor(monitor_name, config.reactor);
+	if (retval <= 0)
+		mon_usage(1, monitor_name,
+			  "ikm: failed to set %s reactor, is it available?",
+			  config.reactor);
+
+}
+
+/*
+ * ikm_reset_reactor - restore the initially stored reactor
+ *
+ * Do nothing if we should not set reactors.
+ */
+void ikm_reset_reactor(char *monitor_name)
+{
+	if (config.reactor && config.initial_reactor)
+		ikm_write_reactor(monitor_name, config.initial_reactor);
+}
+
 static int ikm_has_id(char *monitor_name)
 {
 	char path[MAX_PATH];
@@ -698,18 +734,7 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
 	if (retval)
 		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);
-	}
+	ikm_set_reactor(full_name);
 
 	if (config.trace) {
 		inst = ikm_setup_trace_instance(nested_name);
@@ -744,14 +769,12 @@ 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);
+	ikm_reset_reactor(full_name);
 
 	return 1;
 
 out_free_instance:
 	ikm_destroy_trace_instance(inst);
-	if (config.reactor && config.initial_reactor)
-		ikm_write_reactor(full_name, config.initial_reactor);
+	ikm_reset_reactor(full_name);
 	return -1;
 }
-- 
2.55.0


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

* [RFC PATCH 13/20] tools/rv: Implement BPF monitor loading and tracing
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (11 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 12/20] tools/rv: Export functionality for in_kernel monitors Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:34   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 14/20] tools/rv: Implement BPF monitor registration logic Gabriele Monaco
                   ` (7 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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/Makefile              |   8 +-
 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     | 681 ++++++++++++++++++++
 tools/verification/rv/src/rv.c              |   6 +-
 6 files changed, 734 insertions(+), 3 deletions(-)
 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..af83566b930c 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -27,13 +27,17 @@ endif
 RV		:= $(OUTPUT)rv
 RV_IN		:= $(RV)-in.o
 
+TOOLS_INCLUDES = -I$(srctree)/tools/include
+
 VERSION		:= $(shell sh -c "make -sC ../../.. kernelversion | grep -v make")
 DOCSRC		:= ../../../Documentation/tools/rv/
 
 FEATURE_TESTS	:= libtraceevent
 FEATURE_TESTS	+= libtracefs
+FEATURE_TESTS	+= libbpf
 FEATURE_DISPLAY	:= libtraceevent
 FEATURE_DISPLAY	+= libtracefs
+FEATURE_DISPLAY	+= libbpf
 
 all: $(RV)
 
@@ -55,9 +59,9 @@ ifeq ($(config),1)
   include Makefile.config
 endif
 
-CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES)
+CFLAGS		+= $(INCLUDES) $(LIB_INCLUDES) $(TOOLS_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..60ce23589102
--- /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_run_monitor(char *monitor_name, int argc, char **argv);
+#else
+static inline int bpf_run_monitor(char *monitor_name, int argc, char **argv)
+{
+	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..e13ae35678cb
--- /dev/null
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -0,0 +1,681 @@
+// 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 <inttypes.h>
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include <bpf/btf.h>
+#include <linux/compiler.h>
+#include <linux/math.h>
+
+#include <bpf_monitor.h>
+#include <in_kernel.h>
+#include <utils.h>
+#include <rv.h>
+
+static char bpf_monitor_paths[][MAX_PATH] = {
+	"./bpf_monitors/",
+	"/usr/share/rv/bpf_monitors/",
+	"", /* Marker */
+};
+
+/* Path used for development monitors, searched first */
+#define DEVEL_PATH 0
+
+#define MAX_ENUMS 64
+#define MAX_LINKS 16
+#define RV_TRACE_EVENT	0
+#define RV_TRACE_ERROR	1
+#define BPF_PIN_BASE_PATH "/sys/fs/bpf/rv"
+#define RV_TRACE_STRUCT "rv_trace_entry"
+
+enum da_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 da_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 da_field field_metadata[FIELD_MAX];
+};
+
+/*
+ * bpf_fill_monitor_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 right location.
+ */
+static void bpf_fill_monitor_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_monitor_paths[DEVEL_PATH], MAX_PATH, "%s/bpf_monitors", dir);
+	}
+}
+
+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)
+{
+	static char buf[16];
+
+	if (state < ctx->num_states && ctx->state_names[state][0] != '\0')
+		return ctx->state_names[state];
+
+	snprintf(buf, sizeof(buf), "%u", state);
+	return buf;
+}
+
+static const char *get_event_name(struct bpf_monitor_ctx *ctx, uint32_t event)
+{
+	static char buf[16];
+
+	if (event < ctx->num_events && ctx->event_names[event][0] != '\0')
+		return ctx->event_names[event];
+
+	snprintf(buf, sizeof(buf), "%u", event);
+	return buf;
+}
+
+/*
+ * 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.
+ *
+ * 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;
+}
+
+/*
+ * 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 da_field_id id,
+				  const uint8_t *raw,
+				  const struct bpf_monitor_ctx *ctx)
+{
+	const struct da_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] == RV_TRACE_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 {
+		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).
+ */
+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 enum_name[64];
+	int type_id, vlen;
+	int count = 0;
+
+	snprintf(enum_name, sizeof(enum_name), "%ss_%s", enum_kind, ctx->monitor_name);
+	type_id = btf__find_by_name_kind(btf, enum_name, BTF_KIND_ENUM);
+	if (type_id <= 0) {
+		err_msg("bpf: could not find enum '%s' in BTF\n", enum_name);
+		return -1;
+	}
+	enum_type = btf__type_by_id(btf, type_id);
+	if (!enum_type) {
+		err_msg("bpf: could not get enum type for '%s'\n", enum_name);
+		return -1;
+	}
+
+	enums = btf_enum(enum_type);
+	vlen = btf_vlen(enum_type);
+
+	snprintf(enum_name, sizeof(enum_name), "%s_max_%s", enum_kind, ctx->monitor_name);
+	for (int i = 0; i < vlen && count < MAX_ENUMS; i++) {
+		const char *name = btf__name_by_offset(btf, enums[i].name_off);
+		size_t name_len;
+		const char *padding;
+
+		if (!name || !strcmp(name, enum_name))
+			continue;
+
+		padding = strrchr(name, '_');
+		name_len = strlen(name);
+		if (padding && !strcmp(ctx->monitor_name, padding + 1))
+			name_len = (size_t)(padding - name);
+
+		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++;
+	}
+
+	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_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_object - search for BPF monitor object file in all directories
+ */
+static int find_bpf_object(const char *monitor_name, char *path_out, size_t path_len)
+{
+	char path[MAX_PATH];
+
+	bpf_fill_monitor_paths();
+	for (int i = 0; bpf_monitor_paths[i][0]; i++) {
+		size_t size = snprintf(path, sizeof(path), "%s/%s.o",
+				       bpf_monitor_paths[i], monitor_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;
+}
+
+/*
+ * bpf_setup_ring_buffer - set up the ring buffer to trace events
+ *
+ * Find the ring buffer map, set up the events handler, and consume any
+ * pending data to start fresh.
+ */
+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[64];
+
+	if (extract_btf_info(obj, ctx))
+		return NULL;
+
+	snprintf(ringbuf_name, sizeof(ringbuf_name), "da_ringbuf_%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;
+	}
+
+	ring_buffer__consume(rb);
+
+	return rb;
+}
+
+/*
+ * reset_monitor_maps - clear all elements from monitor's maps
+ */
+static void reset_monitor_maps(struct bpf_object *obj)
+{
+	struct bpf_map *map;
+	bool err = false;
+
+	bpf_object__for_each_map(map, obj) {
+		enum bpf_map_type type = bpf_map__type(map);
+		int value_size = bpf_map__value_size(map);
+		int fd = bpf_map__fd(map);
+
+		if (fd < 0 || bpf_map__is_internal(map))
+			continue;
+
+		switch (type) {
+		case BPF_MAP_TYPE_HASH:
+		case BPF_MAP_TYPE_PERCPU_HASH: {
+			void *key = malloc(bpf_map__key_size(map));
+
+			if (!key) {
+				err = true;
+				break;
+			}
+
+			while (!err && bpf_map_get_next_key(fd, NULL, key) == 0)
+				err |= bpf_map_delete_elem(fd, key);
+
+			free(key);
+			debug_msg("bpf: reset hash map %s\n", bpf_map__name(map));
+			break;
+		}
+
+		case BPF_MAP_TYPE_PERCPU_ARRAY:
+			/*
+			 * Per-CPU maps require a buffer for all CPUs with each
+			 * element padded to 8 bytes
+			 */
+			value_size = libbpf_num_possible_cpus() *
+				     round_up(bpf_map__value_size(map), 8);
+			fallthrough;
+		case BPF_MAP_TYPE_ARRAY: {
+			uint32_t max_entries = bpf_map__max_entries(map);
+			void *zero_value = calloc(1, value_size);
+
+			if (!zero_value) {
+				err = true;
+				break;
+			}
+
+			for (uint32_t idx = 0; !err && idx < max_entries; idx++)
+				err |= bpf_map_update_elem(fd, &idx, zero_value, BPF_ANY);
+
+			free(zero_value);
+			debug_msg("bpf: zeroed array map %s\n", bpf_map__name(map));
+			break;
+		}
+		default:
+		}
+	}
+	if (err)
+		err_msg("bpf: errors during maps reset, continuing anyway.\n");
+}
+
+/*
+ * open_bpf_monitor - open and load a BPF monitor object
+ *
+ * If path is NULL, searches for the monitor by name. Otherwise uses the
+ * provided path directly.
+ *
+ * Returns loaded BPF object on success, NULL on error.
+ */
+static struct bpf_object *open_bpf_monitor(const char *monitor_name, const char *path)
+{
+	struct bpf_object *obj = NULL;
+	char _path[MAX_PATH];
+	int res;
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.pin_root_path = BPF_PIN_BASE_PATH,
+	);
+
+	if (!path) {
+		if (!find_bpf_object(monitor_name, _path, sizeof(_path))) {
+			err_msg("bpf: error finding monitor %s\n", monitor_name);
+			return NULL;
+		}
+		path = _path;
+	}
+
+	obj = bpf_object__open_file(path, &opts);
+	if (!obj) {
+		err_msg("bpf: error opening object file: %s\n", strerror(errno));
+		return NULL;
+	}
+
+	res = bpf_object__load(obj);
+	if (res) {
+		err_msg("bpf: error loading object file: %s\n", strerror(-res));
+		return NULL;
+	}
+
+	return obj;
+}
+
+/*
+ * attach_bpf_handlers - attach and pin all BPF programs
+ *
+ * Attaches all non-struct_ops programs and stores links in the provided array.
+ * Reuses existing pinned links when available.
+ *
+ * Returns number of attached programs 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 res = 0;
+
+	bpf_object__for_each_program(prog, obj) {
+		struct bpf_link *link = NULL;
+		char pin_path[MAX_PATH];
+		const char *prog_name;
+		bool reused = true;
+
+		if (bpf_program__type(prog) == BPF_PROG_TYPE_STRUCT_OPS)
+			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);
+		snprintf(pin_path, sizeof(pin_path), "%s/%s_%s",
+			 BPF_PIN_BASE_PATH, monitor_name, prog_name);
+
+		if (access(pin_path, F_OK) == 0)
+			link = bpf_link__open(pin_path);
+		if (!link) {
+			reused = false;
+			unlink(pin_path);
+			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 (!reused) {
+			res = bpf_link__pin(link, pin_path);
+			if (res) {
+				err_msg("bpf: failed to pin link '%s': %s\n",
+					prog_name, strerror(-res));
+				return -1;
+			}
+		}
+	}
+	return res;
+}
+
+/*
+ * 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;
+	char structops_pin[MAX_PATH];
+	int res, link_count = 0, retval = -1;
+
+	/* If struct_ops is not registered this is not a BPF monitor */
+	snprintf(structops_pin, sizeof(structops_pin), "%s/rv_%s_kern",
+		 BPF_PIN_BASE_PATH, monitor_name);
+	if (access(structops_pin, F_OK) != 0)
+		return 0;
+
+	if (__ikm_read_enable(monitor_name) == 1) {
+		err_msg("bpf: monitor %s (BPF) is already enabled\n", monitor_name);
+		return -1;
+	}
+
+	strncpy(ctx.monitor_name, monitor_name, sizeof(ctx.monitor_name) - 1);
+
+	res = parse_arguments(monitor_name, argc, argv);
+	if (res)
+		mon_usage(1, monitor_name, "bpf: failed parsing arguments");
+
+	ikm_set_reactor(monitor_name);
+
+	libbpf_set_print(libbpf_print_fn);
+
+	obj = open_bpf_monitor(monitor_name, NULL);
+	if (!obj)
+		goto cleanup;
+
+	if (config.trace) {
+		rb = bpf_setup_ring_buffer(obj, &ctx);
+		if (!rb)
+			goto cleanup;
+	}
+
+	reset_monitor_maps(obj);
+
+	res = attach_bpf_handlers(monitor_name, obj, links, &link_count);
+	if (res < 0)
+		goto cleanup;
+
+	res = ikm_enable(monitor_name);
+	if (res < 0) {
+		err_msg("bpf: error enabling the monitor: %s\n", monitor_name);
+		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:
+	ikm_disable(monitor_name);
+	reset_monitor_maps(obj);
+	ikm_reset_reactor(monitor_name);
+
+	for (int i = 0; i < link_count; i++) {
+		bpf_link__unpin(links[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 09e0d8598619..e957e45ee2b1 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;
 
@@ -123,7 +124,10 @@ static void rv_mon(int argc, char **argv)
 	 * Call all possible monitor implementations, looking
 	 * for the [monitor].
 	 */
-	run += ikm_run_monitor(monitor_name, argc-1, &argv[1]);
+	run += bpf_run_monitor(monitor_name, argc-1, &argv[1]);
+
+	if (!run)
+		run += ikm_run_monitor(monitor_name, argc-1, &argv[1]);
 
 	if (!run)
 		err_msg("rv: monitor %s does not exist\n", monitor_name);
-- 
2.55.0


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

* [RFC PATCH 14/20] tools/rv: Implement BPF monitor registration logic
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (12 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 13/20] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:38   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 15/20] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
                   ` (6 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

Add support for registering and unregistering BPF monitors from
userspace via the struct_ops mechanism. The implementation scans known
paths for BPF monitor objects, loads them, and attaches their struct_ops
maps.
After registration, BPF monitors appear as normal in-kernel monitors the
RV sysfs interface works seamlessly (besides tracepoints!).

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/include/bpf_monitor.h |   7 +
 tools/verification/rv/src/bpf_monitor.c     | 338 ++++++++++++++++++++
 tools/verification/rv/src/rv.c              |   4 +
 3 files changed, 349 insertions(+)

diff --git a/tools/verification/rv/include/bpf_monitor.h b/tools/verification/rv/include/bpf_monitor.h
index 60ce23589102..beb98b63c5f8 100644
--- a/tools/verification/rv/include/bpf_monitor.h
+++ b/tools/verification/rv/include/bpf_monitor.h
@@ -4,11 +4,18 @@
 
 #ifdef HAVE_LIBBPF
 int bpf_run_monitor(char *monitor_name, int argc, char **argv);
+void rv_bpf(int argc, char **argv);
 #else
 static inline int bpf_run_monitor(char *monitor_name, int argc, char **argv)
 {
 	return 0;
 }
+
+static inline void rv_bpf(int argc, char **argv)
+{
+	fprintf(stderr, "rv: BPF support not compiled in\n");
+	exit(1);
+}
 #endif /* HAVE_LIBBPF */
 
 #endif
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index e13ae35678cb..7e24a71dfac7 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -11,6 +11,7 @@
 #include <unistd.h>
 #include <dirent.h>
 #include <libgen.h>
+#include <getopt.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <bpf/libbpf.h>
@@ -679,3 +680,340 @@ int bpf_run_monitor(char *monitor_name, int argc, char **argv)
 
 	return retval;
 }
+
+/*
+ * register_monitor_from_file - register a single BPF monitor via struct_ops
+ *
+ * Opens, loads, and attaches a BPF monitor's struct_ops map, then pins the
+ * resulting link.
+ *
+ * Returns 0 on success, -1 on error
+ */
+static int register_monitor_from_file(const char *path, const char *name)
+{
+	struct bpf_object *obj = NULL;
+	struct bpf_link *link = NULL;
+	struct bpf_map *map;
+	char map_name[MAX_DA_NAME_LEN + 8];
+	char pin_path[MAX_PATH];
+	int res, retval = -1;
+
+	obj = open_bpf_monitor(name, path);
+	if (!obj)
+		goto cleanup;
+
+	snprintf(map_name, sizeof(map_name), "rv_%s_kern", name);
+	map = bpf_object__find_map_by_name(obj, map_name);
+	if (!map) {
+		err_msg("bpf: error finding struct_ops map for %s\n", name);
+		goto cleanup;
+	}
+
+	link = bpf_map__attach_struct_ops(map);
+	if (!link) {
+		err_msg("bpf: error attaching struct_ops for %s: %s\n",
+			name, strerror(errno));
+		goto cleanup;
+	}
+
+	snprintf(pin_path, sizeof(pin_path), "%s/%s", BPF_PIN_BASE_PATH, map_name);
+	res = bpf_link__pin(link, pin_path);
+	if (res) {
+		err_msg("bpf: error pinning link for %s: %s\n",
+			name, strerror(-res));
+		goto cleanup;
+	}
+
+	debug_msg("bpf: registered %s\n", name);
+	retval = 0;
+
+cleanup:
+	bpf_link__destroy(link);
+	bpf_object__close(obj);
+
+	return retval;
+}
+
+/*
+ * register_monitors_from_path - register all BPF monitors from a path
+ *
+ * Scans directory for *.o files and registers each one via struct_ops.
+ *
+ * Returns number of monitors registered from this path
+ */
+static int register_monitors_from_path(const char *path, bool *error)
+{
+	struct dirent *entry;
+	DIR *dir;
+	char *ext;
+	int count = 0;
+
+	dir = opendir(path);
+	if (!dir)
+		return 0;
+
+	while ((entry = readdir(dir)) != NULL) {
+		char name[MAX_DA_NAME_LEN], mon_path[MAX_PATH];
+		size_t size;
+
+		if (entry->d_name[0] == '.')
+			continue;
+
+		ext = strrchr(entry->d_name, '.');
+		if (!ext || strcmp(ext, ".o") != 0)
+			continue;
+
+		size = snprintf(mon_path, sizeof(mon_path), "%s/%s",
+				path, entry->d_name);
+		if (size >= MAX_PATH) {
+			err_msg("bpf: path too long: %s/%s\n", path, entry->d_name);
+			*error = true;
+			continue;
+		}
+
+		strncpy(name, entry->d_name, sizeof(name) - 1);
+		name[sizeof(name) - 1] = '\0';
+		ext = strrchr(name, '.');
+		if (ext)
+			*ext = '\0';
+
+		if (register_monitor_from_file(mon_path, name) == 0)
+			count++;
+		else
+			*error = true;
+	}
+
+	closedir(dir);
+	return count;
+}
+
+/*
+ * bpf_struct_ops_supported - check if the kernel supports BPF monitors
+ */
+static bool bpf_struct_ops_supported(void)
+{
+	struct btf *btf;
+	int type_id;
+
+	btf = btf__load_vmlinux_btf();
+	if (!btf)
+		return false;
+
+	type_id = btf__find_by_name_kind(btf, "bpf_struct_ops_rv_monitor", BTF_KIND_STRUCT);
+	btf__free(btf);
+
+	if (type_id <= 0)
+		return false;
+
+	return true;
+}
+
+/*
+ * bpf_register_monitors - register all BPF monitors via struct_ops
+ *
+ * Scans known paths for BPF monitor objects, loads them, and attaches their
+ * struct_ops maps. The resulting links are pinned so monitors remain
+ * registered with the kernel RV subsystem even after this process exits.
+ *
+ * Returns command exit status (SUCCESS/FAILURE)
+ */
+static int bpf_register_monitors(void)
+{
+	int count = 0;
+	bool error = false;
+
+	bpf_fill_monitor_paths();
+
+	for (int i = 0; bpf_monitor_paths[i][0]; i++)
+		count += register_monitors_from_path(bpf_monitor_paths[i], &error);
+
+	printf("registered %d BPF monitor(s)\n", count);
+
+	return error ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+/*
+ * is_struct_ops - return true if d_name matches rv_<mon>_kern
+ *
+ * Also store <mon> in name_out when returning true.
+ */
+static bool is_struct_ops(char *d_name, char *name_out)
+{
+	const int rvlen = strlen("rv_"), kernlen = strlen("_kern");
+	int len = strlen(d_name);
+
+	if (len > rvlen + kernlen && !strncmp(d_name, "rv_", rvlen) &&
+	    !strcmp(d_name + len - kernlen, "_kern")) {
+		len -= rvlen + kernlen;
+		if (name_out) {
+			if (len >= MAX_DA_NAME_LEN)
+				len = MAX_DA_NAME_LEN - 1;
+			strncpy(name_out, d_name + rvlen, len);
+			name_out[len] = '\0';
+		}
+		return true;
+	}
+	return false;
+}
+
+enum unregister_pass {
+	CHECK_ACTIVE,
+	UNLINK_HANDLERS,
+	UNLINK_STRUCT_OPS,
+	MAX_PASSES,
+};
+
+/*
+ * bpf_unregister_monitors - unregister all BPF monitors
+ *
+ * Removes all pinned links and maps from BPF_PIN_BASE_PATH, including the
+ * struct_ops. This unregisters the monitors from the RV subsystem and cleans
+ * up all associated resources.
+ *
+ * First iterate over the directory to check if any BPF monitor is active,
+ * this is best effort. Then unlink all handlers and at last all struct_ops.
+ *
+ * Returns command exit status (SUCCESS/FAILURE)
+ */
+static int bpf_unregister_monitors(void)
+{
+	struct dirent *entry;
+	DIR *dir;
+	int count = 0;
+
+	dir = opendir(BPF_PIN_BASE_PATH);
+	if (!dir) {
+		err_msg("bpf: cannot open %s: %s\n",
+			BPF_PIN_BASE_PATH, strerror(errno));
+		return EXIT_FAILURE;
+	}
+
+	for (int pass = CHECK_ACTIVE; pass < MAX_PASSES; pass++) {
+		rewinddir(dir);
+		while ((entry = readdir(dir)) != NULL) {
+			char pin_path[MAX_PATH];
+			char mon_name[MAX_DA_NAME_LEN];
+			int is_ops;
+
+			if (entry->d_name[0] == '.')
+				continue;
+
+			is_ops = is_struct_ops(entry->d_name, mon_name);
+
+			switch (pass) {
+			case CHECK_ACTIVE:
+				if (is_ops && __ikm_read_enable(mon_name) == 1) {
+					err_msg("bpf: monitor %s is enabled, cannot unregister\n",
+						mon_name);
+					closedir(dir);
+					return EXIT_FAILURE;
+				}
+				continue;
+			case UNLINK_HANDLERS:
+				if (is_ops)
+					continue;
+				fallthrough;
+			case UNLINK_STRUCT_OPS:
+				snprintf(pin_path, sizeof(pin_path), "%s/%s",
+					 BPF_PIN_BASE_PATH, entry->d_name);
+
+				if (unlink(pin_path)) {
+					err_msg("bpf: failed to unlink %s: %s\n", pin_path,
+						strerror(errno));
+				} else if (is_ops) {
+					count++;
+					debug_msg("bpf: unregistered %s\n", mon_name);
+				}
+				break;
+			}
+		}
+	}
+
+	closedir(dir);
+
+	printf("unregistered %d BPF monitor(s)\n", count);
+	return EXIT_SUCCESS;
+}
+
+/*
+ * rv_bpf - handle BPF monitor registration commands
+ */
+void rv_bpf(int argc, char **argv)
+{
+	static const char *const usage[] = {
+		"",
+		"  usage: rv bpf [-h] [-v] {register,unregister}",
+		"",
+		"\tmanage BPF monitor registration",
+		"",
+		"\t-h/--help: print this menu",
+		"\t-v/--verbose: print debug messages",
+		"",
+		"\tregister:   register all available BPF monitors",
+		"\tunregister: unregister all BPF monitors",
+		NULL,
+	};
+	int print_help = 0, retval = EXIT_SUCCESS;
+
+	while (1) {
+		static struct option long_options[] = {
+			{"help",	no_argument,		0, 'h'},
+			{"verbose",	no_argument,		0, 'v'},
+			{0, 0, 0, 0}
+		};
+		int option_index = 0;
+		int c = getopt_long(argc, argv, "hv", long_options, &option_index);
+
+		/* detect the end of the options */
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'h':
+			print_help = 1;
+			retval = EXIT_SUCCESS;
+			break;
+		case 'v':
+			config.debug = 1;
+			break;
+		default:
+			print_help = 1;
+			retval = EXIT_FAILURE;
+			break;
+		}
+	}
+
+	/* requires at least one argument after options */
+	if (optind >= argc && !print_help) {
+		print_help = 1;
+		retval = EXIT_FAILURE;
+	}
+
+	if (print_help) {
+		fprintf(stderr, "rv version %s\n", VERSION);
+		for (int i = 0; usage[i]; i++)
+			fprintf(stderr, "%s\n", usage[i]);
+		exit(retval);
+	}
+
+	if (!bpf_struct_ops_supported()) {
+		err_msg("bpf: kernel does not support BPF monitors\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (!strcmp(argv[optind], "register")) {
+		retval = bpf_register_monitors();
+		exit(retval);
+	}
+
+	if (!strcmp(argv[optind], "unregister")) {
+		retval = bpf_unregister_monitors();
+		exit(retval);
+	}
+
+	/* invalid sub-command */
+	fprintf(stderr, "rv version %s\n", VERSION);
+	for (int i = 0; usage[i]; i++)
+		fprintf(stderr, "%s\n", usage[i]);
+	exit(EXIT_FAILURE);
+}
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index e957e45ee2b1..3e6e5e786746 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -149,6 +149,7 @@ static void usage(int exit_val, const char *fmt, ...)
 		"	command: run one of the following command:",
 		"	  list: list all available monitors",
 		"	  mon:  run a monitor",
+		"	  bpf:  manage BPF monitors registration",
 		"",
 		"	[command options]: each command has its own set of options",
 		"		           run rv command -h for further information",
@@ -189,6 +190,9 @@ int main(int argc, char **argv)
 	if (!strcmp(argv[1], "list"))
 		rv_list(--argc, &argv[1]);
 
+	if (!strcmp(argv[1], "bpf"))
+		rv_bpf(argc - 1, &argv[1]);
+
 	if (!strcmp(argv[1], "mon")) {
 		/*
 		 * monitor's main should monitor should_stop() function.
-- 
2.55.0


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

* [RFC PATCH 15/20] tools/rv: Copy stripped bpf_atomic.h from libarena
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (13 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 14/20] tools/rv: Implement BPF monitor registration logic Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:38   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 16/20] tools/rv: Add BPF monitors Gabriele Monaco
                   ` (5 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 16/20] tools/rv: Add BPF monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (14 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 15/20] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:40   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 17/20] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
                   ` (4 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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                       |  27 +-
 tools/verification/models/nohz.dot            |  16 +
 tools/verification/models/tqueue.dot          |  15 +
 tools/verification/rv/Makefile                |  43 +-
 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          | 400 ++++++++++++++++++
 tools/verification/rv/bpf_monitors/nohz.c     |  47 ++
 tools/verification/rv/bpf_monitors/nohz.h     |  49 +++
 tools/verification/rv/bpf_monitors/tqueue.c   |  35 ++
 tools/verification/rv/bpf_monitors/tqueue.h   |  47 ++
 12 files changed, 702 insertions(+), 10 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..a02837c4110d 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
@@ -29,8 +34,6 @@
  */
 #define DA_MON_NAME CONCATENATE(da_mon_, MONITOR_NAME)
 
-static struct rv_monitor rv_this;
-
 /*
  * Hook to allow the implementation of hybrid automata: define it with a
  * function that takes curr_state, event and next_state and returns true if the
@@ -77,6 +80,12 @@ static struct rv_monitor rv_this;
 #define da_id_type int
 #endif
 
+#ifdef __BPF__
+#include "da_monitor_bpf.h"
+#else
+static struct rv_monitor rv_this;
+#endif
+
 static void react(enum states curr_state, enum events event)
 {
 	rv_react(&rv_this,
@@ -128,6 +137,7 @@ static inline bool da_monitoring(struct da_monitor *da_mon)
 	return smp_load_acquire(&da_mon->monitoring);
 }
 
+#ifndef __BPF__
 /*
  * da_monitor_enabled - checks if the monitor is enabled
  */
@@ -143,6 +153,7 @@ static inline bool da_monitor_enabled(void)
 
 	return 1;
 }
+#endif
 
 /*
  * da_monitor_handling_event - checks if the monitor is ready to handle events
@@ -159,6 +170,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 +697,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 +819,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 +860,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 af83566b930c..bdd28dc1fd2f 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -35,9 +35,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)
 
@@ -45,7 +49,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
@@ -70,19 +74,52 @@ 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)/tools/lib
+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..ee421ebcf1b5
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
@@ -0,0 +1,400 @@
+/* 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 trace_rv_retries_error(...) do {} while (0)
+#define pr_warn(fmt, ...) bpf_printk(fmt, ##__VA_ARGS__)
+#define da_monitor_enabled() likely(da_monitor_enabled_bpf())
+#define da_implicit_guard()
+#define IS_ENABLED(conf) 0
+
+#define RV_TRACE_EVENT	0
+#define RV_TRACE_ERROR	1
+
+/*
+ * 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 da_monitor_map CONCATENATE(da_monitor_map_, MONITOR_NAME)
+#define da_ringbuf CONCATENATE(da_ringbuf_, MONITOR_NAME)
+#define rv_this_enabled CONCATENATE(rv_enabled_, MONITOR_NAME)
+
+struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 256 * 1024);
+	__uint(pinning, LIBBPF_PIN_BY_NAME);
+} da_ringbuf SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, bool);
+	__uint(pinning, LIBBPF_PIN_BY_NAME);
+} rv_this_enabled 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);
+
+static inline bool da_monitor_enabled_bpf(void)
+{
+	uint32_t key = 0;
+	bool *enabled = bpf_map_lookup_elem(&rv_this_enabled, &key);
+
+	return enabled && *enabled;
+}
+
+void bpf_rv_react(char *name__str, char *msg, u32 msg__sz) __ksym;
+
+#define rv_react(mon, fmt, ...)							  \
+	({									  \
+		char ___msg[256];						  \
+										  \
+		if (BPF_SNPRINTF(___msg, sizeof(___msg), fmt, ##__VA_ARGS__) > 0) \
+			bpf_rv_react(__stringify(MONITOR_NAME), ___msg,		  \
+				     sizeof(___msg));				  \
+	})
+
+/*
+ * 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);
+	__uint(pinning, LIBBPF_PIN_BY_NAME);
+} da_monitor_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor(void)
+{
+	__u32 key = 0;
+	union rv_task_monitor *mon = bpf_map_lookup_elem(&da_monitor_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);
+	__uint(pinning, LIBBPF_PIN_BY_NAME);
+} da_monitor_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor(void)
+{
+	__u32 key = 0;
+	union rv_task_monitor *mon = bpf_map_lookup_elem(&da_monitor_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);
+	__uint(pinning, LIBBPF_PIN_BY_NAME);
+} da_monitor_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(&da_monitor_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;
+	/* Possible with monitor manually disabled: handlers still active */
+	if (unlikely(!da_monitor_enabled()))
+		return NULL;
+
+	bpf_map_update_elem(&da_monitor_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(&da_monitor_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,
+				     uint8_t type)
+{
+	struct rv_trace_entry *entry;
+	static const char stub_comm[] = "<XXX>";
+
+	entry = bpf_ringbuf_reserve(&da_ringbuf, 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);
+}
+
+SEC("struct_ops/enable")
+int da_monitor_enable_bpf(void)
+{
+	uint32_t key = 0;
+	bool enabled = true;
+
+	bpf_map_update_elem(&rv_this_enabled, &key, &enabled, BPF_ANY);
+	return 0;
+}
+
+SEC("struct_ops/disable")
+void da_monitor_disable_bpf(void)
+{
+	uint32_t key = 0;
+	bool enabled = false;
+
+	bpf_map_update_elem(&rv_this_enabled, &key, &enabled, BPF_ANY);
+}
+
+SEC("struct_ops/reset")
+void da_monitor_reset_bpf(void)
+{
+	/* Userspace resets maps */
+}
+
+#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..5bded9408b6d
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/nohz.c
@@ -0,0 +1,47 @@
+// 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 the monitor is to allow a single optional sched_tick from the
+ * stopped state in the model.
+ */
+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;
+}
+
+SEC(".struct_ops.link")
+struct rv_monitor rv_nohz_kern = {
+	.name = "nohz",
+	.description = "tick does not run when stopped.",
+	.enable = da_monitor_enable_bpf,
+	.disable = da_monitor_disable_bpf,
+	.reset = da_monitor_reset_bpf,
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
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..c7516da01b65
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/tqueue.c
@@ -0,0 +1,35 @@
+// 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 (!(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;
+}
+
+SEC(".struct_ops.link")
+struct rv_monitor rv_tqueue_kern = {
+	.name = "tqueue",
+	.description = "enqueue and dequeue tasks.",
+	.enable = da_monitor_enable_bpf,
+	.disable = da_monitor_disable_bpf,
+	.reset = da_monitor_reset_bpf,
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
diff --git a/tools/verification/rv/bpf_monitors/tqueue.h b/tools/verification/rv/bpf_monitors/tqueue.h
new file mode 100644
index 000000000000..64a0af1dc924
--- /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 related	[flat|nested] 39+ messages in thread

* [RFC PATCH 17/20] tools/rv: Define CONFIG_X86_64 statically for BPF monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (15 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 16/20] tools/rv: Add BPF monitors Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:05 ` [RFC PATCH 18/20] verification/rvgen: Add support " Gabriele Monaco
                   ` (3 subsequent siblings)
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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 | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index 7e24a71dfac7..d2d1d90bbf73 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -505,6 +505,12 @@ static struct bpf_object *open_bpf_monitor(const char *monitor_name, const char
 
 	LIBBPF_OPTS(bpf_object_open_opts, opts,
 		.pin_root_path = BPF_PIN_BASE_PATH,
+		/* 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
 	);
 
 	if (!path) {
-- 
2.55.0


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

* [RFC PATCH 18/20] verification/rvgen: Add support for BPF monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (16 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 17/20] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:41   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 19/20] tools/rv: Add selftest for rv bpf Gabriele Monaco
                   ` (2 subsequent siblings)
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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    | 26 +++++++++++++++++++
 5 files changed, 85 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..7af176ea0a29
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
@@ -0,0 +1,26 @@
+// 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%%
+SEC(".struct_ops.link")
+struct rv_monitor rv_%%MODEL_NAME%%_kern = {
+	.name = "%%MODEL_NAME%%",
+	.description = "%%DESCRIPTION%%",
+	.enable = da_monitor_enable_bpf,
+	.disable = da_monitor_disable_bpf,
+	.reset = da_monitor_reset_bpf,
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
-- 
2.55.0


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

* [RFC PATCH 19/20] tools/rv: Add selftest for rv bpf
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (17 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 18/20] verification/rvgen: Add support " Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-08-31  9:44   ` sashiko-bot
  2026-08-31  9:05 ` [RFC PATCH 20/20] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
  2026-09-01 18:35 ` [RFC PATCH 00/20] rv: Add support for BPF monitors Nam Cao
  20 siblings, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik

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

Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
 tools/verification/rv/tests/rv_bpf.t | 104 +++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 tools/verification/rv/tests/rv_bpf.t

diff --git a/tools/verification/rv/tests/rv_bpf.t b/tools/verification/rv/tests/rv_bpf.t
new file mode 100644
index 000000000000..902343f0d3f1
--- /dev/null
+++ b/tools/verification/rv/tests/rv_bpf.t
@@ -0,0 +1,104 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+source ../tests/engine.sh
+test_begin
+
+set_timeout 30s
+
+RVDIR=/sys/kernel/tracing/rv
+
+check "verify bpf subcommand help" \
+	"$RV bpf --help" 0 "manage BPF monitor registration"
+
+if $RV bpf unregister 2>&1 | grep -q "kernel does not support BPF monitors"; then
+	test_end
+	exit 0
+fi
+
+if ! mount | grep -q /sys/fs/bpf; then
+	mount -t bpf bpf /sys/fs/bpf
+fi
+
+check "nohz does not exist before register" \
+	"$RV mon nohz" 1 "monitor nohz does not exist"
+
+check "tqueue does not exist before register" \
+	"$RV mon tqueue" 1 "monitor tqueue does not exist"
+
+check "register BPF monitors" \
+	"$RV bpf register" 0 "registered [0-9]\+ BPF monitor(s)"
+
+set_expected_timeout 2s
+
+header="^[[:space:]]\+\(\([][A-Z_x<>-]\+\||\)[[:space:]]*\)\+$"
+type="\(event\|error\)[[:space:]]\+"
+genpid="[0-9]\+[[:space:]]\+"
+selfpid="\$pid[[:space:]]\+"
+cpu="\[[0-9]\{3\}\][[:space:]]\+"
+state="[a-z_]\+ "
+trace_task="${genpid}${cpu}${type}${genpid}${state}"
+trace_task_self="${genpid}${cpu}${type}${selfpid}${state}"
+trace_cpu="${genpid}${cpu}${type}${state}"
+trace_cpu_self="${selfpid}${cpu}${type}${state}"
+
+check_if_exists "run a BPF monitor without parameters" \
+	"$RV mon nohz" "$RVDIR/monitors/nohz" "" "."
+
+check_if_exists "run a per-task BPF monitor without parameters" \
+	"$RV mon tqueue" "$RVDIR/monitors/tqueue" "" "."
+
+check_if_exists "run per-task BPF monitor with tracing" \
+	"$RV mon tqueue -t" "$RVDIR/monitors/tqueue" \
+	"$header" "$trace_task_self" "\($header\|$trace_task\)"
+
+check_if_exists "run per-task BPF monitor tracing also self" \
+	"$RV mon tqueue -t -s" "$RVDIR/monitors/tqueue" \
+	"$trace_task_self" "" "\($header\|$trace_task\)"
+
+check_if_exists "run per-cpu BPF monitor with tracing" \
+	"$RV mon nohz -t" "$RVDIR/monitors/nohz" \
+	"$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" "$RVDIR/monitors/nohz" \
+#	"$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+
+lines_before=$(($(dmesg | wc -l) + 1))
+check_if_exists "run a BPF monitor with a reactor" \
+	"$RV mon nohz -r printk && dmesg | tail -n +$lines_before" "$RVDIR/monitors/nohz" \
+	"rv: monitor nohz does not allow event"
+
+set_timeout 30s
+
+check "already enabled monitor returns error" \
+	"echo 1 > $RVDIR/monitors/nohz/enable; $RV mon nohz" 1 \
+	"monitor nohz (BPF) is already enabled"
+[ -n "$TEST_COUNT" ] && echo 0 > $RVDIR/monitors/nohz/enable
+
+check "already enabled monitor prevents unregistration" \
+	"echo 1 > $RVDIR/monitors/nohz/enable; $RV bpf unregister" 1 \
+	"monitor nohz is enabled, cannot unregister" \
+	"unregistered [0-9]\+ BPF monitor(s)"
+[ -n "$TEST_COUNT" ] && echo 0 > $RVDIR/monitors/nohz/enable
+
+check "unregister BPF monitors" \
+	"$RV bpf unregister" 0 "unregistered [0-9]\+ BPF monitor(s)"
+
+check "nohz does not exist after unregister" \
+	"$RV mon nohz" 1 "monitor nohz does not exist"
+
+check "tqueue does not exist after unregister" \
+	"$RV mon tqueue" 1 "monitor tqueue does not exist"
+
+check "nothing left to unregister" \
+	"$RV bpf unregister" 0 "unregistered 0 BPF monitor(s)"
+
+# Error handling tests
+check "bpf without subcommand" \
+	"$RV bpf" 1 "usage: rv bpf"
+
+check "invalid bpf subcommand" \
+	"$RV bpf invalid" 1 "usage: rv bpf"
+
+test_end
-- 
2.55.0


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

* [RFC PATCH 20/20] verification/rvgen: Add selftest for rvgen -b
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (18 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 19/20] tools/rv: Add selftest for rv bpf Gabriele Monaco
@ 2026-08-31  9:05 ` Gabriele Monaco
  2026-09-01 18:35 ` [RFC PATCH 00/20] rv: Add support for BPF monitors Nam Cao
  20 siblings, 0 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-08-31  9:05 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
	Gabriele Monaco
  Cc: 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      | 40 ++++++++++++++
 .../tests/golden/da_bpf_cpu/da_bpf_cpu.h      | 47 ++++++++++++++++
 .../tests/golden/da_bpf_obj/da_bpf_obj.c      | 54 +++++++++++++++++++
 .../tests/golden/da_bpf_obj/da_bpf_obj.h      | 47 ++++++++++++++++
 .../verification/rvgen/tests/rvgen_monitor.t  | 11 ++++
 5 files changed, 199 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..37659b2ebce2
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
@@ -0,0 +1,40 @@
+// 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;
+}
+
+SEC(".struct_ops.link")
+struct rv_monitor rv_da_bpf_cpu_kern = {
+	.name = "da_bpf_cpu",
+	.description = "auto-generated",
+	.enable = da_monitor_enable_bpf,
+	.disable = da_monitor_disable_bpf,
+	.reset = da_monitor_reset_bpf,
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
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..bbd46615a1a5
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
@@ -0,0 +1,54 @@
+// 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;
+}
+
+SEC(".struct_ops.link")
+struct rv_monitor rv_da_bpf_obj_kern = {
+	.name = "da_bpf_obj",
+	.description = "auto-generated",
+	.enable = da_monitor_enable_bpf,
+	.disable = da_monitor_disable_bpf,
+	.reset = da_monitor_reset_bpf,
+	.enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
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 related	[flat|nested] 39+ messages in thread

* Re: [RFC PATCH 06/20] rv: Add rv_get_monitor_by_name()
  2026-08-31  9:05 ` [RFC PATCH 06/20] rv: Add rv_get_monitor_by_name() Gabriele Monaco
@ 2026-08-31  9:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:24 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] rv_is_container_monitor() fails to identify an empty container if it is the last element in the list, leading to a NULL pointer dereference when enabled.
--

commit a8e1e1f414fc69c200ed8ba3fee4d10346fe86df
Author: Gabriele Monaco <gmonaco@redhat.com>

rv: Add rv_get_monitor_by_name()
    
Add a helper function to get the rv_monitor pointer for a registered
monitor given its name and add its first user in rv_register_monitor().

> diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
> index f77c5865f41ab..33919c5fbd6ad 100644
> --- a/kernel/trace/rv/rv.c
> +++ b/kernel/trace/rv/rv.c
[ ... ]
> @@ -229,6 +230,18 @@ bool rv_is_container_monitor(struct rv_monitor *mon)
>  	return next->parent == mon || !mon->enable;
>  }

[Severity: High]
This isn't a bug introduced by this patch, but there is a pre-existing
issue in rv_is_container_monitor(). Does this function incorrectly classify
an empty container monitor if it happens to be the last element in
rv_monitors_list?

Looking at the rest of rv_is_container_monitor():

bool rv_is_container_monitor(struct rv_monitor *mon)
{
	...
	if (list_is_last(&mon->list, &rv_monitors_list))
		return false;

	next = list_next_entry(mon, list);
	return next->parent == mon || !mon->enable;
}

If an empty container monitor (which has a NULL enable function) is the last
element, it returns false early. This bypasses the !mon->enable check,
treating the empty container as a regular monitor.

If a user then writes to its tracefs enable file, rv_enable_single() appears
to unconditionally call the enable function:

	retval = mon->enable();

Since the empty container monitor has a NULL enable pointer, will this lead
to a kernel panic via a NULL pointer dereference when enabled?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=6

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

* Re: [RFC PATCH 09/20] rv: Handle unregistered monitors safely in tracefs
  2026-08-31  9:05 ` [RFC PATCH 09/20] rv: Handle unregistered monitors safely in tracefs Gabriele Monaco
@ 2026-08-31  9:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:24 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The validation check `rv_is_monitor_registered` is vulnerable to the ABA problem, allowing operations on a stale file descriptor to improperly manipulate a newly registered monitor.
--

commit abab0e30c495cd011906fb219545b8b62d268a11
Author: Gabriele Monaco <gmonaco@redhat.com>

rv: Handle unregistered monitors safely in tracefs

This patch adds checks to ensure that a tracefs file descriptor's private_data
still points to a registered monitor before allowing read or write operations.
It aims to prevent use-after-free scenarios when pluggable BPF monitors are
dynamically unregistered while their files are still open.

> diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c
> index 33919c5fbd6ad..6ddd3b92da1db 100644
> --- a/kernel/trace/rv/rv.c
> +++ b/kernel/trace/rv/rv.c
> @@ -245,13 +245,31 @@ struct rv_monitor *rv_get_monitor_by_name(const char *name)
>  /*
>   * This section collects the monitor/ files and folders.
>   */
> +
> +bool rv_is_monitor_registered(struct rv_monitor *mon)
> +{
> +	struct rv_monitor *m;
> +
> +	list_for_each_entry_rcu(m, &rv_monitors_list, list,
> +				lockdep_is_held(&rv_interface_lock)) {
> +		if (m == mon)
> +			return true;

[Severity: Medium]
Is it possible for a newly registered monitor to be allocated at the exact
same memory address as a previously unregistered monitor?

If a privileged user opens a monitor's tracefs file, the monitor is then 
unloaded (freeing its memory), and a subsequently loaded BPF monitor happens
to be allocated at that exact same memory address, it appears this pointer 
equality check would incorrectly evaluate to true for the stale file descriptor.

> +	}
> +	return false;
> +}

[ ... ]

> @@ -384,6 +402,9 @@ static ssize_t monitor_enable_write_data(struct file *filp, const char __user *u
>  
>  	guard(mutex)(&rv_interface_lock);
>  
> +	if (!rv_is_monitor_registered(mon))
> +		return -ENODEV;
> +
>  	if (val)
>  		retval = rv_enable_monitor(mon);

[Severity: Medium]
If the registration check passes due to the reallocation scenario described
above, could a write to a stale file descriptor improperly enable or disable
the newly registered, unrelated monitor?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=9

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

* Re: [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points
  2026-08-31  9:05 ` [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points Gabriele Monaco
@ 2026-08-31  9:32   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:32 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: bpf, linux-trace-kernel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `sched_enqueue_tp` tracepoint is invoked before the scheduler class has actually enqueued the task, resulting in trace consumers observing stale task state.
--

commit cd98b289fdbaccae680f140f19c0e9bd5dd972b6
Author: Nam Cao <namcao@linutronix.de>

sched: Add task enqueue/dequeue trace points

This commit adds tracepoints into the scheduler's enqueue and dequeue paths
to allow tracing task scheduling events.

> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index f78275192036b..8561ca324943a 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
[ ... ]
> @@ -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_sched_enqueue_tp(p, cpu_of(rq));
> +
[Severity: Medium]
Because this tracepoint is placed at the beginning of the function, prior to
p->sched_class->enqueue_task(), will trace consumers observe stale task
state?

For example, it seems that when the tracepoint triggers here, metrics like
se.vruntime have not yet been denormalized for the new runqueue, the EEVDF
se.deadline has not been recomputed, and on_rq is still 0.

Does it make sense to move this after the class-specific enqueue operation
completes, mirroring how dequeue_task() handles this by emitting
trace_sched_dequeue_tp() after calling p->sched_class->dequeue_task()?

>  	if (!(flags & ENQUEUE_NOCLOCK))
>  		update_rq_clock(rq);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=1

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

* Re: [RFC PATCH 13/20] tools/rv: Implement BPF monitor loading and tracing
  2026-08-31  9:05 ` [RFC PATCH 13/20] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
@ 2026-08-31  9:34   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:34 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Medium] A failure to reset one BPF map aborts the reset process for all subsequent maps.
- [Medium] Memory and file descriptor leak when `bpf_object__load` fails.
- [Medium] Reused BPF links are not unpinned during cleanup, leaving their BPF programs permanently attached to the kernel.
- [Low] Calling `get_state_name` twice in the same printf argument list causes both fields to print the same value if BTF lookup fails.
- [Low] `extract_enum_names` fails to strip the monitor name suffix if the monitor name contains an underscore.
- [Low] Missing bounds check on `data_sz` in `handle_event` can lead to out-of-bounds memory reads.
--

commit 18d2a63fa7007876f35f76e7406322321e0a0eb2
Author: Gabriele Monaco <gmonaco@redhat.com>

tools/rv: Implement BPF monitor loading and tracing

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

> diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
> new file mode 100644
> index 0000000000000..e13ae35678cb8
> --- /dev/null
> +++ b/tools/verification/rv/src/bpf_monitor.c
[ ... ]
> +static int handle_event(void *ctx, void *data, size_t data_sz)
> +{

[Severity: Low]
Should this callback validate that data_sz is large enough to cover the
fields accessed by read_field()?

Without verifying that the BTF metadata offsets fall within the provided
data_sz, could a truncated or malformed ring buffer event lead to an
out-of-bounds memory read?

[ ... ]
> +	if (entry[FIELD_EVENT_TYPE] == RV_TRACE_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 {
> +		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');

[Severity: Low]
Since get_state_name() returns a pointer to a single static buffer when
the state ID is not found, will calling it twice in the same printf
argument list cause both fields to print the exact same string as the
buffer is overwritten?

> +	}
> +
> +	return 0;
> +}
[ ... ]
> +static int extract_enum_names(const struct btf *btf, const char *enum_kind,
> +			       char dest[][MAX_DA_NAME_LEN], struct bpf_monitor_ctx *ctx)
> +{
[ ... ]
> +		padding = strrchr(name, '_');
> +		name_len = strlen(name);
> +		if (padding && !strcmp(ctx->monitor_name, padding + 1))
> +			name_len = (size_t)(padding - name);

[Severity: Low]
Could this fail to strip the suffix if the monitor name itself contains
an underscore?

Since strrchr() finds the last occurrence of '_', if ctx->monitor_name
is something like my_monitor, padding + 1 will evaluate to monitor,
causing the strcmp check to fail.

[ ... ]
> +static void reset_monitor_maps(struct bpf_object *obj)
> +{
> +	struct bpf_map *map;
> +	bool err = false;
> +
> +	bpf_object__for_each_map(map, obj) {
[ ... ]
> +		case BPF_MAP_TYPE_HASH:
> +		case BPF_MAP_TYPE_PERCPU_HASH: {
> +			void *key = malloc(bpf_map__key_size(map));
> +
> +			if (!key) {
> +				err = true;
> +				break;
> +			}
> +
> +			while (!err && bpf_map_get_next_key(fd, NULL, key) == 0)
> +				err |= bpf_map_delete_elem(fd, key);

[Severity: Medium]
Since the err flag is declared outside the loop and not reset to false
per map, does a failure to clear one map cause the while loop to be
skipped for all subsequent maps? Stale data might be left behind,
potentially causing incorrect monitor behavior.

[ ... ]
> +static struct bpf_object *open_bpf_monitor(const char *monitor_name, const char *path)
> +{
[ ... ]
> +	obj = bpf_object__open_file(path, &opts);
> +	if (!obj) {
> +		err_msg("bpf: error opening object file: %s\n", strerror(errno));
> +		return NULL;
> +	}
> +
> +	res = bpf_object__load(obj);
> +	if (res) {
> +		err_msg("bpf: error loading object file: %s\n", strerror(-res));
> +		return NULL;
> +	}

[Severity: Medium]
If bpf_object__load() fails, should this error path call
bpf_object__close() to prevent leaking the bpf_object and its open
file descriptors?

[ ... ]
> +static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
> +				struct bpf_link **links, int *link_count)
> +{
[ ... ]
> +		if (access(pin_path, F_OK) == 0)
> +			link = bpf_link__open(pin_path);
[ ... ]
> +int bpf_run_monitor(char *monitor_name, int argc, char **argv)
> +{
[ ... ]
> +	for (int i = 0; i < link_count; i++) {
> +		bpf_link__unpin(links[i]);

[Severity: Medium]
If a link is reused and reopened via bpf_link__open(), will
bpf_link__unpin() fail during cleanup?

Libbpf does not populate the internal pin_path property for reopened
links, which means the pin file could remain on the filesystem and the
BPF program might stay attached to the kernel tracepoint indefinitely
after the tool exits.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=13

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

* Re: [RFC PATCH 14/20] tools/rv: Implement BPF monitor registration logic
  2026-08-31  9:05 ` [RFC PATCH 14/20] tools/rv: Implement BPF monitor registration logic Gabriele Monaco
@ 2026-08-31  9:38   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:38 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Missing directory creation for BPF_PIN_BASE_PATH causes immediate pinning failure on fresh systems.

Pre-existing issues:
- [High] A single failure when resetting map elements causes all subsequent maps to be silently skipped.
--

commit 540f0c8b5ced934d1d1110f3772b2b0284013689
Author: Gabriele Monaco <gmonaco@redhat.com>

tools/rv: Implement BPF monitor registration logic
    
Add support for registering and unregistering BPF monitors from
userspace via the struct_ops mechanism. The implementation scans known
paths for BPF monitor objects, loads them, and attaches their struct_ops
maps.

> diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
> index e13ae35678cb8..7e24a71dfac70 100644
> --- a/tools/verification/rv/src/bpf_monitor.c
> +++ b/tools/verification/rv/src/bpf_monitor.c
[ ... ]
> @@ -679,3 +680,340 @@ int bpf_run_monitor(char *monitor_name, int argc, char **argv)
>  
>  	return retval;
>  }
> +
> +/*
> + * register_monitor_from_file - register a single BPF monitor via struct_ops
> + *
> + * Opens, loads, and attaches a BPF monitor's struct_ops map, then pins the
> + * resulting link.
> + *
> + * Returns 0 on success, -1 on error
> + */
> +static int register_monitor_from_file(const char *path, const char *name)
> +{
[ ... ]
> +	link = bpf_map__attach_struct_ops(map);
> +	if (!link) {
> +		err_msg("bpf: error attaching struct_ops for %s: %s\n",
> +			name, strerror(errno));
> +		goto cleanup;
> +	}
> +
> +	snprintf(pin_path, sizeof(pin_path), "%s/%s", BPF_PIN_BASE_PATH, map_name);
> +	res = bpf_link__pin(link, pin_path);

[Severity: High]
How does this code handle the case where the BPF_PIN_BASE_PATH directory
does not exist yet?

It appears that if the parent directory has not been manually created by the
user beforehand, bpf_link__pin() will return -ENOENT. This causes a
regression out-of-the-box on fresh systems where the registration tool
systematically fails.

Neither the kernel RV subsystem nor libbpf auto-creates this namespace
directory, which breaks the functionality of the tool for local
administrators.

[ ... ]

[Severity: High]
This is a pre-existing issue, but could the error handling in
reset_monitor_maps() inadvertently skip resetting subsequent maps on failure?

tools/verification/rv/src/bpf_monitor.c:reset_monitor_maps() {
    ...
			while (!err && bpf_map_get_next_key(fd, NULL, key) == 0)
				err |= bpf_map_delete_elem(fd, key);
    ...
			for (uint32_t idx = 0; !err && idx < max_entries; idx++)
				err |= bpf_map_update_elem(fd, &idx, zero_value, BPF_ANY);
    ...
}

If an error occurs while clearing a map (e.g., malloc failure for the key, or
bpf_map_update_elem failure), the err flag is set to true. The inner loops
for all subsequent maps strictly require !err in their conditions, meaning
they will be silently skipped without being reset.

This leaves stale state from previous runs in the maps, which could corrupt
the monitor's internal state machine when bpf_run_monitor() is called.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=14

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

* Re: [RFC PATCH 15/20] tools/rv: Copy stripped bpf_atomic.h from libarena
  2026-08-31  9:05 ` [RFC PATCH 15/20] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
@ 2026-08-31  9:38   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:38 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: bpf, linux-trace-kernel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `smp_mb()` macro performs an atomic read-modify-write on an uninitialized local stack variable `__val`, which will be rejected by the BPF verifier on non-x86 architectures, breaking the tool completely.
--

commit 8906134488c74fb8663c6c7d837a2ee2c7d3ebb4
Author: Gabriele Monaco <gmonaco@redhat.com>

tools/rv: Copy stripped bpf_atomic.h from libarena

This commit adds bpf_atomic.h to tools/verification/rv/bpf_monitors,
providing atomic read/write macros like READ_ONCE() and try_cmpxchg(). It
is a stripped down copy of the version currently present in
selftests/bpf/libarena.

> 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 0000000000000..f834ea91c9d34
> --- /dev/null
> +++ b/tools/verification/rv/bpf_monitors/bpf_atomic.h
> @@ -0,0 +1,105 @@

[ ... ]

> +#define smp_mb()                                 \
> +	({                                       \
> +		volatile unsigned long __val;    \
> +		__sync_fetch_and_add(&__val, 0); \
> +	})

[Severity: Medium]
Will this macro cause the BPF verifier to reject the program on non-x86
architectures?

Because __val is an uninitialized local stack variable, the
__sync_fetch_and_add() instruction performs a read-modify-write on
uninitialized memory. The BPF verifier strictly enforces that memory read
by atomic instructions is initialized.

Since the variable evaluates as STACK_INVALID, the verifier will block the
monitor program with an invalid read from stack error. Should __val be
initialized to zero before the atomic fetch-and-add?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=15

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

* Re: [RFC PATCH 16/20] tools/rv: Add BPF monitors
  2026-08-31  9:05 ` [RFC PATCH 16/20] tools/rv: Add BPF monitors Gabriele Monaco
@ 2026-08-31  9:40   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:40 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tqueue monitor causes false positive invalid state transitions for tasks scheduling during do_exit().
--

commit 22065a19b365cd05306cad5f1def6f25a73bdc87
Author: Gabriele Monaco <gmonaco@redhat.com>

tools/rv: Add BPF monitors

This commit adds the code for 2 initial BPF monitors, both DA (the only
currently supported type): queue and nohz. It also integrates BPF monitor
building into the main rv Makefile.

> diff --git a/tools/verification/rv/bpf_monitors/tqueue.c b/tools/verification/rv/bpf_monitors/tqueue.c
> new file mode 100644
> index 0000000000000..c7516da01b65c
> --- /dev/null
> +++ b/tools/verification/rv/bpf_monitors/tqueue.c
> @@ -0,0 +1,35 @@

[ ... ]

> +SEC("tp_btf/sched_dequeue_tp")
> +int BPF_PROG(handle_sched_dequeue, struct task_struct *tsk, int cpu)
> +{
> +	if (!(tsk->flags & PF_EXITING))
> +		da_handle_start_event(tsk, sched_dequeue_tqueue);

[Severity: Medium]
Does skipping the dequeue event for exiting tasks here cause false positive
state transitions when the task is later enqueued?

If a task calls do_exit() (which sets PF_EXITING) and is preempted or sleeps
before reaching trace_sched_process_exit(), this logic skips the
sched_dequeue event. The state machine then incorrectly remains in the
"enqueued" state.

> +	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);

[Severity: Medium]
When the exiting task is rescheduled, handle_sched_enqueue() unconditionally
processes the sched_enqueue event.

Since the corresponding dequeue event was skipped above, won't this un-paired
enqueue cause the monitor to transition to an invalid state, breaking the
intended functionality and flooding the trace logs with false positive
violations?

> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=16

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

* Re: [RFC PATCH 18/20] verification/rvgen: Add support for BPF monitors
  2026-08-31  9:05 ` [RFC PATCH 18/20] verification/rvgen: Add support " Gabriele Monaco
@ 2026-08-31  9:41   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:41 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Generated BPF monitor arrays for state and event names lack null termination if names are 32 characters or longer, leading to out-of-bounds reads.
--

commit 53fecfe68f54b9383a7c0b099f9a1311ed5ff3ef
Author: Gabriele Monaco <gmonaco@redhat.com>

verification/rvgen: Add support for BPF monitors

This commit adds the -b flag to the rvgen monitor tool to generate BPF
monitors for deterministic automata. It updates the code generator to emit
BPF-compatible array structures and hooks while maintaining the existing
in-kernel monitor support.

> diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
> index 22938ce1bf6cb..5be9fe44a19b2 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];")

[Severity: Medium]
Could this lead to out-of-bounds reads in BPF trace output if the monitor
model has state or event names of 32 characters or longer?

When a fixed-size character array is initialized with a string literal equal
to or larger than its size, the null terminator is silently omitted in C.
If the names generated from the model are exactly 32 bytes or longer, these
arrays will lack a null terminator.

Since the BPF verifier permits reading .rodata, string formatting helpers
reading from these fields could run past the array bounds until a null byte
is encountered in adjacent memory. This could cause garbled trace output and
a minor info leak.

Would it make sense to enforce truncation of the source string during
generation, or explicitly ensure null termination here?

> +        else:
> +            buff.append(f"\tchar *state_names[state_max{self.enum_suffix}];")
> +            buff.append(f"\tchar *event_names[event_max{self.enum_suffix}];")

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=18

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

* Re: [RFC PATCH 19/20] tools/rv: Add selftest for rv bpf
  2026-08-31  9:05 ` [RFC PATCH 19/20] tools/rv: Add selftest for rv bpf Gabriele Monaco
@ 2026-08-31  9:44   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-08-31  9:44 UTC (permalink / raw)
  To: Gabriele Monaco; +Cc: linux-trace-kernel, bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] ABA vulnerability in tracefs UAF mitigation due to pointer reuse.
--

commit d6f30a79c0307745af03ca13f0fdeb754d0c04a9
Author: Gabriele Monaco <gmonaco@redhat.com>

tools/rv: Add selftest for rv bpf
    
Add selftest cases for the rv userspace tool BPF commands and monitors,
test BPF monitors registration, unregistration and functionality,
including traces and reactors.

> diff --git a/tools/verification/rv/tests/rv_bpf.t b/tools/verification/rv/tests/rv_bpf.t
> new file mode 100644
> index 0000000000000..902343f0d3f10
> --- /dev/null
> +++ b/tools/verification/rv/tests/rv_bpf.t
[ ... ]
> +check "already enabled monitor returns error" \
> +	"echo 1 > $RVDIR/monitors/nohz/enable; $RV mon nohz" 1 \
> +	"monitor nohz (BPF) is already enabled"
> +[ -n "$TEST_COUNT" ] && echo 0 > $RVDIR/monitors/nohz/enable
> +
> +check "already enabled monitor prevents unregistration" \
> +	"echo 1 > $RVDIR/monitors/nohz/enable; $RV bpf unregister" 1 \
> +	"monitor nohz is enabled, cannot unregister" \
> +	"unregistered [0-9]\+ BPF monitor(s)"
> +[ -n "$TEST_COUNT" ] && echo 0 > $RVDIR/monitors/nohz/enable
> +
> +check "unregister BPF monitors" \
> +	"$RV bpf unregister" 0 "unregistered [0-9]\+ BPF monitor(s)"

[Severity: Medium]
Could this sequence of registration and unregistration expose an ABA
vulnerability in the tracefs use-after-free mitigation?

When a monitor is unregistered here, its struct rv_monitor inside the
bpf_struct_ops map can be freed. If a user still holds a tracefs file
(such as enable or reactors) open for this monitor, filp->private_data
becomes a dangling pointer.

If a new monitor is then loaded and allocated at the exact same memory
address, the pointer check in monitor_enable_write_data() might pass
incorrectly:

kernel/trace/rv/rv.c:monitor_enable_write_data() {
	...
	guard(mutex)(&rv_interface_lock);

	if (!rv_is_monitor_registered(mon))
		return -ENODEV;

	if (val)
		retval = rv_enable_monitor(mon);
	...
}

Since rv_is_monitor_registered() simply checks if the raw pointer mon
exists in rv_monitors_list, it would return true for the new monitor.
This would allow a privileged user to accidentally or intentionally
alter the state of the newly registered monitor using the old file
descriptor.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831090524.106845-1-gmonaco@redhat.com?part=19

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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
                   ` (19 preceding siblings ...)
  2026-08-31  9:05 ` [RFC PATCH 20/20] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
@ 2026-09-01 18:35 ` Nam Cao
  2026-09-02  6:52   ` Gabriele Monaco
  20 siblings, 1 reply; 39+ messages in thread
From: Nam Cao @ 2026-09-01 18:35 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, linux-trace-kernel, bpf
  Cc: Gabriele Monaco, Steven Rostedt, Wen Yang, Tobias Schaffner,
	Viktor Malik

Gabriele Monaco <gmonaco@redhat.com> writes:
> 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.

I am not familiar with BPF. What is the benefit of BPF monitors,
compared to the existing DA monitors?

Nam

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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-01 18:35 ` [RFC PATCH 00/20] rv: Add support for BPF monitors Nam Cao
@ 2026-09-02  6:52   ` Gabriele Monaco
  2026-09-02  7:46     ` Nam Cao
  2026-09-03  1:57     ` Alexei Starovoitov
  0 siblings, 2 replies; 39+ messages in thread
From: Gabriele Monaco @ 2026-09-02  6:52 UTC (permalink / raw)
  To: Nam Cao, linux-kernel, linux-trace-kernel, bpf
  Cc: Steven Rostedt, Wen Yang, Tobias Schaffner, Viktor Malik

On Tue, 2026-09-01 at 20:35 +0200, Nam Cao wrote:
> Gabriele Monaco <gmonaco@redhat.com> writes:
> > 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.
> 
> I am not familiar with BPF. What is the benefit of BPF monitors,
> compared to the existing DA monitors?

I should definitely have included it in the cover letter.. I'm writing it
everywhere (will present at LPC) but forgot it here.

Essentially BPF monitors can be pluggable, folks writing their own monitors
won't need to submit a patch or maintain a separate tree, which is useful for
domain-specific models.
By being pluggable you also don't need to reboot to use a new/updated monitor.

Think of being able to distribute a more granular set of rules for RTapp, I
remember we had conversation along those lines, not all rules apply to all
contexts and what you send upstream has to be general, what you keep for
yourself doesn't.

Having monitors in BPF brings also other perks over kernel modules: a whole
bunch of readily available probe types (uprobes, fprobes, all unexported
tracepoints that are cumbersome for modules), the map infrastructure for
allocation is arguably easier and the code is verified when loaded against
common issues (NULL pointer access, unbound loops, etc.).

That said, I try to mimic as much as possible the in-kernel functionality, but
some things are not the same (event/error tracepoints).

These support DA only because BPF loading needs a userspace component and the RV
tool doesn't support LTL and HA yet, there shouldn't be any technical reason not
to extend to those in the future.

Gabriele


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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-02  6:52   ` Gabriele Monaco
@ 2026-09-02  7:46     ` Nam Cao
  2026-09-03  1:57     ` Alexei Starovoitov
  1 sibling, 0 replies; 39+ messages in thread
From: Nam Cao @ 2026-09-02  7:46 UTC (permalink / raw)
  To: Gabriele Monaco, linux-kernel, linux-trace-kernel, bpf
  Cc: Steven Rostedt, Wen Yang, Tobias Schaffner, Viktor Malik

Gabriele Monaco <gmonaco@redhat.com> writes:
> I should definitely have included it in the cover letter.. I'm writing it
> everywhere (will present at LPC) but forgot it here.
>
> Essentially BPF monitors can be pluggable, folks writing their own monitors
> won't need to submit a patch or maintain a separate tree, which is useful for
> domain-specific models.
> By being pluggable you also don't need to reboot to use a new/updated monitor.
>
> Think of being able to distribute a more granular set of rules for RTapp, I
> remember we had conversation along those lines, not all rules apply to all
> contexts and what you send upstream has to be general, what you keep for
> yourself doesn't.
>
> Having monitors in BPF brings also other perks over kernel modules: a whole
> bunch of readily available probe types (uprobes, fprobes, all unexported
> tracepoints that are cumbersome for modules), the map infrastructure for
> allocation is arguably easier and the code is verified when loaded against
> common issues (NULL pointer access, unbound loops, etc.).
>
> That said, I try to mimic as much as possible the in-kernel functionality, but
> some things are not the same (event/error tracepoints).
>
> These support DA only because BPF loading needs a userspace component and the RV
> tool doesn't support LTL and HA yet, there shouldn't be any technical reason not
> to extend to those in the future.

Cool. I also wanted to do something like this, for these exact same
reasons. Let me look at your patches..

Nam

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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-02  6:52   ` Gabriele Monaco
  2026-09-02  7:46     ` Nam Cao
@ 2026-09-03  1:57     ` Alexei Starovoitov
  2026-09-03  7:21       ` Gabriele Monaco
  2026-09-04 11:23       ` Tomas Glozar
  1 sibling, 2 replies; 39+ messages in thread
From: Alexei Starovoitov @ 2026-09-03  1:57 UTC (permalink / raw)
  To: Gabriele Monaco, Nam Cao, linux-kernel, linux-trace-kernel, bpf
  Cc: Steven Rostedt, Wen Yang, Tobias Schaffner, Viktor Malik

On Tue Sep 1, 2026 at 11:52 PM PDT, Gabriele Monaco wrote:
> On Tue, 2026-09-01 at 20:35 +0200, Nam Cao wrote:
>> Gabriele Monaco <gmonaco@redhat.com> writes:
>> > 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.
>> 
>> I am not familiar with BPF. What is the benefit of BPF monitors,
>> compared to the existing DA monitors?
>
> I should definitely have included it in the cover letter.. I'm writing it
> everywhere (will present at LPC) but forgot it here.
>
> Essentially BPF monitors can be pluggable, folks writing their own monitors
> won't need to submit a patch or maintain a separate tree, which is useful for
> domain-specific models.
> By being pluggable you also don't need to reboot to use a new/updated monitor.
>
> Think of being able to distribute a more granular set of rules for RTapp, I
> remember we had conversation along those lines, not all rules apply to all
> contexts and what you send upstream has to be general, what you keep for
> yourself doesn't.
>
> Having monitors in BPF brings also other perks over kernel modules: a whole
> bunch of readily available probe types (uprobes, fprobes, all unexported
> tracepoints that are cumbersome for modules), the map infrastructure for
> allocation is arguably easier and the code is verified when loaded against
> common issues (NULL pointer access, unbound loops, etc.).
>
> That said, I try to mimic as much as possible the in-kernel functionality, but
> some things are not the same (event/error tracepoints).
>
> These support DA only because BPF loading needs a userspace component and the RV
> tool doesn't support LTL and HA yet, there shouldn't be any technical reason not
> to extend to those in the future.

I don't think bpf fits here. I haven't seen active use of RV and even less
so of any request from people who want this kind of programmability.

So Nack for now. Sorry.


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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-03  1:57     ` Alexei Starovoitov
@ 2026-09-03  7:21       ` Gabriele Monaco
  2026-09-03 13:02         ` Steven Rostedt
  2026-09-04 11:23       ` Tomas Glozar
  1 sibling, 1 reply; 39+ messages in thread
From: Gabriele Monaco @ 2026-09-03  7:21 UTC (permalink / raw)
  To: Alexei Starovoitov, Nam Cao, linux-kernel, linux-trace-kernel,
	bpf
  Cc: Steven Rostedt, Wen Yang, Tobias Schaffner, Viktor Malik

On Wed, 2026-09-02 at 18:57 -0700, Alexei Starovoitov wrote:
> I don't think bpf fits here. I haven't seen active use of RV and even less
> so of any request from people who want this kind of programmability.
> 
> So Nack for now. Sorry.

Thanks for your opinion, however it sounds a bit harsh to me.

RV is clearly not a popular tool, but I wouldn't say it doesn't have active use:
we're actively using it to validate the deadline scheduler/server changes and it
already found a number of issues (e.g. [1], [2]).
The RTapp monitors are used to validate if userspace real-time applications
interact with the kernel in the best way for predictability, as far as I'm aware
that's regularly used [3].

In my eyes, the missing piece of RV is in fact this programmability. Adding a
monitor now requires to write in-tree built-in kernel code.

There are strong use cases for domain-specific rules that aren't general enough
to belong in the kernel tree:
* stricter RTapp rules that may not apply to all use-cases
* verify some exotic system within Linux (e.g. a co-kernel [4])
* modelling for functional safety certification process (used for in-vehicle
OSes like RHIVOS)

BPF seems the natural tool for the job to me. Mind that RV monitors would be
simple users of BPF, requiring only modifications in the RV subsystem but using
standard BPF facilities.

I'm totally open to discuss on this though.

Thanks,
Gabriele

[1] - https://lore.kernel.org/lkml/20260522125833.264145-1-gmonaco@redhat.com
[2] - https://lore.kernel.org/lkml/20260113085159.114226-3-gmonaco@redhat.com
[3] -
https://lore.kernel.org/linux-rt-users/20260826132153.2476006-1-bigeasy@linutronix.de/T/#t
[4] -
https://lore.kernel.org/lkml/20260827072400.45734-1-tobias.schaffner@siemens.com


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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-03  7:21       ` Gabriele Monaco
@ 2026-09-03 13:02         ` Steven Rostedt
  2026-09-04  3:30           ` Alexei Starovoitov
  0 siblings, 1 reply; 39+ messages in thread
From: Steven Rostedt @ 2026-09-03 13:02 UTC (permalink / raw)
  To: Gabriele Monaco
  Cc: Alexei Starovoitov, Nam Cao, linux-kernel, linux-trace-kernel,
	bpf, Wen Yang, Tobias Schaffner, Viktor Malik, Linus Torvalds

On Thu, 03 Sep 2026 09:21:18 +0200
Gabriele Monaco <gmonaco@redhat.com> wrote:

> On Wed, 2026-09-02 at 18:57 -0700, Alexei Starovoitov wrote:
> > I don't think bpf fits here. I haven't seen active use of RV and even less
> > so of any request from people who want this kind of programmability.
> > 
> > So Nack for now. Sorry.  
> 
> Thanks for your opinion, however it sounds a bit harsh to me.
> 
> RV is clearly not a popular tool, but I wouldn't say it doesn't have active use:
> we're actively using it to validate the deadline scheduler/server changes and it
> already found a number of issues (e.g. [1], [2]).
> The RTapp monitors are used to validate if userspace real-time applications
> interact with the kernel in the best way for predictability, as far as I'm aware
> that's regularly used [3].
> 
> In my eyes, the missing piece of RV is in fact this programmability. Adding a
> monitor now requires to write in-tree built-in kernel code.
> 
> There are strong use cases for domain-specific rules that aren't general enough
> to belong in the kernel tree:
> * stricter RTapp rules that may not apply to all use-cases
> * verify some exotic system within Linux (e.g. a co-kernel [4])
> * modelling for functional safety certification process (used for in-vehicle
> OSes like RHIVOS)
> 
> BPF seems the natural tool for the job to me. Mind that RV monitors would be
> simple users of BPF, requiring only modifications in the RV subsystem but using
> standard BPF facilities.
> 

As RV would simply be a user of BPF and not modifying BPF infrastructure,
I'm not so sure you have the jurisdiction to NAK it. It would be like me
NAKing how you create a trace event.

RV monitors are a very good way to make sure the system is preforming
properly. This looks exactly like a perfect use case for using BPF instead
of having to install modules.

Either BPF is a tool for the kernel or it isn't. You can't judge where BPF
gets used. You can only judge on modifications to BFP.

-- Steve


> I'm totally open to discuss on this though.
> 
> Thanks,
> Gabriele
> 
> [1] - https://lore.kernel.org/lkml/20260522125833.264145-1-gmonaco@redhat.com
> [2] - https://lore.kernel.org/lkml/20260113085159.114226-3-gmonaco@redhat.com
> [3] -
> https://lore.kernel.org/linux-rt-users/20260826132153.2476006-1-bigeasy@linutronix.de/T/#t
> [4] -
> https://lore.kernel.org/lkml/20260827072400.45734-1-tobias.schaffner@siemens.com


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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-03 13:02         ` Steven Rostedt
@ 2026-09-04  3:30           ` Alexei Starovoitov
  2026-09-04 11:43             ` Steven Rostedt
  0 siblings, 1 reply; 39+ messages in thread
From: Alexei Starovoitov @ 2026-09-04  3:30 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Gabriele Monaco, Nam Cao, LKML, linux-trace-kernel, bpf, Wen Yang,
	Tobias Schaffner, Viktor Malik, Linus Torvalds

On Thu, Sep 3, 2026 at 6:01 AM Steven Rostedt <rostedt@goodmis.org> wrote:
>
> On Thu, 03 Sep 2026 09:21:18 +0200
> Gabriele Monaco <gmonaco@redhat.com> wrote:
>
> > On Wed, 2026-09-02 at 18:57 -0700, Alexei Starovoitov wrote:
> > > I don't think bpf fits here. I haven't seen active use of RV and even less
> > > so of any request from people who want this kind of programmability.
> > >
> > > So Nack for now. Sorry.
> >
> > Thanks for your opinion, however it sounds a bit harsh to me.
> >
> > RV is clearly not a popular tool, but I wouldn't say it doesn't have active use:
> > we're actively using it to validate the deadline scheduler/server changes and it
> > already found a number of issues (e.g. [1], [2]).
> > The RTapp monitors are used to validate if userspace real-time applications
> > interact with the kernel in the best way for predictability, as far as I'm aware
> > that's regularly used [3].
> >
> > In my eyes, the missing piece of RV is in fact this programmability. Adding a
> > monitor now requires to write in-tree built-in kernel code.
> >
> > There are strong use cases for domain-specific rules that aren't general enough
> > to belong in the kernel tree:
> > * stricter RTapp rules that may not apply to all use-cases
> > * verify some exotic system within Linux (e.g. a co-kernel [4])
> > * modelling for functional safety certification process (used for in-vehicle
> > OSes like RHIVOS)
> >
> > BPF seems the natural tool for the job to me. Mind that RV monitors would be
> > simple users of BPF, requiring only modifications in the RV subsystem but using
> > standard BPF facilities.
> >
>
> As RV would simply be a user of BPF and not modifying BPF infrastructure,
> I'm not so sure you have the jurisdiction to NAK it. It would be like me
> NAKing how you create a trace event.
>
> RV monitors are a very good way to make sure the system is preforming
> properly. This looks exactly like a perfect use case for using BPF instead
> of having to install modules.
>
> Either BPF is a tool for the kernel or it isn't. You can't judge where BPF
> gets used. You can only judge on modifications to BFP.

Ok.
Please add
Nacked-by: Alexei Starovoitov <ast@kernel.org>

when you submit it to Linus.
Let him decide whether bpf maintainers have an authority
to say where bpf is used in the kernel.

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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-03  1:57     ` Alexei Starovoitov
  2026-09-03  7:21       ` Gabriele Monaco
@ 2026-09-04 11:23       ` Tomas Glozar
  1 sibling, 0 replies; 39+ messages in thread
From: Tomas Glozar @ 2026-09-04 11:23 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Gabriele Monaco, Nam Cao, linux-kernel, linux-trace-kernel, bpf,
	Steven Rostedt, Wen Yang, Tobias Schaffner, Viktor Malik

On Thu, Sep 3, 2026 at 4:03 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> I don't think bpf fits here. I haven't seen active use of RV and even less
> so of any request from people who want this kind of programmability.
>
> So Nack for now. Sorry.
>

A major application of RV is monitoring the combined behavior of the
kernel and an external application, see [1] [2]. Adopting RV for these
use cases is difficult because the monitors are currently built into
the kernel. Blocking BPF adoption because it is not widely used risks
a deadlock: RV not getting enough users, because it cannot use BPF,
and BPF rejecting RV because it lacks sufficient use.

Of course, I might be entirely wrong: RV may not end up being
interesting enough to users even with the BPF hooks in place, or it
might grow enough with only in-kernel monitors for you to take it
seriously. However, we cannot predict that in advance.

(Note also that the idea to use BPF in RV dates back to 2022 [3].)

[1] https://lore.kernel.org/linux-trace-kernel/20260827072400.45734-1-tobias.schaffner@siemens.com/T/#e8a8b21136a60ad7c8f67b07c66f71de0ec0c1075
[2] https://docs.kernel.org/trace/rv/monitor_rtapp.html
[3] https://gitlab.com/linux-rv-tools/dot2bpf

Tomas


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

* Re: [RFC PATCH 00/20] rv: Add support for BPF monitors
  2026-09-04  3:30           ` Alexei Starovoitov
@ 2026-09-04 11:43             ` Steven Rostedt
  0 siblings, 0 replies; 39+ messages in thread
From: Steven Rostedt @ 2026-09-04 11:43 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Gabriele Monaco, Nam Cao, LKML, linux-trace-kernel, bpf, Wen Yang,
	Tobias Schaffner, Viktor Malik, Linus Torvalds

On Thu, 3 Sep 2026 20:30:19 -0700
Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote:

> > > On Wed, 2026-09-02 at 18:57 -0700, Alexei Starovoitov wrote:  
> > > > I don't think bpf fits here. I haven't seen active use of RV and even less
> > > > so of any request from people who want this kind of programmability.
> > > >
> > > > So Nack for now. Sorry.  
> > >

> 
> Ok.
> Please add
> Nacked-by: Alexei Starovoitov <ast@kernel.org>
> 
> when you submit it to Linus.
> Let him decide whether bpf maintainers have an authority
> to say where bpf is used in the kernel.

When this is ready, I'll be happy to inform Linus on the pull request
that you NACKed it, if you are still against it by then. He's Cc'd on
this conversation now. But you left out any technical reason for the
NACK. You basically just said "I don't see how this is useful to me".
That's not a valid reason for a NACK.

Gabriele will be presenting this work at Linux Plumbers[1]. This will
not be going in before then. I would strongly recommend attending
Gabriele's session and we can then go into the technical arguments
about the use of BPF there. If you bring up valid technical reasons
against it, then we will either work with you to solve those technical
issues, and if they can't be solved then sure, it will not go in. But
if it's just your opinion about the work not being useful to you, then
I will push this forward, as it is very useful to others.

Cheers,

-- Steve

[1] https://lpc.events/event/20/contributions/2446/

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

end of thread, other threads:[~2026-09-04 11:43 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31  9:05 [RFC PATCH 00/20] rv: Add support for BPF monitors Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 01/20] sched: Add task enqueue/dequeue trace points Gabriele Monaco
2026-08-31  9:32   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 02/20] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 03/20] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 04/20] rv: Use static arrays for rv_monitor name and description Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 05/20] rv: Add in-kernel support for BPF monitors Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 06/20] rv: Add rv_get_monitor_by_name() Gabriele Monaco
2026-08-31  9:24   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 07/20] rv: Add reactors support to BPF monitors Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 08/20] rv: Cast result of model_get_*_name() Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 09/20] rv: Handle unregistered monitors safely in tracefs Gabriele Monaco
2026-08-31  9:24   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 10/20] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 11/20] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 12/20] tools/rv: Export functionality for in_kernel monitors Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 13/20] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
2026-08-31  9:34   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 14/20] tools/rv: Implement BPF monitor registration logic Gabriele Monaco
2026-08-31  9:38   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 15/20] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
2026-08-31  9:38   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 16/20] tools/rv: Add BPF monitors Gabriele Monaco
2026-08-31  9:40   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 17/20] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
2026-08-31  9:05 ` [RFC PATCH 18/20] verification/rvgen: Add support " Gabriele Monaco
2026-08-31  9:41   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 19/20] tools/rv: Add selftest for rv bpf Gabriele Monaco
2026-08-31  9:44   ` sashiko-bot
2026-08-31  9:05 ` [RFC PATCH 20/20] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
2026-09-01 18:35 ` [RFC PATCH 00/20] rv: Add support for BPF monitors Nam Cao
2026-09-02  6:52   ` Gabriele Monaco
2026-09-02  7:46     ` Nam Cao
2026-09-03  1:57     ` Alexei Starovoitov
2026-09-03  7:21       ` Gabriele Monaco
2026-09-03 13:02         ` Steven Rostedt
2026-09-04  3:30           ` Alexei Starovoitov
2026-09-04 11:43             ` Steven Rostedt
2026-09-04 11:23       ` Tomas Glozar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).