* [PATCH v8 00/14] rv: Add task latency over budget RV monitor
@ 2026-11-05 17:49 wen.yang
2026-11-05 17:49 ` [PATCH v8 01/14] rv: Force environment reset action on HA monitor start wen.yang
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
This series introduces tlob (task latency over budget), a per-task
hybrid automaton RV monitor that measures elapsed wall-clock time across
a user-delimited code section and emits an error when the elapsed time
exceeds a configurable budget.
The monitor tracks four states (running, waiting, sleeping, stopped)
driven by sched_switch and sched_wakeup tracepoints. A single clock
invariant, clk_elapsed < BUDGET_NS(), is enforced by a per-task hrtimer
(HRTIMER_MODE_REL_HARD). On expiry error_env_tlob is emitted; which
phase consumed the budget (running, waiting or sleeping) is recovered
from event_tlob in user space (see monitor_tlob.rst).
Tasks are registered for monitoring by writing to the tracefs monitor
file:
echo "p:myregion /path/to/binary:OFFSET_START OFFSET_STOP threshold=NS" \
>> /sys/kernel/tracing/rv/monitors/tlob/monitor
Two uprobes delimit the measured section without modifying the target
binary. Multiple uprobe pairs can be active simultaneously. A task may
repeat the START/STOP sequence any number of times: the entry parks in
the automaton's "stopped" state and restarts in place, reusing the
same pre-allocated slot.
Changes in v8
=============
This version is a comprehensive rework addressing Gabriele Monaco's
review on v7, with substantial simplifications to both framework
support and the tlob monitor itself:
1. HA timer teardown and underflow fixes (Patches 1-5):
- Deleted ha_timer_ready() completely. Following Gabriele's
suggestion, ha_monitor_free_env() now checks da_monitoring() to
disarm the hrtimer on the storage free path (Patch 4).
- Isolated the HA underflow and timer free path KUnit tests into a
dedicated compilation unit (kernel/trace/rv/rv_ha_kunit.c) guarded
by CONFIG_RV_HA_MONITOR, avoiding polluting rv_monitors_test.c
(Patches 3, 5).
2. Framework per-object storage improvements (Patches 6-8):
- Added DA_OBJ_EMBEDDED_TYPE support based on Gabriele's PoC.
Per-object DA storage can now directly embed monitor-specific
target data inside struct da_monitor_storage, completely eliminating
secondary dynamic memory allocations and double pointer dereferences
(Patch 8).
- Added smp_store_release/smp_load_acquire memory barriers between
da_storage_set_target() and da_storage_is_empty(), and ensured
da_get_target() returns NULL if the storage slot is not yet filled.
3. Generic uprobe helper infrastructure (Patch 9):
- Extracted reusable rv_uprobe registration and synchronization
helpers into include/rv/rv_uprobe.h and kernel/trace/rv/rv_uprobe.c.
4. Major tlob simplification (Patch 11):
- Reduced tlob.c from ~1065 lines to ~660 lines (-38% code size).
- Completely removed the private mempool (tlob_record_pool) and custom
allocator; tlob now relies entirely on the framework's pre-allocated
storage via DA_MON_POOL_SIZE.
- Removed the parallel state machine (OPEN, CLOSED, RETIRED) and
tlob_binding_lock. Window lifecycle is now 100% managed by the
automaton states and framework storage lifecycle.
- Replaced complex quiesce/reap/RCU callouts with a clean RCU walk
(tlob_release_binding()) executed after rv_uprobe_unregister_sync().
- Optimized timer re-arming on the scheduler hot path.
- Refined error semantics: nested START is detected as an illegal event
(running x start) and reported via error_tlob without altering the
running window; pool exhaustion cleanly returns -ENOSPC.
5. Test coverage and selftests expansion (Patches 12-14):
- Added 3 new selftests covering critical edge cases:
* uprobe_task_exit.tc: verifies task exit/SIGKILL inside an active
window disarms the timer with no orphaned/late violations.
* uprobe_nested_start.tc: verifies nested START reporting and
window integrity.
* uprobe_pool_exhausted.tc: verifies the 64-task cap and graceful
rejection of the 65th task.
- Extended tlob_target with "nest" mode.
- All 13 selftests and all KUnit test suites pass cleanly.
Series structure
================
Part 1: Independent fixes and enhancements to the RV/HA framework
Patch 1: rv: Force environment reset action on HA monitor start
Patch 2: rv: Fix ha_monitor timer and invariant underflow
Patch 3: rv: Add a KUnit test for the HA timer underflow
Patch 4: rv: Disarm HA timers on the storage free path
Patch 5: rv: Add a KUnit test for the storage free path
Patch 6: rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL
overridable
Patch 7: rv: Introduce DA_MON_ALLOCATION_STRATEGY
Patch 8: rv: Support embedded target types in per-object DA storage
Patch 9: rv: Add generic uprobe infrastructure for RV monitors
Part 2: The tlob monitor and models
Patch 10: rv: Add tlob model DOT file
Patch 11: rv: Add tlob hybrid automaton monitor
Part 3: Testing and selftests
Patch 12: rv: Add KUnit tests for the tlob monitor
Patch 13: selftests/verification: Add tlob selftests
Patch 14: selftests/ftrace: Walk up parent dirs to find test.d/functions
v7: https://lore.kernel.org/all/cover.1791745088.git.wen.yang@linux.dev/
v6: https://lore.kernel.org/all/cover.1787243842.git.wen.yang@linux.dev/
v4: https://lore.kernel.org/all/cover.1783524627.git.wen.yang@linux.dev/
v3: https://lore.kernel.org/all/cover.1780847473.git.wen.yang@linux.dev/
v2: https://lore.kernel.org/all/cover.1778522945.git.wen.yang@linux.dev/
v1: https://lore.kernel.org/all/cover.1776020428.git.wen.yang@linux.dev/
Gabriele Monaco (1):
rv: Force environment reset action on HA monitor start
Wen Yang (13):
rv: Fix ha_monitor timer and invariant underflow
rv: Add a KUnit test for the HA timer underflow
rv: Disarm HA timers on the storage free path
rv: Add a KUnit test for the storage free path
rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL
overridable
rv: Introduce DA_MON_ALLOCATION_STRATEGY
rv: Support embedded target types in per-object DA storage
rv: Add generic uprobe infrastructure for RV monitors
rv: Add tlob model DOT file
rv: Add tlob hybrid automaton monitor
rv: Add KUnit tests for the tlob monitor
selftests/verification: Add tlob selftests
selftests/ftrace: Walk up parent dirs to find test.d/functions
Documentation/trace/rv/index.rst | 1 +
Documentation/trace/rv/monitor_tlob.rst | 162 ++++
include/rv/da_monitor.h | 226 +++++-
include/rv/ha_monitor.h | 53 +-
include/rv/rv_uprobe.h | 35 +
kernel/trace/rv/Kconfig | 5 +
kernel/trace/rv/Makefile | 3 +
kernel/trace/rv/monitors/nomiss/nomiss.c | 2 +-
kernel/trace/rv/monitors/opid/opid.h | 1 +
kernel/trace/rv/monitors/tlob/Kconfig | 12 +
kernel/trace/rv/monitors/tlob/tlob.c | 743 ++++++++++++++++++
kernel/trace/rv/monitors/tlob/tlob.h | 143 ++++
kernel/trace/rv/monitors/tlob/tlob_kunit.c | 151 ++++
kernel/trace/rv/monitors/tlob/tlob_kunit.h | 18 +
kernel/trace/rv/monitors/tlob/tlob_trace.h | 20 +
kernel/trace/rv/rv_ha_kunit.c | 201 +++++
kernel/trace/rv/rv_monitors_test.c | 2 +
kernel/trace/rv/rv_trace.h | 1 +
kernel/trace/rv/rv_uprobe.c | 56 ++
tools/testing/selftests/ftrace/ftracetest | 19 +-
.../testing/selftests/verification/.gitignore | 1 +
tools/testing/selftests/verification/Makefile | 4 +
tools/testing/selftests/verification/config | 3 +
.../selftests/verification/test.d/functions | 9 +
.../test.d/tlob/run_tlob_tests.sh | 32 +
.../verification/test.d/tlob/uprobe_bind.tc | 47 ++
.../test.d/tlob/uprobe_disable_live.tc | 79 ++
.../verification/test.d/tlob/uprobe_multi.tc | 58 ++
.../test.d/tlob/uprobe_multi_instance.tc | 65 ++
.../test.d/tlob/uprobe_nested_start.tc | 62 ++
.../test.d/tlob/uprobe_no_event.tc | 17 +
.../test.d/tlob/uprobe_overlap_register.tc | 66 ++
.../test.d/tlob/uprobe_pool_exhausted.tc | 75 ++
.../test.d/tlob/uprobe_restart.tc | 66 ++
.../test.d/tlob/uprobe_restart_violation.tc | 48 ++
.../test.d/tlob/uprobe_task_exit.tc | 65 ++
.../test.d/tlob/uprobe_unbind_live.tc | 65 ++
.../test.d/tlob/uprobe_violation.tc | 53 ++
.../selftests/verification/tlob_target.c | 156 ++++
tools/verification/models/tlob.dot | 27 +
tools/verification/rvgen/rvgen/dot2c.py | 2 +
41 files changed, 2829 insertions(+), 25 deletions(-)
create mode 100644 Documentation/trace/rv/monitor_tlob.rst
create mode 100644 include/rv/rv_uprobe.h
create mode 100644 kernel/trace/rv/monitors/tlob/Kconfig
create mode 100644 kernel/trace/rv/monitors/tlob/tlob.c
create mode 100644 kernel/trace/rv/monitors/tlob/tlob.h
create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.h
create mode 100644 kernel/trace/rv/monitors/tlob/tlob_trace.h
create mode 100644 kernel/trace/rv/rv_ha_kunit.c
create mode 100644 kernel/trace/rv/rv_uprobe.c
create mode 100755 tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_nested_start.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_pool_exhausted.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_task_exit.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc
create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
create mode 100644 tools/testing/selftests/verification/tlob_target.c
create mode 100644 tools/verification/models/tlob.dot
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v8 01/14] rv: Force environment reset action on HA monitor start
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 02/14] rv: Fix ha_monitor timer and invariant underflow wen.yang
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Gabriele Monaco <gmonaco@redhat.com>
Currently the monitor variables with a storage (e.g. clocks) are
initialised as invalid, then the first explicit reset() sets them as
valid for constraint. This only adds extra complexity to handle the
invalid case in constraints check.
Apply a reset() by default when starting monitors instead of resetting
to an invalid state. If monitors have no stored variable (hence no reset
function) add a macro HA_NO_RESET to stub it, this is all transparently
handled by rvgen.
The reset() action on ns-granularity clocks needs the (possibly cached)
current time, read it directly there. This might cause a double call to
ktime_get_ns() within the same da_handle_start_run_event() but will
occur only the first time and is harmless.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
Tested-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/ha_monitor.h | 8 +++++++-
kernel/trace/rv/monitors/opid/opid.h | 1 +
tools/verification/rvgen/rvgen/dot2c.py | 2 ++
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 6e1c7fe5449a..93ac99ff7f1d 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -159,19 +159,25 @@ static void ha_monitor_destroy(void)
/* Should be supplied by the monitor */
static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
static bool ha_verify_constraint(struct ha_monitor *ha_mon,
enum states curr_state,
enum events event,
enum states next_state,
u64 time_ns);
+#ifdef HA_NO_RESET
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) { }
+#endif
/*
* ha_monitor_reset_all_stored - reset all environment variables in the monitor
*/
static inline void ha_monitor_reset_all_stored(struct ha_monitor *ha_mon)
{
+ u64 time_ns = ha_get_ns();
+
for (int i = 0; i < ENV_MAX_STORED; i++)
- WRITE_ONCE(ha_mon->env_store[i], ENV_INVALID_VALUE);
+ ha_reset_env(ha_mon, i, time_ns);
}
/*
diff --git a/kernel/trace/rv/monitors/opid/opid.h b/kernel/trace/rv/monitors/opid/opid.h
index fb0aa4c28aa6..f85b1959c2fb 100644
--- a/kernel/trace/rv/monitors/opid/opid.h
+++ b/kernel/trace/rv/monitors/opid/opid.h
@@ -28,6 +28,7 @@ enum envs_opid {
};
_Static_assert(env_max_stored_opid <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_NO_RESET
struct automaton_opid {
char *state_names[state_max_opid];
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index 22938ce1bf6c..1532e6b6e199 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -90,6 +90,8 @@ class Dot2c(Automata):
' "Not enough slots");')
if {"ns", "us", "ms", "s"}.intersection(self.env_types.values()):
buff.append("#define HA_CLK_NS")
+ if len(self.env_stored) == 0:
+ buff.append("#define HA_NO_RESET")
buff.append("")
return buff
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 02/14] rv: Fix ha_monitor timer and invariant underflow
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
2026-11-05 17:49 ` [PATCH v8 01/14] rv: Force environment reset action on HA monitor start wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang, sashiko-bot
From: Wen Yang <wen.yang@linux.dev>
ha_start_timer_ns() and ha_start_timer_jiffy() compute the remaining
budget as expire - passed. Once the elapsed time passed exceeds the
budget expire (e.g. a state transition after the invariant should have
fired, before the timer callback ran), the unsigned subtraction
underflows to a huge value and the timer is scheduled far into the
future instead of expiring immediately, so the monitor stops enforcing
the budget.
ha_check_invariant_ns() computes store >= time_ns - expire_ns, which
underflows when the system uptime is still below expire_ns (a monitor
armed early in boot with a large budget), reporting a false violation.
Clamp the timer remainder to 0 (fire immediately) when passed >= expire,
and rewrite the invariant check as store + expire_ns >= time_ns so
neither side can underflow. Both timer flavours (jiffy and hrtimer) are
fixed.
Fixes: f5587d1b6ec9 ("rv: Add Hybrid Automata monitor type")
Fixes: ab2900ae252b ("rv: Simplify hybrid automata monitors's clock variables")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/ha_monitor.h | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 93ac99ff7f1d..ebb7f91e3447 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -351,7 +351,11 @@ static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64
static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon, enum envs env,
u64 time_ns, u64 expire_ns)
{
- return READ_ONCE(ha_mon->env_store[env]) >= time_ns - expire_ns;
+ /*
+ * Compare as store + expire >= time to avoid underflow when the
+ * system uptime is still below expire_ns (early boot, large budget).
+ */
+ return READ_ONCE(ha_mon->env_store[env]) + expire_ns >= time_ns;
}
/*
* ha_invariant_passed_ns - prepare the invariant and return the time since reset
@@ -410,6 +414,17 @@ static inline void ha_inv_to_guard(struct ha_monitor *ha_mon, enum envs env,
WRITE_ONCE(ha_mon->env_store[env], READ_ONCE(ha_mon->env_store[env]) - value);
}
+/*
+ * ha_time_left - time left before expire, 0 once it already passed
+ *
+ * A plain expire - passed underflows and would arm the timer far in the
+ * future. Unit-agnostic: callers pass ns or jiffies consistently.
+ */
+static inline u64 ha_time_left(u64 passed, u64 expire)
+{
+ return passed < expire ? expire - passed : 0;
+}
+
#if HA_TIMER_TYPE == HA_TIMER_WHEEL
/*
* Helper functions to handle the monitor timer.
@@ -439,15 +454,17 @@ static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env
{
u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns);
- mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed);
+ mod_timer(&ha_mon->timer,
+ get_jiffies_64() + ha_time_left(passed, expire));
}
static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
u64 expire, u64 time_ns)
{
u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns);
+ u64 delay = ha_time_left(passed, expire);
ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED,
- nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns);
+ nsecs_to_jiffies(delay + TICK_NSEC - 1), time_ns);
}
/*
* ha_cancel_timer - Cancel the timer
@@ -494,7 +511,8 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
if (RV_MON_TYPE == RV_MON_PER_CPU)
mode |= HRTIMER_MODE_PINNED;
- hrtimer_start(&ha_mon->hrtimer, ns_to_ktime(expire - passed), mode);
+ hrtimer_start(&ha_mon->hrtimer,
+ ns_to_ktime(ha_time_left(passed, expire)), mode);
}
static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
u64 expire, u64 time_ns)
@@ -502,7 +520,8 @@ static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env
u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns);
ha_start_timer_ns(ha_mon, ENV_MAX_STORED,
- jiffies_to_nsecs(expire - passed), time_ns);
+ jiffies_to_nsecs(ha_time_left(passed, expire)),
+ time_ns);
}
/*
* ha_cancel_timer - Cancel the timer
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 03/14] rv: Add a KUnit test for the HA timer underflow
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
2026-11-05 17:49 ` [PATCH v8 01/14] rv: Force environment reset action on HA monitor start wen.yang
2026-11-05 17:49 ` [PATCH v8 02/14] rv: Fix ha_monitor timer and invariant underflow wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 04/14] rv: Disarm HA timers on the storage free path wen.yang
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
Add rv_ha_kunit.c as a standalone test suite with rvtest, a minimal
per-object HA monitor, to test both underflows fixed by the previous
patch: an invariant checked while the uptime is below the budget holds,
and a timer armed after the budget elapsed expires now. Without that fix
both expectations fail.
Building the HA test in its own compilation unit keeps ha_monitor.h
isolated from non-monitor test runners and avoids macro conflicts. The
suite is guarded by IS_ENABLED(CONFIG_RV_HA_MONITOR) so kernels without
HA monitors build cleanly.
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/rv_ha_kunit.c | 152 ++++++++++++++++++++++++++++++++++
2 files changed, 153 insertions(+)
create mode 100644 kernel/trace/rv/rv_ha_kunit.c
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index cdbf68c84f5a..4b84b83b942b 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_MONITORS_KUNIT_TEST) += rv_ha_kunit.o
diff --git a/kernel/trace/rv/rv_ha_kunit.c b/kernel/trace/rv/rv_ha_kunit.c
new file mode 100644
index 000000000000..e8aaee110fae
--- /dev/null
+++ b/kernel/trace/rv/rv_ha_kunit.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for RV hybrid automata (HA) framework
+ */
+#include <kunit/test.h>
+#include <linux/rv.h>
+
+#if IS_ENABLED(CONFIG_RV_HA_MONITOR)
+
+#include <rv_trace.h>
+
+/* rvtest: a minimal per-object HA monitor driven directly by the tests. */
+#define MONITOR_NAME rvtest
+#define RV_MON_TYPE RV_MON_PER_OBJ
+#define HA_TIMER_TYPE HA_TIMER_HRTIMER
+typedef void *monitor_target;
+
+#define rv_monitoring_on() true
+#define trace_rv_retries_error(...) do { } while (0)
+#define trace_event_rvtest(...) do { } while (0)
+#define trace_error_rvtest(...) do { } while (0)
+#define trace_error_env_rvtest(...) do { } while (0)
+
+enum states_rvtest {
+ idle_rvtest,
+ armed_rvtest,
+ state_max_rvtest,
+};
+
+#define INVALID_STATE state_max_rvtest
+
+enum events_rvtest {
+ start_rvtest,
+ event_max_rvtest,
+};
+
+enum envs_rvtest {
+ clk_rvtest,
+ env_max_rvtest,
+ env_max_stored_rvtest = env_max_rvtest,
+};
+
+#define HA_CLK_NS
+
+struct automaton_rvtest {
+ char *state_names[state_max_rvtest];
+ char *event_names[event_max_rvtest];
+ char *env_names[env_max_rvtest];
+ unsigned char function[state_max_rvtest][event_max_rvtest];
+ unsigned char initial_state;
+ bool final_states[state_max_rvtest];
+};
+
+static const struct automaton_rvtest automaton_rvtest = {
+ .state_names = { "idle", "armed" },
+ .event_names = { "start" },
+ .env_names = { "clk" },
+ .function = {
+ { armed_rvtest }, /* idle */
+ { INVALID_STATE }, /* armed */
+ },
+ .initial_state = idle_rvtest,
+ .final_states = { 1, 0 },
+};
+
+#include <rv/ha_monitor.h>
+
+#define RVTEST_BUDGET_NS (300ULL * NSEC_PER_MSEC)
+
+static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_rvtest env,
+ u64 time_ns)
+{
+ return ha_get_clk_ns(ha_mon, env, time_ns);
+}
+
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_rvtest env,
+ u64 time_ns)
+{
+ ha_reset_clk_ns(ha_mon, env, time_ns);
+}
+
+static bool ha_verify_constraint(struct ha_monitor *ha_mon,
+ enum states curr_state, enum events event,
+ enum states next_state, u64 time_ns)
+{
+ return true;
+}
+
+static const struct rv_kunit_mon rvtest_mon __maybe_unused = RV_MON_OPS_INIT();
+
+/*
+ * rv_test_rvtest_underflow - the HA clock math must not underflow as u64
+ *
+ * An invariant checked while the uptime is below the budget holds, and a
+ * timer armed after the budget elapsed expires now.
+ */
+static void rv_test_rvtest_underflow(struct kunit *test)
+{
+ struct ha_monitor *ha_mon;
+ struct da_monitor *da_mon;
+ const int id = 4242;
+
+ da_monitor_init();
+ rv_this.enabled = 1;
+
+ da_handle_start_run_event(id, NULL, start_rvtest);
+
+ scoped_guard(rcu)
+ da_mon = da_get_monitor(id, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, da_mon);
+ ha_mon = to_ha_monitor(da_mon);
+
+ /* Uptime below the budget: time_ns - expire_ns would underflow. */
+ ha_reset_clk_ns(ha_mon, clk_rvtest, 0);
+ KUNIT_EXPECT_TRUE(test, ha_check_invariant_ns(ha_mon, clk_rvtest, 0,
+ RVTEST_BUDGET_NS));
+
+ /*
+ * Budget already elapsed: expire - passed would underflow and queue
+ * the timer at KTIME_SEC_MAX. Reset first so the expiry does not
+ * report.
+ */
+ da_monitor_reset(&ha_mon->da_mon);
+ ha_reset_clk_ns(ha_mon, clk_rvtest, 0);
+ ha_start_timer_ns(ha_mon, clk_rvtest, RVTEST_BUDGET_NS,
+ RVTEST_BUDGET_NS + NSEC_PER_SEC);
+ KUNIT_EXPECT_TRUE(test,
+ ktime_before(hrtimer_get_expires(&ha_mon->hrtimer),
+ ktime_get() + NSEC_PER_SEC));
+
+ ha_cancel_timer_sync(ha_mon);
+ da_destroy_storage(id);
+ da_monitor_destroy();
+ rv_this.enabled = 0;
+}
+
+static struct kunit_case rv_ha_kunit_cases[] = {
+ KUNIT_CASE(rv_test_rvtest_underflow),
+ {}
+};
+
+static struct kunit_suite rv_ha_kunit_suite = {
+ .name = "rv_ha",
+ .test_cases = rv_ha_kunit_cases,
+};
+
+kunit_test_suite(rv_ha_kunit_suite);
+
+#endif /* CONFIG_RV_HA_MONITOR */
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests for RV hybrid automata (HA) framework");
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 04/14] rv: Disarm HA timers on the storage free path
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (2 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 05/14] rv: Add a KUnit test for " wen.yang
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
da_destroy_storage() cancels the HA timer through da_monitor_reset_hook()
and only then unlinks the storage with hash_del_rcu(). A handler on
another CPU that looked the storage up before the unlink can still call
ha_start_timer_*() after the cancel. The timer then stays queued on a
storage that kfree_rcu() frees a grace period later, and fires on freed
memory, reporting a violation for an object that no longer exists.
A monitor cannot close this from its own handlers: any cancel it issues
precedes the unlink. The first point no reader can race with is the
RCU callback that frees the storage.
Add da_monitor_free_hook(), called right before a per-object storage is
freed, from the RCU callback and from da_monitor_destroy(). HA monitors
implement it by calling ha_cancel_timer_sync() if da_monitoring() is true.
Replace kfree_rcu() with call_rcu() so there is a callback to hook.
Cancelling synchronously from the RCU callback is safe: HA hrtimers are
always armed in HARD mode, so hrtimer_cancel() only spins, and on
PREEMPT_RT, where timer_delete_sync() may sleep, RCU callbacks run in
the rcuc kthread.
Storages whose monitor never started are skipped via da_monitoring().
Fixes: 4a24127bd6cb ("rv: Add support for per-object monitors in DA/HA")
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/da_monitor.h | 27 ++++++++++++++++++++++++++-
include/rv/ha_monitor.h | 8 ++++++++
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index e3cf85c9ce55..c7f9733fb8a4 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -67,6 +67,17 @@ static struct rv_monitor rv_this;
#define da_monitor_sync_hook()
#endif
+/*
+ * Called on the last reference to a storage, right before it is freed.
+ * Monitors embedding a timer disarm it here: an earlier point races with a
+ * reader that resolved the storage before hash_del_rcu(), and
+ * tracepoint_synchronize_unregister() only covers tracepoint probes.
+ * Default: no-op.
+ */
+#ifndef da_monitor_free_hook
+#define da_monitor_free_hook(da_mon)
+#endif
+
/*
* Type for the target id, default to int but can be overridden.
* A long type can work as hash table key (PER_OBJ) but will be downgraded to
@@ -543,6 +554,15 @@ static inline monitor_target da_get_target_by_id(da_id_type id)
return mon_storage->target;
}
+static void da_kfree_cb(struct rcu_head *head)
+{
+ struct da_monitor_storage *ms =
+ container_of(head, struct da_monitor_storage, rcu);
+
+ da_monitor_free_hook(&ms->rv.da_mon);
+ kfree(ms);
+}
+
/*
* da_destroy_storage - destroy the per-object storage
*
@@ -564,7 +584,11 @@ static inline void da_destroy_storage(da_id_type id)
return;
da_monitor_reset_hook(&mon_storage->rv.da_mon);
hash_del_rcu(&mon_storage->node);
- kfree_rcu(mon_storage, rcu);
+ /*
+ * A reader from before hash_del_rcu() may still arm; the free
+ * callback runs past it, so the hook disarms there.
+ */
+ call_rcu(&mon_storage->rcu, da_kfree_cb);
}
static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
@@ -607,6 +631,7 @@ static inline void da_monitor_destroy(void)
* pending, we can safely assume no concurrent user.
*/
hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) {
+ da_monitor_free_hook(&mon_storage->rv.da_mon);
hash_del_rcu(&mon_storage->node);
kfree(mon_storage);
}
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index ebb7f91e3447..3bf1dd15544c 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -29,6 +29,7 @@ static inline void ha_monitor_reset_env(struct da_monitor *da_mon);
static inline void ha_setup_timer(struct ha_monitor *ha_mon);
static inline bool ha_cancel_timer(struct ha_monitor *ha_mon);
static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon);
+static inline void ha_monitor_free_env(struct da_monitor *da_mon);
static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
enum states curr_state,
enum events event,
@@ -38,6 +39,7 @@ static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
#define da_monitor_init_hook ha_monitor_init_env
#define da_monitor_reset_hook ha_monitor_reset_env
#define da_monitor_sync_hook() synchronize_rcu()
+#define da_monitor_free_hook ha_monitor_free_env
#if !defined(HA_SKIP_AUTO_CLEANUP) && RV_MON_TYPE == RV_MON_PER_TASK
/*
@@ -551,6 +553,12 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon) { }
#endif
+static inline void ha_monitor_free_env(struct da_monitor *da_mon)
+{
+ if (da_monitoring(da_mon))
+ ha_cancel_timer_sync(to_ha_monitor(da_mon));
+}
+
#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
#ifdef RV_MON_OPS_INIT
#undef RV_MON_OPS_INIT
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 05/14] rv: Add a KUnit test for the storage free path
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (3 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 04/14] rv: Disarm HA timers on the storage free path wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 06/14] rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL overridable wen.yang
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
A per-object HA monitor whose timer is armed through a pointer resolved
before da_destroy_storage() outlives the storage and reports against a
gone entity. Real handlers resolve and use a storage under one
guard(rcu)(), so the fixture issues the two steps separately to reach
that window.
Add rv_test_rvtest_arm_after_destroy() to rv_ha_kunit.c to verify that
the free hook properly disarms the timer before the storage is freed.
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
kernel/trace/rv/rv_ha_kunit.c | 51 ++++++++++++++++++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/rv/rv_ha_kunit.c b/kernel/trace/rv/rv_ha_kunit.c
index e8aaee110fae..29ee8a739739 100644
--- a/kernel/trace/rv/rv_ha_kunit.c
+++ b/kernel/trace/rv/rv_ha_kunit.c
@@ -3,12 +3,16 @@
* KUnit tests for RV hybrid automata (HA) framework
*/
#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/delay.h>
#include <linux/rv.h>
#if IS_ENABLED(CONFIG_RV_HA_MONITOR)
#include <rv_trace.h>
+static atomic_t rvtest_expired;
+
/* rvtest: a minimal per-object HA monitor driven directly by the tests. */
#define MONITOR_NAME rvtest
#define RV_MON_TYPE RV_MON_PER_OBJ
@@ -19,7 +23,7 @@ typedef void *monitor_target;
#define trace_rv_retries_error(...) do { } while (0)
#define trace_event_rvtest(...) do { } while (0)
#define trace_error_rvtest(...) do { } while (0)
-#define trace_error_env_rvtest(...) do { } while (0)
+#define trace_error_env_rvtest(...) atomic_inc(&rvtest_expired)
enum states_rvtest {
idle_rvtest,
@@ -134,8 +138,53 @@ static void rv_test_rvtest_underflow(struct kunit *test)
rv_this.enabled = 0;
}
+/*
+ * rv_test_rvtest_arm_after_destroy - a storage outlived by its own timer
+ *
+ * da_destroy_storage() cancels the timer, then unlinks:
+ *
+ * da_monitor_reset_hook() cancels
+ * hash_del_rcu()
+ * call_rcu()
+ *
+ * A reader that resolved the storage before the unlink can arm past that
+ * cancel. Handlers resolve and use under one guard(rcu)(), so the two steps
+ * are issued separately here.
+ *
+ * The timer is still queued after the destroy, and it fires -- reporting a
+ * violation for an entity that is gone.
+ */
+static void rv_test_rvtest_arm_after_destroy(struct kunit *test)
+{
+ const int id = 4242;
+ struct da_monitor *stale;
+
+ atomic_set(&rvtest_expired, 0);
+ da_monitor_init();
+ rv_this.enabled = 1;
+
+ da_handle_start_run_event(id, NULL, start_rvtest);
+
+ scoped_guard(rcu) /* resolve before the unlink */
+ stale = da_get_monitor(id, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, stale);
+
+ da_destroy_storage(id); /* cancel, unlink, call_rcu */
+ ha_start_timer_ns(to_ha_monitor(stale), clk_rvtest,
+ RVTEST_BUDGET_NS, ha_get_ns());
+
+ rcu_barrier();
+
+ /* A timer the free path missed expires here. */
+ msleep(600);
+ KUNIT_EXPECT_EQ(test, atomic_read(&rvtest_expired), 0);
+ da_monitor_destroy();
+ rv_this.enabled = 0;
+}
+
static struct kunit_case rv_ha_kunit_cases[] = {
KUNIT_CASE(rv_test_rvtest_underflow),
+ KUNIT_CASE(rv_test_rvtest_arm_after_destroy),
{}
};
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 06/14] rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL overridable
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (4 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 05/14] rv: Add a KUnit test for " wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY wen.yang
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
Wrap da_monitor_reset_hook, da_monitor_sync_hook, and EVENT_NONE_LBL with
ifndef guards so HA-based monitors can substitute their own implementations
before including this header.
Overrides of da_monitor_reset_hook must still call ha_monitor_reset_env()
or cancel outstanding timers to avoid timer UAF.
No behaviour change for monitors that do not override these macros.
Reviewed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/ha_monitor.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h
index 3bf1dd15544c..5372e5dfe3c5 100644
--- a/include/rv/ha_monitor.h
+++ b/include/rv/ha_monitor.h
@@ -37,8 +37,14 @@ static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
da_id_type id);
#define da_monitor_event_hook ha_monitor_handle_constraint
#define da_monitor_init_hook ha_monitor_init_env
+
+/* Overrides must still call ha_monitor_reset_env() or cancel the timer. */
+#ifndef da_monitor_reset_hook
#define da_monitor_reset_hook ha_monitor_reset_env
+#endif
+#ifndef da_monitor_sync_hook
#define da_monitor_sync_hook() synchronize_rcu()
+#endif
#define da_monitor_free_hook ha_monitor_free_env
#if !defined(HA_SKIP_AUTO_CLEANUP) && RV_MON_TYPE == RV_MON_PER_TASK
@@ -77,7 +83,9 @@ _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
#define ENV_INVALID_VALUE U64_MAX
/* Error with no event occurs only on timeouts */
#define EVENT_NONE EVENT_MAX
+#ifndef EVENT_NONE_LBL
#define EVENT_NONE_LBL "none"
+#endif
#define ENV_BUFFER_SIZE 64
#ifdef CONFIG_RV_REACTORS
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (5 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 06/14] rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL overridable wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 08/14] rv: Support embedded target types in per-object DA storage wen.yang
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
Per-object DA storage allocation is currently limited to kmalloc on
demand. Add a compile-time selector so monitors can choose among three
strategies:
DA_ALLOC_AUTO (default) - kmalloc per object on the monitor path
DA_ALLOC_POOL - pre-allocated fixed-size mempool;
selected by defining DA_MON_POOL_SIZE
DA_ALLOC_MANUAL - caller pre-inserts storage; framework
only links the target field
nomiss switches from the implicit DA_SKIP_AUTO_ALLOC override to an
explicit DA_MON_ALLOCATION_STRATEGY DA_ALLOC_MANUAL; the behaviours are
equivalent (pre-allocated storage, no allocation on the sched_switch
start path).
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/da_monitor.h | 121 +++++++++++++++++++++--
kernel/trace/rv/monitors/nomiss/nomiss.c | 2 +-
2 files changed, 112 insertions(+), 11 deletions(-)
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index c7f9733fb8a4..76169e41d4e5 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -14,7 +14,45 @@
#ifndef _RV_DA_MONITOR_H
#define _RV_DA_MONITOR_H
+/*
+ * Allocation strategy for RV_MON_PER_OBJ monitors, selected before including
+ * this header. Defining DA_MON_POOL_SIZE N implies DA_ALLOC_POOL.
+ *
+ * DA_ALLOC_AUTO - kmalloc on demand; unbounded (default).
+ * DA_ALLOC_POOL - DA_MON_POOL_SIZE pre-allocated slots. The start handler
+ * must run in task context; other handlers only call
+ * da_handle_event() and never allocate.
+ * DA_ALLOC_MANUAL - caller pre-inserts storage; framework links the target.
+ */
+#define DA_ALLOC_AUTO 0
+#define DA_ALLOC_POOL 1
+#define DA_ALLOC_MANUAL 2
+
+#ifdef DA_MON_POOL_SIZE
+#if defined(DA_MON_ALLOCATION_STRATEGY) && \
+ DA_MON_ALLOCATION_STRATEGY != DA_ALLOC_POOL
+#error "DA_MON_POOL_SIZE implies DA_ALLOC_POOL"
+#endif
+#if DA_MON_POOL_SIZE == 0
+#error "DA_MON_POOL_SIZE must be non-zero"
+#endif
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_POOL
+#endif /* DA_MON_POOL_SIZE */
+
+#ifndef DA_MON_ALLOCATION_STRATEGY
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_AUTO
+#endif /* DA_MON_ALLOCATION_STRATEGY */
+
+/* Zero default keeps pool-mode conditionals compile-time constant. */
+#ifndef DA_MON_POOL_SIZE
+#if DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL
+#error "DA_ALLOC_POOL requires DA_MON_POOL_SIZE to be defined and non-zero"
+#endif
+#define DA_MON_POOL_SIZE 0
+#endif /* DA_MON_POOL_SIZE */
+
#include <rv/automata.h>
+#include <linux/mempool.h>
#include <linux/rv.h>
#include <rv/kunit.h>
#include <linux/stringify.h>
@@ -563,6 +601,46 @@ static void da_kfree_cb(struct rcu_head *head)
kfree(ms);
}
+/*
+ * Zero-initialised for non-pool monitors, whose pool paths compile away.
+ */
+static mempool_t da_monitor_pool;
+
+static void da_pool_return_cb(struct rcu_head *head)
+{
+ struct da_monitor_storage *ms =
+ container_of(head, struct da_monitor_storage, rcu);
+
+ da_monitor_free_hook(&ms->rv.da_mon);
+ mempool_free(ms, &da_monitor_pool);
+}
+
+/*
+ * da_create_pool_storage - pop a free pool slot and insert it into the hash.
+ * Returns NULL if the pool is exhausted. Caller holds an RCU read-side CS
+ * and the monitor's serialisation lock.
+ */
+static inline struct da_monitor *
+da_create_pool_storage(da_id_type id, monitor_target target,
+ struct da_monitor *da_mon)
+{
+ struct da_monitor_storage *mon_storage;
+
+ if (da_mon)
+ return da_mon;
+
+ mon_storage = mempool_alloc_preallocated(&da_monitor_pool);
+ if (!mon_storage)
+ return NULL;
+ memset(mon_storage, 0, sizeof(*mon_storage));
+
+ mon_storage->id = id;
+ mon_storage->target = target;
+
+ hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
+ return &mon_storage->rv.da_mon;
+}
+
/*
* da_destroy_storage - destroy the per-object storage
*
@@ -588,7 +666,10 @@ static inline void da_destroy_storage(da_id_type id)
* A reader from before hash_del_rcu() may still arm; the free
* callback runs past it, so the hook disarms there.
*/
- call_rcu(&mon_storage->rcu, da_kfree_cb);
+ if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+ call_rcu(&mon_storage->rcu, da_pool_return_cb);
+ else
+ call_rcu(&mon_storage->rcu, da_kfree_cb);
}
static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
@@ -614,6 +695,13 @@ static inline void da_monitor_reset_state_all(void)
static inline int da_monitor_init(void)
{
hash_init(da_monitor_ht);
+ if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL) {
+ size_t storage_size = sizeof(struct da_monitor_storage);
+
+ return mempool_init_kmalloc_pool(&da_monitor_pool,
+ DA_MON_POOL_SIZE,
+ storage_size);
+ }
return 0;
}
@@ -633,20 +721,33 @@ static inline void da_monitor_destroy(void)
hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) {
da_monitor_free_hook(&mon_storage->rv.da_mon);
hash_del_rcu(&mon_storage->node);
- kfree(mon_storage);
+ if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+ mempool_free(mon_storage, &da_monitor_pool);
+ else
+ kfree(mon_storage);
+ }
+
+ if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL) {
+ rcu_barrier();
+ mempool_exit(&da_monitor_pool);
}
}
/*
- * Allow the per-object monitors to run allocation manually, necessary if the
- * start condition is in a context problematic for allocation (e.g. scheduling).
- * In such case, if the storage was pre-allocated without a target, set it now.
+ * da_prepare_storage - allocate or link per-object monitor storage.
+ * Only the first start allocates; later starts reuse the existing da_mon.
+ * Must run in task context (see the allocation strategies above).
*/
-#ifdef DA_SKIP_AUTO_ALLOC
-#define da_prepare_storage da_fill_empty_storage
-#else
-#define da_prepare_storage da_create_storage
-#endif /* DA_SKIP_AUTO_ALLOC */
+static inline struct da_monitor *
+da_prepare_storage(da_id_type id, monitor_target target,
+ struct da_monitor *da_mon)
+{
+ if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+ return da_create_pool_storage(id, target, da_mon);
+ if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_MANUAL)
+ return da_fill_empty_storage(id, target, da_mon);
+ return da_create_storage(id, target, da_mon);
+}
#endif /* RV_MON_TYPE */
diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c
index 6e47d379f777..c80a2c51c514 100644
--- a/kernel/trace/rv/monitors/nomiss/nomiss.c
+++ b/kernel/trace/rv/monitors/nomiss/nomiss.c
@@ -18,7 +18,7 @@
#define RV_MON_TYPE RV_MON_PER_OBJ
#define HA_TIMER_TYPE HA_TIMER_WHEEL
/* The start condition is on sched_switch, it's dangerous to allocate there */
-#define DA_SKIP_AUTO_ALLOC
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_MANUAL
typedef struct sched_dl_entity *monitor_target;
#include "nomiss.h"
#include <rv/ha_monitor.h>
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 08/14] rv: Support embedded target types in per-object DA storage
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (6 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 09/14] rv: Add generic uprobe infrastructure for RV monitors wen.yang
2026-11-05 17:49 ` [PATCH v8 10/14] rv: Add tlob model DOT file wen.yang
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
A PER_OBJ monitor usually typedefs monitor_target itself, as a pointer to
the monitored object (borrowed: the object exists independently of the
monitor and outlives the storage).
A monitor whose target is monitor-owned data can instead define
DA_OBJ_EMBEDDED_TYPE with a complete struct type: the framework then
derives monitor_target as a pointer to it, embeds one instance in each
per-object storage, and allocates and frees it together with the storage.
On storage creation the caller-supplied target is shallow-copied into
the storage before it is published, so it may be a fully initialised
stack object.
This allows per-object monitors to manage custom per-instance data without
maintaining private mempools, complex multi-stage reference counting, or
out-of-band teardown state machines.
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/da_monitor.h | 82 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 76 insertions(+), 6 deletions(-)
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 76169e41d4e5..ed9e7e740d80 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -61,6 +61,10 @@
#include <linux/slab.h>
#include <linux/hashtable.h>
+#if defined(DA_OBJ_EMBEDDED_TYPE) && RV_MON_TYPE != RV_MON_PER_OBJ
+#error "DA_OBJ_EMBEDDED_TYPE is only supported by RV_MON_PER_OBJ monitors"
+#endif
+
/*
* Per-cpu variables require a unique name although static in some
* configurations (e.g. CONFIG_DEBUG_FORCE_WEAK_PER_CPU or alpha modules).
@@ -453,9 +457,24 @@ static inline void da_monitor_destroy(void)
* Functions to define, init and get a per-object monitor.
*/
+#ifdef DA_OBJ_EMBEDDED_TYPE
+/*
+ * Framework-owned target: one instance is embedded in each storage and
+ * allocated/freed with it. monitor_target keeps pointer semantics, but
+ * da_get_target() points into the storage rather than to a borrowed object.
+ * The header derives the typedef: do not define monitor_target by hand.
+ */
+typedef DA_OBJ_EMBEDDED_TYPE * monitor_target;
+#endif
+
struct da_monitor_storage {
da_id_type id;
+#ifdef DA_OBJ_EMBEDDED_TYPE
+ DA_OBJ_EMBEDDED_TYPE target;
+ bool filled;
+#else
monitor_target target;
+#endif
union rv_task_monitor rv;
struct hlist_node node;
struct rcu_head rcu;
@@ -466,6 +485,40 @@ struct da_monitor_storage {
#endif
static DEFINE_HASHTABLE(da_monitor_ht, DA_MONITOR_HT_BITS);
+#ifdef DA_OBJ_EMBEDDED_TYPE
+
+#define TARGET_PTR(storage) (&(storage)->target)
+
+static inline bool da_storage_is_empty(struct da_monitor_storage *mon_storage)
+{
+ /* Pairs with smp_store_release in da_storage_set_target(). */
+ return !smp_load_acquire(&mon_storage->filled);
+}
+static inline void da_storage_set_target(struct da_monitor_storage *mon_storage,
+ monitor_target target)
+{
+ if (WARN_ON_ONCE(!target))
+ return;
+
+ memcpy(&mon_storage->target, target, sizeof(mon_storage->target));
+ /* Pairs with smp_load_acquire in da_storage_is_empty(). */
+ smp_store_release(&mon_storage->filled, true);
+}
+#else
+
+#define TARGET_PTR(storage) storage->target
+
+static inline bool da_storage_is_empty(struct da_monitor_storage *mon_storage)
+{
+ return !TARGET_PTR(mon_storage);
+}
+static inline void da_storage_set_target(struct da_monitor_storage *mon_storage,
+ monitor_target target)
+{
+ mon_storage->target = target;
+}
+#endif
+
/*
* da_create_empty_storage - pre-allocate an empty storage
*/
@@ -503,7 +556,7 @@ static inline struct da_monitor *da_create_storage(da_id_type id,
if (!mon_storage)
return NULL;
- mon_storage->target = target;
+ da_storage_set_target(mon_storage, target);
return &mon_storage->rv.da_mon;
}
@@ -536,10 +589,20 @@ static struct da_monitor *da_get_monitor(da_id_type id, monitor_target target)
/*
* da_get_target - return the object associated to the monitor
+ *
+ * NULL for an embedded target whose storage was pre-allocated but not filled
+ * yet: a manual-allocation monitor publishes its storage before it has one.
*/
static inline monitor_target da_get_target(struct da_monitor *da_mon)
{
- return container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target;
+ struct da_monitor_storage *mon_storage =
+ container_of(da_mon, struct da_monitor_storage, rv.da_mon);
+
+#ifdef DA_OBJ_EMBEDDED_TYPE
+ if (unlikely(da_storage_is_empty(mon_storage)))
+ return NULL;
+#endif
+ return TARGET_PTR(mon_storage);
}
/*
@@ -572,8 +635,11 @@ static inline struct da_monitor *da_fill_empty_storage(da_id_type id,
monitor_target target,
struct da_monitor *da_mon)
{
- if (unlikely(da_mon && !da_get_target(da_mon)))
- container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target = target;
+ struct da_monitor_storage *mon_storage =
+ container_of(da_mon, struct da_monitor_storage, rv.da_mon);
+
+ if (unlikely(da_mon && da_storage_is_empty(mon_storage)))
+ da_storage_set_target(mon_storage, target);
return da_mon;
}
@@ -589,7 +655,11 @@ static inline monitor_target da_get_target_by_id(da_id_type id)
if (unlikely(!mon_storage))
return NULL;
- return mon_storage->target;
+#ifdef DA_OBJ_EMBEDDED_TYPE
+ if (unlikely(da_storage_is_empty(mon_storage)))
+ return NULL;
+#endif
+ return TARGET_PTR(mon_storage);
}
static void da_kfree_cb(struct rcu_head *head)
@@ -635,7 +705,7 @@ da_create_pool_storage(da_id_type id, monitor_target target,
memset(mon_storage, 0, sizeof(*mon_storage));
mon_storage->id = id;
- mon_storage->target = target;
+ da_storage_set_target(mon_storage, target);
hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
return &mon_storage->rv.da_mon;
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 09/14] rv: Add generic uprobe infrastructure for RV monitors
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (7 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 08/14] rv: Support embedded target types in per-object DA storage wen.yang
@ 2026-11-05 17:49 ` wen.yang
2026-11-05 17:49 ` [PATCH v8 10/14] rv: Add tlob model DOT file wen.yang
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
Monitors that instrument user-space function boundaries need to register
uprobes and deregister them safely. Provide a thin wrapper so monitors
share a single implementation of this boilerplate.
struct rv_uprobe embeds struct uprobe_consumer directly, avoiding a
separate heap allocation per probe. The struct holds an inode reference
(acquired via igrab()) for the probed binary for the full uprobe lifetime,
as uprobe_register() does not take an inode reference. On unregister,
the inode reference is released with iput() only after
rv_uprobe_unregister_sync().
rv_uprobe_unregister_sync() calls uprobe_unregister_sync(), draining
all in-flight handlers before the containing struct may be freed.
The API provides register, nosync unregister, a global handler barrier
(rv_uprobe_unregister_sync), and an active-state predicate.
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
include/rv/rv_uprobe.h | 35 +++++++++++++++++++++++
kernel/trace/rv/Kconfig | 4 +++
kernel/trace/rv/Makefile | 1 +
kernel/trace/rv/rv_uprobe.c | 56 +++++++++++++++++++++++++++++++++++++
4 files changed, 96 insertions(+)
create mode 100644 include/rv/rv_uprobe.h
create mode 100644 kernel/trace/rv/rv_uprobe.c
diff --git a/include/rv/rv_uprobe.h b/include/rv/rv_uprobe.h
new file mode 100644
index 000000000000..b93c2a81bc22
--- /dev/null
+++ b/include/rv/rv_uprobe.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Wen Yang <wen.yang@linux.dev> */
+/*
+ * Generic uprobe infrastructure for RV monitors.
+ */
+
+#ifndef _RV_UPROBE_H
+#define _RV_UPROBE_H
+
+#include <linux/types.h>
+#include <linux/uprobes.h>
+
+struct inode;
+struct pt_regs;
+
+/*
+ * Free the containing struct only after rv_uprobe_unregister_nosync() +
+ * rv_uprobe_unregister_sync() + iput(p->inode).
+ *
+ * The inode reference does not pin the dentry, so callers that re-resolve the
+ * probed path must hold a struct path: 9p cache=none remints an inode per
+ * lookup.
+ */
+struct rv_uprobe {
+ struct uprobe_consumer uc;
+ struct uprobe *uprobe;
+ struct inode *inode;
+};
+
+bool rv_uprobe_is_registered(const struct rv_uprobe *p);
+int rv_uprobe_register(struct inode *inode, loff_t offset, struct rv_uprobe *p);
+void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
+void rv_uprobe_unregister_sync(void);
+
+#endif /* _RV_UPROBE_H */
diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig
index efa930f94ea4..2bc412304293 100644
--- a/kernel/trace/rv/Kconfig
+++ b/kernel/trace/rv/Kconfig
@@ -86,6 +86,10 @@ source "kernel/trace/rv/monitors/nomiss/Kconfig"
# Add new monitors here
+config RV_UPROBE
+ bool
+ depends on RV && UPROBES
+
config RV_REACTORS
bool "Runtime verification reactors"
default y
diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile
index 4b84b83b942b..8dc14a1819b8 100644
--- a/kernel/trace/rv/Makefile
+++ b/kernel/trace/rv/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o
# Add new monitors here
+obj-$(CONFIG_RV_UPROBE) += rv_uprobe.o
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
diff --git a/kernel/trace/rv/rv_uprobe.c b/kernel/trace/rv/rv_uprobe.c
new file mode 100644
index 000000000000..1c7f8466aeed
--- /dev/null
+++ b/kernel/trace/rv/rv_uprobe.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic uprobe infrastructure for RV monitors.
+ */
+#include <linux/fs.h>
+#include <linux/uprobes.h>
+#include <rv/rv_uprobe.h>
+
+/*
+ * uprobe_register() takes no inode reference of its own, so grab one here and
+ * drop it only after rv_uprobe_unregister_sync().
+ */
+int rv_uprobe_register(struct inode *inode, loff_t offset, struct rv_uprobe *p)
+{
+ int ret;
+
+ p->inode = igrab(inode);
+ if (!p->inode)
+ return -ESTALE;
+
+ p->uprobe = uprobe_register(inode, offset, 0, &p->uc);
+ if (IS_ERR(p->uprobe)) {
+ ret = PTR_ERR(p->uprobe);
+ p->uprobe = NULL;
+ iput(p->inode);
+ p->inode = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rv_uprobe_register);
+
+bool rv_uprobe_is_registered(const struct rv_uprobe *p)
+{
+ return p && p->uprobe;
+}
+EXPORT_SYMBOL_GPL(rv_uprobe_is_registered);
+
+/* The inode stays held: in-flight handlers may still reach it. */
+void rv_uprobe_unregister_nosync(struct rv_uprobe *p)
+{
+ if (!p || !p->uprobe)
+ return;
+
+ uprobe_unregister_nosync(p->uprobe, &p->uc);
+ p->uprobe = NULL;
+}
+EXPORT_SYMBOL_GPL(rv_uprobe_unregister_nosync);
+
+/* Global barrier over every probe dequeued so far. */
+void rv_uprobe_unregister_sync(void)
+{
+ uprobe_unregister_sync();
+}
+EXPORT_SYMBOL_GPL(rv_uprobe_unregister_sync);
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v8 10/14] rv: Add tlob model DOT file
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
` (8 preceding siblings ...)
2026-11-05 17:49 ` [PATCH v8 09/14] rv: Add generic uprobe infrastructure for RV monitors wen.yang
@ 2026-11-05 17:49 ` wen.yang
9 siblings, 0 replies; 11+ messages in thread
From: wen.yang @ 2026-11-05 17:49 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Nam Cao, linux-trace-kernel, linux-kernel, Wen Yang
From: Wen Yang <wen.yang@linux.dev>
Add the Graphviz DOT specification of the tlob hybrid automaton to
tools/verification/models/.
The model has four states (stopped, running, waiting, sleeping) and six
events (start, stop, sleep, preempt, wakeup, switch_in). A single clock
invariant, clk_elapsed < BUDGET_NS(), holds in running, waiting and
sleeping; stopped has no invariant, hence no timer.
wakeup is a self-loop on running and waiting: a task that is already
queued can still be the target of a traced wakeup, either by waking
itself or through ttwu_runnable() while preempted, and neither case ends
the budget window. stopped takes every scheduler event as a self-loop,
so a task keeps going through the scheduler between windows.
Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Wen Yang <wen.yang@linux.dev>
---
tools/verification/models/tlob.dot | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
create mode 100644 tools/verification/models/tlob.dot
diff --git a/tools/verification/models/tlob.dot b/tools/verification/models/tlob.dot
new file mode 100644
index 000000000000..021a4d823548
--- /dev/null
+++ b/tools/verification/models/tlob.dot
@@ -0,0 +1,27 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_stopped"};
+ {node [shape = plaintext] "running"};
+ {node [shape = plaintext] "waiting"};
+ {node [shape = plaintext] "sleeping"};
+ {node [shape = ellipse] "stopped"};
+ "__init_stopped" -> "stopped";
+ "running" [label = "running\nclk_elapsed <= BUDGET_NS()"];
+ "waiting" [label = "waiting\nclk_elapsed <= BUDGET_NS()"];
+ "sleeping" [label = "sleeping\nclk_elapsed <= BUDGET_NS()"];
+ "stopped" [label = "stopped", color = green3];
+ "running" -> "sleeping" [ label = "sleep" ];
+ "running" -> "waiting" [ label = "preempt" ];
+ "running" -> "running" [ label = "wakeup" ];
+ "waiting" -> "running" [ label = "switch_in" ];
+ "waiting" -> "waiting" [ label = "wakeup" ];
+ "sleeping" -> "waiting" [ label = "wakeup" ];
+ "running" -> "stopped" [ label = "stop" ];
+ "stopped" -> "running" [ label = "start;reset(clk_elapsed)" ];
+ "stopped" -> "stopped" [ label = "preempt\nsleep\nswitch_in\nwakeup" ];
+ { rank = min ;
+ "__init_stopped";
+ "stopped";
+ }
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-05 17:50 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-05 17:49 [PATCH v8 00/14] rv: Add task latency over budget RV monitor wen.yang
2026-11-05 17:49 ` [PATCH v8 01/14] rv: Force environment reset action on HA monitor start wen.yang
2026-11-05 17:49 ` [PATCH v8 02/14] rv: Fix ha_monitor timer and invariant underflow wen.yang
2026-11-05 17:49 ` [PATCH v8 03/14] rv: Add a KUnit test for the HA timer underflow wen.yang
2026-11-05 17:49 ` [PATCH v8 04/14] rv: Disarm HA timers on the storage free path wen.yang
2026-11-05 17:49 ` [PATCH v8 05/14] rv: Add a KUnit test for " wen.yang
2026-11-05 17:49 ` [PATCH v8 06/14] rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL overridable wen.yang
2026-11-05 17:49 ` [PATCH v8 07/14] rv: Introduce DA_MON_ALLOCATION_STRATEGY wen.yang
2026-11-05 17:49 ` [PATCH v8 08/14] rv: Support embedded target types in per-object DA storage wen.yang
2026-11-05 17:49 ` [PATCH v8 09/14] rv: Add generic uprobe infrastructure for RV monitors wen.yang
2026-11-05 17:49 ` [PATCH v8 10/14] rv: Add tlob model DOT file wen.yang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox